-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathAgentBridge.csproj
More file actions
312 lines (312 loc) · 25.5 KB
/
Copy pathAgentBridge.csproj
File metadata and controls
312 lines (312 loc) · 25.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<!-- Resource manifest base name for Resources/Dictionary (neutral + it/fr/es/de/ru
satellites). Explicit so Dictionary.Designer.cs never depends on the default. -->
<RootNamespace>AgentBridge</RootNamespace>
<!-- The executable is named `agent` (agent.exe on Windows, agent on Linux/macOS),
like `qwen` in Qwen Code. -->
<AssemblyName>agent</AssemblyName>
<!-- Release gate (see AGENTS.md): IsPrerelease=true while iterating → the version gets
"-prerelease" and the GitHub release workflow skips. Set IsPrerelease=false only when
the test cycles proved the version works → real release: pushing master with the gate
off triggers release.yml automatically (tag v1.yy.MM.dd created by the workflow).
Date-based auto version, same scheme as UISupportBlazor/UISupportGeneric
(1.yy.MM.dd, e.g. 1.26.08.09). -->
<IsPrerelease>true</IsPrerelease>
<!-- NuGet wait marker (release.ps1 / release.yml): true = the release build must wait for
today's core dependency packages on nuget.org (a core repo changed today and its
package is still propagating); false = no core repo changed since its last tag, so the
wait is skipped. Default true (conservative: a manual gate-off push without
release.ps1 still waits). -->
<NuGetWait>true</NuGetWait>
<!-- NuGetWaitPackages: the comma-separated package ids the release build waits for when
<NuGetWait> is true. release.ps1 narrows it to the repos that actually changed today so
the wait exits as soon as those propagate instead of burning the full 30 minutes on
repos that never publish today's version; the default (all wait-checked packages) is
the conservative fallback for a manual gate-off push. -->
<NuGetWaitPackages>graphene.aiorchestrator,alltomarkdown,mermaidrendering,graphene.reversemarkdown,uisupportgeneric,systemextra</NuGetWaitPackages>
<!-- Naiad (transitive dep of Graphene.AIOrchestrator) SponsorCheck: the property is
required when building Release; set in every project consuming the package. -->
<Papyrine_SponsorshipLicenseIgnored>true</Papyrine_SponsorshipLicenseIgnored>
<!-- ReleaseDate: date of the intended release version 1.yy.MM.dd. Empty (default) →
the BUILD date is used (DateTime.Now, local to wherever msbuild runs); release.ps1
pins it to the LOCAL push date in the gate-off commit, so CI (DateTime.Now = UTC)
can never derive the previous UTC day across the local/UTC-midnight boundary and
reuse an already-released version (release.yml would skip it). -->
<ReleaseDate Condition="'$(ReleaseDate)' == ''">$([System.DateTime]::Now.ToString("yy.MM.dd"))</ReleaseDate>
<Version Condition="'$(IsPrerelease)' == 'true'">1.$(ReleaseDate)-prerelease</Version>
<Version Condition="'$(IsPrerelease)' != 'true'">1.$(ReleaseDate)</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Terminal.Gui" Version="2.4.17" />
<PackageReference Include="Terminal.Gui.Editor" Version="2.5.7" />
<!-- Dual-reference pattern (as in UISupportBlazor): the local sibling project wins when
present (solution builds); the NuGet package (1.* floating = always latest) is restored
when the sibling is absent — CI builds against the published package. -->
<ProjectReference Include="..\AIOrchestrator\AIOrchestrator.csproj" Condition="Exists('..\AIOrchestrator\AIOrchestrator.csproj')" />
<PackageReference Include="Graphene.AIOrchestrator" Version="1.*" Condition="!Exists('..\AIOrchestrator\AIOrchestrator.csproj')" />
<!-- Auto-start on boot (SystemExtra.Util — Task Scheduler task on Windows, systemd service
on Linux/macOS). Same dual-reference pattern as AIOrchestrator: the local sibling wins
in solution builds, the NuGet package is restored when the sibling is absent (CI). -->
<ProjectReference Include="..\SystemExtra\SystemExtra.csproj" Condition="Exists('..\SystemExtra\SystemExtra.csproj')" />
<PackageReference Include="SystemExtra" Version="1.*" Condition="!Exists('..\SystemExtra\SystemExtra.csproj')" />
<!-- Tool plugins (DocumentTool, SpreadsheetTool, OfficeTool) are loaded DYNAMICALLY from
the Tools/ folder (see ToolPlugins.cs + AIOrchestrator.ToolPluginHost): the agent sets
in AgentTools.cs pass tool NAMES, so no AgentBridge code references a plugin type and
NO Project/Package reference to a plugin exists here (see ShipToolPlugins below). -->
<!-- Kokoro neural TTS (same engine/voices as AIOffice.VoiceAgent.Win). The package ships
voices/ + voices-zh/ as content (copied to the output directory); since 0.8.4 the
phonemizer is MisakiSharp (pure managed — no espeak-ng binaries are shipped or needed).
Only kokoro.onnx (~325 MB, not tracked in git) is handled by the DownloadKokoroModel
target below. TTS endpoints return 501 until the model file is present. -->
<PackageReference Include="KokoroSharp" Version="0.8.4" />
<!-- Native ONNX Runtime engine (per-OS/arch, selected by the build RID): KokoroSharp only
references the managed wrapper (Microsoft.ML.OnnxRuntime.Managed), which contains no
native library — without this the released archives ship TTS that fails at inference
(works on this dev machine only because Windows 11 24H2+ provides onnxruntime.dll in
System32). 1.23.0 matches the minimum required by YoloDotNet.ExecutionProvider.Cpu
(AIOrchestrator object detection); its managed assembly shadows the .Managed 1.22.0
wrapper pulled by KokoroSharp, keeping the managed+native pair consistent at runtime.
ExcludeAssets="build;buildTransitive": the package's buildTransitive props
unconditionally declare runtimes/win-arm64 native files that are NOT shipped (the
MSB3030 phantom-copy failure on every non-win-x64 publish — WSL and Windows
cross-publish to linux-arm64 alike) and copy the win-x64 onnxruntime.dll into the
output for AnyCPU projects; both drop 13 MB+ of junk. The correct per-RID native lib
is still bundled into the single file via IncludeNativeLibrariesForSelfExtract.
GPU build (Gpu.Windows): the CUDA EP ships in onnxruntime_providers_cuda.dll (~400 MB,
loaded on demand) — the TTS (and plugins like PodcastTool) run on CUDA automatically
when the CUDA Toolkit 12.x + cuDNN 9.x are installed (the engine probes the toolkit
dirs and sets the PATH itself — no manual setup); machines without CUDA keep the CPU
EP (the provider DLL is never loaded). -->
<PackageReference Include="Microsoft.ML.OnnxRuntime.Gpu.Windows" Version="1.23.0" ExcludeAssets="build;buildTransitive" />
<!-- Linux GPU parity: the same CUDA execution provider for the linux RIDs. Without it a
Linux machine with a big NVIDIA GPU could never accelerate Kokoro (only the CPU
libonnxruntime.so ships) — the CUDA EP is loaded on demand, machines without CUDA
keep the CPU EP. Same pinned version → the per-RID libonnxruntime.so + CUDA provider
form a consistent pair with the 1.23.0 managed wrapper. -->
<PackageReference Include="Microsoft.ML.OnnxRuntime.Gpu.Linux" Version="1.23.0" Condition="'$(RuntimeIdentifier)' == 'linux-x64' or '$(RuntimeIdentifier)' == 'linux-arm64'" ExcludeAssets="build;buildTransitive" />
<!-- SIP telephony (incoming auto-answer + PIN, outgoing calls, RTP audio loop to the
agent): SIPSorcery implements the UAS/UAC, REGISTER, RFC 4733 DTMF and the
G.711/G.722 codecs on its own; the OPUS codec is bundled inside SIPSorcery (via
Concentus) but not offered by default — SipBridge reorders the offer so the STT
gets wideband (16 kHz) audio: Opus → G.722 → PCMA → PCMU. Concentus is referenced
directly for the Opus DECODE on the receive path (SIPSorcery only exposes the
encode/decode through its AudioEncoder wrapper, which we reuse for sending). -->
<PackageReference Include="SIPSorcery" Version="10.0.15" />
<PackageReference Include="Concentus" Version="2.2.2" />
<!-- Telegram client API (MTProto userbot) for the text-chat medium: incoming/outgoing
messages and file attachments, no audio (the Telegram Client API has no audio-call
support — see docs/telegram.md). Config lives in telegram.json next to the executable
(never overwritten by updates — see AutoUpdate.cs) and is editable from the TUI or by
hand. Session state (auth keys) persists in a .session file so no re-login is needed
after the first pairing. -->
<PackageReference Include="WTelegramClient" Version="4.4.8" />
<!-- The processing-indicator cue (played to the SIP caller while the agent computes) ships
as an EMBEDDED resource — a copied content file proved unreliable on the OneDrive-synced
bin folder (the file vanished, the indicator silently disabled: "asset not found" on real
calls). An embedded resource travels inside the assembly, immune to the deployed file state. -->
<EmbeddedResource Include="assets\processing-indicator.wav" LogicalName="AgentBridge.assets.processing-indicator.wav" />
<!-- The AGENT ASCII art (startup banner + Help → About) travels inside the assembly:
a loose .txt in the output proved unreliable on the OneDrive-synced bin folder
(same lesson as the processing-indicator.wav). Loaded once by ConsoleTui. -->
<EmbeddedResource Include="AGRNT_ascii_art.txt" LogicalName="AgentBridge.assets.agent-ascii-art.txt" />
<!-- appsettings.json is USER-EDITABLE configuration: the single-config-directory rule
(AppConfig + docs-dev/RELEASE-CHECKLIST.md) forbids shipping it next to the executable.
The Web SDK would copy the repo file into the output; instead it is EMBEDDED and
AppConfig.Initialize() seeds it into PersistentData\ on the first run. -->
<Content Update="appsettings.json" CopyToOutputDirectory="Never" CopyToPublishDirectory="Never" />
<EmbeddedResource Include="appsettings.json" LogicalName="AgentBridge.appsettings.default.json" />
</ItemGroup>
<!-- e2e/ hosts standalone tooling (TuiSmoke, run_e2e.ps1) and tools/ hosts standalone
developer tooling (ConsolePuppetInjector) — never part of this project. WITHOUT these
Remove items the Web SDK's default globbing compiles their Program.cs AND obj-generated
AssemblyInfo files into AgentBridge (duplicate `Program` with the top-level statements,
duplicate assembly attributes → CS0579) and every build FAILS silently. Keep them. -->
<ItemGroup>
<Compile Remove="e2e\**" />
<Content Remove="e2e\**" />
<None Remove="e2e\**" />
<EmbeddedResource Remove="e2e\**" />
<Compile Remove="tools\**" />
<Content Remove="tools\**" />
<None Remove="tools\**" />
<EmbeddedResource Remove="tools\**" />
</ItemGroup>
<!-- Localized UI strings (EN neutral + it/fr/es/de/ru satellites) — see Resources/Dictionary.*.resx.
The satellite files are picked up automatically by the SDK; the neutral file drives the
generated Designer used from Tui.cs / Program.cs. -->
<ItemGroup>
<Compile Update="Resources\Dictionary.Designer.cs">
<DependentUpon>Dictionary.resx</DependentUpon>
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources\Dictionary.resx">
<LastGenOutput>Dictionary.Designer.cs</LastGenOutput>
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
</ItemGroup>
<!-- User documentation + setup scripts: docs/ is the END-USER documentation and ships
next to the executable (bin output, publish output and therefore every release
archive). The DEVELOPER documentation lives in docs-dev/ and is deliberately NOT
included here — it stays in the repository only. media/ (README demo assets) is
also excluded. The user-facing setup scripts (setup-telegram.*, sip-config.*) ship
alongside in scripts/. Keep this list in sync with AGENTS.md "Documentation: two
types". -->
<ItemGroup>
<None Include="docs\**\*" CopyToOutputDirectory="PreserveNewest" TargetPath="docs/%(RecursiveDir)%(Filename)%(Extension)" />
<None Include="scripts\setup-telegram.bat;scripts\setup-telegram.sh;scripts\sip-config.bat;scripts\sip-config.ps1;scripts\sip-config.sh" CopyToOutputDirectory="PreserveNewest" TargetPath="scripts/%(Filename)%(Extension)" />
<!-- The OfficeManager web app (16-bit office, served at /OfficeManager + /ws/office hub):
ships next to the executable like docs/ so the static files + WebSocket protocol are
always available wherever the server runs. Developer-only content (docs-dev/, tools/,
the GameBase requirements doc) stays out of the shipped copy, like everywhere else. -->
<None Include="OfficeManager\**\*" Exclude="OfficeManager\docs-dev\**;OfficeManager\tools\**;OfficeManager\GameBase.txt" CopyToOutputDirectory="PreserveNewest" TargetPath="OfficeManager/%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>
<!-- Ship the tool plugins into Tools/<Plugin>/ so the dynamic ToolPluginHost loader can pick
them up at startup. PLUGIN DISCOVERY IS DYNAMIC: any sibling *Tool repo whose csproj
name ends with "Tool" is a plugin (DocumentTool, SpreadsheetTool, OfficeTool,
OfficeSupportTool, ... future ones) — no fixed list to maintain; adding a new plugin
repo just works. Skipped when the sibling repos are absent (empty item → the target
Conditions are false) — CI/standalone deploys fetch the plugin release zips from GitHub
Releases (see .github/workflows/release.yml). The item is declared OUTSIDE the targets
because MSBuild evaluates a target's Condition before its body: an in-body item would
make the Condition always false.
The plugin's OWN files (assembly, pdb, xml, deps.json) are ALWAYS refreshed, so a
changed plugin never stays stale in the host bin. The extra dependencies travel with the
plugin ONLY when the host bin does not already provide them (NON-HOST DEPENDENCIES RULE,
AGENT_TOOLS_GUIDE §8): AIOrchestrator and its graph stay with the host, the plugin's
unique deps (e.g. HtmlToOpenXml/AngleSharp for OfficeSupportTool) travel with it
automatically — the Exists(%(Filename)) condition implements the "closure minus host
bin" filter mechanically, no per-plugin dependency lists to maintain. -->
<ItemGroup>
<_PluginProject Include="..\*Tool\*Tool.csproj" />
</ItemGroup>
<Target Name="ShipToolPlugins" AfterTargets="Build" Condition="'@(_PluginProject)' != ''">
<MSBuild Projects="@(_PluginProject)" Targets="Build" Properties="Configuration=$(Configuration);RuntimeIdentifier=;CopyLocalLockFileAssemblies=true" />
</Target>
<!-- Runs the shipping helper once per discovered plugin (the MSBuild task batches on the
per-project metadata Dir/Name, so each plugin lands in its own Tools/<Plugin>/ folder). -->
<Target Name="ShipOnePlugin" AfterTargets="ShipToolPlugins" Condition="'@(_PluginProject)' != ''">
<ItemGroup>
<_PluginJob Include="@(_PluginProject)">
<Dir>%(RootDir)%(Directory)</Dir>
<Name>%(Filename)</Name>
</_PluginJob>
</ItemGroup>
<MSBuild Projects="$(MSBuildThisFileDirectory)ShipOnePlugin.targets" Targets="Ship" Properties="PluginDir=%(_PluginJob.Dir);PluginName=%(_PluginJob.Name);Configuration=$(Configuration);OutputPath=$(OutputPath)" />
</Target>
<!-- Copies the tool plugins into a publish output (same rationale as AIOffice's
CopyToolPluginsToPublish): the Content-item registration drops files outside the
project directory, so a direct Copy is used. -->
<Target Name="CopyToolPluginsToPublish" AfterTargets="Publish" Condition="'$(PublishDir)' != '' and Exists('$(OutputPath)Tools')">
<ItemGroup>
<_ToolPluginPublish Include="$(OutputPath)Tools\**\*" />
</ItemGroup>
<Copy SourceFiles="@(_ToolPluginPublish)" DestinationFiles="@(_ToolPluginPublish->'$(PublishDir)Tools\%(RecursiveDir)%(Filename)%(Extension)')" />
</Target>
<!-- Provide kokoro.onnx in the output directory (the 325 MB model binary is not tracked in
git). Preferred source: the sibling VoiceAgent build output (already downloaded there on
dev machines); fallback: the local copy at the project root; last resort: download with
curl (same URL as AIOffice.VoiceAgent.Win). -->
<Target Name="DownloadKokoroModel" BeforeTargets="Build;Publish" Condition="!Exists('$(OutputPath)kokoro.onnx')">
<MakeDir Directories="$(OutputPath)" />
<Copy Condition="Exists('..\AIOffice.VoiceAgent.Win\bin\$(Configuration)\net10.0-windows10.0.19041.0\kokoro.onnx')" SourceFiles="..\AIOffice.VoiceAgent.Win\bin\$(Configuration)\net10.0-windows10.0.19041.0\kokoro.onnx" DestinationFiles="$(OutputPath)kokoro.onnx" />
<Copy Condition="!Exists('$(OutputPath)kokoro.onnx') and Exists('kokoro.onnx')" SourceFiles="kokoro.onnx" DestinationFiles="$(OutputPath)kokoro.onnx" />
<Exec Condition="!Exists('$(OutputPath)kokoro.onnx')" Command="curl -L --fail -o "$(OutputPath)kokoro.onnx" "https://github.com/Lyrcaxis/KokoroSharpBinaries/releases/download/v2.0.0/kokoro.onnx"" />
<Message Text="kokoro.onnx ready at $(OutputPath)" Importance="high" />
</Target>
<!-- The KokoroSharp content (voices/ + voices-zh/) and kokoro.onnx land in $(OutputPath), but
`dotnet publish -o <dir>` does not carry them over: the publish dir must contain them or
the TTS endpoints stay broken. Copy them explicitly (CI release archives rely on this).
Since KokoroSharp 0.8.4 there is no espeak/ content anymore (MisakiSharp phonemizer). -->
<Target Name="CopyTtsAssetsToPublish" AfterTargets="Publish">
<ItemGroup>
<_TtsVoiceFiles Include="$(OutputPath)voices\**\*" />
</ItemGroup>
<Copy SourceFiles="@(_TtsVoiceFiles)" DestinationFiles="@(_TtsVoiceFiles->'$(PublishDir)voices\%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="true" />
<Copy SourceFiles="$(OutputPath)kokoro.onnx" DestinationFolder="$(PublishDir)" Condition="Exists('$(OutputPath)kokoro.onnx')" />
<!-- The Microsoft.ML.OnnxRuntime package also carries C++ import libraries
(runtimes/win-x64/native/*.lib) — build-time only, junk in the archive. -->
<Delete Files="$(PublishDir)onnxruntime.lib;$(PublishDir)onnxruntime_providers_shared.lib;$(PublishDir)onnxruntime_providers_cuda.lib;$(PublishDir)onnxruntime_providers_tensorrt.lib" Condition="Exists('$(PublishDir)onnxruntime.lib')" />
</Target>
<!-- SearchPioneer.Lingua language models (Lingua/LanguageModels/<lang>/*.json.br): the
package's own build/ target copies them ONLY into the output of projects that reference
it directly — Graphene.AIOrchestrator is a transitive dependency here (restored via
NuGet in CI, where the sibling project is absent), so released archives never carry
Lingua\ and the first LanguageDetector call (podcast topic language, document
language-word tracking) throws "Could not find a part of the path ... Lingua\
LanguageModels\<lang>\...". Copy the models explicitly, like the TTS assets above.
Source: the restored package content (version kept in sync with AIOrchestrator.csproj). -->
<Target Name="CopyLinguaModelsToOutput" AfterTargets="Build" Condition="'$(NuGetPackageRoot)' != '' and Exists('$(NuGetPackageRoot)searchpioneer.lingua\2.0.0\contentFiles\any\any\Lingua')">
<ItemGroup>
<_LinguaModelFiles Include="$(NuGetPackageRoot)searchpioneer.lingua\2.0.0\contentFiles\any\any\Lingua\**\*" />
</ItemGroup>
<Copy SourceFiles="@(_LinguaModelFiles)" DestinationFiles="@(_LinguaModelFiles->'$(OutputPath)Lingua\%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="true" />
</Target>
<Target Name="CopyLinguaModelsToPublish" AfterTargets="Publish" Condition="'$(PublishDir)' != '' and Exists('$(NuGetPackageRoot)searchpioneer.lingua\2.0.0\contentFiles\any\any\Lingua')">
<ItemGroup>
<_LinguaPublish Include="$(NuGetPackageRoot)searchpioneer.lingua\2.0.0\contentFiles\any\any\Lingua\**\*" />
</ItemGroup>
<Copy SourceFiles="@(_LinguaPublish)" DestinationFiles="@(_LinguaPublish->'$(PublishDir)Lingua\%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="true" />
</Target>
<!-- Microsoft.Playwright driver (.playwright/): the package's build targets place the
per-RID node driver + package under $(OutputPath).playwright, but dotnet publish does
not carry them over — released archives shipped an incomplete .playwright (only the
package + LICENSE) and the first Playwright.CreateAsync() inside the single-file exe
threw NullReferenceException (WebTool browser launch failed; observed on the win-x64
install 2026-09-05). Copy the whole payload into the publish dir like the TTS assets.
Each RID build carries its own driver (win32_x64/linux-x64/darwin-*) so the Linux and
macOS archives get theirs too. -->
<Target Name="CopyPlaywrightToPublish" AfterTargets="Publish" Condition="'$(PublishDir)' != '' and Exists('$(OutputPath).playwright')">
<ItemGroup>
<_PlaywrightFiles Include="$(OutputPath).playwright\**\*" />
</ItemGroup>
<Copy SourceFiles="@(_PlaywrightFiles)" DestinationFiles="@(_PlaywrightFiles->'$(PublishDir).playwright\%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="true" />
</Target>
<!-- Optional Windows voice bridge: when the sibling AIOffice.VoiceAgent.Win build output
exists, copy its whole output (exe + voices/ + espeak/ + kokoro.onnx + runtime deps)
into a voiceagent/ subfolder so POST /v1/voice/listen can spawn it. The subfolder is
deliberate: VoiceAgent.Win pins its own KokoroSharp (0.6.7), and copying it flat next
to the server would shadow this project's 0.8.4 KokoroSharp.dll — TtsEngine then dies
with "Could not load type KokoroSharp.KokoroWavSynthesizer" and EVERY /v1/control call
fails, leaving the TUI without a session. Runs on Publish too so the single-file
production exe has the voice bridge alongside.
CI/public releases have NO sibling build: release.yml (win-x64 job) downloads the
AIOffice.VoiceAgent.Win self-contained zip from its own GitHub release into
publish/voiceagent/ instead — see .github/workflows/release.yml. -->
<Target Name="CopyVoiceAgentOutput" AfterTargets="Build;Publish" Condition="$([MSBuild]::IsOSPlatform('Windows')) and Exists('..\AIOffice.VoiceAgent.Win\bin\$(Configuration)\net10.0-windows10.0.19041.0\AIOffice.VoiceAgent.Win.exe')">
<ItemGroup>
<_VoiceAgentFiles Include="..\AIOffice.VoiceAgent.Win\bin\$(Configuration)\net10.0-windows10.0.19041.0\**\*" Exclude="..\AIOffice.VoiceAgent.Win\bin\$(Configuration)\net10.0-windows10.0.19041.0\logs\**\*" />
</ItemGroup>
<Copy SourceFiles="@(_VoiceAgentFiles)" DestinationFiles="@(_VoiceAgentFiles->'$(OutputPath)voiceagent\%(RecursiveDir)%(Filename)%(Extension)')" />
<Message Text="VoiceAgent.Win output copied to $(OutputPath)voiceagent\" Importance="high" />
</Target>
<!-- Speech-to-text agent for the SIP telephony loop: the cross-platform AIOffice.VoiceAgent
executable (whisper.net) is spawned with the transcribe command to turn the RTP audio of a
call into text. Copied into its own voiceagent-stt/ subfolder (like VoiceAgent.Win) so its
dependencies cannot shadow the server's KokoroSharp. kokoro.onnx/voices are excluded: the
transcribe path never uses TTS, and they would add ~350 MB to the archive. On Linux/macOS
the deploy script must place the executable in voiceagent-stt/ alongside the server. -->
<Target Name="CopySttAgentOutput" AfterTargets="Build;Publish" Condition="$([MSBuild]::IsOSPlatform('Windows')) and Exists('..\AIOffice.VoiceAgent\bin\$(Configuration)\net10.0\AIOffice.VoiceAgent.exe')">
<ItemGroup>
<_SttFiles Include="..\AIOffice.VoiceAgent\bin\$(Configuration)\net10.0\**\*" Exclude="..\AIOffice.VoiceAgent\bin\$(Configuration)\net10.0\logs\**\*;..\AIOffice.VoiceAgent\bin\$(Configuration)\net10.0\kokoro.onnx;..\AIOffice.VoiceAgent\bin\$(Configuration)\net10.0\voices\**\*" />
</ItemGroup>
<Copy SourceFiles="@(_SttFiles)" DestinationFiles="@(_SttFiles->'$(OutputPath)voiceagent-stt\%(RecursiveDir)%(Filename)%(Extension)')" />
<Message Text="VoiceAgent (STT) output copied to $(OutputPath)voiceagent-stt\" Importance="high" />
</Target>
<!-- Root user-editable config must NEVER sit next to the executable (single-config-directory
rule, AppConfig + docs-dev/RELEASE-CHECKLIST.md). providers.json is copied in by
AIOrchestrator's Content items (ProjectReference) and appsettings.json by the Web SDK;
the copies are removed after Build and after Publish as a safety net — the app reads both
from PersistentData\ (seeded/migrated at startup by AppConfig.Initialize()). -->
<Target Name="RemoveRootConfigJson" AfterTargets="Build;Publish">
<Delete Files="$(OutputPath)appsettings.json;$(OutputPath)providers.json" Condition="Exists('$(OutputPath)appsettings.json') or Exists('$(OutputPath)providers.json')" />
<Delete Files="$(PublishDir)appsettings.json;$(PublishDir)providers.json" Condition="'$(PublishDir)' != '' and (Exists('$(PublishDir)appsettings.json') or Exists('$(PublishDir)providers.json'))" />
</Target>
</Project>