@sumicom/quicksave 0.8.14 → 0.8.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/dist/ai/cardBuilder.d.ts +21 -2
  2. package/dist/ai/cardBuilder.d.ts.map +1 -1
  3. package/dist/ai/cardBuilder.js +100 -6
  4. package/dist/ai/cardBuilder.js.map +1 -1
  5. package/dist/ai/claudeCliProvider.d.ts +25 -2
  6. package/dist/ai/claudeCliProvider.d.ts.map +1 -1
  7. package/dist/ai/claudeCliProvider.js +76 -19
  8. package/dist/ai/claudeCliProvider.js.map +1 -1
  9. package/dist/ai/codexAppServer/cardAdapter.d.ts.map +1 -1
  10. package/dist/ai/codexAppServer/cardAdapter.js +87 -7
  11. package/dist/ai/codexAppServer/cardAdapter.js.map +1 -1
  12. package/dist/ai/codexAppServer/provider.d.ts +3 -0
  13. package/dist/ai/codexAppServer/provider.d.ts.map +1 -1
  14. package/dist/ai/codexAppServer/provider.js +15 -4
  15. package/dist/ai/codexAppServer/provider.js.map +1 -1
  16. package/dist/ai/codexMcpProvider.d.ts.map +1 -1
  17. package/dist/ai/codexMcpProvider.js +6 -0
  18. package/dist/ai/codexMcpProvider.js.map +1 -1
  19. package/dist/ai/provider.d.ts +3 -0
  20. package/dist/ai/provider.d.ts.map +1 -1
  21. package/dist/ai/provider.js.map +1 -1
  22. package/dist/ai/queuedUserPrompts.d.ts +6 -0
  23. package/dist/ai/queuedUserPrompts.d.ts.map +1 -1
  24. package/dist/ai/queuedUserPrompts.js +9 -0
  25. package/dist/ai/queuedUserPrompts.js.map +1 -1
  26. package/dist/ai/realtimeTranscription.d.ts +2 -0
  27. package/dist/ai/realtimeTranscription.d.ts.map +1 -1
  28. package/dist/ai/realtimeTranscription.js +14 -0
  29. package/dist/ai/realtimeTranscription.js.map +1 -1
  30. package/dist/ai/sessionManager.d.ts +12 -0
  31. package/dist/ai/sessionManager.d.ts.map +1 -1
  32. package/dist/ai/sessionManager.js +73 -12
  33. package/dist/ai/sessionManager.js.map +1 -1
  34. package/dist/ai/voiceIntermediary/historyStore.d.ts +54 -0
  35. package/dist/ai/voiceIntermediary/historyStore.d.ts.map +1 -0
  36. package/dist/ai/voiceIntermediary/historyStore.js +159 -0
  37. package/dist/ai/voiceIntermediary/historyStore.js.map +1 -0
  38. package/dist/ai/voiceIntermediary/index.d.ts +13 -0
  39. package/dist/ai/voiceIntermediary/index.d.ts.map +1 -0
  40. package/dist/ai/voiceIntermediary/index.js +13 -0
  41. package/dist/ai/voiceIntermediary/index.js.map +1 -0
  42. package/dist/ai/voiceIntermediary/llm.d.ts +52 -0
  43. package/dist/ai/voiceIntermediary/llm.d.ts.map +1 -0
  44. package/dist/ai/voiceIntermediary/llm.js +63 -0
  45. package/dist/ai/voiceIntermediary/llm.js.map +1 -0
  46. package/dist/ai/voiceIntermediary/manager.d.ts +58 -0
  47. package/dist/ai/voiceIntermediary/manager.d.ts.map +1 -0
  48. package/dist/ai/voiceIntermediary/manager.js +134 -0
  49. package/dist/ai/voiceIntermediary/manager.js.map +1 -0
  50. package/dist/ai/voiceIntermediary/memory.d.ts +22 -0
  51. package/dist/ai/voiceIntermediary/memory.d.ts.map +1 -0
  52. package/dist/ai/voiceIntermediary/memory.js +109 -0
  53. package/dist/ai/voiceIntermediary/memory.js.map +1 -0
  54. package/dist/ai/voiceIntermediary/session.d.ts +81 -0
  55. package/dist/ai/voiceIntermediary/session.d.ts.map +1 -0
  56. package/dist/ai/voiceIntermediary/session.js +567 -0
  57. package/dist/ai/voiceIntermediary/session.js.map +1 -0
  58. package/dist/ai/voiceIntermediary/tools.d.ts +58 -0
  59. package/dist/ai/voiceIntermediary/tools.d.ts.map +1 -0
  60. package/dist/ai/voiceIntermediary/tools.js +277 -0
  61. package/dist/ai/voiceIntermediary/tools.js.map +1 -0
  62. package/dist/ai/voiceIntermediary/tts.d.ts +28 -0
  63. package/dist/ai/voiceIntermediary/tts.d.ts.map +1 -0
  64. package/dist/ai/voiceIntermediary/tts.js +66 -0
  65. package/dist/ai/voiceIntermediary/tts.js.map +1 -0
  66. package/dist/ai/voiceLog.d.ts +18 -0
  67. package/dist/ai/voiceLog.d.ts.map +1 -0
  68. package/dist/ai/voiceLog.js +74 -0
  69. package/dist/ai/voiceLog.js.map +1 -0
  70. package/dist/ai/voiceStream.d.ts +5 -0
  71. package/dist/ai/voiceStream.d.ts.map +1 -1
  72. package/dist/ai/voiceStream.js +29 -5
  73. package/dist/ai/voiceStream.js.map +1 -1
  74. package/dist/handlers/legacyBusAdapter.d.ts.map +1 -1
  75. package/dist/handlers/legacyBusAdapter.js +8 -0
  76. package/dist/handlers/legacyBusAdapter.js.map +1 -1
  77. package/dist/handlers/messageHandler.d.ts +14 -0
  78. package/dist/handlers/messageHandler.d.ts.map +1 -1
  79. package/dist/handlers/messageHandler.js +150 -1
  80. package/dist/handlers/messageHandler.js.map +1 -1
  81. package/dist/index.js +2 -2
  82. package/dist/service/ipcClient.js +1 -1
  83. package/dist/service/run.d.ts.map +1 -1
  84. package/dist/service/run.js +14 -1
  85. package/dist/service/run.js.map +1 -1
  86. package/dist/service/types.js +1 -1
  87. package/package.json +3 -3
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Voice intermediary ("AI coworker") — a daemon-side tool-calling LLM that
3
+ * interprets the coding agent's output and lets the user steer it by voice.
4
+ * Brain + TTS ride the same OpenAI-compatible endpoint as STT (VoiceConfig).
5
+ *
6
+ * See docs/references/quicksave-architecture.en.md (section two).
7
+ */
8
+ export { VoiceIntermediaryManager } from './manager.js';
9
+ export type { VoiceManagerBridge } from './manager.js';
10
+ export { VOICE_AGENT_TOOLS, formatCardForBrain } from './tools.js';
11
+ export type { CodingSessionBridge } from './tools.js';
12
+ export { loadMemory, appendMemory } from './memory.js';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ai/voiceIntermediary/index.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AACxD,YAAY,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACnE,YAAY,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,13 @@
1
+ // SPDX-FileCopyrightText: 2026 King Young Technology
2
+ // SPDX-License-Identifier: MIT
3
+ /**
4
+ * Voice intermediary ("AI coworker") — a daemon-side tool-calling LLM that
5
+ * interprets the coding agent's output and lets the user steer it by voice.
6
+ * Brain + TTS ride the same OpenAI-compatible endpoint as STT (VoiceConfig).
7
+ *
8
+ * See docs/references/quicksave-architecture.en.md (section two).
9
+ */
10
+ export { VoiceIntermediaryManager } from './manager.js';
11
+ export { VOICE_AGENT_TOOLS, formatCardForBrain } from './tools.js';
12
+ export { loadMemory, appendMemory } from './memory.js';
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/ai/voiceIntermediary/index.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,+BAA+B;AAE/B;;;;;;GAMG;AACH,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAExD,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEnE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Minimal OpenAI-compatible chat-completions client for the voice intermediary
3
+ * brain. Raw `fetch` (no SDK) to `{baseUrl}/chat/completions`, mirroring the
4
+ * transcription client in `voiceTranscription.ts` — so STT, the brain, and TTS
5
+ * all ride the SAME cloud provider configured once in `VoiceConfig`.
6
+ */
7
+ import type { VoiceConfig } from '@sumicom/quicksave-shared';
8
+ export interface ToolCall {
9
+ id: string;
10
+ type: 'function';
11
+ function: {
12
+ name: string;
13
+ arguments: string;
14
+ };
15
+ }
16
+ /** OpenAI chat message shape (request + response). */
17
+ export interface ChatMessage {
18
+ role: 'system' | 'user' | 'assistant' | 'tool';
19
+ content: string | null;
20
+ tool_calls?: ToolCall[];
21
+ tool_call_id?: string;
22
+ }
23
+ export interface ToolSchema {
24
+ type: 'function';
25
+ function: {
26
+ name: string;
27
+ description: string;
28
+ parameters: Record<string, unknown>;
29
+ };
30
+ }
31
+ export interface ChatResult {
32
+ /** Assistant text to speak (may be empty when the turn is pure tool calls). */
33
+ content: string;
34
+ toolCalls: ToolCall[];
35
+ }
36
+ export declare class VoiceLlmError extends Error {
37
+ readonly cause?: unknown | undefined;
38
+ constructor(message: string, cause?: unknown | undefined);
39
+ }
40
+ /** `fetch` is injectable for tests. */
41
+ export type FetchLike = typeof fetch;
42
+ /**
43
+ * One chat-completions round. Returns the assistant's spoken text plus any tool
44
+ * calls it wants run. The caller drives the tool loop.
45
+ */
46
+ export declare function chatCompletion(config: VoiceConfig, opts: {
47
+ messages: ChatMessage[];
48
+ tools: ToolSchema[];
49
+ signal?: AbortSignal;
50
+ fetchImpl?: FetchLike;
51
+ }): Promise<ChatResult>;
52
+ //# sourceMappingURL=llm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm.d.ts","sourceRoot":"","sources":["../../../src/ai/voiceIntermediary/llm.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/C;AAED,sDAAsD;AACtD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC;IAC/C,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,CAAC,EAAE,QAAQ,EAAE,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACrC,CAAC;CACH;AAED,MAAM,WAAW,UAAU;IACzB,+EAA+E;IAC/E,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,QAAQ,EAAE,CAAC;CACvB;AAED,qBAAa,aAAc,SAAQ,KAAK;IACT,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO;gBAAzC,OAAO,EAAE,MAAM,EAAW,KAAK,CAAC,EAAE,OAAO,YAAA;CAItD;AAYD,uCAAuC;AACvC,MAAM,MAAM,SAAS,GAAG,OAAO,KAAK,CAAC;AAErC;;;GAGG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE;IACJ,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB,GACA,OAAO,CAAC,UAAU,CAAC,CA0CrB"}
@@ -0,0 +1,63 @@
1
+ // SPDX-FileCopyrightText: 2026 King Young Technology
2
+ // SPDX-License-Identifier: MIT
3
+ export class VoiceLlmError extends Error {
4
+ cause;
5
+ constructor(message, cause) {
6
+ super(message);
7
+ this.cause = cause;
8
+ this.name = 'VoiceLlmError';
9
+ }
10
+ }
11
+ function apiUrl(baseUrl, path) {
12
+ return `${baseUrl.trim().replace(/\/+$/, '')}${path}`;
13
+ }
14
+ function authHeaders(config) {
15
+ const headers = { 'Content-Type': 'application/json' };
16
+ if (config.apiKey.trim())
17
+ headers.Authorization = `Bearer ${config.apiKey.trim()}`;
18
+ return headers;
19
+ }
20
+ /**
21
+ * One chat-completions round. Returns the assistant's spoken text plus any tool
22
+ * calls it wants run. The caller drives the tool loop.
23
+ */
24
+ export async function chatCompletion(config, opts) {
25
+ const model = config.agentModel?.trim();
26
+ if (!model)
27
+ throw new VoiceLlmError('No agentModel configured for the voice intermediary.');
28
+ const doFetch = opts.fetchImpl ?? fetch;
29
+ let res;
30
+ try {
31
+ res = await doFetch(apiUrl(config.baseUrl, '/chat/completions'), {
32
+ method: 'POST',
33
+ headers: authHeaders(config),
34
+ body: JSON.stringify({
35
+ model,
36
+ messages: opts.messages,
37
+ tools: opts.tools,
38
+ tool_choice: 'auto',
39
+ }),
40
+ signal: opts.signal,
41
+ });
42
+ }
43
+ catch (err) {
44
+ throw new VoiceLlmError('Could not reach the chat-completions endpoint.', err);
45
+ }
46
+ if (!res.ok) {
47
+ const detail = await res.text().catch(() => '');
48
+ throw new VoiceLlmError(`Chat completion failed (${res.status})${detail ? `: ${detail.slice(0, 200)}` : ''}`);
49
+ }
50
+ let data;
51
+ try {
52
+ data = await res.json();
53
+ }
54
+ catch (err) {
55
+ throw new VoiceLlmError('Chat-completions response was not valid JSON.', err);
56
+ }
57
+ const message = data?.choices?.[0]?.message;
58
+ return {
59
+ content: (message?.content ?? '').trim(),
60
+ toolCalls: Array.isArray(message?.tool_calls) ? message.tool_calls : [],
61
+ };
62
+ }
63
+ //# sourceMappingURL=llm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm.js","sourceRoot":"","sources":["../../../src/ai/voiceIntermediary/llm.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,+BAA+B;AAuC/B,MAAM,OAAO,aAAc,SAAQ,KAAK;IACA;IAAtC,YAAY,OAAe,EAAW,KAAe;QACnD,KAAK,CAAC,OAAO,CAAC,CAAC;QADqB,UAAK,GAAL,KAAK,CAAU;QAEnD,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAC9B,CAAC;CACF;AAED,SAAS,MAAM,CAAC,OAAe,EAAE,IAAY;IAC3C,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC;AACxD,CAAC;AAED,SAAS,WAAW,CAAC,MAAmB;IACtC,MAAM,OAAO,GAA2B,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC;IAC/E,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE;QAAE,OAAO,CAAC,aAAa,GAAG,UAAU,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;IACnF,OAAO,OAAO,CAAC;AACjB,CAAC;AAKD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,MAAmB,EACnB,IAKC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC;IACxC,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,aAAa,CAAC,sDAAsD,CAAC,CAAC;IAE5F,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC;IACxC,IAAI,GAAa,CAAC;IAClB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,mBAAmB,CAAC,EAAE;YAC/D,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC;YAC5B,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,KAAK;gBACL,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,WAAW,EAAE,MAAM;aACpB,CAAC;YACF,MAAM,EAAE,IAAI,CAAC,MAAM;SACpB,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,aAAa,CAAC,gDAAgD,EAAE,GAAG,CAAC,CAAC;IACjF,CAAC;IAED,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QAChD,MAAM,IAAI,aAAa,CAAC,2BAA2B,GAAG,CAAC,MAAM,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAChH,CAAC;IAED,IAAI,IAAa,CAAC;IAClB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAC1B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,aAAa,CAAC,+CAA+C,EAAE,GAAG,CAAC,CAAC;IAChF,CAAC;IAED,MAAM,OAAO,GAAI,IAEf,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC;IAE1B,OAAO;QACL,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;QACxC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,OAAQ,CAAC,UAAW,CAAC,CAAC,CAAC,EAAE;KAC1E,CAAC;AACJ,CAAC"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Owns the per-session voice intermediaries and bridges them to the rest of the
3
+ * daemon. Emits a single `'event'` (sessionId, VoiceAgentEvent) that `run.ts`
4
+ * republishes on `/sessions/:sessionId/voice-agent`. Holds synthesized audio in
5
+ * a small LRU so the PWA can fetch bytes by id (metadata-first delivery).
6
+ */
7
+ import { EventEmitter } from 'events';
8
+ import type { CardEvent, CardStreamEnd, ClaudeUserInputRequestPayload, VoiceAgentPlaybackEventRequestPayload, VoiceConfig } from '@sumicom/quicksave-shared';
9
+ import type { FetchLike } from './llm.js';
10
+ import type { CodingSessionBridge } from './tools.js';
11
+ import { type VoiceTurnMeta } from './session.js';
12
+ /** The SessionManager surface the manager needs, beyond the tool bridge. */
13
+ export type VoiceManagerBridge = CodingSessionBridge & {
14
+ getSessionCwd(sessionId: string): string | undefined;
15
+ isOpen(sessionId: string): boolean;
16
+ };
17
+ interface StoredAudio {
18
+ audio: Buffer;
19
+ mimeType: string;
20
+ }
21
+ export declare class VoiceIntermediaryManager extends EventEmitter {
22
+ private readonly bridge;
23
+ /** Injected for tests. */
24
+ private readonly fetchImpl?;
25
+ private readonly sessions;
26
+ private readonly audio;
27
+ constructor(bridge: VoiceManagerBridge,
28
+ /** Injected for tests. */
29
+ fetchImpl?: FetchLike | undefined);
30
+ /**
31
+ * Attach (or refresh) the voice agent for a coding session. `active` is true
32
+ * only when a brain model is configured and the session is live — i.e. the
33
+ * agent will actually respond. Attaching an idle/mis-configured session is a
34
+ * no-op that reports `active:false` rather than erroring.
35
+ */
36
+ attach(sessionId: string, config: VoiceConfig): {
37
+ ok: boolean;
38
+ active: boolean;
39
+ };
40
+ detach(sessionId: string): void;
41
+ isAttached(sessionId: string): boolean;
42
+ handleUtterance(sessionId: string, text: string, meta?: VoiceTurnMeta): Promise<void>;
43
+ recordPlaybackEvent(event: VoiceAgentPlaybackEventRequestPayload): void;
44
+ /**
45
+ * Proactively nudge the agent when the coding side raises a permission prompt,
46
+ * so it can narrate "it wants to run X — shall I allow it?" without the user
47
+ * having to ask. No-op when no agent is attached to that session.
48
+ */
49
+ notifyPendingPermission(request: ClaudeUserInputRequestPayload): void;
50
+ recordCardEvent(event: CardEvent): void;
51
+ recordStreamEnd(result: CardStreamEnd): void;
52
+ notifyStreamEnd(result: CardStreamEnd): void;
53
+ getAudio(audioId: string): StoredAudio | null;
54
+ private storeAudio;
55
+ private emitEvent;
56
+ }
57
+ export {};
58
+ //# sourceMappingURL=manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../../src/ai/voiceIntermediary/manager.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,KAAK,EACV,SAAS,EACT,aAAa,EACb,6BAA6B,EAE7B,qCAAqC,EACrC,WAAW,EACZ,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,EAA4B,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAE5E,4EAA4E;AAC5E,MAAM,MAAM,kBAAkB,GAAG,mBAAmB,GAAG;IACrD,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACrD,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;CACpC,CAAC;AAEF,UAAU,WAAW;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAKD,qBAAa,wBAAyB,SAAQ,YAAY;IAKtD,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,0BAA0B;IAC1B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;IAN7B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA+C;IACxE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkC;gBAGrC,MAAM,EAAE,kBAAkB;IAC3C,0BAA0B;IACT,SAAS,CAAC,EAAE,SAAS,YAAA;IAKxC;;;;;OAKG;IACH,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE;IA2BhF,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAK/B,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAIhC,eAAe,CACnB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,aAAkB,GACvB,OAAO,CAAC,IAAI,CAAC;IAehB,mBAAmB,CAAC,KAAK,EAAE,qCAAqC,GAAG,IAAI;IAIvE;;;;OAIG;IACH,uBAAuB,CAAC,OAAO,EAAE,6BAA6B,GAAG,IAAI;IAUrE,eAAe,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IAIvC,eAAe,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI;IAI5C,eAAe,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI;IAM5C,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAI7C,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,SAAS;CAGlB"}
@@ -0,0 +1,134 @@
1
+ // SPDX-FileCopyrightText: 2026 King Young Technology
2
+ // SPDX-License-Identifier: MIT
3
+ /**
4
+ * Owns the per-session voice intermediaries and bridges them to the rest of the
5
+ * daemon. Emits a single `'event'` (sessionId, VoiceAgentEvent) that `run.ts`
6
+ * republishes on `/sessions/:sessionId/voice-agent`. Holds synthesized audio in
7
+ * a small LRU so the PWA can fetch bytes by id (metadata-first delivery).
8
+ */
9
+ import { EventEmitter } from 'events';
10
+ import { randomUUID } from 'crypto';
11
+ import { VoiceIntermediarySession } from './session.js';
12
+ /** Keep at most this many recent utterances of audio addressable for fetch. */
13
+ const AUDIO_CACHE_CAP = 32;
14
+ export class VoiceIntermediaryManager extends EventEmitter {
15
+ bridge;
16
+ fetchImpl;
17
+ sessions = new Map();
18
+ audio = new Map();
19
+ constructor(bridge,
20
+ /** Injected for tests. */
21
+ fetchImpl) {
22
+ super();
23
+ this.bridge = bridge;
24
+ this.fetchImpl = fetchImpl;
25
+ }
26
+ /**
27
+ * Attach (or refresh) the voice agent for a coding session. `active` is true
28
+ * only when a brain model is configured and the session is live — i.e. the
29
+ * agent will actually respond. Attaching an idle/mis-configured session is a
30
+ * no-op that reports `active:false` rather than erroring.
31
+ */
32
+ attach(sessionId, config) {
33
+ const cwd = this.bridge.getSessionCwd(sessionId);
34
+ const live = this.bridge.isOpen(sessionId);
35
+ const active = !!config.agentModel?.trim() && !!cwd && live;
36
+ if (!cwd || !live)
37
+ return { ok: true, active: false };
38
+ const existing = this.sessions.get(sessionId);
39
+ if (existing) {
40
+ existing.updateConfig(config, cwd);
41
+ return { ok: true, active };
42
+ }
43
+ const session = new VoiceIntermediarySession({
44
+ sessionId,
45
+ cwd,
46
+ config,
47
+ bridge: this.bridge,
48
+ fetchImpl: this.fetchImpl,
49
+ callbacks: {
50
+ emit: (event) => this.emitEvent(sessionId, event),
51
+ storeAudio: (a, mime) => this.storeAudio(a, mime),
52
+ },
53
+ });
54
+ this.sessions.set(sessionId, session);
55
+ return { ok: true, active };
56
+ }
57
+ detach(sessionId) {
58
+ this.sessions.get(sessionId)?.close();
59
+ this.sessions.delete(sessionId);
60
+ }
61
+ isAttached(sessionId) {
62
+ return this.sessions.has(sessionId);
63
+ }
64
+ async handleUtterance(sessionId, text, meta = {}) {
65
+ const trimmed = text.trim();
66
+ if (!trimmed)
67
+ return;
68
+ const session = this.sessions.get(sessionId);
69
+ if (!session) {
70
+ this.emitEvent(sessionId, { kind: 'error', message: '語音同事尚未啟動,請先 attach。' });
71
+ return;
72
+ }
73
+ if (!session.hasBrain()) {
74
+ this.emitEvent(sessionId, { kind: 'error', message: '尚未設定語音 agent 模型(agentModel)。' });
75
+ return;
76
+ }
77
+ await session.handleUtterance(trimmed, meta);
78
+ }
79
+ recordPlaybackEvent(event) {
80
+ this.sessions.get(event.sessionId)?.recordPlaybackEvent(event);
81
+ }
82
+ /**
83
+ * Proactively nudge the agent when the coding side raises a permission prompt,
84
+ * so it can narrate "it wants to run X — shall I allow it?" without the user
85
+ * having to ask. No-op when no agent is attached to that session.
86
+ */
87
+ notifyPendingPermission(request) {
88
+ const session = this.sessions.get(request.sessionId);
89
+ if (!session || !session.hasBrain())
90
+ return;
91
+ const tool = request.toolName ?? request.title;
92
+ const detail = request.toolInput ? ` 內容:${safeBrief(request.toolInput)}` : '';
93
+ void session.notify(`目前工作需要權限執行「${tool}」。${detail} 這可能不可逆,先別自己決定——用講的說明並詢問使用者,得到口頭同意再用 respond_to_permission 回覆(request_id:${request.requestId})。`);
94
+ }
95
+ recordCardEvent(event) {
96
+ this.sessions.get(event.sessionId)?.recordCardEvent(event);
97
+ }
98
+ recordStreamEnd(result) {
99
+ this.sessions.get(result.sessionId)?.recordStreamEnd(result);
100
+ }
101
+ notifyStreamEnd(result) {
102
+ const session = this.sessions.get(result.sessionId);
103
+ if (!session || !session.hasBrain())
104
+ return;
105
+ void session.notifyCodingTurnEnded(result);
106
+ }
107
+ getAudio(audioId) {
108
+ return this.audio.get(audioId) ?? null;
109
+ }
110
+ storeAudio(audio, mimeType) {
111
+ const id = randomUUID();
112
+ this.audio.set(id, { audio, mimeType });
113
+ while (this.audio.size > AUDIO_CACHE_CAP) {
114
+ const oldest = this.audio.keys().next().value;
115
+ if (oldest === undefined)
116
+ break;
117
+ this.audio.delete(oldest);
118
+ }
119
+ return id;
120
+ }
121
+ emitEvent(sessionId, event) {
122
+ this.emit('event', sessionId, event);
123
+ }
124
+ }
125
+ function safeBrief(input) {
126
+ try {
127
+ const s = JSON.stringify(input);
128
+ return s.length > 120 ? `${s.slice(0, 120)}…` : s;
129
+ }
130
+ catch {
131
+ return '';
132
+ }
133
+ }
134
+ //# sourceMappingURL=manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manager.js","sourceRoot":"","sources":["../../../src/ai/voiceIntermediary/manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ export type MemorySection = 'preference' | 'decision' | 'fact' | 'note';
2
+ export declare function globalMemoryPath(): string;
3
+ export declare function workspaceMemoryPath(cwd: string): string;
4
+ /**
5
+ * Load the combined memory text for a workspace, ready to splice into the
6
+ * brain's system prompt. Returns '' when nothing has been remembered yet so the
7
+ * caller can cheaply skip the section.
8
+ */
9
+ export declare function loadMemory(cwd: string): Promise<string>;
10
+ /**
11
+ * Append a remembered fact to the WORKSPACE memory file under its section,
12
+ * creating the file/section as needed. Append-only and idempotent on exact
13
+ * duplicates so repeated "remember X" doesn't pile up identical bullets.
14
+ */
15
+ export declare function appendMemory(cwd: string, note: string, section?: MemorySection): Promise<void>;
16
+ /**
17
+ * Pure markdown transform: ensure `- <bullet>` exists under `## <header>`.
18
+ * Inserts the bullet at the end of an existing section, or appends a fresh
19
+ * section. Exported for unit testing.
20
+ */
21
+ export declare function upsertBullet(content: string, header: string, bullet: string): string;
22
+ //# sourceMappingURL=memory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../../src/ai/voiceIntermediary/memory.ts"],"names":[],"mappings":"AAsBA,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC;AAUxE,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEvD;AAWD;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAS7D;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,aAAsB,GAC9B,OAAO,CAAC,IAAI,CAAC,CASf;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CA4BpF"}
@@ -0,0 +1,109 @@
1
+ // SPDX-FileCopyrightText: 2026 King Young Technology
2
+ // SPDX-License-Identifier: MIT
3
+ /**
4
+ * Workspace memory for the voice intermediary — a plain, human-editable
5
+ * markdown file, NOT a database. Mirrors the `AGENTS.md` / Claude-memory model:
6
+ * standing preferences, decisions (trust boundaries) and project facts the
7
+ * "AI coworker" should carry between voice sessions.
8
+ *
9
+ * Two tiers, both optional, loaded together (global first, workspace second so
10
+ * project rules win on conflict):
11
+ * - global `~/.quicksave/voice-memory.md` (cross-project style)
12
+ * - workspace `<cwd>/.quicksave/voice-memory.md` (per-project boundaries)
13
+ *
14
+ * Deliberately separate from the coding agent's own `CLAUDE.md` / `AGENTS.md`:
15
+ * those steer the coding agent, so writing coworker notes there would pollute
16
+ * its context. We READ those elsewhere for grounding but only WRITE here.
17
+ */
18
+ import { homedir } from 'os';
19
+ import { join, dirname } from 'path';
20
+ import { readFile, writeFile, mkdir } from 'fs/promises';
21
+ /** Section slug → human-readable markdown header (the file is for human eyes). */
22
+ const SECTION_HEADERS = {
23
+ preference: '偏好',
24
+ decision: '常駐決定(界線)',
25
+ fact: '專案常識',
26
+ note: '筆記',
27
+ };
28
+ export function globalMemoryPath() {
29
+ return join(homedir(), '.quicksave', 'voice-memory.md');
30
+ }
31
+ export function workspaceMemoryPath(cwd) {
32
+ return join(cwd, '.quicksave', 'voice-memory.md');
33
+ }
34
+ async function readIfPresent(path) {
35
+ try {
36
+ const text = await readFile(path, 'utf8');
37
+ return text.trim().length > 0 ? text.trim() : null;
38
+ }
39
+ catch {
40
+ return null;
41
+ }
42
+ }
43
+ /**
44
+ * Load the combined memory text for a workspace, ready to splice into the
45
+ * brain's system prompt. Returns '' when nothing has been remembered yet so the
46
+ * caller can cheaply skip the section.
47
+ */
48
+ export async function loadMemory(cwd) {
49
+ const [global, workspace] = await Promise.all([
50
+ readIfPresent(globalMemoryPath()),
51
+ readIfPresent(workspaceMemoryPath(cwd)),
52
+ ]);
53
+ const parts = [];
54
+ if (global)
55
+ parts.push(`# 跨專案記憶 (global)\n\n${global}`);
56
+ if (workspace)
57
+ parts.push(`# 此工作區記憶 (workspace)\n\n${workspace}`);
58
+ return parts.join('\n\n');
59
+ }
60
+ /**
61
+ * Append a remembered fact to the WORKSPACE memory file under its section,
62
+ * creating the file/section as needed. Append-only and idempotent on exact
63
+ * duplicates so repeated "remember X" doesn't pile up identical bullets.
64
+ */
65
+ export async function appendMemory(cwd, note, section = 'note') {
66
+ const trimmed = note.trim();
67
+ if (!trimmed)
68
+ return;
69
+ const path = workspaceMemoryPath(cwd);
70
+ const existing = (await readIfPresent(path)) ?? '';
71
+ const next = upsertBullet(existing, SECTION_HEADERS[section], trimmed);
72
+ if (next === existing)
73
+ return;
74
+ await mkdir(dirname(path), { recursive: true });
75
+ await writeFile(path, next.endsWith('\n') ? next : `${next}\n`, 'utf8');
76
+ }
77
+ /**
78
+ * Pure markdown transform: ensure `- <bullet>` exists under `## <header>`.
79
+ * Inserts the bullet at the end of an existing section, or appends a fresh
80
+ * section. Exported for unit testing.
81
+ */
82
+ export function upsertBullet(content, header, bullet) {
83
+ const bulletLine = `- ${bullet}`;
84
+ const lines = content.length > 0 ? content.split('\n') : [];
85
+ // Already present anywhere → no-op (cheap dedupe).
86
+ if (lines.some((l) => l.trim() === bulletLine))
87
+ return content;
88
+ const headerLine = `## ${header}`;
89
+ const headerIdx = lines.findIndex((l) => l.trim() === headerLine);
90
+ if (headerIdx === -1) {
91
+ const block = `${headerLine}\n${bulletLine}`;
92
+ return lines.length > 0 ? `${content.replace(/\n+$/, '')}\n\n${block}` : block;
93
+ }
94
+ // Find the end of this section (next `## ` header or EOF) and insert before it.
95
+ let insertAt = lines.length;
96
+ for (let i = headerIdx + 1; i < lines.length; i++) {
97
+ if (lines[i].startsWith('## ')) {
98
+ insertAt = i;
99
+ break;
100
+ }
101
+ }
102
+ // Trim trailing blank lines inside the section before inserting.
103
+ let tail = insertAt;
104
+ while (tail > headerIdx + 1 && lines[tail - 1].trim() === '')
105
+ tail--;
106
+ lines.splice(tail, 0, bulletLine);
107
+ return lines.join('\n');
108
+ }
109
+ //# sourceMappingURL=memory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.js","sourceRoot":"","sources":["../../../src/ai/voiceIntermediary/memory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * One voice-intermediary conversation bound to a single coding session. Holds
3
+ * the brain's active message window in RAM and mirrors model-visible messages
4
+ * into append-only JSONL so a restarted voice agent can resume its own context.
5
+ * Coding-agent cards remain a separate implementation transcript read through
6
+ * tools. Each user utterance runs a tool-calling
7
+ * loop per user utterance: narrate → maybe call silent tools → speak the result.
8
+ */
9
+ import type { CardEvent, CardStreamEnd, VoiceAgentPlaybackEventRequestPayload, VoiceAgentEvent, VoiceConfig } from '@sumicom/quicksave-shared';
10
+ import { type ChatMessage, type FetchLike } from './llm.js';
11
+ import { type CodingSessionBridge } from './tools.js';
12
+ import { VoiceHistoryStore } from './historyStore.js';
13
+ export interface VoiceSessionCallbacks {
14
+ emit: (event: VoiceAgentEvent) => void;
15
+ /** Persist audio bytes and return an id the PWA fetches on demand. */
16
+ storeAudio: (audio: Buffer, mimeType: string) => string;
17
+ }
18
+ export interface VoiceSessionOpts {
19
+ sessionId: string;
20
+ cwd: string;
21
+ config: VoiceConfig;
22
+ bridge: CodingSessionBridge;
23
+ callbacks: VoiceSessionCallbacks;
24
+ /** Injected for tests. */
25
+ fetchImpl?: FetchLike;
26
+ /** Injected for tests. */
27
+ historyStore?: VoiceHistoryStore;
28
+ }
29
+ export interface VoiceTurnMeta {
30
+ turnId?: string;
31
+ interactionId?: string;
32
+ utteranceId?: string;
33
+ }
34
+ export declare class VoiceIntermediarySession {
35
+ readonly sessionId: string;
36
+ private cwd;
37
+ private config;
38
+ private readonly bridge;
39
+ private readonly cb;
40
+ private readonly fetchImpl?;
41
+ private readonly history;
42
+ private messages;
43
+ private systemPrompt;
44
+ private ready;
45
+ /** Serializes turns so overlapping utterances don't interleave tool calls. */
46
+ private chain;
47
+ private closed;
48
+ /** Set after a provider clearly rejects the TTS endpoint; keep later turns text-only. */
49
+ private ttsUnavailableReason;
50
+ /** Passive mirror of the coding agent's newest live card stream. */
51
+ private readonly liveCards;
52
+ private readonly liveNotes;
53
+ private pendingPlaybackNote;
54
+ constructor(opts: VoiceSessionOpts);
55
+ /** Refresh config (e.g. user changed model/voice) without losing history. */
56
+ updateConfig(config: VoiceConfig, cwd?: string): void;
57
+ /** True when a brain model is configured, i.e. the agent can actually respond. */
58
+ hasBrain(): boolean;
59
+ private init;
60
+ /** Handle a final user utterance (STT transcript). Serialized per session. */
61
+ handleUtterance(text: string, meta?: VoiceTurnMeta): Promise<void>;
62
+ /** Proactively wake the agent on a coding-side event (e.g. permission pending). */
63
+ notify(systemNote: string): Promise<void>;
64
+ /** Wake the voice brain when the coding agent finishes a turn. The brain may no-op. */
65
+ notifyCodingTurnEnded(result: CardStreamEnd): Promise<void>;
66
+ close(): void;
67
+ /** Passively record coding-agent card updates. Does not wake or interrupt the voice LLM. */
68
+ recordCardEvent(event: CardEvent): void;
69
+ /** Record turn completion metadata without triggering a voice response. */
70
+ recordStreamEnd(result: CardStreamEnd): void;
71
+ recordPlaybackEvent(event: VoiceAgentPlaybackEventRequestPayload): void;
72
+ private runTurn;
73
+ private speak;
74
+ private messagesWithLiveContext;
75
+ private liveContextForBrain;
76
+ private trimLiveCards;
77
+ private maybeCompactHistory;
78
+ }
79
+ export declare function sanitizeMessagesForChatCompletion(messages: readonly ChatMessage[]): ChatMessage[];
80
+ export declare function buildSystemPrompt(memory: string): string;
81
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/ai/voiceIntermediary/session.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,OAAO,KAAK,EAEV,SAAS,EACT,aAAa,EACb,qCAAqC,EACrC,eAAe,EACf,WAAW,EACZ,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAkB,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AAE5E,OAAO,EAAsD,KAAK,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAG1G,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAStD,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;IACvC,sEAAsE;IACtE,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;CACzD;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,SAAS,EAAE,qBAAqB,CAAC;IACjC,0BAA0B;IAC1B,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,0BAA0B;IAC1B,YAAY,CAAC,EAAE,iBAAiB,CAAC;CAClC;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,wBAAwB;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAC7C,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAwB;IAC3C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAY;IACvC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAE5C,OAAO,CAAC,QAAQ,CAAqB;IACrC,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,KAAK,CAAgB;IAC7B,8EAA8E;IAC9E,OAAO,CAAC,KAAK,CAAoC;IACjD,OAAO,CAAC,MAAM,CAAS;IACvB,yFAAyF;IACzF,OAAO,CAAC,oBAAoB,CAAM;IAClC,oEAAoE;IACpE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA2B;IACrD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgB;IAC1C,OAAO,CAAC,mBAAmB,CAAM;gBAErB,IAAI,EAAE,gBAAgB;IAWlC,6EAA6E;IAC7E,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI;IAMrD,kFAAkF;IAClF,QAAQ,IAAI,OAAO;YAIL,IAAI;IAalB,8EAA8E;IAC9E,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAOtE,mFAAmF;IACnF,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMzC,uFAAuF;IACvF,qBAAqB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAqB3D,KAAK,IAAI,IAAI;IAKb,4FAA4F;IAC5F,eAAe,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IA+BvC,2EAA2E;IAC3E,eAAe,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI;IAM5C,mBAAmB,CAAC,KAAK,EAAE,qCAAqC,GAAG,IAAI;YAgBzD,OAAO;YA+IP,KAAK;IAgGnB,OAAO,CAAC,uBAAuB;IAgB/B,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,aAAa;YAQP,mBAAmB;CAsBlC;AAED,wBAAgB,iCAAiC,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,GAAG,WAAW,EAAE,CAkCjG;AAeD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAsCxD"}