vellum 0.2.1 → 0.2.2
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/README.md +15 -2
- package/bun.lock +5 -2
- package/package.json +4 -2
- package/scripts/capture-x-graphql.ts +562 -0
- package/scripts/ipc/check-swift-decoder-drift.ts +2 -1
- package/scripts/test.sh +5 -0
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +133 -34
- package/src/__tests__/account-registry.test.ts +2 -1
- package/src/__tests__/agent-heartbeat-service.test.ts +250 -0
- package/src/__tests__/asset-materialize-tool.test.ts +16 -15
- package/src/__tests__/asset-search-tool.test.ts +23 -22
- package/src/__tests__/attachments-store.test.ts +56 -127
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +5 -4
- package/src/__tests__/browser-skill-endstate.test.ts +4 -3
- package/src/__tests__/call-bridge.test.ts +385 -0
- package/src/__tests__/call-constants.test.ts +40 -0
- package/src/__tests__/call-orchestrator.test.ts +130 -4
- package/src/__tests__/call-recovery.test.ts +518 -0
- package/src/__tests__/call-routes-http.test.ts +459 -0
- package/src/__tests__/call-state-machine.test.ts +143 -0
- package/src/__tests__/call-store.test.ts +216 -1
- package/src/__tests__/cli-discover.test.ts +1 -1
- package/src/__tests__/commit-message-enrichment-service.test.ts +148 -7
- package/src/__tests__/compaction.benchmark.test.ts +176 -0
- package/src/__tests__/computer-use-tools.test.ts +250 -0
- package/src/__tests__/config-schema.test.ts +299 -3
- package/src/__tests__/conflict-store.test.ts +2 -1
- package/src/__tests__/contacts-tools.test.ts +331 -0
- package/src/__tests__/conversation-store.test.ts +30 -32
- package/src/__tests__/credential-security-invariants.test.ts +4 -0
- package/src/__tests__/date-context.test.ts +373 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +129 -0
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -3
- package/src/__tests__/followup-tools.test.ts +303 -0
- package/src/__tests__/handlers-twitter-config.test.ts +718 -0
- package/src/__tests__/intent-routing.test.ts +64 -57
- package/src/__tests__/ipc-roundtrip.benchmark.test.ts +237 -0
- package/src/__tests__/ipc-snapshot.test.ts +62 -28
- package/src/__tests__/llm-usage-store.test.ts +3 -8
- package/src/__tests__/media-generate-image.test.ts +1 -1
- package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
- package/src/__tests__/memory-retrieval.benchmark.test.ts +430 -0
- package/src/__tests__/parallel-tool.benchmark.test.ts +294 -0
- package/src/__tests__/playbook-tools.test.ts +342 -0
- package/src/__tests__/profile-compiler.test.ts +2 -1
- package/src/__tests__/provider-streaming.benchmark.test.ts +773 -0
- package/src/__tests__/recurrence-engine-rruleset.test.ts +78 -0
- package/src/__tests__/recurrence-engine.test.ts +69 -0
- package/src/__tests__/recurrence-types.test.ts +71 -0
- package/src/__tests__/registry.test.ts +5 -3
- package/src/__tests__/relay-server.test.ts +633 -0
- package/src/__tests__/reminder-store.test.ts +6 -3
- package/src/__tests__/reminder.test.ts +43 -77
- package/src/__tests__/run-orchestrator-assistant-events.test.ts +8 -4
- package/src/__tests__/run-orchestrator.test.ts +4 -4
- package/src/__tests__/runtime-attachment-metadata.test.ts +7 -6
- package/src/__tests__/runtime-runs-http.test.ts +4 -4
- package/src/__tests__/runtime-runs.test.ts +4 -4
- package/src/__tests__/schedule-store.test.ts +482 -0
- package/src/__tests__/schedule-tools.test.ts +700 -0
- package/src/__tests__/scheduler-recurrence.test.ts +329 -0
- package/src/__tests__/server-history-render.test.ts +14 -13
- package/src/__tests__/session-error.test.ts +28 -0
- package/src/__tests__/session-init.benchmark.test.ts +462 -0
- package/src/__tests__/session-queue.test.ts +71 -48
- package/src/__tests__/session-runtime-assembly.test.ts +161 -0
- package/src/__tests__/session-surfaces-task-progress.test.ts +104 -0
- package/src/__tests__/signup-e2e.test.ts +2 -1
- package/src/__tests__/skill-projection.benchmark.test.ts +328 -0
- package/src/__tests__/skill-script-runner.test.ts +159 -0
- package/src/__tests__/speaker-identification.test.ts +52 -0
- package/src/__tests__/subagent-manager-notify.test.ts +42 -10
- package/src/__tests__/subagent-tools.test.ts +141 -41
- package/src/__tests__/task-compiler.test.ts +2 -1
- package/src/__tests__/task-runner.test.ts +2 -1
- package/src/__tests__/task-scheduler.test.ts +2 -1
- package/src/__tests__/task-tools.test.ts +49 -56
- package/src/__tests__/tool-audit-listener.test.ts +1 -0
- package/src/__tests__/tool-domain-event-publisher.test.ts +2 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +500 -0
- package/src/__tests__/tool-executor.test.ts +13 -17
- package/src/__tests__/turn-commit.test.ts +218 -3
- package/src/__tests__/twilio-provider.test.ts +143 -0
- package/src/__tests__/twilio-routes.test.ts +789 -0
- package/src/__tests__/twitter-auth-handler.test.ts +581 -0
- package/src/__tests__/view-image-tool.test.ts +217 -0
- package/src/__tests__/workspace-git-service.test.ts +186 -0
- package/src/__tests__/workspace-heartbeat-service.test.ts +13 -3
- package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
- package/src/bundler/app-bundler.ts +12 -8
- package/src/calls/call-bridge.ts +95 -0
- package/src/calls/call-constants.ts +43 -5
- package/src/calls/call-domain.ts +276 -0
- package/src/calls/call-orchestrator.ts +43 -17
- package/src/calls/call-recovery.ts +207 -0
- package/src/calls/call-state-machine.ts +68 -0
- package/src/calls/call-store.ts +192 -5
- package/src/calls/relay-server.ts +41 -4
- package/src/calls/speaker-identification.ts +213 -0
- package/src/calls/twilio-provider.ts +10 -6
- package/src/calls/twilio-routes.ts +90 -76
- package/src/calls/types.ts +1 -1
- package/src/cli/config-commands.ts +334 -0
- package/src/cli/core-commands.ts +776 -0
- package/src/cli/doordash.ts +251 -1
- package/src/cli/ipc-client.ts +82 -0
- package/src/cli/map.ts +246 -0
- package/src/cli/twitter.ts +575 -0
- package/src/cli.ts +7 -5
- package/src/commands/__tests__/cc-command-registry.test.ts +319 -0
- package/src/commands/cc-command-registry.ts +209 -0
- package/src/config/bundled-skills/contacts/SKILL.md +39 -0
- package/src/config/bundled-skills/contacts/TOOLS.json +122 -0
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +9 -0
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +9 -0
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +9 -0
- package/src/config/bundled-skills/document/SKILL.md +18 -0
- package/src/config/bundled-skills/document/TOOLS.json +53 -0
- package/src/config/bundled-skills/document/tools/document-create.ts +9 -0
- package/src/config/bundled-skills/document/tools/document-update.ts +9 -0
- package/src/config/bundled-skills/doordash/SKILL.md +82 -23
- package/src/config/bundled-skills/followups/SKILL.md +32 -0
- package/src/config/bundled-skills/followups/TOOLS.json +100 -0
- package/src/config/bundled-skills/followups/tools/followup-create.ts +9 -0
- package/src/config/bundled-skills/followups/tools/followup-list.ts +9 -0
- package/src/config/bundled-skills/followups/tools/followup-resolve.ts +9 -0
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +1 -23
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -1
- package/src/config/bundled-skills/playbooks/SKILL.md +31 -0
- package/src/config/bundled-skills/playbooks/TOOLS.json +126 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +9 -0
- package/src/config/bundled-skills/reminder/SKILL.md +20 -0
- package/src/config/bundled-skills/reminder/TOOLS.json +67 -0
- package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +9 -0
- package/src/config/bundled-skills/reminder/tools/reminder-create.ts +9 -0
- package/src/config/bundled-skills/reminder/tools/reminder-list.ts +9 -0
- package/src/config/bundled-skills/schedule/SKILL.md +74 -0
- package/src/config/bundled-skills/schedule/TOOLS.json +135 -0
- package/src/config/bundled-skills/schedule/tools/schedule-create.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-list.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-update.ts +9 -0
- package/src/config/bundled-skills/subagent/SKILL.md +25 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +107 -0
- package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-message.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-read.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-status.ts +9 -0
- package/src/config/bundled-skills/tasks/SKILL.md +28 -0
- package/src/config/bundled-skills/tasks/TOOLS.json +256 -0
- package/src/config/bundled-skills/tasks/tools/task-delete.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-add.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-show.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-update.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-run.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-save.ts +9 -0
- package/src/config/bundled-skills/twitter/SKILL.md +134 -0
- package/src/config/bundled-skills/watcher/SKILL.md +27 -0
- package/src/config/bundled-skills/watcher/TOOLS.json +147 -0
- package/src/config/bundled-skills/watcher/tools/watcher-create.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-list.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-update.ts +9 -0
- package/src/config/defaults.ts +33 -0
- package/src/config/loader.ts +4 -1
- package/src/config/schema.ts +161 -1
- package/src/config/system-prompt.ts +61 -16
- package/src/config/templates/IDENTITY.md +7 -0
- package/src/config/types.ts +4 -0
- package/src/contacts/contact-store.ts +4 -4
- package/src/daemon/assistant-attachments.ts +10 -0
- package/src/daemon/classifier.ts +3 -1
- package/src/daemon/computer-use-session.ts +3 -1
- package/src/daemon/date-context.ts +136 -0
- package/src/daemon/handlers/apps.ts +16 -1
- package/src/daemon/handlers/browser.ts +54 -0
- package/src/daemon/handlers/computer-use.ts +7 -1
- package/src/daemon/handlers/config.ts +163 -5
- package/src/daemon/handlers/diagnostics.ts +5 -1
- package/src/daemon/handlers/documents.ts +18 -29
- package/src/daemon/handlers/home-base.ts +5 -1
- package/src/daemon/handlers/index.ts +40 -277
- package/src/daemon/handlers/misc.ts +9 -1
- package/src/daemon/handlers/publish.ts +6 -1
- package/src/daemon/handlers/sessions.ts +65 -12
- package/src/daemon/handlers/shared.ts +36 -1
- package/src/daemon/handlers/signing.ts +37 -0
- package/src/daemon/handlers/skills.ts +20 -6
- package/src/daemon/handlers/subagents.ts +8 -3
- package/src/daemon/handlers/twitter-auth.ts +169 -0
- package/src/daemon/handlers/work-items.ts +384 -68
- package/src/daemon/ipc-contract-inventory.json +28 -4
- package/src/daemon/ipc-contract.ts +133 -37
- package/src/daemon/ipc-protocol.ts +7 -2
- package/src/daemon/lifecycle.ts +21 -0
- package/src/daemon/main.ts +10 -4
- package/src/daemon/ride-shotgun-handler.ts +74 -10
- package/src/daemon/server.ts +143 -26
- package/src/daemon/session-agent-loop.ts +887 -0
- package/src/daemon/session-attachments.ts +28 -5
- package/src/daemon/session-error.ts +24 -3
- package/src/daemon/session-lifecycle.ts +147 -0
- package/src/daemon/session-media-retry.ts +147 -0
- package/src/daemon/session-messaging.ts +145 -0
- package/src/daemon/session-notifiers.ts +164 -0
- package/src/daemon/session-process.ts +2 -2
- package/src/daemon/session-queue-manager.ts +1 -0
- package/src/daemon/session-runtime-assembly.ts +52 -0
- package/src/daemon/session-skill-tools.ts +124 -5
- package/src/daemon/session-slash.ts +3 -0
- package/src/daemon/session-surfaces.ts +77 -2
- package/src/daemon/session-tool-setup.ts +216 -2
- package/src/daemon/session-usage.ts +0 -2
- package/src/daemon/session.ts +114 -1404
- package/src/daemon/video-thumbnail.ts +60 -0
- package/src/doordash/client.ts +121 -27
- package/src/doordash/queries.ts +1 -2
- package/src/export/formatter.ts +3 -1
- package/src/followups/followup-store.ts +4 -2
- package/src/followups/types.ts +6 -0
- package/src/hooks/templates.ts +1 -1
- package/src/index.ts +32 -1153
- package/src/memory/attachments-store.ts +28 -83
- package/src/memory/channel-delivery-store.ts +7 -21
- package/src/memory/clarification-resolver.ts +6 -5
- package/src/memory/contradiction-checker.ts +3 -2
- package/src/memory/conversation-key-store.ts +10 -29
- package/src/memory/conversation-store.ts +2 -1
- package/src/memory/db.ts +96 -2
- package/src/memory/entity-extractor.ts +6 -3
- package/src/memory/items-extractor.ts +5 -4
- package/src/memory/jobs-store.ts +3 -2
- package/src/memory/llm-usage-store.ts +1 -2
- package/src/memory/runs-store.ts +1 -2
- package/src/memory/schema.ts +23 -2
- package/src/messaging/style-analyzer.ts +3 -2
- package/src/messaging/thread-summarizer.ts +8 -12
- package/src/messaging/triage-engine.ts +4 -2
- package/src/providers/openrouter/client.ts +20 -0
- package/src/providers/registry.ts +8 -0
- package/src/runtime/http-server.ts +108 -20
- package/src/runtime/routes/attachment-routes.ts +2 -3
- package/src/runtime/routes/call-routes.ts +140 -0
- package/src/runtime/routes/channel-routes.ts +5 -10
- package/src/runtime/routes/conversation-routes.ts +5 -5
- package/src/runtime/routes/run-routes.ts +2 -2
- package/src/runtime/run-orchestrator.ts +9 -3
- package/src/schedule/recurrence-engine.ts +138 -0
- package/src/schedule/recurrence-types.ts +67 -0
- package/src/schedule/schedule-store.ts +102 -57
- package/src/schedule/scheduler.ts +9 -6
- package/src/security/oauth2.ts +29 -4
- package/src/security/secret-allowlist.ts +46 -0
- package/src/skills/clawhub.ts +1 -1
- package/src/subagent/manager.ts +40 -8
- package/src/swarm/backend-claude-code.ts +64 -9
- package/src/swarm/worker-prompts.ts +2 -1
- package/src/tasks/SPEC.md +34 -28
- package/src/tasks/ephemeral-permissions.ts +16 -7
- package/src/tasks/task-compiler.ts +5 -4
- package/src/tasks/task-runner.ts +10 -5
- package/src/tasks/task-scheduler.ts +1 -1
- package/src/tasks/tool-sanitizer.ts +36 -0
- package/src/tools/assets/search.ts +4 -4
- package/src/tools/browser/api-map.ts +220 -0
- package/src/tools/browser/auto-navigate.ts +270 -0
- package/src/tools/browser/browser-execution.ts +2 -1
- package/src/tools/browser/browser-manager.ts +2 -2
- package/src/tools/browser/network-recorder.ts +5 -4
- package/src/tools/browser/x-auto-navigate.ts +207 -0
- package/src/tools/calls/call-end.ts +17 -67
- package/src/tools/calls/call-start.ts +24 -85
- package/src/tools/calls/call-status.ts +35 -51
- package/src/tools/claude-code/claude-code.ts +77 -11
- package/src/tools/contacts/contact-merge.ts +46 -78
- package/src/tools/contacts/contact-search.ts +35 -79
- package/src/tools/contacts/contact-upsert.ts +35 -108
- package/src/tools/credentials/vault.ts +20 -4
- package/src/tools/document/document-tool.ts +71 -144
- package/src/tools/executor.ts +129 -10
- package/src/tools/followups/followup_create.ts +46 -88
- package/src/tools/followups/followup_list.ts +34 -74
- package/src/tools/followups/followup_resolve.ts +31 -66
- package/src/tools/host-terminal/cli-discover.ts +2 -1
- package/src/tools/host-terminal/host-shell.ts +10 -0
- package/src/tools/memory/handlers.ts +5 -4
- package/src/tools/network/__tests__/web-search.test.ts +427 -0
- package/src/tools/network/script-proxy/__tests__/logging.test.ts +248 -0
- package/src/tools/network/script-proxy/__tests__/policy.test.ts +234 -0
- package/src/tools/network/script-proxy/__tests__/router.test.ts +76 -0
- package/src/tools/network/web-fetch.ts +18 -6
- package/src/tools/playbooks/index.ts +4 -5
- package/src/tools/playbooks/playbook-create.ts +3 -47
- package/src/tools/playbooks/playbook-delete.ts +1 -25
- package/src/tools/playbooks/playbook-list.ts +1 -28
- package/src/tools/playbooks/playbook-update.ts +3 -51
- package/src/tools/reminder/reminder.ts +5 -78
- package/src/tools/schedule/create.ts +69 -74
- package/src/tools/schedule/delete.ts +21 -47
- package/src/tools/schedule/list.ts +55 -74
- package/src/tools/schedule/update.ts +77 -84
- package/src/tools/subagent/abort.ts +29 -58
- package/src/tools/subagent/message.ts +30 -63
- package/src/tools/subagent/read.ts +53 -84
- package/src/tools/subagent/spawn.ts +43 -82
- package/src/tools/subagent/status.ts +42 -71
- package/src/tools/swarm/delegate.ts +2 -1
- package/src/tools/tasks/index.ts +8 -8
- package/src/tools/tasks/task-delete.ts +60 -88
- package/src/tools/tasks/task-list.ts +31 -52
- package/src/tools/tasks/task-run.ts +72 -108
- package/src/tools/tasks/task-save.ts +33 -65
- package/src/tools/tasks/work-item-enqueue.ts +183 -215
- package/src/tools/tasks/work-item-list.ts +33 -63
- package/src/tools/tasks/work-item-remove.ts +45 -97
- package/src/tools/tasks/work-item-update.ts +91 -163
- package/src/tools/terminal/backends/native.ts +3 -1
- package/src/tools/tool-manifest.ts +0 -62
- package/src/tools/types.ts +6 -0
- package/src/tools/ui-surface/definitions.ts +3 -1
- package/src/tools/watch/screen-watch.ts +3 -1
- package/src/tools/watcher/create.ts +52 -98
- package/src/tools/watcher/delete.ts +20 -46
- package/src/tools/watcher/digest.ts +36 -70
- package/src/tools/watcher/list.ts +49 -79
- package/src/tools/watcher/update.ts +45 -91
- package/src/twitter/client.ts +690 -0
- package/src/twitter/session.ts +91 -0
- package/src/usage/types.ts +0 -1
- package/src/util/truncate.ts +6 -0
- package/src/watcher/providers/slack.ts +2 -1
- package/src/watcher/watcher-store.ts +3 -2
- package/src/work-items/work-item-store.ts +27 -2
- package/src/workspace/commit-message-enrichment-service.ts +31 -7
- package/src/workspace/git-service.ts +87 -22
- package/src/workspace/provider-commit-message-generator.ts +242 -0
- package/src/workspace/turn-commit.ts +62 -3
- package/src/tools/contacts/index.ts +0 -4
- package/src/tools/document/index.ts +0 -5
- package/src/tools/followups/index.ts +0 -3
- package/src/tools/subagent/index.ts +0 -5
- /package/src/__tests__/{memory-context-benchmark.test.ts → memory-context-benchmark.benchmark.test.ts} +0 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Watch and call notifier registration/unregistration, extracted from
|
|
3
|
+
* the Session constructor and dispose/abort methods.
|
|
4
|
+
*
|
|
5
|
+
* Notifier callbacks read from the provided context object at invocation
|
|
6
|
+
* time (not registration time), so they always see the latest sendToClient
|
|
7
|
+
* and messages references even after updateClient().
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Message } from '../providers/types.js';
|
|
11
|
+
import type { ServerMessage } from './ipc-protocol.js';
|
|
12
|
+
import { createAssistantMessage } from '../agent/message-types.js';
|
|
13
|
+
import * as conversationStore from '../memory/conversation-store.js';
|
|
14
|
+
import {
|
|
15
|
+
registerWatchStartNotifier,
|
|
16
|
+
unregisterWatchStartNotifier,
|
|
17
|
+
registerWatchCommentaryNotifier,
|
|
18
|
+
unregisterWatchCommentaryNotifier,
|
|
19
|
+
registerWatchCompletionNotifier,
|
|
20
|
+
unregisterWatchCompletionNotifier,
|
|
21
|
+
pruneWatchSessions,
|
|
22
|
+
} from '../tools/watch/watch-state.js';
|
|
23
|
+
import type { WatchSession } from '../tools/watch/watch-state.js';
|
|
24
|
+
import { lastCommentaryBySession, lastSummaryBySession } from './watch-handler.js';
|
|
25
|
+
import {
|
|
26
|
+
registerCallQuestionNotifier,
|
|
27
|
+
unregisterCallQuestionNotifier,
|
|
28
|
+
registerCallCompletionNotifier,
|
|
29
|
+
unregisterCallCompletionNotifier,
|
|
30
|
+
} from '../calls/call-state.js';
|
|
31
|
+
import { getCallSession, getCallEvents } from '../calls/call-store.js';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Subset of Session state that notifier callbacks need to read at
|
|
35
|
+
* invocation time. Properties are read lazily from this reference.
|
|
36
|
+
*/
|
|
37
|
+
export interface NotifierSessionContext {
|
|
38
|
+
sendToClient: (msg: ServerMessage) => void;
|
|
39
|
+
messages: Message[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Register watch and call notifiers for a session. Call once during
|
|
44
|
+
* construction; the notifier callbacks close over `ctx` so they see
|
|
45
|
+
* live sendToClient/messages values.
|
|
46
|
+
*/
|
|
47
|
+
export function registerSessionNotifiers(
|
|
48
|
+
conversationId: string,
|
|
49
|
+
ctx: NotifierSessionContext,
|
|
50
|
+
): void {
|
|
51
|
+
registerWatchStartNotifier(conversationId, (session: WatchSession) => {
|
|
52
|
+
ctx.sendToClient({
|
|
53
|
+
type: 'watch_started',
|
|
54
|
+
sessionId: conversationId,
|
|
55
|
+
watchId: session.watchId,
|
|
56
|
+
durationSeconds: session.durationSeconds,
|
|
57
|
+
intervalSeconds: session.intervalSeconds,
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
registerWatchCommentaryNotifier(conversationId, (_session: WatchSession) => {
|
|
62
|
+
const commentary = lastCommentaryBySession.get(conversationId);
|
|
63
|
+
if (commentary) {
|
|
64
|
+
lastCommentaryBySession.delete(conversationId);
|
|
65
|
+
ctx.sendToClient({
|
|
66
|
+
type: 'assistant_text_delta',
|
|
67
|
+
text: commentary,
|
|
68
|
+
sessionId: conversationId,
|
|
69
|
+
});
|
|
70
|
+
ctx.sendToClient({
|
|
71
|
+
type: 'message_complete',
|
|
72
|
+
sessionId: conversationId,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
registerWatchCompletionNotifier(conversationId, (_session: WatchSession) => {
|
|
78
|
+
const summary = lastSummaryBySession.get(conversationId);
|
|
79
|
+
if (summary) {
|
|
80
|
+
lastSummaryBySession.delete(conversationId);
|
|
81
|
+
ctx.sendToClient({
|
|
82
|
+
type: 'assistant_text_delta',
|
|
83
|
+
text: summary,
|
|
84
|
+
sessionId: conversationId,
|
|
85
|
+
});
|
|
86
|
+
ctx.sendToClient({
|
|
87
|
+
type: 'message_complete',
|
|
88
|
+
sessionId: conversationId,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
registerCallQuestionNotifier(conversationId, (callSessionId: string, question: string) => {
|
|
94
|
+
const callSession = getCallSession(callSessionId);
|
|
95
|
+
const callee = callSession?.toNumber ?? 'the caller';
|
|
96
|
+
const questionText = `**Live call question** (to ${callee}):\n\n${question}\n\n_Reply in this thread to answer._`;
|
|
97
|
+
|
|
98
|
+
conversationStore.addMessage(
|
|
99
|
+
conversationId,
|
|
100
|
+
'assistant',
|
|
101
|
+
JSON.stringify([{ type: 'text', text: questionText }]),
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
ctx.messages.push(createAssistantMessage(questionText));
|
|
105
|
+
|
|
106
|
+
ctx.sendToClient({
|
|
107
|
+
type: 'assistant_text_delta',
|
|
108
|
+
text: questionText,
|
|
109
|
+
sessionId: conversationId,
|
|
110
|
+
});
|
|
111
|
+
ctx.sendToClient({
|
|
112
|
+
type: 'message_complete',
|
|
113
|
+
sessionId: conversationId,
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
registerCallCompletionNotifier(conversationId, (callSessionId: string) => {
|
|
118
|
+
const callSession = getCallSession(callSessionId);
|
|
119
|
+
const events = getCallEvents(callSessionId);
|
|
120
|
+
const duration = callSession?.endedAt && callSession?.startedAt
|
|
121
|
+
? Math.round((callSession.endedAt - callSession.startedAt) / 1000)
|
|
122
|
+
: null;
|
|
123
|
+
const durationStr = duration !== null ? ` (${duration}s)` : '';
|
|
124
|
+
const summaryText = `**Call completed**${durationStr}. ${events.length} event(s) recorded.`;
|
|
125
|
+
|
|
126
|
+
conversationStore.addMessage(
|
|
127
|
+
conversationId,
|
|
128
|
+
'assistant',
|
|
129
|
+
JSON.stringify([{ type: 'text', text: summaryText }]),
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
ctx.messages.push(createAssistantMessage(summaryText));
|
|
133
|
+
|
|
134
|
+
ctx.sendToClient({
|
|
135
|
+
type: 'assistant_text_delta',
|
|
136
|
+
text: summaryText,
|
|
137
|
+
sessionId: conversationId,
|
|
138
|
+
});
|
|
139
|
+
ctx.sendToClient({
|
|
140
|
+
type: 'message_complete',
|
|
141
|
+
sessionId: conversationId,
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Unregister watch notifiers and prune watch sessions. Called during
|
|
148
|
+
* abort when the session is actively processing.
|
|
149
|
+
*/
|
|
150
|
+
export function unregisterWatchNotifiers(conversationId: string): void {
|
|
151
|
+
unregisterWatchStartNotifier(conversationId);
|
|
152
|
+
unregisterWatchCommentaryNotifier(conversationId);
|
|
153
|
+
unregisterWatchCompletionNotifier(conversationId);
|
|
154
|
+
pruneWatchSessions(conversationId);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Unregister call notifiers. Called during dispose regardless of
|
|
159
|
+
* processing state (notifiers are registered in the constructor).
|
|
160
|
+
*/
|
|
161
|
+
export function unregisterCallNotifiers(conversationId: string): void {
|
|
162
|
+
unregisterCallQuestionNotifier(conversationId);
|
|
163
|
+
unregisterCallCompletionNotifier(conversationId);
|
|
164
|
+
}
|
|
@@ -55,7 +55,7 @@ export interface ProcessSessionContext {
|
|
|
55
55
|
currentPage?: string;
|
|
56
56
|
/** Request-scoped skill IDs preactivated via slash resolution. */
|
|
57
57
|
preactivatedSkillIds?: string[];
|
|
58
|
-
persistUserMessage(content: string, attachments: UserMessageAttachment[], requestId?: string): string;
|
|
58
|
+
persistUserMessage(content: string, attachments: UserMessageAttachment[], requestId?: string, metadata?: Record<string, unknown>): string;
|
|
59
59
|
runAgentLoop(
|
|
60
60
|
content: string,
|
|
61
61
|
userMessageId: string,
|
|
@@ -155,7 +155,7 @@ export function drainQueue(session: ProcessSessionContext, reason: QueueDrainRea
|
|
|
155
155
|
// resolves early (no runAgentLoop call), so we must continue draining.
|
|
156
156
|
let userMessageId: string;
|
|
157
157
|
try {
|
|
158
|
-
userMessageId = session.persistUserMessage(resolvedContent, next.attachments, next.requestId);
|
|
158
|
+
userMessageId = session.persistUserMessage(resolvedContent, next.attachments, next.requestId, next.metadata);
|
|
159
159
|
} catch (err) {
|
|
160
160
|
const message = err instanceof Error ? err.message : String(err);
|
|
161
161
|
log.error({ err, conversationId: session.conversationId, requestId: next.requestId }, 'Failed to persist queued message');
|
|
@@ -312,6 +312,44 @@ export function stripWorkspaceTopLevelContext(messages: Message[]): Message[] {
|
|
|
312
312
|
}).filter((message): message is NonNullable<typeof message> => message !== null);
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
+
/**
|
|
316
|
+
* Prepend temporal context to a user message so the model has
|
|
317
|
+
* authoritative date/time grounding each turn.
|
|
318
|
+
*/
|
|
319
|
+
export function injectTemporalContext(message: Message, temporalContext: string): Message {
|
|
320
|
+
return {
|
|
321
|
+
...message,
|
|
322
|
+
content: [
|
|
323
|
+
{ type: 'text', text: temporalContext },
|
|
324
|
+
...message.content,
|
|
325
|
+
],
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Strip `<temporal_context>` blocks injected by `injectTemporalContext`.
|
|
331
|
+
* Called after the agent run to prevent temporal context from persisting
|
|
332
|
+
* in session history.
|
|
333
|
+
*
|
|
334
|
+
* Uses a specific prefix (`<temporal_context>\nToday:`) so that
|
|
335
|
+
* user-authored text that happens to start with `<temporal_context>`
|
|
336
|
+
* is preserved.
|
|
337
|
+
*/
|
|
338
|
+
const TEMPORAL_INJECTED_PREFIX = '<temporal_context>\nToday:';
|
|
339
|
+
|
|
340
|
+
export function stripTemporalContext(messages: Message[]): Message[] {
|
|
341
|
+
return messages.map((message) => {
|
|
342
|
+
if (message.role !== 'user') return message;
|
|
343
|
+
const nextContent = message.content.filter((block) => {
|
|
344
|
+
if (block.type !== 'text') return true;
|
|
345
|
+
return !block.text.startsWith(TEMPORAL_INJECTED_PREFIX);
|
|
346
|
+
});
|
|
347
|
+
if (nextContent.length === message.content.length) return message;
|
|
348
|
+
if (nextContent.length === 0) return null;
|
|
349
|
+
return { ...message, content: nextContent };
|
|
350
|
+
}).filter((message): message is NonNullable<typeof message> => message !== null);
|
|
351
|
+
}
|
|
352
|
+
|
|
315
353
|
/**
|
|
316
354
|
* Strip `<active_workspace>` (and legacy `<active_dynamic_page>`) blocks
|
|
317
355
|
* injected by `injectActiveSurfaceContext`. Called after the agent run to
|
|
@@ -344,6 +382,7 @@ export function applyRuntimeInjections(
|
|
|
344
382
|
activeSurface?: ActiveSurfaceContext | null;
|
|
345
383
|
workspaceTopLevelContext?: string | null;
|
|
346
384
|
channelCapabilities?: ChannelCapabilities | null;
|
|
385
|
+
temporalContext?: string | null;
|
|
347
386
|
},
|
|
348
387
|
): Message[] {
|
|
349
388
|
let result = runMessages;
|
|
@@ -378,6 +417,19 @@ export function applyRuntimeInjections(
|
|
|
378
417
|
}
|
|
379
418
|
}
|
|
380
419
|
|
|
420
|
+
// Temporal context is injected before workspace top-level so it
|
|
421
|
+
// appears after workspace context in the final message content
|
|
422
|
+
// (both are prepended, so later injections appear first).
|
|
423
|
+
if (options.temporalContext) {
|
|
424
|
+
const userTail = result[result.length - 1];
|
|
425
|
+
if (userTail && userTail.role === 'user') {
|
|
426
|
+
result = [
|
|
427
|
+
...result.slice(0, -1),
|
|
428
|
+
injectTemporalContext(userTail, options.temporalContext),
|
|
429
|
+
];
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
381
433
|
// Workspace top-level context is injected last so it appears first
|
|
382
434
|
// (prepended) in the user message content, keeping cache breakpoints
|
|
383
435
|
// anchored to the trailing blocks.
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import type { Message, ToolDefinition } from '../providers/types.js';
|
|
12
12
|
import type { SkillSummary, SkillToolManifest } from '../config/skills.js';
|
|
13
|
+
import type { ActiveSkillEntry } from '../skills/active-skill-tools.js';
|
|
13
14
|
import { loadSkillCatalog } from '../config/skills.js';
|
|
14
15
|
import { deriveActiveSkills } from '../skills/active-skill-tools.js';
|
|
15
16
|
|
|
@@ -38,6 +39,32 @@ export interface SkillToolProjection {
|
|
|
38
39
|
allowedToolNames: Set<string>;
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Session-scoped cache for skill projection. Avoids re-scanning the entire
|
|
44
|
+
* conversation history and re-reading the filesystem on every agent turn.
|
|
45
|
+
*
|
|
46
|
+
* Each session should own its own cache instance to prevent cross-session
|
|
47
|
+
* state bleed.
|
|
48
|
+
*/
|
|
49
|
+
export interface SkillProjectionCache {
|
|
50
|
+
/** Cached deriveActiveSkills result. */
|
|
51
|
+
derived?: {
|
|
52
|
+
/** Number of messages in history when this cache was last computed. */
|
|
53
|
+
messageCount: number;
|
|
54
|
+
/** Reference to the first message when cache was computed. Compaction
|
|
55
|
+
* replaces the first message with a new summary object, so a reference
|
|
56
|
+
* mismatch signals that history was rewritten even if the count matches. */
|
|
57
|
+
firstMessage: Message | undefined;
|
|
58
|
+
/** IDs already seen — used for deduplication during incremental scans. */
|
|
59
|
+
seenIds: Set<string>;
|
|
60
|
+
/** The accumulated active skill entries. */
|
|
61
|
+
entries: ActiveSkillEntry[];
|
|
62
|
+
};
|
|
63
|
+
/** Cached skill catalog. Invalidated when the session is marked stale
|
|
64
|
+
* (e.g. skill directories changed on disk while a run is in progress). */
|
|
65
|
+
catalog?: SkillSummary[];
|
|
66
|
+
}
|
|
67
|
+
|
|
41
68
|
export interface ProjectSkillToolsOptions {
|
|
42
69
|
/** Skill IDs that should be treated as active regardless of history markers. */
|
|
43
70
|
preactivatedSkillIds?: string[];
|
|
@@ -49,6 +76,12 @@ export interface ProjectSkillToolsOptions {
|
|
|
49
76
|
* unregistered and re-registered with the updated definitions.
|
|
50
77
|
*/
|
|
51
78
|
previouslyActiveSkillIds?: Map<string, string>;
|
|
79
|
+
/**
|
|
80
|
+
* Session-scoped projection cache. When provided, projectSkillTools will
|
|
81
|
+
* avoid redundant deriveActiveSkills scans and loadSkillCatalog filesystem
|
|
82
|
+
* reads across agent turns.
|
|
83
|
+
*/
|
|
84
|
+
cache?: SkillProjectionCache;
|
|
52
85
|
}
|
|
53
86
|
|
|
54
87
|
// ---------------------------------------------------------------------------
|
|
@@ -73,6 +106,82 @@ function loadManifestForSkill(skill: SkillSummary): SkillToolManifest | null {
|
|
|
73
106
|
}
|
|
74
107
|
}
|
|
75
108
|
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
// Cache helpers
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Return active skill entries, using the projection cache when available.
|
|
115
|
+
*
|
|
116
|
+
* History is append-only within a session (messages are only added, never
|
|
117
|
+
* mutated in place). If history.length hasn't changed since the last scan,
|
|
118
|
+
* the cached result is returned immediately. If new messages were appended,
|
|
119
|
+
* only the delta is scanned and merged. If history shrank (e.g. compression
|
|
120
|
+
* replaced earlier messages), the cache is invalidated and a full rescan
|
|
121
|
+
* is performed.
|
|
122
|
+
*/
|
|
123
|
+
function getCachedActiveSkills(
|
|
124
|
+
history: Message[],
|
|
125
|
+
cache?: SkillProjectionCache,
|
|
126
|
+
): ActiveSkillEntry[] {
|
|
127
|
+
if (!cache) return deriveActiveSkills(history);
|
|
128
|
+
|
|
129
|
+
const cached = cache.derived;
|
|
130
|
+
|
|
131
|
+
// Fast path: history unchanged since last scan. Both the count and the
|
|
132
|
+
// first message reference must match — compaction can rewrite history
|
|
133
|
+
// without changing the total count.
|
|
134
|
+
if (cached && cached.messageCount === history.length && cached.firstMessage === history[0]) {
|
|
135
|
+
return cached.entries;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// History grew (and first message is unchanged) — scan only the new messages.
|
|
139
|
+
if (cached && cached.messageCount < history.length && cached.firstMessage === history[0]) {
|
|
140
|
+
const delta = history.slice(cached.messageCount);
|
|
141
|
+
const newEntries = deriveActiveSkills(delta);
|
|
142
|
+
|
|
143
|
+
// Merge: add any entries not already seen.
|
|
144
|
+
let changed = false;
|
|
145
|
+
for (const entry of newEntries) {
|
|
146
|
+
if (!cached.seenIds.has(entry.id)) {
|
|
147
|
+
cached.seenIds.add(entry.id);
|
|
148
|
+
cached.entries.push(entry);
|
|
149
|
+
changed = true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
cached.messageCount = history.length;
|
|
154
|
+
if (changed) {
|
|
155
|
+
log.debug(
|
|
156
|
+
{ newEntries: newEntries.length, total: cached.entries.length },
|
|
157
|
+
'Incremental skill derivation found new entries',
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
return cached.entries;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// History shrank, compaction rewrote it, or no cache yet — full rescan.
|
|
164
|
+
const entries = deriveActiveSkills(history);
|
|
165
|
+
const seenIds = new Set(entries.map((e) => e.id));
|
|
166
|
+
cache.derived = { messageCount: history.length, firstMessage: history[0], seenIds, entries };
|
|
167
|
+
return entries;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Return the skill catalog, caching it across agent turns.
|
|
172
|
+
*
|
|
173
|
+
* The cache is invalidated when the session is marked stale (e.g. skill
|
|
174
|
+
* directories changed on disk while the session is still processing).
|
|
175
|
+
*/
|
|
176
|
+
function getCachedCatalog(cache?: SkillProjectionCache): SkillSummary[] {
|
|
177
|
+
if (!cache) return loadSkillCatalog();
|
|
178
|
+
|
|
179
|
+
if (!cache.catalog) {
|
|
180
|
+
cache.catalog = loadSkillCatalog();
|
|
181
|
+
}
|
|
182
|
+
return cache.catalog;
|
|
183
|
+
}
|
|
184
|
+
|
|
76
185
|
// ---------------------------------------------------------------------------
|
|
77
186
|
// Main export
|
|
78
187
|
// ---------------------------------------------------------------------------
|
|
@@ -90,7 +199,7 @@ export function projectSkillTools(
|
|
|
90
199
|
history: Message[],
|
|
91
200
|
options?: ProjectSkillToolsOptions,
|
|
92
201
|
): SkillToolProjection {
|
|
93
|
-
const contextEntries =
|
|
202
|
+
const contextEntries = getCachedActiveSkills(history, options?.cache);
|
|
94
203
|
const preactivated = options?.preactivatedSkillIds ?? [];
|
|
95
204
|
const prevActive = options?.previouslyActiveSkillIds ?? new Map<string, string>();
|
|
96
205
|
|
|
@@ -128,8 +237,8 @@ export function projectSkillTools(
|
|
|
128
237
|
return { toolDefinitions: [], allowedToolNames: new Set() };
|
|
129
238
|
}
|
|
130
239
|
|
|
131
|
-
// Load the catalog
|
|
132
|
-
const catalog =
|
|
240
|
+
// Load the catalog (cached for session lifetime) and index by ID
|
|
241
|
+
const catalog = getCachedCatalog(options?.cache);
|
|
133
242
|
const catalogById = new Map<string, SkillSummary>();
|
|
134
243
|
for (const skill of catalog) {
|
|
135
244
|
catalogById.set(skill.id, skill);
|
|
@@ -138,6 +247,9 @@ export function projectSkillTools(
|
|
|
138
247
|
const allToolDefinitions: ToolDefinition[] = [];
|
|
139
248
|
const allToolNames = new Set<string>();
|
|
140
249
|
const successfulEntries = new Map<string, string>();
|
|
250
|
+
// Track skills already unregistered in the version-change branch so the
|
|
251
|
+
// transiently-failed cleanup loop doesn't double-decrement their refcount.
|
|
252
|
+
const alreadyUnregistered = new Set<string>();
|
|
141
253
|
|
|
142
254
|
for (const skillId of activeIds) {
|
|
143
255
|
const skill = catalogById.get(skillId);
|
|
@@ -178,7 +290,14 @@ export function projectSkillTools(
|
|
|
178
290
|
// Hash changed — unregister stale tools, then re-register with new definitions
|
|
179
291
|
log.info({ skillId, prevHash, currentHash }, 'Skill version changed, re-registering tools');
|
|
180
292
|
unregisterSkillTools(skillId);
|
|
181
|
-
|
|
293
|
+
alreadyUnregistered.add(skillId);
|
|
294
|
+
try {
|
|
295
|
+
registerSkillTools(tools);
|
|
296
|
+
} catch (err) {
|
|
297
|
+
log.error({ err, skillId }, 'Failed to re-register skill tools after version change');
|
|
298
|
+
// Don't add to successfulEntries — will be cleaned up as transiently-failed
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
182
301
|
} else {
|
|
183
302
|
// Hash unchanged — check if the bundled status drifted (e.g. a
|
|
184
303
|
// managed skill override was added/removed with identical content).
|
|
@@ -204,7 +323,7 @@ export function projectSkillTools(
|
|
|
204
323
|
// skill would be re-registered when it recovers next turn, inflating the
|
|
205
324
|
// refcount since the prior registration was never decremented.
|
|
206
325
|
for (const id of prevActive.keys()) {
|
|
207
|
-
if (activeIds.has(id) && !successfulEntries.has(id)) {
|
|
326
|
+
if (activeIds.has(id) && !successfulEntries.has(id) && !alreadyUnregistered.has(id)) {
|
|
208
327
|
log.info({ skillId: id }, 'Unregistering tools for transiently-failed skill');
|
|
209
328
|
unregisterSkillTools(id);
|
|
210
329
|
}
|
|
@@ -51,6 +51,9 @@ const PROVIDER_MODEL_SHORTCUTS: Record<string, { provider: string; model: string
|
|
|
51
51
|
|
|
52
52
|
// Fireworks
|
|
53
53
|
'fireworks': { provider: 'fireworks', model: 'accounts/fireworks/models/kimi-k2p5', displayName: 'Kimi K2.5' },
|
|
54
|
+
|
|
55
|
+
// OpenRouter
|
|
56
|
+
'openrouter': { provider: 'openrouter', model: 'x-ai/grok-4', displayName: 'Grok 4 (OpenRouter)' },
|
|
54
57
|
};
|
|
55
58
|
|
|
56
59
|
/** Reverse lookup: model ID → provider, derived from PROVIDER_MODEL_SHORTCUTS. */
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
ServerMessage,
|
|
5
5
|
SurfaceType,
|
|
6
6
|
SurfaceData,
|
|
7
|
+
CardSurfaceData,
|
|
7
8
|
DynamicPageSurfaceData,
|
|
8
9
|
FileUploadSurfaceData,
|
|
9
10
|
UiSurfaceShow,
|
|
@@ -20,6 +21,74 @@ import {
|
|
|
20
21
|
const log = getLogger('session-surfaces');
|
|
21
22
|
|
|
22
23
|
const MAX_UNDO_DEPTH = 10;
|
|
24
|
+
const TASK_PROGRESS_TEMPLATE_FIELDS = ['title', 'status', 'steps'] as const;
|
|
25
|
+
|
|
26
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
27
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizeCardShowData(input: Record<string, unknown>, rawData: Record<string, unknown>): CardSurfaceData {
|
|
31
|
+
const normalized: Record<string, unknown> = { ...rawData };
|
|
32
|
+
|
|
33
|
+
// Older prompt examples sent template/templateData at the top level.
|
|
34
|
+
if (typeof normalized.template !== 'string' && typeof input.template === 'string') {
|
|
35
|
+
normalized.template = input.template;
|
|
36
|
+
}
|
|
37
|
+
if (!isPlainObject(normalized.templateData) && isPlainObject(input.templateData)) {
|
|
38
|
+
normalized.templateData = input.templateData;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// task_progress cards need a title for Swift parsing; fall back when missing.
|
|
42
|
+
if (normalized.template === 'task_progress' && typeof normalized.title !== 'string') {
|
|
43
|
+
if (typeof input.title === 'string' && input.title.trim().length > 0) {
|
|
44
|
+
normalized.title = input.title;
|
|
45
|
+
} else if (isPlainObject(normalized.templateData) && typeof normalized.templateData.title === 'string') {
|
|
46
|
+
normalized.title = normalized.templateData.title;
|
|
47
|
+
} else {
|
|
48
|
+
normalized.title = 'Task Progress';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (normalized.template === 'task_progress' && typeof normalized.body !== 'string') {
|
|
53
|
+
normalized.body = '';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return normalized as unknown as CardSurfaceData;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function normalizeTaskProgressCardPatch(existingCard: CardSurfaceData, patch: Record<string, unknown>): Record<string, unknown> {
|
|
60
|
+
if (existingCard.template !== 'task_progress') {
|
|
61
|
+
return patch;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const normalizedPatch: Record<string, unknown> = { ...patch };
|
|
65
|
+
const mergedTemplateData: Record<string, unknown> = isPlainObject(existingCard.templateData)
|
|
66
|
+
? { ...existingCard.templateData }
|
|
67
|
+
: {};
|
|
68
|
+
|
|
69
|
+
let updatedTemplateData = false;
|
|
70
|
+
|
|
71
|
+
if (isPlainObject(normalizedPatch.templateData)) {
|
|
72
|
+
Object.assign(mergedTemplateData, normalizedPatch.templateData);
|
|
73
|
+
updatedTemplateData = true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Accept top-level task_progress fields from older prompt examples and
|
|
77
|
+
// move them into templateData where the Swift client expects them.
|
|
78
|
+
for (const key of TASK_PROGRESS_TEMPLATE_FIELDS) {
|
|
79
|
+
if (key in normalizedPatch) {
|
|
80
|
+
mergedTemplateData[key] = normalizedPatch[key];
|
|
81
|
+
delete normalizedPatch[key];
|
|
82
|
+
updatedTemplateData = true;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (updatedTemplateData) {
|
|
87
|
+
normalizedPatch.templateData = mergedTemplateData;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return normalizedPatch;
|
|
91
|
+
}
|
|
23
92
|
|
|
24
93
|
/**
|
|
25
94
|
* Subset of Session state that surface helpers need access to.
|
|
@@ -447,7 +516,10 @@ export async function surfaceProxyResolver(
|
|
|
447
516
|
const surfaceId = uuid();
|
|
448
517
|
const surfaceType = input.surface_type as SurfaceType;
|
|
449
518
|
const title = typeof input.title === 'string' ? input.title : undefined;
|
|
450
|
-
const
|
|
519
|
+
const rawData = isPlainObject(input.data) ? input.data : {};
|
|
520
|
+
const data = (surfaceType === 'card'
|
|
521
|
+
? normalizeCardShowData(input, rawData)
|
|
522
|
+
: rawData) as SurfaceData;
|
|
451
523
|
const actions = input.actions as Array<{ id: string; label: string; style?: string }> | undefined;
|
|
452
524
|
// Interactive surfaces default to awaiting user action.
|
|
453
525
|
const hasActions = Array.isArray(actions) && actions.length > 0;
|
|
@@ -502,12 +574,15 @@ export async function surfaceProxyResolver(
|
|
|
502
574
|
|
|
503
575
|
if (toolName === 'ui_update') {
|
|
504
576
|
const surfaceId = input.surface_id as string;
|
|
505
|
-
|
|
577
|
+
let patch = (isPlainObject(input.data) ? input.data : {}) as Record<string, unknown>;
|
|
506
578
|
|
|
507
579
|
// Merge the partial patch into the stored full surface data
|
|
508
580
|
const stored = ctx.surfaceState.get(surfaceId);
|
|
509
581
|
let mergedData: SurfaceData;
|
|
510
582
|
if (stored) {
|
|
583
|
+
if (stored.surfaceType === 'card') {
|
|
584
|
+
patch = normalizeTaskProgressCardPatch(stored.data as CardSurfaceData, patch);
|
|
585
|
+
}
|
|
511
586
|
// Push current HTML to undo stack for dynamic pages
|
|
512
587
|
if (stored.surfaceType === 'dynamic_page') {
|
|
513
588
|
const currentHtml = (stored.data as DynamicPageSurfaceData).html;
|