@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
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
* the Conversation constructor and dispose/abort methods.
|
|
4
4
|
*
|
|
5
5
|
* Notifier callbacks read from the provided context object at invocation
|
|
6
|
-
* time (not registration time), so they always see the latest
|
|
7
|
-
*
|
|
6
|
+
* time (not registration time), so they always see the latest messages
|
|
7
|
+
* reference. They emit through the conversation, whose sink is fixed for its
|
|
8
|
+
* life, so an out-of-turn notification reaches every subscribed client
|
|
9
|
+
* without any per-turn wiring.
|
|
8
10
|
*/
|
|
9
11
|
|
|
10
12
|
import { createAssistantMessage } from "../agent/message-types.js";
|
|
@@ -32,7 +34,7 @@ import { restingTrust } from "./trust-context-types.js";
|
|
|
32
34
|
* invocation time. Properties are read lazily from this reference.
|
|
33
35
|
*/
|
|
34
36
|
export interface NotifierConversationContext {
|
|
35
|
-
|
|
37
|
+
emit: (msg: AssistantEvent) => void;
|
|
36
38
|
messages: Message[];
|
|
37
39
|
trustContext?: TrustContext;
|
|
38
40
|
}
|
|
@@ -77,12 +79,12 @@ export function registerConversationNotifiers(
|
|
|
77
79
|
|
|
78
80
|
ctx.messages.push(createAssistantMessage(questionText));
|
|
79
81
|
|
|
80
|
-
ctx.
|
|
82
|
+
ctx.emit({
|
|
81
83
|
type: "assistant_text_delta",
|
|
82
84
|
text: questionText,
|
|
83
85
|
conversationId: conversationId,
|
|
84
86
|
});
|
|
85
|
-
ctx.
|
|
87
|
+
ctx.emit({
|
|
86
88
|
type: "message_complete",
|
|
87
89
|
conversationId: conversationId,
|
|
88
90
|
messageId: msg.id,
|
|
@@ -97,12 +99,12 @@ export function registerConversationNotifiers(
|
|
|
97
99
|
const speakerLabel = speaker === "caller" ? "Caller" : "Assistant";
|
|
98
100
|
const transcriptText = `**Live call transcript**\n${speakerLabel}: ${text}`;
|
|
99
101
|
|
|
100
|
-
ctx.
|
|
102
|
+
ctx.emit({
|
|
101
103
|
type: "assistant_text_delta",
|
|
102
104
|
text: transcriptText,
|
|
103
105
|
conversationId: conversationId,
|
|
104
106
|
});
|
|
105
|
-
ctx.
|
|
107
|
+
ctx.emit({
|
|
106
108
|
type: "message_complete",
|
|
107
109
|
conversationId: conversationId,
|
|
108
110
|
source: "aux",
|
|
@@ -113,12 +115,12 @@ export function registerConversationNotifiers(
|
|
|
113
115
|
registerCallCompletionNotifier(conversationId, (callSessionId: string) => {
|
|
114
116
|
const summaryText = buildCallCompletionMessage(callSessionId);
|
|
115
117
|
|
|
116
|
-
ctx.
|
|
118
|
+
ctx.emit({
|
|
117
119
|
type: "assistant_text_delta",
|
|
118
120
|
text: summaryText,
|
|
119
121
|
conversationId: conversationId,
|
|
120
122
|
});
|
|
121
|
-
ctx.
|
|
123
|
+
ctx.emit({
|
|
122
124
|
type: "message_complete",
|
|
123
125
|
conversationId: conversationId,
|
|
124
126
|
source: "aux",
|
|
@@ -60,7 +60,6 @@ import {
|
|
|
60
60
|
} from "./conversation-slash.js";
|
|
61
61
|
import { getModelInfo } from "./handlers/config-model.js";
|
|
62
62
|
import { preactivateHostProxySkills } from "./host-proxy-preactivation.js";
|
|
63
|
-
import { bindInteractiveTurnSender } from "./interactive-turn-sender.js";
|
|
64
63
|
import type { UserMessageAttachment } from "./message-protocol.js";
|
|
65
64
|
import { buildTransportHints } from "./transport-hints.js";
|
|
66
65
|
import { sameTrustIdentity, type TrustContext } from "./trust-context-types.js";
|
|
@@ -1293,26 +1292,11 @@ async function drainSingleMessage(
|
|
|
1293
1292
|
drainLoopOptions.isHiddenPrompt = true;
|
|
1294
1293
|
}
|
|
1295
1294
|
|
|
1296
|
-
// By the time the drain runs, the conversation-level sender is usually the
|
|
1297
|
-
// no-op the just-finished interactive turn's restore (or conversation
|
|
1298
|
-
// birth) left behind, and the drain bypasses `processMessage`, so without
|
|
1299
|
-
// a fresh bind the PermissionPrompter's confirmation_request (which reads
|
|
1300
|
-
// the conversation-level sender, not the turn's `onEvent`) would be
|
|
1301
|
-
// emitted into nothing and hang until the permission timeout. Same
|
|
1302
|
-
// bind/restore contract as `processMessage`.
|
|
1303
|
-
const restoreSender =
|
|
1304
|
-
next.isInteractive === true
|
|
1305
|
-
? bindInteractiveTurnSender(conversation)
|
|
1306
|
-
: undefined;
|
|
1307
|
-
|
|
1308
1295
|
conversation
|
|
1309
1296
|
.runAgentLoop(agentLoopContent, userMessageId, {
|
|
1310
1297
|
...drainLoopOptions,
|
|
1311
1298
|
onEvent: next.onEvent,
|
|
1312
1299
|
})
|
|
1313
|
-
.finally(() => {
|
|
1314
|
-
restoreSender?.();
|
|
1315
|
-
})
|
|
1316
1300
|
.catch((err) => {
|
|
1317
1301
|
const message = err instanceof Error ? err.message : String(err);
|
|
1318
1302
|
log.error(
|
|
@@ -1795,14 +1779,6 @@ async function drainBatch(
|
|
|
1795
1779
|
drainLoopOptions.isHiddenPrompt = true;
|
|
1796
1780
|
}
|
|
1797
1781
|
|
|
1798
|
-
// Same sender contract as drainSingleMessage: an interactive drained batch
|
|
1799
|
-
// must rebind the conversation-level sender or prompter confirmations
|
|
1800
|
-
// raised during this turn reach no client.
|
|
1801
|
-
const restoreSender =
|
|
1802
|
-
drainLoopOptions.isInteractive === true
|
|
1803
|
-
? bindInteractiveTurnSender(conversation)
|
|
1804
|
-
: undefined;
|
|
1805
|
-
|
|
1806
1782
|
// Fire-and-forget: runAgentLoop's finally block recursively calls drainQueue
|
|
1807
1783
|
// when this run completes. Mirrors drainSingleMessage.
|
|
1808
1784
|
conversation
|
|
@@ -1810,9 +1786,6 @@ async function drainBatch(
|
|
|
1810
1786
|
...drainLoopOptions,
|
|
1811
1787
|
onEvent: fanOutOnEvent,
|
|
1812
1788
|
})
|
|
1813
|
-
.finally(() => {
|
|
1814
|
-
restoreSender?.();
|
|
1815
|
-
})
|
|
1816
1789
|
.catch((err) => {
|
|
1817
1790
|
const message = err instanceof Error ? err.message : String(err);
|
|
1818
1791
|
log.error(
|
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
type RenderedSlackTranscriptMessage,
|
|
45
45
|
renderSlackTranscriptWithProvenance,
|
|
46
46
|
} from "../messaging/providers/slack/render-transcript.js";
|
|
47
|
+
import { isGuardianCardRow } from "../notifications/approval-card-data.js";
|
|
47
48
|
import {
|
|
48
49
|
getMessages as defaultGetMessages,
|
|
49
50
|
type MessageRow,
|
|
@@ -520,7 +521,7 @@ function injectActiveSurfaceContext(
|
|
|
520
521
|
"RULES FOR WORKSPACE MODIFICATION:",
|
|
521
522
|
`1. Use \`file_edit\` to make surgical changes to app files. The file path is \`${getAppDirPath(ctx.appId)}/<path>\`.`,
|
|
522
523
|
"2. Use `file_write` to create new files or rewrite files.",
|
|
523
|
-
"3. Use `file_read` to read any file
|
|
524
|
+
"3. Use `file_read` to read any file before editing, and `code_search` when you need a line reference.",
|
|
524
525
|
"4. Use `bash ls` to see all files in the app directory.",
|
|
525
526
|
`5. Call \`app_refresh\` with app_id "${ctx.appId}" ONCE after all changes are complete.`,
|
|
526
527
|
"6. NEVER respond with only text — the user expects a visual update.",
|
|
@@ -1513,10 +1514,16 @@ export function loadSlackChronologicalContext(
|
|
|
1513
1514
|
allRows,
|
|
1514
1515
|
options.trustClass,
|
|
1515
1516
|
);
|
|
1517
|
+
// Applied after the compaction boundary, never before it: that filter
|
|
1518
|
+
// indexes into the row list when no watermark is set, so dropping rows
|
|
1519
|
+
// earlier would shift what it cuts. Same rule and same ordering constraint
|
|
1520
|
+
// as `Conversation.loadFromDb` -- Slack builds the provider history from
|
|
1521
|
+
// rows rather than `this.messages`, so a rule applied only there would
|
|
1522
|
+
// exempt this channel entirely.
|
|
1516
1523
|
const rows = filterRowsAfterSlackCompactionBoundary(
|
|
1517
1524
|
messageRowsToSlackTranscriptRows(scopedRows),
|
|
1518
1525
|
options,
|
|
1519
|
-
);
|
|
1526
|
+
).filter((row) => !isGuardianCardRow(row.content));
|
|
1520
1527
|
return assembleSlackChronologicalContext(rows, capabilities, {
|
|
1521
1528
|
contextSummary: resolveCapabilities(options.trustClass).canAccessMemory
|
|
1522
1529
|
? options.contextSummary
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
} from "../providers/connection-resolution.js";
|
|
28
28
|
import { RateLimitProvider } from "../providers/ratelimit.js";
|
|
29
29
|
import { listProviders } from "../providers/registry.js";
|
|
30
|
+
import { broadcastMessage } from "../runtime/assistant-event-hub.js";
|
|
30
31
|
import { getSubagentManager } from "../subagent/index.js";
|
|
31
32
|
import { getSandboxWorkingDir } from "../util/platform.js";
|
|
32
33
|
import { Conversation } from "./conversation.js";
|
|
@@ -156,7 +157,6 @@ export async function getOrCreateConversation(
|
|
|
156
157
|
options?: ConversationCreateOptions,
|
|
157
158
|
): Promise<Conversation> {
|
|
158
159
|
let conversation = findConversation(conversationId);
|
|
159
|
-
const sendToClient = () => {};
|
|
160
160
|
|
|
161
161
|
// `taskRunId` and `ephemeral` are per-call scopes, not durable conversation
|
|
162
162
|
// metadata, so they are stripped before the remaining options are merged
|
|
@@ -232,7 +232,9 @@ export async function getOrCreateConversation(
|
|
|
232
232
|
conversationId,
|
|
233
233
|
provider,
|
|
234
234
|
systemPrompt,
|
|
235
|
-
|
|
235
|
+
// Top-level conversations deliver to the SSE hub for their whole life,
|
|
236
|
+
// so every subscribed client sees every event with no per-turn wiring.
|
|
237
|
+
broadcastMessage,
|
|
236
238
|
workingDir,
|
|
237
239
|
{
|
|
238
240
|
maxTokens,
|
|
@@ -240,8 +242,6 @@ export async function getOrCreateConversation(
|
|
|
240
242
|
modelOverride: storedOptions?.modelOverride,
|
|
241
243
|
},
|
|
242
244
|
);
|
|
243
|
-
newConversation.updateClient(sendToClient, true);
|
|
244
|
-
|
|
245
245
|
// Ensure the conversations row exists before hydrating from DB.
|
|
246
246
|
// `getOrCreateConversation` builds the in-memory Conversation, but
|
|
247
247
|
// the persisted row is what `loadFromDb` reads for conversationType,
|
|
@@ -138,6 +138,16 @@ const ModelActionSchema = SurfaceActionSchema.extend({
|
|
|
138
138
|
|
|
139
139
|
const MAX_UNDO_DEPTH = 10;
|
|
140
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Whether a surface-action turn (a user clicked a button or submitted a form on
|
|
143
|
+
* a UI surface) runs with a human declared present. The handler does not yet
|
|
144
|
+
* learn the actor's interface, so it cannot declare presence the way the send
|
|
145
|
+
* route does; the turn runs non-interactive, which is what the state-derived
|
|
146
|
+
* fallback resolved to between turns. Deriving it from the actor's interface
|
|
147
|
+
* is a policy change tracked separately.
|
|
148
|
+
*/
|
|
149
|
+
const SURFACE_ACTION_TURN_IS_INTERACTIVE = false;
|
|
150
|
+
|
|
141
151
|
/**
|
|
142
152
|
* Pending surface types that do not hold the one-interactive-surface-at-a-time
|
|
143
153
|
* lock. Each renders content the user reads (or settles on its own) rather
|
|
@@ -1372,7 +1382,7 @@ export function openChannelSetupPanel(
|
|
|
1372
1382
|
data: safeParseSurfaceData("channel_setup", data) ?? {},
|
|
1373
1383
|
});
|
|
1374
1384
|
|
|
1375
|
-
ctx.
|
|
1385
|
+
ctx.emit({
|
|
1376
1386
|
type: "open_panel",
|
|
1377
1387
|
panelType: "channel_setup",
|
|
1378
1388
|
data,
|
|
@@ -1510,7 +1520,7 @@ function pushUndoState(
|
|
|
1510
1520
|
export function handleSurfaceUndo(ctx: Conversation, surfaceId: string): void {
|
|
1511
1521
|
const stack = ctx.surfaceUndoStacks.get(surfaceId);
|
|
1512
1522
|
if (!stack || stack.length === 0) {
|
|
1513
|
-
ctx.
|
|
1523
|
+
ctx.emit({
|
|
1514
1524
|
type: "ui_surface_undo_result",
|
|
1515
1525
|
conversationId: ctx.conversationId,
|
|
1516
1526
|
surfaceId,
|
|
@@ -1523,7 +1533,7 @@ export function handleSurfaceUndo(ctx: Conversation, surfaceId: string): void {
|
|
|
1523
1533
|
const previousHtml = stack.pop()!;
|
|
1524
1534
|
const stored = ctx.surfaceState.get(surfaceId);
|
|
1525
1535
|
if (!stored || stored.surfaceType !== "dynamic_page") {
|
|
1526
|
-
ctx.
|
|
1536
|
+
ctx.emit({
|
|
1527
1537
|
type: "ui_surface_undo_result",
|
|
1528
1538
|
conversationId: ctx.conversationId,
|
|
1529
1539
|
surfaceId,
|
|
@@ -1557,7 +1567,7 @@ export function handleSurfaceUndo(ctx: Conversation, surfaceId: string): void {
|
|
|
1557
1567
|
html: previousHtml,
|
|
1558
1568
|
};
|
|
1559
1569
|
s.data = revertedData;
|
|
1560
|
-
ctx.
|
|
1570
|
+
ctx.emit({
|
|
1561
1571
|
type: "ui_surface_update",
|
|
1562
1572
|
conversationId: ctx.conversationId,
|
|
1563
1573
|
surfaceId: sid,
|
|
@@ -1594,7 +1604,7 @@ export function handleSurfaceUndo(ctx: Conversation, surfaceId: string): void {
|
|
|
1594
1604
|
html: previousHtml,
|
|
1595
1605
|
};
|
|
1596
1606
|
stored.data = revertedData;
|
|
1597
|
-
ctx.
|
|
1607
|
+
ctx.emit({
|
|
1598
1608
|
type: "ui_surface_update",
|
|
1599
1609
|
conversationId: ctx.conversationId,
|
|
1600
1610
|
surfaceId,
|
|
@@ -1602,7 +1612,7 @@ export function handleSurfaceUndo(ctx: Conversation, surfaceId: string): void {
|
|
|
1602
1612
|
});
|
|
1603
1613
|
}
|
|
1604
1614
|
|
|
1605
|
-
ctx.
|
|
1615
|
+
ctx.emit({
|
|
1606
1616
|
type: "ui_surface_undo_result",
|
|
1607
1617
|
conversationId: ctx.conversationId,
|
|
1608
1618
|
surfaceId,
|
|
@@ -2212,6 +2222,7 @@ export async function handleSurfaceAction(
|
|
|
2212
2222
|
activeSurfaceId: surfaceId,
|
|
2213
2223
|
displayContent,
|
|
2214
2224
|
sourceActorPrincipalId,
|
|
2225
|
+
isInteractive: SURFACE_ACTION_TURN_IS_INTERACTIVE,
|
|
2215
2226
|
// Rides the metadata bag rather than a typed option: the queue
|
|
2216
2227
|
// round-trips `metadata` but not `PersistMessageOptions`.
|
|
2217
2228
|
metadata: { scripted: isSyntheticSurfaceActionContent(content) },
|
|
@@ -2274,6 +2285,7 @@ export async function handleSurfaceAction(
|
|
|
2274
2285
|
activeSurfaceId: surfaceId,
|
|
2275
2286
|
displayContent,
|
|
2276
2287
|
sourceActorPrincipalId,
|
|
2288
|
+
isInteractive: SURFACE_ACTION_TURN_IS_INTERACTIVE,
|
|
2277
2289
|
scripted: isSyntheticSurfaceActionContent(content),
|
|
2278
2290
|
})
|
|
2279
2291
|
.catch((err) => {
|
|
@@ -2460,6 +2472,7 @@ export async function handleSurfaceAction(
|
|
|
2460
2472
|
activeSurfaceId: surfaceId,
|
|
2461
2473
|
displayContent,
|
|
2462
2474
|
sourceActorPrincipalId,
|
|
2475
|
+
isInteractive: SURFACE_ACTION_TURN_IS_INTERACTIVE,
|
|
2463
2476
|
// Rides the metadata bag rather than a typed option: the queue
|
|
2464
2477
|
// round-trips `metadata` but not `PersistMessageOptions`.
|
|
2465
2478
|
metadata: { scripted: isSyntheticSurfaceActionContent(content) },
|
|
@@ -2532,6 +2545,7 @@ export async function handleSurfaceAction(
|
|
|
2532
2545
|
activeSurfaceId: surfaceId,
|
|
2533
2546
|
displayContent,
|
|
2534
2547
|
sourceActorPrincipalId,
|
|
2548
|
+
isInteractive: SURFACE_ACTION_TURN_IS_INTERACTIVE,
|
|
2535
2549
|
scripted: isSyntheticSurfaceActionContent(content),
|
|
2536
2550
|
})
|
|
2537
2551
|
.catch((err) => {
|
|
@@ -2596,7 +2610,7 @@ export function refreshSurfacesForApp(
|
|
|
2596
2610
|
}
|
|
2597
2611
|
|
|
2598
2612
|
// Push the update to the client
|
|
2599
|
-
ctx.
|
|
2613
|
+
ctx.emit({
|
|
2600
2614
|
type: "ui_surface_update",
|
|
2601
2615
|
conversationId: ctx.conversationId,
|
|
2602
2616
|
surfaceId,
|
|
@@ -3433,7 +3447,7 @@ export async function surfaceProxyResolver(
|
|
|
3433
3447
|
"Sending ui_surface_show to client",
|
|
3434
3448
|
);
|
|
3435
3449
|
|
|
3436
|
-
ctx.
|
|
3450
|
+
ctx.emit({
|
|
3437
3451
|
type: "ui_surface_show",
|
|
3438
3452
|
conversationId: ctx.conversationId,
|
|
3439
3453
|
surfaceId,
|
|
@@ -3552,7 +3566,7 @@ export async function surfaceProxyResolver(
|
|
|
3552
3566
|
mergedData = patch;
|
|
3553
3567
|
}
|
|
3554
3568
|
|
|
3555
|
-
ctx.
|
|
3569
|
+
ctx.emit({
|
|
3556
3570
|
type: "ui_surface_update",
|
|
3557
3571
|
conversationId: ctx.conversationId,
|
|
3558
3572
|
surfaceId,
|
|
@@ -3610,7 +3624,7 @@ export async function surfaceProxyResolver(
|
|
|
3610
3624
|
isError: true,
|
|
3611
3625
|
};
|
|
3612
3626
|
}
|
|
3613
|
-
ctx.
|
|
3627
|
+
ctx.emit({
|
|
3614
3628
|
type: "ui_surface_complete",
|
|
3615
3629
|
conversationId: ctx.conversationId,
|
|
3616
3630
|
surfaceId,
|
|
@@ -3618,7 +3632,7 @@ export async function surfaceProxyResolver(
|
|
|
3618
3632
|
submittedData: lastAction.data,
|
|
3619
3633
|
});
|
|
3620
3634
|
} else {
|
|
3621
|
-
ctx.
|
|
3635
|
+
ctx.emit({
|
|
3622
3636
|
type: "ui_surface_dismiss",
|
|
3623
3637
|
conversationId: ctx.conversationId,
|
|
3624
3638
|
surfaceId,
|
|
@@ -3720,7 +3734,7 @@ export async function surfaceProxyResolver(
|
|
|
3720
3734
|
// Inline-only preview card emitted during app_create — do not open a
|
|
3721
3735
|
// workspace panel and do not register surface state. The client renders
|
|
3722
3736
|
// this as a tappable inline card that opens the app on demand.
|
|
3723
|
-
ctx.
|
|
3737
|
+
ctx.emit({
|
|
3724
3738
|
type: "ui_surface_show",
|
|
3725
3739
|
conversationId: ctx.conversationId,
|
|
3726
3740
|
surfaceId,
|
|
@@ -3750,7 +3764,7 @@ export async function surfaceProxyResolver(
|
|
|
3750
3764
|
title: app.name,
|
|
3751
3765
|
});
|
|
3752
3766
|
|
|
3753
|
-
ctx.
|
|
3767
|
+
ctx.emit({
|
|
3754
3768
|
type: "ui_surface_show",
|
|
3755
3769
|
conversationId: ctx.conversationId,
|
|
3756
3770
|
surfaceId,
|
|
@@ -245,9 +245,7 @@ export function createToolExecutor(
|
|
|
245
245
|
toolUseId?: string,
|
|
246
246
|
) => Promise<ToolExecutionResult> {
|
|
247
247
|
// Register the conversation's sendToClient for browser screencast surface messages
|
|
248
|
-
registerConversationSender(ctx.conversationId, (msg) =>
|
|
249
|
-
ctx.sendToClient(msg),
|
|
250
|
-
);
|
|
248
|
+
registerConversationSender(ctx.conversationId, (msg) => ctx.emit(msg));
|
|
251
249
|
|
|
252
250
|
// Execution-layer allowlist gate (`subagentToolGateMode === "execution"`,
|
|
253
251
|
// see {@link SubagentToolGateMode}): rejects non-allowlisted calls BEFORE
|
|
@@ -440,7 +438,7 @@ export function createToolExecutor(
|
|
|
440
438
|
sendToClient: (msg) => {
|
|
441
439
|
// Tool context's sendToClient uses a loose { type: string; [key: string]: unknown }
|
|
442
440
|
// signature, but at runtime these are always AssistantEvent instances.
|
|
443
|
-
ctx.
|
|
441
|
+
ctx.emit(msg as AssistantEvent);
|
|
444
442
|
if (msg.type === "ui_surface_show") {
|
|
445
443
|
// The tool-context sendToClient signature is loose, so the show
|
|
446
444
|
// message's fields are untyped here; map them through the same
|
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
derefToolResultReReads,
|
|
42
42
|
postTurnTruncateToolResults,
|
|
43
43
|
} from "../context/post-turn-tool-result-truncation.js";
|
|
44
|
+
import { isGuardianCardRow } from "../notifications/approval-card-data.js";
|
|
44
45
|
import { PermissionPrompter } from "../permissions/prompter.js";
|
|
45
46
|
import { SecretPrompter } from "../permissions/secret-prompter.js";
|
|
46
47
|
import type { UserDecision } from "../permissions/types.js";
|
|
@@ -319,7 +320,21 @@ export class Conversation {
|
|
|
319
320
|
/** @internal */ prompter: PermissionPrompter;
|
|
320
321
|
/** @internal */ secretPrompter: SecretPrompter;
|
|
321
322
|
private executor: ToolExecutor;
|
|
322
|
-
/**
|
|
323
|
+
/**
|
|
324
|
+
* The conversation's event sink, fixed for its whole life. Top-level
|
|
325
|
+
* conversations deliver to the SSE hub, so every subscribed client sees
|
|
326
|
+
* every event without any per-turn wiring; a subagent's sink re-envelopes
|
|
327
|
+
* its events under the parent conversation. Reached only through
|
|
328
|
+
* {@link emit}, which also notifies {@link addEventObserver} observers.
|
|
329
|
+
*/
|
|
330
|
+
private readonly sendToClient: (msg: AssistantEvent) => void;
|
|
331
|
+
/**
|
|
332
|
+
* Observers notified after every {@link emit}, in registration order. An
|
|
333
|
+
* observer sees the event after the sink delivered it, so anything it does
|
|
334
|
+
* in response (e.g. voice auto-resolving a confirmation) lands on the wire
|
|
335
|
+
* after the event itself.
|
|
336
|
+
*/
|
|
337
|
+
private readonly eventObservers = new Set<(msg: AssistantEvent) => void>();
|
|
323
338
|
/** @internal */ workingDir: string;
|
|
324
339
|
/** @internal */ allowedToolNames?: Set<string>;
|
|
325
340
|
/**
|
|
@@ -435,7 +450,18 @@ export class Conversation {
|
|
|
435
450
|
* @internal
|
|
436
451
|
*/
|
|
437
452
|
currentCallSite?: LLMCallSite;
|
|
438
|
-
/**
|
|
453
|
+
/**
|
|
454
|
+
* Whether no human is present to see UI or answer prompts. Derived from the
|
|
455
|
+
* in-flight turn's interactivity ({@link currentTurnIsNonInteractive}); a
|
|
456
|
+
* conversation with no turn in flight has no client. Presence is a property
|
|
457
|
+
* of the turn, never of where events are delivered, so there is no setter:
|
|
458
|
+
* dispatch paths declare interactivity per turn (`isInteractive` on
|
|
459
|
+
* `runAgentLoop`, or a wake's pin), and this reads it.
|
|
460
|
+
* @internal
|
|
461
|
+
*/
|
|
462
|
+
get hasNoClient(): boolean {
|
|
463
|
+
return this.currentTurnIsNonInteractive ?? true;
|
|
464
|
+
}
|
|
439
465
|
/**
|
|
440
466
|
* For subagent conversations, the id of the parent that spawned this one; set
|
|
441
467
|
* once at construction and never reassigned. `undefined` for top-level
|
|
@@ -757,10 +783,10 @@ export class Conversation {
|
|
|
757
783
|
this.workingDir = workingDir;
|
|
758
784
|
this.sendToClient = sendToClient;
|
|
759
785
|
this.graphMemory = new ConversationGraphMemory(conversationId);
|
|
760
|
-
|
|
786
|
+
// The prompter emits through the conversation so its confirmation_request
|
|
787
|
+
// reaches the sink and every observer (voice policy) like any other event.
|
|
788
|
+
this.prompter = new PermissionPrompter((msg) => this.emit(msg));
|
|
761
789
|
this.prompter.setOnStateChanged((requestId, state, source, toolUseId) => {
|
|
762
|
-
// Route through emitConfirmationStateChanged so the event reaches
|
|
763
|
-
// the client via sendToClient (wired to the SSE hub for HTTP conversations).
|
|
764
790
|
this.emitConfirmationStateChanged({
|
|
765
791
|
conversationId: this.conversationId,
|
|
766
792
|
requestId,
|
|
@@ -1316,6 +1342,16 @@ export class Conversation {
|
|
|
1316
1342
|
parsedMessages.length,
|
|
1317
1343
|
);
|
|
1318
1344
|
for (const [index, message] of parsedMessages.entries()) {
|
|
1345
|
+
// Applied after the compaction slice, never before it: the slice and
|
|
1346
|
+
// `rowToHistoryIndex` are both computed against the full row list, so
|
|
1347
|
+
// dropping earlier would shift them. A dropped row maps to a null
|
|
1348
|
+
// history index exactly like a fully-injected user row that strips to
|
|
1349
|
+
// nothing. `index` is shared with `slicedDbMessages`, which
|
|
1350
|
+
// `parsedMessages` maps 1:1.
|
|
1351
|
+
if (isGuardianCardRow(slicedDbMessages[index]?.content)) {
|
|
1352
|
+
preRepairIndexBySlicedRow[index] = null;
|
|
1353
|
+
continue;
|
|
1354
|
+
}
|
|
1319
1355
|
const stripped =
|
|
1320
1356
|
index < preStrippedCount
|
|
1321
1357
|
? stripInjectionsForCompaction([message])
|
|
@@ -1418,7 +1454,7 @@ export class Conversation {
|
|
|
1418
1454
|
messageCount: this.messages.length,
|
|
1419
1455
|
});
|
|
1420
1456
|
|
|
1421
|
-
this.restoreSurfaceStateFromHistory();
|
|
1457
|
+
this.restoreSurfaceStateFromHistory(parsedMessages);
|
|
1422
1458
|
this.graphMemory.restoreState();
|
|
1423
1459
|
|
|
1424
1460
|
// Row→history correspondence for this load: slice offset, then the
|
|
@@ -1450,14 +1486,19 @@ export class Conversation {
|
|
|
1450
1486
|
* populate surfaceState so that findConversationBySurfaceId works for
|
|
1451
1487
|
* surfaces restored from history (e.g. after daemon restart).
|
|
1452
1488
|
*
|
|
1453
|
-
*
|
|
1454
|
-
*
|
|
1455
|
-
*
|
|
1456
|
-
*
|
|
1489
|
+
* Scans the live (non-compacted) window only, never all DB rows, because
|
|
1490
|
+
* surface IDs are not globally unique and restoring stale compacted
|
|
1491
|
+
* surfaces would let findConversationBySurfaceId route actions to the wrong
|
|
1492
|
+
* conversation.
|
|
1493
|
+
*
|
|
1494
|
+
* Takes that window as rows rather than reading `this.messages`, because a
|
|
1495
|
+
* surface's lifecycle and the model's context are different questions. A
|
|
1496
|
+
* guardian card is absent from `this.messages`, but it is exactly the card
|
|
1497
|
+
* whose Approve/Reject buttons must still route after a restart.
|
|
1457
1498
|
*/
|
|
1458
|
-
private restoreSurfaceStateFromHistory(): void {
|
|
1499
|
+
private restoreSurfaceStateFromHistory(liveWindow: Message[]): void {
|
|
1459
1500
|
this.surfaceState.clear();
|
|
1460
|
-
for (const msg of
|
|
1501
|
+
for (const msg of liveWindow) {
|
|
1461
1502
|
if (!Array.isArray(msg.content)) {
|
|
1462
1503
|
continue;
|
|
1463
1504
|
}
|
|
@@ -1489,31 +1530,55 @@ export class Conversation {
|
|
|
1489
1530
|
await this.loadFromDb();
|
|
1490
1531
|
}
|
|
1491
1532
|
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1533
|
+
/**
|
|
1534
|
+
* Deliver an event through the conversation's sink, then to every observer.
|
|
1535
|
+
* The single emission point for conversation-level events (activity state,
|
|
1536
|
+
* confirmation prompts and state, notifier output, out-of-turn pushes); the
|
|
1537
|
+
* agent loop's own stream rides its per-turn `onEvent`, which defaults to
|
|
1538
|
+
* this when the caller passes none.
|
|
1539
|
+
*/
|
|
1540
|
+
readonly emit = (msg: AssistantEvent): void => {
|
|
1541
|
+
try {
|
|
1542
|
+
this.sendToClient(msg);
|
|
1543
|
+
} catch (err) {
|
|
1544
|
+
log.warn(
|
|
1545
|
+
{ err, conversationId: this.conversationId, type: msg.type },
|
|
1546
|
+
"conversation sink threw",
|
|
1547
|
+
);
|
|
1548
|
+
}
|
|
1549
|
+
for (const observer of this.eventObservers) {
|
|
1503
1550
|
try {
|
|
1504
|
-
|
|
1551
|
+
observer(msg);
|
|
1505
1552
|
} catch (err) {
|
|
1506
1553
|
log.warn(
|
|
1507
|
-
{ err, conversationId: this.conversationId },
|
|
1508
|
-
"
|
|
1554
|
+
{ err, conversationId: this.conversationId, type: msg.type },
|
|
1555
|
+
"conversation event observer threw",
|
|
1509
1556
|
);
|
|
1510
1557
|
}
|
|
1511
1558
|
}
|
|
1559
|
+
};
|
|
1560
|
+
|
|
1561
|
+
/**
|
|
1562
|
+
* Observe every event this conversation emits, after the sink delivered it.
|
|
1563
|
+
* For policy layered on delivery (voice auto-resolves approval prompts it
|
|
1564
|
+
* has no UI for), not for delivery itself. Returns the disposer.
|
|
1565
|
+
*/
|
|
1566
|
+
addEventObserver(observer: (msg: AssistantEvent) => void): () => void {
|
|
1567
|
+
this.eventObservers.add(observer);
|
|
1568
|
+
return () => {
|
|
1569
|
+
this.eventObservers.delete(observer);
|
|
1570
|
+
};
|
|
1512
1571
|
}
|
|
1513
1572
|
|
|
1514
|
-
/**
|
|
1515
|
-
|
|
1516
|
-
|
|
1573
|
+
/**
|
|
1574
|
+
* Re-emit the last activity state so a client that reconnected mid-phase
|
|
1575
|
+
* sees the current phase instead of the last one it received before
|
|
1576
|
+
* disconnecting. The send route calls this on every interactive send.
|
|
1577
|
+
*/
|
|
1578
|
+
replayActivityState(): void {
|
|
1579
|
+
if (this.lastActivityStateMsg) {
|
|
1580
|
+
this.emit(this.lastActivityStateMsg);
|
|
1581
|
+
}
|
|
1517
1582
|
}
|
|
1518
1583
|
|
|
1519
1584
|
setSubagentAllowedTools(tools: Set<string> | undefined): void {
|
|
@@ -1801,7 +1866,7 @@ export class Conversation {
|
|
|
1801
1866
|
} {
|
|
1802
1867
|
return enqueueMessageImpl(this, {
|
|
1803
1868
|
...options,
|
|
1804
|
-
onEvent: options.onEvent ?? this.
|
|
1869
|
+
onEvent: options.onEvent ?? this.emit,
|
|
1805
1870
|
});
|
|
1806
1871
|
}
|
|
1807
1872
|
|
|
@@ -1976,14 +2041,7 @@ export class Conversation {
|
|
|
1976
2041
|
type: "confirmation_state_changed",
|
|
1977
2042
|
...params,
|
|
1978
2043
|
} as AssistantEvent;
|
|
1979
|
-
|
|
1980
|
-
this.sendToClient(msg);
|
|
1981
|
-
} catch (err) {
|
|
1982
|
-
log.warn(
|
|
1983
|
-
{ err, conversationId: this.conversationId },
|
|
1984
|
-
"sendToClient threw in emitConfirmationStateChanged",
|
|
1985
|
-
);
|
|
1986
|
-
}
|
|
2044
|
+
this.emit(msg);
|
|
1987
2045
|
}
|
|
1988
2046
|
|
|
1989
2047
|
emitActivityState(
|
|
@@ -2008,14 +2066,7 @@ export class Conversation {
|
|
|
2008
2066
|
...(statusText ? { statusText } : {}),
|
|
2009
2067
|
} as AssistantEvent;
|
|
2010
2068
|
this.lastActivityStateMsg = msg;
|
|
2011
|
-
|
|
2012
|
-
this.sendToClient(msg);
|
|
2013
|
-
} catch (err) {
|
|
2014
|
-
log.warn(
|
|
2015
|
-
{ err, conversationId: this.conversationId },
|
|
2016
|
-
"sendToClient threw in emitActivityState",
|
|
2017
|
-
);
|
|
2018
|
-
}
|
|
2069
|
+
this.emit(msg);
|
|
2019
2070
|
}
|
|
2020
2071
|
|
|
2021
2072
|
/**
|
|
@@ -2069,7 +2120,7 @@ export class Conversation {
|
|
|
2069
2120
|
onEvent?: (msg: AssistantEvent) => void,
|
|
2070
2121
|
): void {
|
|
2071
2122
|
try {
|
|
2072
|
-
(onEvent ?? this.
|
|
2123
|
+
(onEvent ?? this.emit)({
|
|
2073
2124
|
type: "context_window_usage",
|
|
2074
2125
|
conversationId: this.conversationId,
|
|
2075
2126
|
tokens,
|
|
@@ -2421,11 +2472,11 @@ export class Conversation {
|
|
|
2421
2472
|
) {
|
|
2422
2473
|
await this.agentLoop.compactionCircuit.recordOutcome(
|
|
2423
2474
|
result.summaryFailed,
|
|
2424
|
-
this.
|
|
2475
|
+
this.emit,
|
|
2425
2476
|
);
|
|
2426
2477
|
}
|
|
2427
2478
|
if (result.compacted) {
|
|
2428
|
-
await applyCompactionResult(this, result, this.
|
|
2479
|
+
await applyCompactionResult(this, result, this.emit, null, {
|
|
2429
2480
|
slackContextCompactionWatermarkTs:
|
|
2430
2481
|
fixedBoundarySlackWatermarkTs ??
|
|
2431
2482
|
getSlackCompactionWatermarkForPrefix(
|
|
@@ -2681,6 +2732,8 @@ export class Conversation {
|
|
|
2681
2732
|
titleText?: string;
|
|
2682
2733
|
/** See {@link runAgentLoopImpl} — hidden machine-signal turn marker. */
|
|
2683
2734
|
isHiddenPrompt?: boolean;
|
|
2735
|
+
/** See {@link runAgentLoopImpl}: triggering row's daemon-authored kind. */
|
|
2736
|
+
messageKind?: string;
|
|
2684
2737
|
/**
|
|
2685
2738
|
* See {@link runAgentLoopImpl}: the row the end-of-turn reply
|
|
2686
2739
|
* notification treats as the prompt this turn answers.
|
|
@@ -2722,7 +2775,7 @@ export class Conversation {
|
|
|
2722
2775
|
this,
|
|
2723
2776
|
content,
|
|
2724
2777
|
userMessageId,
|
|
2725
|
-
onEvent ?? this.
|
|
2778
|
+
onEvent ?? this.emit,
|
|
2726
2779
|
rest,
|
|
2727
2780
|
);
|
|
2728
2781
|
}
|
|
@@ -2748,7 +2801,7 @@ export class Conversation {
|
|
|
2748
2801
|
this.cacheWarmAbort = undefined;
|
|
2749
2802
|
return processMessageImpl(this, {
|
|
2750
2803
|
...options,
|
|
2751
|
-
onEvent: options.onEvent ?? this.
|
|
2804
|
+
onEvent: options.onEvent ?? this.emit,
|
|
2752
2805
|
});
|
|
2753
2806
|
}
|
|
2754
2807
|
|
|
@@ -130,7 +130,7 @@ function pushStepsUpdate(
|
|
|
130
130
|
};
|
|
131
131
|
const updatedData = { ...card, templateData: updatedTemplateData };
|
|
132
132
|
stored.data = updatedData;
|
|
133
|
-
ctx.
|
|
133
|
+
ctx.emit({
|
|
134
134
|
type: "ui_surface_update",
|
|
135
135
|
conversationId: ctx.conversationId,
|
|
136
136
|
surfaceId: SURFACE_ID,
|
|
@@ -218,7 +218,7 @@ export function updateDoordashProgress(
|
|
|
218
218
|
},
|
|
219
219
|
} satisfies CardSurfaceData;
|
|
220
220
|
ctx.surfaceState.set(SURFACE_ID, { surfaceType: "card", data });
|
|
221
|
-
ctx.
|
|
221
|
+
ctx.emit({
|
|
222
222
|
type: "ui_surface_show",
|
|
223
223
|
conversationId: ctx.conversationId,
|
|
224
224
|
surfaceId: SURFACE_ID,
|