forked from Manuel/meeting-assistant
Merge pull request 'Update meeting dependencies and synchronize recording lifecycle tests' (#57) from renovate/microsoft.agents.ai.openai-1.x into main
This commit is contained in:
commit
d35f83cca5
6 files changed
+82
-10
No files matched your search
@@ -58,14 +58,21 @@ public sealed class AudioMixingTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public async Task CompositeAudioSourceWaitsForMatchingStreamsBeforeMixing()
|
public async Task CompositeAudioSourceWaitsForMatchingStreamsBeforeMixing()
|
||||||
{
|
{
|
||||||
var microphone = new DelayedAudioSource(TimeSpan.Zero, Pcm16(2_000));
|
var microphone = new ControlledAudioSource(Pcm16(2_000));
|
||||||
var system = new DelayedAudioSource(TimeSpan.FromMilliseconds(100), Pcm16(10_000));
|
var system = new ControlledAudioSource(Pcm16(10_000));
|
||||||
var source = CreateSource(microphone, system);
|
var source = CreateSource(microphone, system);
|
||||||
|
using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||||
|
await using var chunks = source.CaptureAsync(cancellation.Token).GetAsyncEnumerator(cancellation.Token);
|
||||||
|
var nextChunk = chunks.MoveNextAsync().AsTask();
|
||||||
|
|
||||||
var chunks = await ReadChunks(source);
|
microphone.Release();
|
||||||
|
await microphone.WaitUntilDeliveredAsync();
|
||||||
|
Assert.False(nextChunk.IsCompleted);
|
||||||
|
|
||||||
var chunk = Assert.Single(chunks);
|
system.Release();
|
||||||
Assert.Equal(12_000, BitConverter.ToInt16(chunk.Pcm));
|
Assert.True(await nextChunk);
|
||||||
|
Assert.Equal(12_000, BitConverter.ToInt16(chunks.Current.Pcm));
|
||||||
|
Assert.False(await chunks.MoveNextAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -187,6 +194,30 @@ public sealed class AudioMixingTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class ControlledAudioSource : IMeetingAudioSource
|
||||||
|
{
|
||||||
|
private readonly AudioChunk chunk;
|
||||||
|
private readonly TaskCompletionSource release = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
private readonly TaskCompletionSource delivered = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
|
||||||
|
public ControlledAudioSource(byte[] pcm)
|
||||||
|
{
|
||||||
|
chunk = new AudioChunk(pcm, 16000, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Release() => release.TrySetResult();
|
||||||
|
|
||||||
|
public Task WaitUntilDeliveredAsync() => delivered.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||||
|
|
||||||
|
public async IAsyncEnumerable<AudioChunk> CaptureAsync(
|
||||||
|
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await release.Task.WaitAsync(cancellationToken);
|
||||||
|
yield return chunk;
|
||||||
|
delivered.TrySetResult();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class DelayedAudioSource : IMeetingAudioSource
|
private sealed class DelayedAudioSource : IMeetingAudioSource
|
||||||
{
|
{
|
||||||
private readonly TimeSpan delay;
|
private readonly TimeSpan delay;
|
||||||
|
|||||||
@@ -53,8 +53,14 @@ public sealed class RecordingCoordinatorTests
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return File.Exists(path) &&
|
if (!File.Exists(path))
|
||||||
File.ReadAllText(path).Contains(text, StringComparison.Ordinal);
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
|
||||||
|
using var reader = new StreamReader(stream);
|
||||||
|
return reader.ReadToEnd().Contains(text, StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
catch (IOException)
|
catch (IOException)
|
||||||
{
|
{
|
||||||
@@ -3290,6 +3296,7 @@ public sealed class RecordingCoordinatorTests
|
|||||||
|
|
||||||
await coordinator.StartAsync(CancellationToken.None);
|
await coordinator.StartAsync(CancellationToken.None);
|
||||||
await audioSource.WaitUntilCapturedAsync();
|
await audioSource.WaitUntilCapturedAsync();
|
||||||
|
await artifactStore.WaitUntilTranscribingAsync();
|
||||||
|
|
||||||
await coordinator.StopAsync(CancellationToken.None);
|
await coordinator.StopAsync(CancellationToken.None);
|
||||||
|
|
||||||
@@ -3433,6 +3440,7 @@ public sealed class RecordingCoordinatorTests
|
|||||||
{
|
{
|
||||||
var audioSource = new CapturedChunkThenCancelAudioSource(new AudioChunk([1, 0, 2, 0], 16000, 1));
|
var audioSource = new CapturedChunkThenCancelAudioSource(new AudioChunk([1, 0, 2, 0], 16000, 1));
|
||||||
var artifactStore = new InMemoryMeetingArtifactStore();
|
var artifactStore = new InMemoryMeetingArtifactStore();
|
||||||
|
var metadataProvider = new BlockingMeetingMetadataProvider(new MeetingMetadata("Summary failure", [], "", null));
|
||||||
var coordinator = new MeetingRecordingCoordinator(
|
var coordinator = new MeetingRecordingCoordinator(
|
||||||
audioSource,
|
audioSource,
|
||||||
new TestSpeechRecognitionPipelineFactory(new FinalSegmentOnAudioCompletionProvider()),
|
new TestSpeechRecognitionPipelineFactory(new FinalSegmentOnAudioCompletionProvider()),
|
||||||
@@ -3443,11 +3451,15 @@ public sealed class RecordingCoordinatorTests
|
|||||||
new InMemoryRecordedAudioStore(),
|
new InMemoryRecordedAudioStore(),
|
||||||
new CapturingMeetingSummaryPipeline(succeeded: false),
|
new CapturingMeetingSummaryPipeline(succeeded: false),
|
||||||
Options.Create(CreateOptionsWithoutFinalizer()),
|
Options.Create(CreateOptionsWithoutFinalizer()),
|
||||||
NullLogger<MeetingRecordingCoordinator>.Instance);
|
NullLogger<MeetingRecordingCoordinator>.Instance,
|
||||||
|
meetingMetadataProvider: metadataProvider);
|
||||||
|
|
||||||
await coordinator.StartAsync(CancellationToken.None);
|
await coordinator.StartAsync(CancellationToken.None);
|
||||||
|
await metadataProvider.WaitUntilRequestedAsync();
|
||||||
await audioSource.WaitUntilCapturedAsync();
|
await audioSource.WaitUntilCapturedAsync();
|
||||||
|
|
||||||
|
metadataProvider.Release();
|
||||||
|
await artifactStore.WaitUntilTranscribingAsync();
|
||||||
await coordinator.StopAsync(CancellationToken.None);
|
await coordinator.StopAsync(CancellationToken.None);
|
||||||
|
|
||||||
Assert.Equal(
|
Assert.Equal(
|
||||||
@@ -3481,6 +3493,7 @@ public sealed class RecordingCoordinatorTests
|
|||||||
|
|
||||||
await coordinator.StartAsync(CancellationToken.None);
|
await coordinator.StartAsync(CancellationToken.None);
|
||||||
await audioSource.WaitUntilCapturedAsync();
|
await audioSource.WaitUntilCapturedAsync();
|
||||||
|
await artifactStore.WaitUntilTranscribingAsync();
|
||||||
|
|
||||||
await coordinator.StopAsync(CancellationToken.None);
|
await coordinator.StopAsync(CancellationToken.None);
|
||||||
|
|
||||||
@@ -4497,6 +4510,7 @@ public sealed class RecordingCoordinatorTests
|
|||||||
|
|
||||||
private sealed class InMemoryMeetingArtifactStore : IMeetingArtifactStore
|
private sealed class InMemoryMeetingArtifactStore : IMeetingArtifactStore
|
||||||
{
|
{
|
||||||
|
private readonly TaskCompletionSource transcribing = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
private readonly bool createAssistantContextFile;
|
private readonly bool createAssistantContextFile;
|
||||||
private bool failNextMetadataUpdate;
|
private bool failNextMetadataUpdate;
|
||||||
|
|
||||||
@@ -4512,6 +4526,11 @@ public sealed class RecordingCoordinatorTests
|
|||||||
|
|
||||||
public List<AssistantContextState> States { get; } = [];
|
public List<AssistantContextState> States { get; } = [];
|
||||||
|
|
||||||
|
public Task WaitUntilTranscribingAsync()
|
||||||
|
{
|
||||||
|
return transcribing.Task.WaitAsync(TimeSpan.FromSeconds(15));
|
||||||
|
}
|
||||||
|
|
||||||
public string? Agenda { get; private set; }
|
public string? Agenda { get; private set; }
|
||||||
|
|
||||||
public DateTimeOffset? ScheduledEnd { get; private set; }
|
public DateTimeOffset? ScheduledEnd { get; private set; }
|
||||||
@@ -4553,6 +4572,10 @@ public sealed class RecordingCoordinatorTests
|
|||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
States.Add(state);
|
States.Add(state);
|
||||||
|
if (state == AssistantContextState.Transcribing)
|
||||||
|
{
|
||||||
|
transcribing.TrySetResult();
|
||||||
|
}
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||||
<MicrosoftSpeechVersion>1.51.2</MicrosoftSpeechVersion>
|
<MicrosoftSpeechVersion>1.52.0</MicrosoftSpeechVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">
|
<PropertyGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Agents.AI.OpenAI" Version="1.21.0" />
|
<PackageReference Include="Microsoft.Agents.AI.OpenAI" Version="1.23.0" />
|
||||||
<PackageReference Include="DiffPlex" Version="1.9.0" />
|
<PackageReference Include="DiffPlex" Version="1.9.0" />
|
||||||
<PackageReference Include="Microsoft.CognitiveServices.Speech" Version="$(MicrosoftSpeechVersion)" />
|
<PackageReference Include="Microsoft.CognitiveServices.Speech" Version="$(MicrosoftSpeechVersion)" />
|
||||||
<PackageReference Include="Microsoft.CognitiveServices.Speech.Extension.MAS" Version="$(MicrosoftSpeechVersion)" ExcludeAssets="build" />
|
<PackageReference Include="Microsoft.CognitiveServices.Speech.Extension.MAS" Version="$(MicrosoftSpeechVersion)" ExcludeAssets="build" />
|
||||||
|
|||||||
+6
@@ -22,6 +22,12 @@ Meeting Assistant SHALL capture microphone input and computer output and combine
|
|||||||
- **WHEN** automated tests run without live audio devices
|
- **WHEN** automated tests run without live audio devices
|
||||||
- **THEN** Meeting Assistant verifies the audio mixer through deterministic source abstractions rather than depending on physical microphone or speaker devices
|
- **THEN** Meeting Assistant verifies the audio mixer through deterministic source abstractions rather than depending on physical microphone or speaker devices
|
||||||
|
|
||||||
|
#### Scenario: Matching-stream verification controls source delivery
|
||||||
|
- **GIVEN** a microphone chunk has been accepted and matching system audio is pending within the supported skew window
|
||||||
|
- **WHEN** automated verification releases the matching system chunk
|
||||||
|
- **THEN** the mixer emits one combined chunk containing both sources
|
||||||
|
- **AND** source delivery is coordinated explicitly rather than assuming a timer continuation will run before the skew cutoff
|
||||||
|
|
||||||
### Requirement: Stopping recording drains captured audio through transcription
|
### Requirement: Stopping recording drains captured audio through transcription
|
||||||
Meeting Assistant SHALL stop capturing new audio when recording mode is stopped, but it SHALL allow already captured audio to finish running through the configured speech recognition pipeline before the recording session completes.
|
Meeting Assistant SHALL stop capturing new audio when recording mode is stopped, but it SHALL allow already captured audio to finish running through the configured speech recognition pipeline before the recording session completes.
|
||||||
|
|
||||||
|
|||||||
+6
@@ -58,6 +58,12 @@ The summary agent SHALL be able to read and write the assistant context body as
|
|||||||
- **WHEN** transcription, final speaker recognition, and summary generation progress
|
- **WHEN** transcription, final speaker recognition, and summary generation progress
|
||||||
- **THEN** Meeting Assistant updates assistant context frontmatter state to `transcribing`, `speaker recognition`, `summarizing`, `finished`, or `error` as appropriate
|
- **THEN** Meeting Assistant updates assistant context frontmatter state to `transcribing`, `speaker recognition`, `summarizing`, `finished`, or `error` as appropriate
|
||||||
|
|
||||||
|
#### Scenario: Summary failure follows initialized transcription
|
||||||
|
- **GIVEN** a meeting has completed its initial metadata collection and reached `transcribing`
|
||||||
|
- **WHEN** recording stops and automatic summary generation fails
|
||||||
|
- **THEN** assistant context transitions from `transcribing` to `summarizing` and then `error`
|
||||||
|
- **AND** observing captured audio alone does not establish that metadata collection has finished
|
||||||
|
|
||||||
#### Scenario: Summary pipeline is invoked
|
#### Scenario: Summary pipeline is invoked
|
||||||
- **WHEN** transcript processing finishes for the current meeting
|
- **WHEN** transcript processing finishes for the current meeting
|
||||||
- **THEN** the agent can read the transcript, assistant context, user notes, glossary, and bound project knowledge, can write the finished markdown summary to the configured summary note, and can update existing project files
|
- **THEN** the agent can read the transcript, assistant context, user notes, glossary, and bound project knowledge, can write the finished markdown summary to the configured summary note, and can update existing project files
|
||||||
|
|||||||
+6
@@ -27,6 +27,12 @@ A `transcript_line` trigger MAY filter by speaker name.
|
|||||||
- **THEN** the written transcript line contains `[redacted]`
|
- **THEN** the written transcript line contains `[redacted]`
|
||||||
- **AND** the written transcript line does not contain `*****`
|
- **AND** the written transcript line does not contain `*****`
|
||||||
|
|
||||||
|
#### Scenario: Live transcript verification does not obstruct durable writes
|
||||||
|
- **GIVEN** a live transcript line is being durably appended and then rewritten by a `transcript_line` rule
|
||||||
|
- **WHEN** automated behavior verification observes the live transcript file
|
||||||
|
- **THEN** its observer permits the ongoing append and rewrite rather than denying the writer access
|
||||||
|
- **AND** verification still requires the completed transcript line to contain the transformed text and no original masked profanity
|
||||||
|
|
||||||
### Requirement: Meeting automation rules support conditions and steps
|
### Requirement: Meeting automation rules support conditions and steps
|
||||||
Meeting Assistant SHALL support rule conditions using an expression engine.
|
Meeting Assistant SHALL support rule conditions using an expression engine.
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user