@vellumai/assistant 0.11.4-staging.1 → 0.11.4-staging.3
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.
- package/AGENTS.md +8 -2
- package/ARCHITECTURE.md +2 -0
- package/docs/architecture/memory.md +15 -0
- package/docs/browser-use-architecture-phase2.md +128 -56
- package/docs/flux-turn-detection-spike.md +11 -6
- package/knip.json +3 -0
- package/openapi.yaml +135 -75
- package/package.json +1 -1
- package/scripts/write-plugin-api-shim.ts +10 -0
- package/src/__tests__/app-control-flow.test.ts +1 -0
- package/src/__tests__/approval-routes-http.test.ts +2 -2
- package/src/__tests__/assistant-feature-flag-guard.test.ts +25 -3
- package/src/__tests__/byok-default-profile-ensure.test.ts +15 -0
- package/src/__tests__/channel-setup-panel-ack.test.ts +1 -1
- package/src/__tests__/compaction-events.test.ts +8 -10
- package/src/__tests__/conversation-agent-loop.test.ts +4 -1
- package/src/__tests__/conversation-confirmation-signals.test.ts +112 -0
- package/src/__tests__/conversation-notifiers-provenance.test.ts +1 -1
- package/src/__tests__/conversation-queue.test.ts +99 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-routes-enabled-plugins.test.ts +1 -1
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +9 -9
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +1 -1
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -1
- package/src/__tests__/conversation-slash-queue.test.ts +3 -0
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-app-open.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +21 -14
- package/src/__tests__/conversation-surfaces-queued-emit.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-standalone.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-state-update.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-table-action.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -1
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +1 -1
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +1 -1
- package/src/__tests__/cu-unified-flow.test.ts +1 -0
- package/src/__tests__/document-sync-tags.test.ts +0 -75
- package/src/__tests__/gateway-only-guard.test.ts +2 -5
- package/src/__tests__/http-user-message-parity.test.ts +1 -1
- package/src/__tests__/init-feature-flag-overrides.test.ts +49 -0
- package/src/__tests__/managed-skill-lifecycle.test.ts +7 -0
- package/src/__tests__/media-generate-image.test.ts +131 -21
- package/src/__tests__/memory-retrieval-hook.test.ts +94 -2
- package/src/__tests__/plugin-api-webhook-url.test.ts +10 -7
- package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +7 -3
- package/src/__tests__/proxy-approval-callback.test.ts +1 -0
- package/src/__tests__/qdrant-manager.test.ts +14 -1
- package/src/__tests__/run-due-schedules.test.ts +21 -0
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +187 -18
- package/src/__tests__/schedule-routes.test.ts +23 -0
- package/src/__tests__/schedule-store.test.ts +17 -0
- package/src/__tests__/secret-ingress-http.test.ts +1 -1
- package/src/__tests__/send-endpoint-busy.test.ts +3 -3
- package/src/__tests__/starter-task-flow.test.ts +5 -4
- package/src/__tests__/subagent-fork-prompt-role.test.ts +1 -1
- package/src/__tests__/subagent-spawn-and-await.test.ts +4 -7
- package/src/__tests__/subagent-tool-gate-mode.test.ts +1 -1
- package/src/__tests__/subagent-tools.test.ts +81 -101
- package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +1 -0
- package/src/__tests__/ui-choice-copy-surfaces.test.ts +1 -1
- package/src/__tests__/ui-shape-teaching.test.ts +33 -0
- package/src/__tests__/ui-visual-surface.test.ts +1 -1
- package/src/__tests__/ui-voice-picker-surface.test.ts +128 -0
- package/src/__tests__/ui-work-result-surface.test.ts +1 -1
- package/src/__tests__/voice-config-update.test.ts +40 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +5 -3
- package/src/__tests__/voice-session-bridge.test.ts +85 -29
- package/src/__tests__/workspace-migration-146-repair-retired-fireworks-deepseek-flash-model-id.test.ts +235 -0
- package/src/acp/session-manager.ts +8 -1
- package/src/api/surfaces.ts +12 -3
- package/src/calls/__tests__/voice-session-bridge.test.ts +21 -10
- package/src/calls/__tests__/voice-triage-escalate.test.ts +8 -0
- package/src/calls/call-controller.ts +16 -7
- package/src/calls/voice-session-bridge.ts +44 -25
- package/src/calls/voice-triage-escalate.ts +1 -0
- package/src/cli/bundled-modules.ts +29 -0
- package/src/cli/commands/db/repair.ts +4 -8
- package/src/cli/commands/domain.ts +6 -3
- package/src/cli/commands/email.ts +6 -3
- package/src/cli/commands/keys.ts +8 -3
- package/src/cli/commands/plugins.ts +85 -36
- package/src/cli/commands/schedules.ts +35 -1
- package/src/cli/lib/bundled-marketplace.json +1 -1
- package/src/config/__tests__/balanced-model-experiment.test.ts +278 -0
- package/src/config/assistant-feature-flags.ts +36 -15
- package/src/config/balanced-model-experiment.ts +35 -0
- package/src/config/bundled-skills/image-studio/SKILL.md +5 -4
- package/src/config/bundled-skills/image-studio/TOOLS.json +1 -1
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +101 -0
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.test.ts +65 -0
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +7 -1
- package/src/config/bundled-skills/settings/tools/shared.ts +16 -0
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +9 -3
- package/src/config/bundled-skills/subagent/SKILL.md +17 -12
- package/src/config/bundled-skills/subagent/TOOLS.json +4 -4
- package/src/config/call-site-defaults.ts +7 -0
- package/src/config/default-profile-catalog.ts +97 -5
- package/src/config/feature-flag-registry.json +11 -11
- package/src/config/skills.ts +9 -2
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +1 -1
- package/src/daemon/conversation-agent-loop.ts +14 -13
- package/src/daemon/conversation-notifiers.ts +11 -9
- package/src/daemon/conversation-store.ts +4 -4
- package/src/daemon/conversation-surfaces.ts +54 -18
- package/src/daemon/conversation-tool-setup.ts +2 -4
- package/src/daemon/conversation.ts +81 -44
- package/src/daemon/doordash-steps.ts +2 -2
- package/src/daemon/lifecycle.ts +14 -1
- package/src/daemon/process-message.ts +0 -23
- package/src/daemon/windows-compiled-entry.ts +4 -0
- package/src/documents/document-store.ts +5 -235
- package/src/hooks/types.ts +5 -0
- package/src/ipc/gateway-flag-listener.ts +17 -3
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +118 -0
- package/src/live-voice/live-voice-manager.ts +16 -3
- package/src/live-voice/live-voice-session.ts +63 -9
- package/src/live-voice/windows-compiled-live-voice.ts +4 -0
- package/src/monitoring/control.ts +1 -0
- package/src/monitoring/db-integrity-sample.ts +4 -5
- package/src/permissions/prompter.ts +1 -5
- package/src/persistence/conversation-queries.ts +66 -16
- package/src/persistence/embeddings/qdrant-manager.ts +84 -49
- package/src/persistence/migrations/360-add-document-workspace-path.ts +5 -14
- package/src/persistence/schema/documents.ts +4 -4
- package/src/plugin-api/constants.ts +8 -0
- package/src/plugin-api/index.ts +5 -1
- package/src/plugin-api/vision-support.test.ts +1 -1
- package/src/plugin-api/webhook-url.ts +13 -11
- package/src/plugins/defaults/main.ts +6 -7
- package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +77 -0
- package/src/plugins/defaults/memory/graph/conversation-graph-memory.ts +24 -6
- package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +53 -5
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +4 -4
- package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +18 -0
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +66 -1
- package/src/plugins/defaults/memory/v3/injector.ts +8 -0
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +23 -9
- package/src/plugins/defaults/memory/worker-control.ts +1 -0
- package/src/plugins/defaults/worker-entrypoints.ts +3 -0
- package/src/plugins/mtime-cache.ts +17 -0
- package/src/prompts/templates/system-sections.ts +0 -7
- package/src/providers/__tests__/context-overflow-error.test.ts +24 -0
- package/src/providers/__tests__/retry-callsite.test.ts +20 -0
- package/src/providers/model-catalog.ts +3 -3
- package/src/providers/model-intents.ts +2 -2
- package/src/providers/openai/chat-completions-provider.ts +16 -7
- package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +11 -6
- package/src/providers/speech-to-text/deepgram-flux-realtime.ts +9 -49
- package/src/routes/control.ts +1 -0
- package/src/routes/route-host-client.ts +1 -0
- package/src/runtime/AGENTS.md +16 -17
- package/src/runtime/agent-wake.ts +15 -12
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +170 -1
- package/src/runtime/routes/__tests__/schedule-routes-disarm-reason.test.ts +215 -0
- package/src/runtime/routes/conversation-list-routes.ts +54 -22
- package/src/runtime/routes/conversation-management-routes.ts +2 -3
- package/src/runtime/routes/conversation-routes.ts +11 -13
- package/src/runtime/routes/documents-routes.ts +3 -222
- package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +2 -0
- package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +3 -0
- package/src/runtime/routes/playground/inject-failures.ts +2 -2
- package/src/runtime/routes/playground/reset-circuit.ts +1 -1
- package/src/runtime/routes/schedule-routes.ts +94 -7
- package/src/runtime/routes/workspace-routes.ts +0 -9
- package/src/runtime/routes/workspace-utils.ts +3 -13
- package/src/runtime/services/conversation-serializer.ts +7 -2
- package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +68 -5
- package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +81 -0
- package/src/schedule/plugin-schedule-availability.ts +58 -0
- package/src/schedule/plugin-schedule-declarations.ts +23 -27
- package/src/schedule/plugin-schedule-reconciler.ts +12 -3
- package/src/schedule/schedule-store.ts +5 -1
- package/src/schedule/scheduler.ts +9 -4
- package/src/schedule/worker-control.ts +1 -0
- package/src/subagent/__tests__/consult-prompt.test.ts +26 -15
- package/src/subagent/consult-context.ts +11 -11
- package/src/subagent/consult-prompt.ts +26 -35
- package/src/subagent/manager.ts +20 -37
- package/src/subagent/notify.ts +7 -1
- package/src/subagent/types.ts +8 -6
- package/src/tools/acp/spawn.ts +6 -4
- package/src/tools/skills/scaffold-managed.ts +25 -7
- package/src/tools/subagent/spawn.ts +28 -88
- package/src/tools/ui-surface/surface-shape-docs.ts +12 -1
- package/src/tts/__tests__/reasoning-tag-filter.test.ts +15 -0
- package/src/tts/reasoning-tag-filter.ts +11 -32
- package/src/util/__tests__/worker-process-command.test.ts +37 -0
- package/src/util/logger.ts +16 -0
- package/src/util/think-tag-stream.ts +95 -0
- package/src/util/worker-process.ts +37 -4
- package/src/windows-compiled-cli.ts +32 -0
- package/src/windows-compiled-entry.ts +4 -0
- package/src/windows-compiled-logger.ts +6 -0
- package/src/windows-compiled-worker-entry.ts +29 -0
- package/src/workspace/byok-default-profile-ensure.ts +15 -6
- package/src/workspace/migrations/146-repair-retired-fireworks-deepseek-flash-model-id.ts +195 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/__tests__/document-workspace-file.test.ts +0 -467
- package/src/subagent/__tests__/consult-transcript.test.ts +0 -184
- package/src/subagent/consult-transcript.ts +0 -90
|
@@ -276,6 +276,46 @@ describe("voice_config_update — tts_voice_id (managed/vellum active)", () => {
|
|
|
276
276
|
expect(result.content).not.toContain("broadcast");
|
|
277
277
|
});
|
|
278
278
|
|
|
279
|
+
test("hints the inline picker for the next managed voice change", async () => {
|
|
280
|
+
makeVellumActive();
|
|
281
|
+
|
|
282
|
+
const result = await run(
|
|
283
|
+
{ setting: "tts_voice_id", value: "aura-2-zeus-en" },
|
|
284
|
+
makeContext(),
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
expect(result.isError).toBe(false);
|
|
288
|
+
expect(result.content).toStartWith("Vellum managed voice updated to");
|
|
289
|
+
expect(result.content).toContain(
|
|
290
|
+
'ui_show { surface_type: "voice_picker", data: {} }',
|
|
291
|
+
);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test("does not hint the picker when the voice is not managed", async () => {
|
|
295
|
+
writeConfig({ services: { tts: { provider: "elevenlabs" } } });
|
|
296
|
+
invalidateConfigCache();
|
|
297
|
+
|
|
298
|
+
const result = await run(
|
|
299
|
+
{ setting: "tts_voice_id", value: "abc123" },
|
|
300
|
+
makeContext(),
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
expect(result.isError).toBe(false);
|
|
304
|
+
expect(result.content).not.toContain("voice_picker");
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test("does not hint the picker for a non-voice setting", async () => {
|
|
308
|
+
makeVellumActive();
|
|
309
|
+
|
|
310
|
+
const result = await run(
|
|
311
|
+
{ setting: "conversation_timeout", value: 30 },
|
|
312
|
+
makeContext(),
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
expect(result.isError).toBe(false);
|
|
316
|
+
expect(result.content).not.toContain("voice_picker");
|
|
317
|
+
});
|
|
318
|
+
|
|
279
319
|
test("rejects a value with characters invalid for any voice id", async () => {
|
|
280
320
|
makeVellumActive();
|
|
281
321
|
|
|
@@ -89,7 +89,8 @@ const CALL_SESSION_ID = "call-session-voice-grant-test";
|
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
91
|
* Create a mock session that, when runAgentLoop is called, emits a
|
|
92
|
-
* confirmation_request through the
|
|
92
|
+
* confirmation_request through the registered event observer before
|
|
93
|
+
* completing.
|
|
93
94
|
*/
|
|
94
95
|
function createMockSession(opts?: {
|
|
95
96
|
confirmationRequestId?: string;
|
|
@@ -118,8 +119,9 @@ function createMockSession(opts?: {
|
|
|
118
119
|
currentRequestId: requestId,
|
|
119
120
|
abort: () => {},
|
|
120
121
|
persistUserMessage: async () => ({ id: "msg-1", deduplicated: false }),
|
|
121
|
-
|
|
122
|
+
addEventObserver: (cb: (msg: AssistantEvent) => void) => {
|
|
122
123
|
clientCallback = cb;
|
|
124
|
+
return () => {};
|
|
123
125
|
},
|
|
124
126
|
handleConfirmationResponse: (
|
|
125
127
|
reqId: string,
|
|
@@ -138,7 +140,7 @@ function createMockSession(opts?: {
|
|
|
138
140
|
_messageId: string,
|
|
139
141
|
options?: { onEvent?: (msg: AssistantEvent) => void },
|
|
140
142
|
) => {
|
|
141
|
-
// Emit a confirmation_request through the
|
|
143
|
+
// Emit a confirmation_request through the event observer
|
|
142
144
|
if (clientCallback) {
|
|
143
145
|
clientCallback({
|
|
144
146
|
type: "confirmation_request",
|
|
@@ -61,7 +61,7 @@ function makeStreamingSession(events: AssistantEvent[]): Conversation {
|
|
|
61
61
|
setTurnChannelContext: () => {},
|
|
62
62
|
setTurnInterfaceContext: () => {},
|
|
63
63
|
setVoiceCallControlPrompt: () => {},
|
|
64
|
-
|
|
64
|
+
addEventObserver: () => () => {},
|
|
65
65
|
ensureActorScopedHistory: async () => {},
|
|
66
66
|
runAgentLoop: async (
|
|
67
67
|
_content: string,
|
|
@@ -116,7 +116,7 @@ function makePersistingStreamingSession(
|
|
|
116
116
|
turnInterfaceContext = ctx;
|
|
117
117
|
},
|
|
118
118
|
setVoiceCallControlPrompt: () => {},
|
|
119
|
-
|
|
119
|
+
addEventObserver: () => () => {},
|
|
120
120
|
ensureActorScopedHistory: async () => {},
|
|
121
121
|
runAgentLoop: async (
|
|
122
122
|
_content: string,
|
|
@@ -254,7 +254,7 @@ describe("voice-session-bridge", () => {
|
|
|
254
254
|
setTurnChannelContext: () => {},
|
|
255
255
|
setTurnInterfaceContext: () => {},
|
|
256
256
|
setVoiceCallControlPrompt: () => {},
|
|
257
|
-
|
|
257
|
+
addEventObserver: () => () => {},
|
|
258
258
|
ensureActorScopedHistory: async () => {},
|
|
259
259
|
runAgentLoop: async () => {
|
|
260
260
|
await new Promise((r) => setTimeout(r, 200));
|
|
@@ -321,6 +321,51 @@ describe("voice-session-bridge", () => {
|
|
|
321
321
|
expect(capturedOptions?.callSite).toBe("callAgent");
|
|
322
322
|
});
|
|
323
323
|
|
|
324
|
+
test("startVoiceTurn declares the turn interactive so approval prompts are raised for its observer", async () => {
|
|
325
|
+
// A caller is on the line. If the turn ran non-interactive the permission
|
|
326
|
+
// checker would deny an approval-gated tool before any prompt existed, and
|
|
327
|
+
// the bridge's approval observer (auto-resolve for a non-guardian caller, a
|
|
328
|
+
// real card for the guardian) would never get to decide. Presence is per
|
|
329
|
+
// turn, so the bridge has to say so.
|
|
330
|
+
const conversation = createConversation("voice bridge interactive test");
|
|
331
|
+
const events: AssistantEvent[] = [
|
|
332
|
+
{ type: "message_complete", conversationId: conversation.id },
|
|
333
|
+
];
|
|
334
|
+
|
|
335
|
+
let capturedOptions: Record<string, unknown> | undefined;
|
|
336
|
+
const session = {
|
|
337
|
+
...makeStreamingSession(events),
|
|
338
|
+
runAgentLoop: async (
|
|
339
|
+
_content: string,
|
|
340
|
+
_messageId: string,
|
|
341
|
+
options?: Record<string, unknown>,
|
|
342
|
+
) => {
|
|
343
|
+
capturedOptions = options;
|
|
344
|
+
const onEvent =
|
|
345
|
+
(options as { onEvent?: (msg: AssistantEvent) => void })?.onEvent ??
|
|
346
|
+
(() => {});
|
|
347
|
+
for (const event of events) {
|
|
348
|
+
onEvent(event);
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
} as unknown as Conversation;
|
|
352
|
+
|
|
353
|
+
injectDeps(() => session);
|
|
354
|
+
|
|
355
|
+
await startVoiceTurn({
|
|
356
|
+
conversationId: conversation.id,
|
|
357
|
+
content: "Hello",
|
|
358
|
+
isInbound: true,
|
|
359
|
+
onTextDelta: () => {},
|
|
360
|
+
onComplete: () => {},
|
|
361
|
+
onError: () => {},
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
365
|
+
|
|
366
|
+
expect(capturedOptions?.isInteractive).toBe(true);
|
|
367
|
+
});
|
|
368
|
+
|
|
324
369
|
test("external AbortSignal triggers turn abort", async () => {
|
|
325
370
|
const conversation = createConversation("voice bridge signal test");
|
|
326
371
|
let abortCalled = false;
|
|
@@ -339,7 +384,7 @@ describe("voice-session-bridge", () => {
|
|
|
339
384
|
setTurnChannelContext: () => {},
|
|
340
385
|
setTurnInterfaceContext: () => {},
|
|
341
386
|
setVoiceCallControlPrompt: () => {},
|
|
342
|
-
|
|
387
|
+
addEventObserver: () => () => {},
|
|
343
388
|
ensureActorScopedHistory: async () => {},
|
|
344
389
|
runAgentLoop: async () => {
|
|
345
390
|
await new Promise((r) => setTimeout(r, 200));
|
|
@@ -704,13 +749,14 @@ describe("voice-session-bridge", () => {
|
|
|
704
749
|
setTurnChannelContext: () => {},
|
|
705
750
|
setTurnInterfaceContext: () => {},
|
|
706
751
|
setVoiceCallControlPrompt: () => {},
|
|
707
|
-
|
|
752
|
+
addEventObserver: (handler: (msg: AssistantEvent) => void) => {
|
|
708
753
|
clientHandler = handler;
|
|
754
|
+
return () => {};
|
|
709
755
|
},
|
|
710
756
|
ensureActorScopedHistory: async () => {},
|
|
711
757
|
runAgentLoop: async () => {
|
|
712
|
-
// Simulate the
|
|
713
|
-
//
|
|
758
|
+
// Simulate the conversation emitting a confirmation_request to its
|
|
759
|
+
// event observer (this is how the real conversation fans out).
|
|
714
760
|
clientHandler({
|
|
715
761
|
type: "confirmation_request",
|
|
716
762
|
requestId: "req-voice-1",
|
|
@@ -796,8 +842,9 @@ describe("voice-session-bridge", () => {
|
|
|
796
842
|
setTurnChannelContext: () => {},
|
|
797
843
|
setTurnInterfaceContext: () => {},
|
|
798
844
|
setVoiceCallControlPrompt: () => {},
|
|
799
|
-
|
|
845
|
+
addEventObserver: (handler: (msg: AssistantEvent) => void) => {
|
|
800
846
|
clientHandler = handler;
|
|
847
|
+
return () => {};
|
|
801
848
|
},
|
|
802
849
|
ensureActorScopedHistory: async () => {},
|
|
803
850
|
runAgentLoop: async () => {
|
|
@@ -879,8 +926,9 @@ describe("voice-session-bridge", () => {
|
|
|
879
926
|
setTurnChannelContext: () => {},
|
|
880
927
|
setTurnInterfaceContext: () => {},
|
|
881
928
|
setVoiceCallControlPrompt: () => {},
|
|
882
|
-
|
|
929
|
+
addEventObserver: (handler: (msg: AssistantEvent) => void) => {
|
|
883
930
|
clientHandler = handler;
|
|
931
|
+
return () => {};
|
|
884
932
|
},
|
|
885
933
|
ensureActorScopedHistory: async () => {},
|
|
886
934
|
runAgentLoop: async () => {
|
|
@@ -946,8 +994,9 @@ describe("voice-session-bridge", () => {
|
|
|
946
994
|
setTurnChannelContext: () => {},
|
|
947
995
|
setTurnInterfaceContext: () => {},
|
|
948
996
|
setVoiceCallControlPrompt: () => {},
|
|
949
|
-
|
|
997
|
+
addEventObserver: (handler: (msg: AssistantEvent) => void) => {
|
|
950
998
|
clientHandler = handler;
|
|
999
|
+
return () => {};
|
|
951
1000
|
},
|
|
952
1001
|
ensureActorScopedHistory: async () => {},
|
|
953
1002
|
runAgentLoop: async () => {
|
|
@@ -1009,8 +1058,9 @@ describe("voice-session-bridge", () => {
|
|
|
1009
1058
|
setTurnChannelContext: () => {},
|
|
1010
1059
|
setTurnInterfaceContext: () => {},
|
|
1011
1060
|
setVoiceCallControlPrompt: () => {},
|
|
1012
|
-
|
|
1061
|
+
addEventObserver: (handler: (msg: AssistantEvent) => void) => {
|
|
1013
1062
|
clientHandler = handler;
|
|
1063
|
+
return () => {};
|
|
1014
1064
|
},
|
|
1015
1065
|
ensureActorScopedHistory: async () => {},
|
|
1016
1066
|
runAgentLoop: async () => {
|
|
@@ -1061,13 +1111,20 @@ describe("voice-session-bridge", () => {
|
|
|
1061
1111
|
// broadcast in voice-session-bridge.ts's confirmation_request branch. The
|
|
1062
1112
|
// fake's handleConfirmationResponse mirrors production's synchronous
|
|
1063
1113
|
// `interaction_resolved` broadcast so the wire order is observable through
|
|
1064
|
-
// the event hub, which serializes publishes in call order.
|
|
1114
|
+
// the event hub, which serializes publishes in call order. Its `emit`
|
|
1115
|
+
// mirrors a real conversation's: the sink (the hub) delivers first, then
|
|
1116
|
+
// observers run, so the bridge's resolution lands after the request.
|
|
1065
1117
|
function makeConfirmationOrderingSession(
|
|
1066
1118
|
conversationId: string,
|
|
1067
1119
|
requestId: string,
|
|
1068
1120
|
): Conversation {
|
|
1069
1121
|
let clientHandler: (msg: AssistantEvent) => void = () => {};
|
|
1122
|
+
const emit = (msg: AssistantEvent) => {
|
|
1123
|
+
broadcastMessage(msg);
|
|
1124
|
+
clientHandler(msg);
|
|
1125
|
+
};
|
|
1070
1126
|
return {
|
|
1127
|
+
emit,
|
|
1071
1128
|
isProcessing: () => false,
|
|
1072
1129
|
persistUserMessage: async () => ({
|
|
1073
1130
|
id: "test-msg-id",
|
|
@@ -1080,12 +1137,13 @@ describe("voice-session-bridge", () => {
|
|
|
1080
1137
|
setTurnChannelContext: () => {},
|
|
1081
1138
|
setTurnInterfaceContext: () => {},
|
|
1082
1139
|
setVoiceCallControlPrompt: () => {},
|
|
1083
|
-
|
|
1140
|
+
addEventObserver: (handler: (msg: AssistantEvent) => void) => {
|
|
1084
1141
|
clientHandler = handler;
|
|
1142
|
+
return () => {};
|
|
1085
1143
|
},
|
|
1086
1144
|
ensureActorScopedHistory: async () => {},
|
|
1087
1145
|
runAgentLoop: async () => {
|
|
1088
|
-
|
|
1146
|
+
emit({
|
|
1089
1147
|
type: "confirmation_request",
|
|
1090
1148
|
requestId,
|
|
1091
1149
|
toolName: "host_bash",
|
|
@@ -1222,8 +1280,9 @@ describe("voice-session-bridge", () => {
|
|
|
1222
1280
|
setTurnChannelContext: () => {},
|
|
1223
1281
|
setTurnInterfaceContext: () => {},
|
|
1224
1282
|
setVoiceCallControlPrompt: () => {},
|
|
1225
|
-
|
|
1283
|
+
addEventObserver: (handler: (msg: AssistantEvent) => void) => {
|
|
1226
1284
|
clientHandler = handler;
|
|
1285
|
+
return () => {};
|
|
1227
1286
|
},
|
|
1228
1287
|
ensureActorScopedHistory: async () => {},
|
|
1229
1288
|
runAgentLoop: async () => {
|
|
@@ -1290,7 +1349,7 @@ describe("voice-session-bridge", () => {
|
|
|
1290
1349
|
setTurnChannelContext: () => {},
|
|
1291
1350
|
setTurnInterfaceContext: () => {},
|
|
1292
1351
|
setVoiceCallControlPrompt: () => {},
|
|
1293
|
-
|
|
1352
|
+
addEventObserver: () => () => {},
|
|
1294
1353
|
ensureActorScopedHistory: async () => {},
|
|
1295
1354
|
runAgentLoop: async () => {},
|
|
1296
1355
|
handleConfirmationResponse: () => {},
|
|
@@ -1349,7 +1408,7 @@ describe("voice-session-bridge", () => {
|
|
|
1349
1408
|
setTurnChannelContext: recordLast("setTurnChannelContext"),
|
|
1350
1409
|
setTurnInterfaceContext: recordLast("setTurnInterfaceContext"),
|
|
1351
1410
|
setVoiceCallControlPrompt: recordLast("setVoiceCallControlPrompt"),
|
|
1352
|
-
|
|
1411
|
+
addEventObserver: () => () => {},
|
|
1353
1412
|
ensureActorScopedHistory: async () => {},
|
|
1354
1413
|
runAgentLoop: async () => {},
|
|
1355
1414
|
handleConfirmationResponse: () => {},
|
|
@@ -1391,15 +1450,12 @@ describe("voice-session-bridge", () => {
|
|
|
1391
1450
|
expect(session.forcePromptSideEffects).toBe(false);
|
|
1392
1451
|
});
|
|
1393
1452
|
|
|
1394
|
-
test("cleanup on early persistUserMessage throw does not detach prior
|
|
1453
|
+
test("cleanup on early persistUserMessage throw does not detach a prior turn's observer", async () => {
|
|
1395
1454
|
const conversation = createConversation(
|
|
1396
1455
|
"voice bridge sender detach guard test",
|
|
1397
1456
|
);
|
|
1398
1457
|
|
|
1399
|
-
const
|
|
1400
|
-
callback: unknown;
|
|
1401
|
-
replace: boolean | undefined;
|
|
1402
|
-
}> = [];
|
|
1458
|
+
const addEventObserverCalls: unknown[] = [];
|
|
1403
1459
|
|
|
1404
1460
|
const session = {
|
|
1405
1461
|
isProcessing: () => false,
|
|
@@ -1415,8 +1471,9 @@ describe("voice-session-bridge", () => {
|
|
|
1415
1471
|
setTurnChannelContext: () => {},
|
|
1416
1472
|
setTurnInterfaceContext: () => {},
|
|
1417
1473
|
setVoiceCallControlPrompt: () => {},
|
|
1418
|
-
|
|
1419
|
-
|
|
1474
|
+
addEventObserver: (observer: unknown) => {
|
|
1475
|
+
addEventObserverCalls.push(observer);
|
|
1476
|
+
return () => {};
|
|
1420
1477
|
},
|
|
1421
1478
|
ensureActorScopedHistory: async () => {},
|
|
1422
1479
|
runAgentLoop: async () => {},
|
|
@@ -1451,10 +1508,9 @@ describe("voice-session-bridge", () => {
|
|
|
1451
1508
|
expect(caught?.message).toBe(
|
|
1452
1509
|
"persist failed before bridge installed callback",
|
|
1453
1510
|
);
|
|
1454
|
-
// The bridge never reached its `conversation.
|
|
1455
|
-
// site, so
|
|
1456
|
-
|
|
1457
|
-
expect(updateClientCalls).toEqual([]);
|
|
1511
|
+
// The bridge never reached its `conversation.addEventObserver(...)`
|
|
1512
|
+
// install site, so no observer was registered for cleanup to detach.
|
|
1513
|
+
expect(addEventObserverCalls).toEqual([]);
|
|
1458
1514
|
});
|
|
1459
1515
|
|
|
1460
1516
|
test("pre-aborted signal triggers immediate abort", async () => {
|
|
@@ -1475,7 +1531,7 @@ describe("voice-session-bridge", () => {
|
|
|
1475
1531
|
setTurnChannelContext: () => {},
|
|
1476
1532
|
setTurnInterfaceContext: () => {},
|
|
1477
1533
|
setVoiceCallControlPrompt: () => {},
|
|
1478
|
-
|
|
1534
|
+
addEventObserver: () => () => {},
|
|
1479
1535
|
ensureActorScopedHistory: async () => {},
|
|
1480
1536
|
runAgentLoop: async () => {
|
|
1481
1537
|
await new Promise((r) => setTimeout(r, 200));
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
rmSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
} from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { Database } from "bun:sqlite";
|
|
11
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
12
|
+
|
|
13
|
+
import { repairRetiredFireworksDeepseekFlashModelIdMigration } from "../workspace/migrations/146-repair-retired-fireworks-deepseek-flash-model-id.js";
|
|
14
|
+
import { WORKSPACE_MIGRATIONS } from "../workspace/migrations/registry.js";
|
|
15
|
+
import { assertNotLiveDb } from "./assert-not-live-db.js";
|
|
16
|
+
|
|
17
|
+
const STALE = "accounts/fireworks/models/deepseek-v4-flash";
|
|
18
|
+
const REPLACEMENT = "accounts/fireworks/models/deepseek-v4-flash-0731";
|
|
19
|
+
|
|
20
|
+
let workspaceDir: string;
|
|
21
|
+
|
|
22
|
+
function freshWorkspace(): void {
|
|
23
|
+
workspaceDir = join(
|
|
24
|
+
tmpdir(),
|
|
25
|
+
`vellum-migration-146-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
26
|
+
);
|
|
27
|
+
mkdirSync(workspaceDir, { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function writeConfig(data: Record<string, unknown>): void {
|
|
31
|
+
writeFileSync(
|
|
32
|
+
join(workspaceDir, "config.json"),
|
|
33
|
+
JSON.stringify(data, null, 2) + "\n",
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function readConfig(): Record<string, unknown> {
|
|
38
|
+
return JSON.parse(readFileSync(join(workspaceDir, "config.json"), "utf-8"));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function seedRows(rows: Array<{ name: string; provider: string }>): void {
|
|
42
|
+
mkdirSync(join(workspaceDir, "data", "db"), { recursive: true });
|
|
43
|
+
const db = new Database(join(workspaceDir, "data", "db", "assistant.db"));
|
|
44
|
+
db.run(`CREATE TABLE IF NOT EXISTS provider_connections (
|
|
45
|
+
name TEXT PRIMARY KEY,
|
|
46
|
+
provider TEXT NOT NULL,
|
|
47
|
+
auth TEXT NOT NULL,
|
|
48
|
+
created_at INTEGER NOT NULL,
|
|
49
|
+
updated_at INTEGER NOT NULL
|
|
50
|
+
)`);
|
|
51
|
+
for (const row of rows) {
|
|
52
|
+
db.query(
|
|
53
|
+
`INSERT INTO provider_connections (name, provider, auth, created_at, updated_at) VALUES (?, ?, '{"type":"api_key"}', 1, 1)`,
|
|
54
|
+
).run(row.name, row.provider);
|
|
55
|
+
}
|
|
56
|
+
db.close();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
freshWorkspace();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
afterEach(() => {
|
|
64
|
+
if (existsSync(workspaceDir)) {
|
|
65
|
+
assertNotLiveDb(workspaceDir);
|
|
66
|
+
rmSync(workspaceDir, { recursive: true, force: true });
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("146-repair-retired-fireworks-deepseek-flash-model-id migration", () => {
|
|
71
|
+
test("has correct migration id and is registered", () => {
|
|
72
|
+
expect(repairRetiredFireworksDeepseekFlashModelIdMigration.id).toBe(
|
|
73
|
+
"146-repair-retired-fireworks-deepseek-flash-model-id",
|
|
74
|
+
);
|
|
75
|
+
expect(WORKSPACE_MIGRATIONS.map((m) => m.id)).toContain(
|
|
76
|
+
"146-repair-retired-fireworks-deepseek-flash-model-id",
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("repairs the stale ID in default, call sites, and profiles", () => {
|
|
81
|
+
writeConfig({
|
|
82
|
+
llm: {
|
|
83
|
+
default: { provider: "fireworks", model: STALE },
|
|
84
|
+
callSites: {
|
|
85
|
+
recall: { model: STALE, maxTokens: 4096 },
|
|
86
|
+
heartbeat: { model: `${STALE}-tuned` },
|
|
87
|
+
malformed: STALE,
|
|
88
|
+
},
|
|
89
|
+
profiles: {
|
|
90
|
+
"cost-optimized": { provider: "vellum", model: STALE },
|
|
91
|
+
legacy: { model: STALE },
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
repairRetiredFireworksDeepseekFlashModelIdMigration.run(workspaceDir);
|
|
97
|
+
|
|
98
|
+
const llm = readConfig().llm as Record<string, any>;
|
|
99
|
+
expect(llm.default.model).toBe(REPLACEMENT);
|
|
100
|
+
expect(llm.callSites.recall.model).toBe(REPLACEMENT);
|
|
101
|
+
expect(llm.callSites.recall.maxTokens).toBe(4096);
|
|
102
|
+
// Non-exact matches and malformed leaves are untouched. The dated
|
|
103
|
+
// replacement itself extends the stale ID, so the exact match must not
|
|
104
|
+
// fire on already-repaired leaves.
|
|
105
|
+
expect(llm.callSites.heartbeat.model).toBe(`${STALE}-tuned`);
|
|
106
|
+
expect(llm.callSites.malformed).toBe(STALE);
|
|
107
|
+
// Managed profiles stamped provider "vellum" carry Fireworks model IDs.
|
|
108
|
+
expect(llm.profiles["cost-optimized"].model).toBe(REPLACEMENT);
|
|
109
|
+
expect(llm.profiles.legacy.model).toBe(REPLACEMENT);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("repairs entry-bound fragments whose row kind is fireworks or vellum", () => {
|
|
113
|
+
seedRows([
|
|
114
|
+
{ name: "my-fireworks", provider: "fireworks" },
|
|
115
|
+
{ name: "managed-alt", provider: "vellum" },
|
|
116
|
+
]);
|
|
117
|
+
writeConfig({
|
|
118
|
+
llm: {
|
|
119
|
+
profiles: {
|
|
120
|
+
bound: { provider: "my-fireworks", model: STALE },
|
|
121
|
+
managed: { provider: "managed-alt", model: STALE },
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
repairRetiredFireworksDeepseekFlashModelIdMigration.run(workspaceDir);
|
|
127
|
+
|
|
128
|
+
const llm = readConfig().llm as Record<string, any>;
|
|
129
|
+
expect(llm.profiles.bound.model).toBe(REPLACEMENT);
|
|
130
|
+
expect(llm.profiles.managed.model).toBe(REPLACEMENT);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("leaves fragments with an explicit other provider untouched", () => {
|
|
134
|
+
seedRows([{ name: "byo-compat", provider: "openai-compatible" }]);
|
|
135
|
+
writeConfig({
|
|
136
|
+
llm: {
|
|
137
|
+
default: { provider: "openai-compatible", model: STALE },
|
|
138
|
+
profiles: {
|
|
139
|
+
byo: { provider: "openai-compatible", model: STALE },
|
|
140
|
+
// Entry-bound profile whose row kind is not repairable.
|
|
141
|
+
compatBound: { provider: "byo-compat", model: STALE },
|
|
142
|
+
// Entry name with no row: dangling, so nothing proves it is a
|
|
143
|
+
// fireworks-kind route.
|
|
144
|
+
dangling: { provider: "ghost", model: STALE },
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
repairRetiredFireworksDeepseekFlashModelIdMigration.run(workspaceDir);
|
|
150
|
+
|
|
151
|
+
const llm = readConfig().llm as Record<string, any>;
|
|
152
|
+
expect(llm.default.model).toBe(STALE);
|
|
153
|
+
expect(llm.profiles.byo.model).toBe(STALE);
|
|
154
|
+
expect(llm.profiles.compatBound.model).toBe(STALE);
|
|
155
|
+
expect(llm.profiles.dangling.model).toBe(STALE);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("leaves entry-name providers untouched when no DB file exists", () => {
|
|
159
|
+
writeConfig({
|
|
160
|
+
llm: {
|
|
161
|
+
profiles: { bound: { provider: "my-fireworks", model: STALE } },
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
repairRetiredFireworksDeepseekFlashModelIdMigration.run(workspaceDir);
|
|
166
|
+
|
|
167
|
+
const llm = readConfig().llm as Record<string, any>;
|
|
168
|
+
expect(llm.profiles.bound.model).toBe(STALE);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("throws when an entry-name provider needs rows and the DB is unreadable", () => {
|
|
172
|
+
// A DB file without the provider_connections table is unqueryable, so
|
|
173
|
+
// the run must fail (and retry later) instead of skipping the profile.
|
|
174
|
+
mkdirSync(join(workspaceDir, "data", "db"), { recursive: true });
|
|
175
|
+
writeFileSync(join(workspaceDir, "data", "db", "assistant.db"), "");
|
|
176
|
+
writeConfig({
|
|
177
|
+
llm: {
|
|
178
|
+
profiles: { bound: { provider: "my-fireworks", model: STALE } },
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
expect(() =>
|
|
183
|
+
repairRetiredFireworksDeepseekFlashModelIdMigration.run(workspaceDir),
|
|
184
|
+
).toThrow();
|
|
185
|
+
const llm = readConfig().llm as Record<string, any>;
|
|
186
|
+
expect(llm.profiles.bound.model).toBe(STALE);
|
|
187
|
+
|
|
188
|
+
// Vendor and absent providers never need the rows, so the same broken
|
|
189
|
+
// DB does not block their repair.
|
|
190
|
+
writeConfig({
|
|
191
|
+
llm: {
|
|
192
|
+
default: { provider: "fireworks", model: STALE },
|
|
193
|
+
profiles: { legacy: { model: STALE } },
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
repairRetiredFireworksDeepseekFlashModelIdMigration.run(workspaceDir);
|
|
197
|
+
const repaired = readConfig().llm as Record<string, any>;
|
|
198
|
+
expect(repaired.default.model).toBe(REPLACEMENT);
|
|
199
|
+
expect(repaired.profiles.legacy.model).toBe(REPLACEMENT);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("is idempotent and a no-op without the stale ID", () => {
|
|
203
|
+
writeConfig({
|
|
204
|
+
llm: {
|
|
205
|
+
default: { provider: "fireworks", model: STALE },
|
|
206
|
+
profiles: { fine: { provider: "fireworks", model: REPLACEMENT } },
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
repairRetiredFireworksDeepseekFlashModelIdMigration.run(workspaceDir);
|
|
211
|
+
const first = readConfig();
|
|
212
|
+
repairRetiredFireworksDeepseekFlashModelIdMigration.run(workspaceDir);
|
|
213
|
+
expect(readConfig()).toEqual(first);
|
|
214
|
+
|
|
215
|
+
const llm = first.llm as Record<string, any>;
|
|
216
|
+
expect(llm.default.model).toBe(REPLACEMENT);
|
|
217
|
+
expect(llm.profiles.fine.model).toBe(REPLACEMENT);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test("handles missing config, missing llm block, and invalid JSON", () => {
|
|
221
|
+
// No config.json at all.
|
|
222
|
+
expect(() =>
|
|
223
|
+
repairRetiredFireworksDeepseekFlashModelIdMigration.run(workspaceDir),
|
|
224
|
+
).not.toThrow();
|
|
225
|
+
|
|
226
|
+
writeConfig({ theme: "dark" });
|
|
227
|
+
repairRetiredFireworksDeepseekFlashModelIdMigration.run(workspaceDir);
|
|
228
|
+
expect(readConfig()).toEqual({ theme: "dark" });
|
|
229
|
+
|
|
230
|
+
writeFileSync(join(workspaceDir, "config.json"), "{not json");
|
|
231
|
+
expect(() =>
|
|
232
|
+
repairRetiredFireworksDeepseekFlashModelIdMigration.run(workspaceDir),
|
|
233
|
+
).not.toThrow();
|
|
234
|
+
});
|
|
235
|
+
});
|
|
@@ -1224,9 +1224,14 @@ export class AcpSessionManager {
|
|
|
1224
1224
|
);
|
|
1225
1225
|
return;
|
|
1226
1226
|
}
|
|
1227
|
+
// The notification turn streams to whoever is watching (the parent's sink is
|
|
1228
|
+
// its client hub), but it is machine-injected with no human asserted to be
|
|
1229
|
+
// present, so it runs non-interactive: a tool that would need approval is
|
|
1230
|
+
// denied rather than left waiting on a prompt nobody may answer.
|
|
1227
1231
|
const enqueueResult = parentConversation.enqueueMessage({
|
|
1228
1232
|
content: message,
|
|
1229
1233
|
metadata: { acpNotification },
|
|
1234
|
+
isInteractive: false,
|
|
1230
1235
|
});
|
|
1231
1236
|
if (enqueueResult.queued || enqueueResult.rejected) {
|
|
1232
1237
|
return;
|
|
@@ -1234,7 +1239,9 @@ export class AcpSessionManager {
|
|
|
1234
1239
|
parentConversation
|
|
1235
1240
|
.persistUserMessage({ content: message, metadata: { acpNotification } })
|
|
1236
1241
|
.then(({ id: messageId }) =>
|
|
1237
|
-
parentConversation.runAgentLoop(message, messageId
|
|
1242
|
+
parentConversation.runAgentLoop(message, messageId, {
|
|
1243
|
+
isInteractive: false,
|
|
1244
|
+
}),
|
|
1238
1245
|
)
|
|
1239
1246
|
.catch((err) => {
|
|
1240
1247
|
log.error(
|
package/src/api/surfaces.ts
CHANGED
|
@@ -488,6 +488,11 @@ export const WorkResultItemSchema = z.object({
|
|
|
488
488
|
status: tolerantString(),
|
|
489
489
|
tone: WorkResultToneSchema.optional().catch(undefined),
|
|
490
490
|
metadata: recordArray(WorkResultMetadataSchema).optional().catch(undefined),
|
|
491
|
+
/**
|
|
492
|
+
* Makes the item row a link. Clients follow an in-app path
|
|
493
|
+
* (`/assistant/skills/<skillId>?tab=history`) in place and open an http(s)
|
|
494
|
+
* URL externally; anything else renders as an unlinked row.
|
|
495
|
+
*/
|
|
491
496
|
href: tolerantString(),
|
|
492
497
|
});
|
|
493
498
|
export type WorkResultItem = z.infer<typeof WorkResultItemSchema>;
|
|
@@ -591,6 +596,7 @@ export const SURFACE_TYPES = [
|
|
|
591
596
|
"skill_card",
|
|
592
597
|
"call_summary",
|
|
593
598
|
"visual",
|
|
599
|
+
"voice_picker",
|
|
594
600
|
] as const;
|
|
595
601
|
|
|
596
602
|
export const SurfaceTypeSchema = z.enum(SURFACE_TYPES);
|
|
@@ -662,9 +668,10 @@ export type SurfaceData =
|
|
|
662
668
|
* and serves verbatim but whose shape it does not model, which is why they
|
|
663
669
|
* are absent from the renderable `SurfaceData` union: `channel_setup` (a
|
|
664
670
|
* side-effect command forwarded to the setup panel), `task_preferences` (a
|
|
665
|
-
* fixed grid that reads no data),
|
|
666
|
-
*
|
|
667
|
-
*
|
|
671
|
+
* fixed grid that reads no data), `voice_picker` (a settings card that reads
|
|
672
|
+
* its own config), and `skill_card` / `call_summary` (cards the daemon appends
|
|
673
|
+
* to history directly, the memory retrospective and a call summary, whose
|
|
674
|
+
* data shape is owned by their client renderers).
|
|
668
675
|
*/
|
|
669
676
|
export interface SurfaceDataByType {
|
|
670
677
|
card: CardSurfaceData;
|
|
@@ -684,6 +691,7 @@ export interface SurfaceDataByType {
|
|
|
684
691
|
skill_card: Record<string, unknown>;
|
|
685
692
|
call_summary: Record<string, unknown>;
|
|
686
693
|
visual: VisualSurfaceData;
|
|
694
|
+
voice_picker: Record<string, unknown>;
|
|
687
695
|
}
|
|
688
696
|
|
|
689
697
|
/** Any surface `data` payload, including the opaque (non-renderable) types. */
|
|
@@ -717,6 +725,7 @@ export const SURFACE_DATA_SCHEMAS: {
|
|
|
717
725
|
skill_card: z.record(z.string(), z.unknown()),
|
|
718
726
|
call_summary: z.record(z.string(), z.unknown()),
|
|
719
727
|
visual: VisualSurfaceDataSchema,
|
|
728
|
+
voice_picker: z.record(z.string(), z.unknown()),
|
|
720
729
|
};
|
|
721
730
|
|
|
722
731
|
/**
|