@vellumai/assistant 0.11.4-staging.2 → 0.11.4-staging.4
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/docs/guardian-request-flow.md +35 -0
- package/knip.json +3 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
- package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
- package/openapi.yaml +136 -76
- 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__/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-load-history-repair.test.ts +209 -0
- package/src/__tests__/conversation-notifiers-provenance.test.ts +1 -1
- package/src/__tests__/conversation-queue.test.ts +39 -62
- 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-runtime-assembly.test.ts +53 -0
- 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__/file-ops-service.test.ts +163 -30
- package/src/__tests__/filesystem-tools.test.ts +23 -24
- package/src/__tests__/gateway-only-guard.test.ts +2 -5
- package/src/__tests__/host-file-read-tool.test.ts +16 -19
- 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__/tool-executor.test.ts +5 -1
- package/src/__tests__/ui-choice-copy-surfaces.test.ts +1 -1
- package/src/__tests__/ui-visual-surface.test.ts +1 -1
- package/src/__tests__/ui-voice-picker-surface.test.ts +1 -1
- package/src/__tests__/ui-work-result-surface.test.ts +1 -1
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +5 -3
- package/src/__tests__/voice-session-bridge.test.ts +85 -29
- package/src/acp/session-manager.ts +8 -1
- package/src/api/events/host-file.ts +2 -2
- package/src/api/surfaces.ts +5 -0
- 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/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/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 +96 -4
- 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-process.ts +0 -27
- package/src/daemon/conversation-runtime-assembly.ts +9 -2
- package/src/daemon/conversation-store.ts +4 -4
- package/src/daemon/conversation-surfaces.ts +27 -13
- package/src/daemon/conversation-tool-setup.ts +2 -4
- package/src/daemon/conversation.ts +104 -51
- package/src/daemon/doordash-steps.ts +2 -2
- package/src/daemon/lifecycle.ts +14 -1
- package/src/daemon/process-message.ts +0 -13
- 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/notifications/AGENTS.md +2 -0
- package/src/notifications/approval-card-data.ts +33 -0
- 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/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/openai/chat-completions-provider.ts +11 -1
- 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 +15 -13
- package/src/tools/__tests__/tool-input-schemas.test.ts +7 -7
- package/src/tools/acp/spawn.ts +6 -4
- package/src/tools/filesystem/read.ts +27 -10
- package/src/tools/host-filesystem/read.ts +27 -15
- package/src/tools/shared/filesystem/file-ops-service.ts +63 -35
- package/src/tools/shared/filesystem/legacy-read-args.ts +22 -0
- package/src/tools/shared/filesystem/types.ts +5 -5
- 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 +1 -1
- package/src/util/__tests__/worker-process-command.test.ts +37 -0
- package/src/util/logger.ts +16 -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/__tests__/document-workspace-file.test.ts +0 -467
- package/src/daemon/interactive-turn-sender.ts +0 -59
- package/src/subagent/__tests__/consult-transcript.test.ts +0 -184
- package/src/subagent/consult-transcript.ts +0 -90
|
@@ -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));
|
|
@@ -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(
|
|
@@ -21,8 +21,8 @@ const HostFileReadRequestSchema = z.object({
|
|
|
21
21
|
targetClientId: z.string().optional(),
|
|
22
22
|
operation: z.literal("read"),
|
|
23
23
|
path: z.string(),
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
startIndex: z.number().optional(),
|
|
25
|
+
maxChars: z.number().optional(),
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
const HostFileWriteRequestSchema = z.object({
|
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>;
|
|
@@ -72,6 +72,7 @@ mock.module("../../persistence/conversation-crud.js", () => ({
|
|
|
72
72
|
|
|
73
73
|
import { setConfig } from "../../__tests__/helpers/set-config.js";
|
|
74
74
|
import { ABORT_WATCHDOG_MS } from "../../daemon/abort-watchdog.js";
|
|
75
|
+
import { VOICE_ESCALATION_CONTINUATION_MESSAGE_KIND } from "../../plugin-api/constants.js";
|
|
75
76
|
import { assistantEventHub } from "../../runtime/assistant-event-hub.js";
|
|
76
77
|
import {
|
|
77
78
|
CALL_OPENING_MARKER,
|
|
@@ -125,7 +126,7 @@ interface FakeConversation {
|
|
|
125
126
|
metadata?: Record<string, unknown>;
|
|
126
127
|
}) => Promise<{ id: string }>;
|
|
127
128
|
workingDir: string;
|
|
128
|
-
|
|
129
|
+
addEventObserver: (observer: unknown) => () => void;
|
|
129
130
|
handleConfirmationResponse: (
|
|
130
131
|
requestId: string,
|
|
131
132
|
decision: string,
|
|
@@ -191,13 +192,15 @@ function makeFakeConversation(opts: {
|
|
|
191
192
|
opts.onPersist?.(persistCount);
|
|
192
193
|
return { id: `msg-${persistCount}` };
|
|
193
194
|
},
|
|
194
|
-
// The install
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
195
|
+
// The install / reset pair marks a turn taking ownership of the
|
|
196
|
+
// conversation (observer registered) vs a turn's cleanup releasing it
|
|
197
|
+
// (the returned disposer invoked).
|
|
198
|
+
addEventObserver: (observer) => {
|
|
199
|
+
opts.events?.push("client:install");
|
|
200
|
+
clientCallback = observer as (msg: unknown) => Promise<void>;
|
|
201
|
+
return () => {
|
|
202
|
+
opts.events?.push("client:reset");
|
|
203
|
+
};
|
|
201
204
|
},
|
|
202
205
|
handleConfirmationResponse: (requestId, decision) => {
|
|
203
206
|
confirmationDecisions.push({ requestId, decision });
|
|
@@ -213,7 +216,7 @@ function makeFakeConversation(opts: {
|
|
|
213
216
|
conversation,
|
|
214
217
|
waitForIdleCalls,
|
|
215
218
|
confirmationDecisions,
|
|
216
|
-
/** Deliver an event the way the conversation would, to the installed
|
|
219
|
+
/** Deliver an event the way the conversation would, to the installed observer. */
|
|
217
220
|
emitToClient: async (msg: unknown) => {
|
|
218
221
|
await clientCallback?.(msg);
|
|
219
222
|
},
|
|
@@ -360,6 +363,10 @@ describe("startVoiceTurn escalation-continuation persistence", () => {
|
|
|
360
363
|
// not merely echo-suppressed live.
|
|
361
364
|
const fake = makeFakeConversation({ processing: false });
|
|
362
365
|
fakeConversation = fake.conversation;
|
|
366
|
+
let runOptions: Record<string, unknown> | undefined;
|
|
367
|
+
fake.conversation.runAgentLoop = async (...args: unknown[]) => {
|
|
368
|
+
runOptions = args[2] as Record<string, unknown>;
|
|
369
|
+
};
|
|
363
370
|
|
|
364
371
|
await startVoiceTurn({
|
|
365
372
|
...makeTurnOptions(),
|
|
@@ -372,7 +379,11 @@ describe("startVoiceTurn escalation-continuation persistence", () => {
|
|
|
372
379
|
expect(fake.lastPersistOpts()?.metadata).toEqual({
|
|
373
380
|
voiceSessionTurn: true,
|
|
374
381
|
hidden: true,
|
|
382
|
+
messageKind: VOICE_ESCALATION_CONTINUATION_MESSAGE_KIND,
|
|
375
383
|
});
|
|
384
|
+
expect(runOptions?.messageKind).toBe(
|
|
385
|
+
VOICE_ESCALATION_CONTINUATION_MESSAGE_KIND,
|
|
386
|
+
);
|
|
376
387
|
});
|
|
377
388
|
|
|
378
389
|
test("the opener prompt is persisted un-hidden (unchanged)", async () => {
|
|
@@ -967,7 +978,7 @@ describe("startVoiceTurn prior-turn teardown barrier", () => {
|
|
|
967
978
|
expect(events).toEqual(["persist", "client:install"]);
|
|
968
979
|
|
|
969
980
|
// Turn 2 arrives during the teardown window: it must not persist or
|
|
970
|
-
// install its
|
|
981
|
+
// install its event observer until turn 1's cleanup has run.
|
|
971
982
|
const turn2 = startVoiceTurn(
|
|
972
983
|
makeTurnOptions(undefined, "conv-teardown-order"),
|
|
973
984
|
);
|
|
@@ -85,6 +85,14 @@ describe("front-door decision rule", () => {
|
|
|
85
85
|
expect(rule.toLowerCase()).toContain("tool");
|
|
86
86
|
});
|
|
87
87
|
|
|
88
|
+
test("escalates when an answer needs personal memory absent from context", () => {
|
|
89
|
+
expect(rule.toLowerCase()).toContain("saved personal fact");
|
|
90
|
+
expect(rule.toLowerCase()).toContain("escalate rather than guessing");
|
|
91
|
+
expect(rule.toLowerCase()).toContain(
|
|
92
|
+
"personal context that is already present is yours to use directly",
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
88
96
|
test("demands a silent decision — no narrated reasoning in spoken output", () => {
|
|
89
97
|
// Regression: a weak front-door model narrated its triage deliberation
|
|
90
98
|
// aloud ("Context is complete — Alex paused...") before the bridge.
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
recordConversationPersistedSeq,
|
|
35
35
|
updateMessageContent,
|
|
36
36
|
} from "../persistence/conversation-crud.js";
|
|
37
|
+
import { VOICE_ESCALATION_CONTINUATION_MESSAGE_KIND } from "../plugin-api/constants.js";
|
|
37
38
|
import { pinnedListeningLanguage } from "../providers/speech-to-text/provider-catalog.js";
|
|
38
39
|
import type { ContentBlock } from "../providers/types.js";
|
|
39
40
|
import { broadcastMessage } from "../runtime/assistant-event-hub.js";
|
|
@@ -783,11 +784,13 @@ export async function startVoiceTurn(
|
|
|
783
784
|
// model wakes, but they are not user speech and must not render as a live
|
|
784
785
|
// user bubble. Their echo is suppressed below (parity with
|
|
785
786
|
// `isEchoSuppressedUserMessage` on the text path).
|
|
787
|
+
const isEscalationContinuation =
|
|
788
|
+
opts.content === ESCALATION_CONTINUATION_CONTENT;
|
|
786
789
|
const isSyntheticVoicePrompt =
|
|
787
790
|
opts.hiddenSyntheticPrompt === true ||
|
|
788
791
|
opts.content === CALL_OPENING_MARKER ||
|
|
789
792
|
opts.content === CALL_VERIFICATION_COMPLETE_MARKER ||
|
|
790
|
-
|
|
793
|
+
isEscalationContinuation;
|
|
791
794
|
|
|
792
795
|
// The escalation-continuation prompt is a pure internal instruction ("give
|
|
793
796
|
// the full answer now"), not a real utterance and not the sort of scaffolding
|
|
@@ -799,8 +802,7 @@ export async function startVoiceTurn(
|
|
|
799
802
|
// affects client display. A caller whose prompt text is not a fixed sentinel
|
|
800
803
|
// opts into the same treatment with `hiddenSyntheticPrompt`.
|
|
801
804
|
const isHiddenSyntheticPrompt =
|
|
802
|
-
opts.hiddenSyntheticPrompt === true ||
|
|
803
|
-
opts.content === ESCALATION_CONTINUATION_CONTENT;
|
|
805
|
+
opts.hiddenSyntheticPrompt === true || isEscalationContinuation;
|
|
804
806
|
|
|
805
807
|
// Build the call-control protocol prompt so the model knows how to emit
|
|
806
808
|
// control markers (ASK_GUARDIAN, END_CALL, etc.) and recognize opener turns.
|
|
@@ -934,10 +936,10 @@ export async function startVoiceTurn(
|
|
|
934
936
|
// failed while no concurrent turn held the lock). Paths where this turn
|
|
935
937
|
// LOST the conversation to a concurrent winner must use `restoreTurnState`
|
|
936
938
|
// instead — this reset-to-defaults would clobber the winner's live state.
|
|
937
|
-
// The
|
|
938
|
-
// (
|
|
939
|
-
//
|
|
940
|
-
let
|
|
939
|
+
// The approval observer is only detached when this turn installed it
|
|
940
|
+
// (`detachApprovalObserver` is set); otherwise cleanup would detach an
|
|
941
|
+
// observer installed by a prior turn.
|
|
942
|
+
let detachApprovalObserver: (() => void) | undefined;
|
|
941
943
|
/**
|
|
942
944
|
* Confirmations this voice turn left pending for the user to answer, and the
|
|
943
945
|
* timers that stop them waiting forever.
|
|
@@ -976,11 +978,10 @@ export async function startVoiceTurn(
|
|
|
976
978
|
conversation.setVoiceCallControlPrompt(null);
|
|
977
979
|
conversation.callSessionId = undefined;
|
|
978
980
|
conversation.forcePromptSideEffects = false;
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
}
|
|
981
|
+
// Detach so the stale closure doesn't act on events from future turns on
|
|
982
|
+
// the same conversation.
|
|
983
|
+
detachApprovalObserver?.();
|
|
984
|
+
detachApprovalObserver = undefined;
|
|
984
985
|
};
|
|
985
986
|
|
|
986
987
|
const requestId = uuidv7();
|
|
@@ -994,6 +995,9 @@ export async function startVoiceTurn(
|
|
|
994
995
|
// `isVoiceSessionUserMessage` for why the channel fields cannot carry it.
|
|
995
996
|
voiceSessionTurn: true,
|
|
996
997
|
...(isHiddenSyntheticPrompt ? { hidden: true } : {}),
|
|
998
|
+
...(isEscalationContinuation
|
|
999
|
+
? { messageKind: VOICE_ESCALATION_CONTINUATION_MESSAGE_KIND }
|
|
1000
|
+
: {}),
|
|
997
1001
|
...(opts.voiceTelemetry
|
|
998
1002
|
? {
|
|
999
1003
|
// Projected onto `TurnTelemetryEvent.client` by
|
|
@@ -1245,24 +1249,25 @@ export async function startVoiceTurn(
|
|
|
1245
1249
|
publishConversationMessagesChanged(opts.conversationId);
|
|
1246
1250
|
}
|
|
1247
1251
|
|
|
1248
|
-
//
|
|
1249
|
-
//
|
|
1252
|
+
// Observe confirmation_request and secret_request events so voice can
|
|
1253
|
+
// auto-deny/auto-allow/auto-resolve them (there is no interactive UI on a
|
|
1254
|
+
// call). Delivery is not this observer's job: the conversation's sink has
|
|
1255
|
+
// already put each event on the wire before the observer sees it, which is
|
|
1256
|
+
// what keeps a resolution's `interaction_resolved` after the request it
|
|
1257
|
+
// answers.
|
|
1250
1258
|
let lastError: string | null = null;
|
|
1251
|
-
|
|
1259
|
+
const handleVoiceEvent = async (msg: AssistantEvent): Promise<void> => {
|
|
1252
1260
|
// The user (or anything else) answered: stop the fallback from firing on a
|
|
1253
1261
|
// request that is already decided.
|
|
1254
1262
|
if (msg.type === "interaction_resolved") {
|
|
1255
1263
|
settleVoiceApproval(msg.requestId);
|
|
1256
1264
|
}
|
|
1257
1265
|
if (msg.type === "confirmation_request") {
|
|
1258
|
-
//
|
|
1259
|
-
//
|
|
1260
|
-
//
|
|
1261
|
-
//
|
|
1262
|
-
//
|
|
1263
|
-
// before `confirmation_request` on the wire, leaving an orphaned card
|
|
1264
|
-
// whose Allow/Deny buttons 404.
|
|
1265
|
-
broadcastMessage(msg);
|
|
1266
|
+
// The request is already on the wire (the sink delivered it before this
|
|
1267
|
+
// observer ran), so a resolution below lands after it: attached clients
|
|
1268
|
+
// (e.g. the web app behind a live-voice room) clear their approval card
|
|
1269
|
+
// only on `interaction_resolved`, and the reverse order would leave an
|
|
1270
|
+
// orphaned card whose Allow/Deny buttons 404.
|
|
1266
1271
|
if (!isGuardian) {
|
|
1267
1272
|
// Non-guardian voice callers have no interactive approval UI.
|
|
1268
1273
|
// The pre-exec gate (tool-approval-handler.ts) handles grant
|
|
@@ -1420,9 +1425,15 @@ export async function startVoiceTurn(
|
|
|
1420
1425
|
conversation.handleSecretResponse(msg.requestId, undefined, "store");
|
|
1421
1426
|
return;
|
|
1422
1427
|
}
|
|
1423
|
-
|
|
1428
|
+
};
|
|
1429
|
+
detachApprovalObserver = conversation.addEventObserver((msg) => {
|
|
1430
|
+
handleVoiceEvent(msg).catch((err) => {
|
|
1431
|
+
log.warn(
|
|
1432
|
+
{ turnId, eventType: msg.type, err },
|
|
1433
|
+
"Voice approval observer failed",
|
|
1434
|
+
);
|
|
1435
|
+
});
|
|
1424
1436
|
});
|
|
1425
|
-
clientCallbackInstalled = true;
|
|
1426
1437
|
|
|
1427
1438
|
// Registered before the agent loop starts so the NEXT turn on this
|
|
1428
1439
|
// conversation waits for this turn's `finally { cleanup() }` — not just
|
|
@@ -1709,11 +1720,19 @@ export async function startVoiceTurn(
|
|
|
1709
1720
|
// ordinary call-agent resolution.
|
|
1710
1721
|
callSite:
|
|
1711
1722
|
opts.routingLeg === "front-door" ? "voiceFrontDoor" : "callAgent",
|
|
1723
|
+
// A caller is on the line, so the turn is interactive: approval prompts
|
|
1724
|
+
// must be raised rather than pre-denied, because the approval observer
|
|
1725
|
+
// above is what decides them (auto-resolve for a non-guardian caller,
|
|
1726
|
+
// a real card the guardian can answer).
|
|
1727
|
+
isInteractive: true,
|
|
1712
1728
|
// The escalation-continuation prompt is a transcript-suppressed machine
|
|
1713
1729
|
// signal (persisted `hidden`), so flag the turn to match — keeps
|
|
1714
1730
|
// prompt-as-user-speech consumers (e.g. title generation) from treating
|
|
1715
1731
|
// it as user speech.
|
|
1716
1732
|
...(isHiddenSyntheticPrompt ? { isHiddenPrompt: true } : {}),
|
|
1733
|
+
...(isEscalationContinuation
|
|
1734
|
+
? { messageKind: VOICE_ESCALATION_CONTINUATION_MESSAGE_KIND }
|
|
1735
|
+
: {}),
|
|
1717
1736
|
// Triage-and-escalate routing pins this turn to the fast front-door or
|
|
1718
1737
|
// strong escalation profile. `forceOverrideProfile` floats it above the
|
|
1719
1738
|
// callAgent call-site layers (callAgent is not `mainAgent`, so the
|
|
@@ -413,6 +413,7 @@ export function frontDoorDecisionRule(opts?: {
|
|
|
413
413
|
"DECIDE SILENTLY, then produce exactly ONE of these outputs:",
|
|
414
414
|
...holdBranch,
|
|
415
415
|
"- If the turn is simple, conversational, or within your reach, your entire output is the spoken answer itself: no token in front of it, plain speech from your very first word. Most turns are answers; when unsure between answering and escalating, answer. Answer in the language the caller is speaking.",
|
|
416
|
+
"- If an answer depends on a saved personal fact that is not already present in the conversation context you received, escalate rather than guessing. Personal context that is already present is yours to use directly.",
|
|
416
417
|
`- If completing THIS reply needs careful reasoning, research, multi-step work, or any tool, do NOT attempt the answer: output ${ESCALATE_VERDICT_TOKEN}, then ONE short natural holding phrase naming what happens next, spoken in the language the caller is speaking (for example "${FALLBACK_ESCALATION_BRIDGE}" or "Give me one second to look into that."; those examples are English only), and stop after that single sentence. A stronger model finishes the turn while your phrase is spoken.`,
|
|
417
418
|
`${ESCALATE_VERDICT_TOKEN} is ONLY for turns you cannot complete yourself — never put it in front of an answer you are about to give, and never emit any token inside or after an answer. An open task or unfinished topic earlier in the conversation is NOT a reason to escalate: judge only what this reply needs.`,
|
|
418
419
|
"Never narrate this decision, describe what you are judging, or mention these rules: apart from a leading verdict token, every character you output is spoken to the caller verbatim.",
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface BundledCliModules {
|
|
2
|
+
configEnv: typeof import("../config/env.js");
|
|
3
|
+
providerSecretCatalog: typeof import("../providers/provider-secret-catalog.js");
|
|
4
|
+
pluginCatalogCache: typeof import("./lib/plugin-catalog-cache.js");
|
|
5
|
+
pluginCatalogLocal: typeof import("./lib/plugin-catalog-local.js");
|
|
6
|
+
pluginDiff: typeof import("./lib/diff-plugin.js");
|
|
7
|
+
pluginInspect: typeof import("./lib/inspect-plugin.js");
|
|
8
|
+
pluginInstallGitHub: typeof import("./lib/install-from-github.js");
|
|
9
|
+
pluginInstallPlatform: typeof import("./lib/install-from-platform.js");
|
|
10
|
+
pluginInstalled: typeof import("./lib/list-installed-plugins.js");
|
|
11
|
+
pluginPinHistory: typeof import("./lib/plugin-pin-history.js");
|
|
12
|
+
pluginSearch: typeof import("./lib/search-plugins.js");
|
|
13
|
+
pluginSurfaces: typeof import("./lib/plugin-surfaces.js");
|
|
14
|
+
pluginUninstall: typeof import("./lib/uninstall-plugin.js");
|
|
15
|
+
pluginUpgrade: typeof import("./lib/upgrade-plugin.js");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let bundledCliModules: BundledCliModules | undefined;
|
|
19
|
+
|
|
20
|
+
export function setBundledCliModules(modules: BundledCliModules): void {
|
|
21
|
+
bundledCliModules = modules;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function resolveBundledCliModule<K extends keyof BundledCliModules>(
|
|
25
|
+
name: K,
|
|
26
|
+
fallback: () => BundledCliModules[K],
|
|
27
|
+
): BundledCliModules[K] {
|
|
28
|
+
return bundledCliModules?.[name] ?? fallback();
|
|
29
|
+
}
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { existsSync } from "node:fs";
|
|
18
|
-
import { createRequire } from "node:module";
|
|
19
18
|
|
|
20
19
|
import type { Command } from "commander";
|
|
21
20
|
|
|
@@ -27,8 +26,6 @@ import { integrityCheckStep } from "./repair-step-integrity.js";
|
|
|
27
26
|
import type { RepairReport, RepairStep, StepResult } from "./repair-steps.js";
|
|
28
27
|
import { formatDurationMs, runRepairSteps } from "./repair-steps.js";
|
|
29
28
|
|
|
30
|
-
const loadModule = createRequire(import.meta.url);
|
|
31
|
-
|
|
32
29
|
// ---------------------------------------------------------------------------
|
|
33
30
|
// Step sequence
|
|
34
31
|
// ---------------------------------------------------------------------------
|
|
@@ -38,10 +35,9 @@ const loadModule = createRequire(import.meta.url);
|
|
|
38
35
|
* structural damage surfaces early. Resolved lazily because the backfill
|
|
39
36
|
* step pulls the DB graph.
|
|
40
37
|
*/
|
|
41
|
-
function repairSteps(): RepairStep[] {
|
|
42
|
-
const { conversationBackfillStep } =
|
|
43
|
-
"./repair-step-conversation-backfill.js"
|
|
44
|
-
) as typeof import("./repair-step-conversation-backfill.js");
|
|
38
|
+
async function repairSteps(): Promise<RepairStep[]> {
|
|
39
|
+
const { conversationBackfillStep } =
|
|
40
|
+
await import("./repair-step-conversation-backfill.js");
|
|
45
41
|
return [integrityCheckStep, conversationBackfillStep];
|
|
46
42
|
}
|
|
47
43
|
|
|
@@ -128,7 +124,7 @@ export function registerDbRepair(parent: Command): void {
|
|
|
128
124
|
|
|
129
125
|
const report = await runRepairSteps(
|
|
130
126
|
{ dbPath },
|
|
131
|
-
repairSteps(),
|
|
127
|
+
await repairSteps(),
|
|
132
128
|
isJson
|
|
133
129
|
? {}
|
|
134
130
|
: {
|