@vellumai/assistant 0.11.3 → 0.11.4-staging.1
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/ARCHITECTURE.md +11 -6
- package/docs/architecture/memory.md +11 -0
- package/docs/architecture/turn-actor.md +70 -0
- package/docs/flux-turn-detection-spike.md +243 -0
- package/docs/stt-provider-onboarding.md +3 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +34 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
- package/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/openapi.yaml +140 -38
- package/package.json +1 -1
- package/scripts/voice-ttft-spike.ts +3 -3
- package/src/__tests__/app-compiler.test.ts +38 -3
- package/src/__tests__/attachments-store.test.ts +21 -12
- package/src/__tests__/byok-default-profile-ensure.test.ts +2 -0
- package/src/__tests__/call-setup-flow-name-capture.test.ts +0 -1
- package/src/__tests__/call-site-routing-provider.test.ts +1 -1
- package/src/__tests__/channel-availability-routes.test.ts +14 -1
- package/src/__tests__/channel-capabilities-dedupe.test.ts +214 -0
- package/src/__tests__/channel-delivery-store.test.ts +14 -14
- package/src/__tests__/config-loader-backfill.test.ts +3 -3
- package/src/__tests__/config-schema.test.ts +25 -10
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +8 -11
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +8 -11
- package/src/__tests__/conversation-agent-loop.test.ts +28 -20
- package/src/__tests__/conversation-attention-store.test.ts +63 -0
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -4
- package/src/__tests__/conversation-fork-crud.test.ts +69 -0
- package/src/__tests__/conversation-fork-referential.test.ts +67 -0
- package/src/__tests__/conversation-fork-retrospective.test.ts +24 -0
- package/src/__tests__/conversation-notifiers-provenance.test.ts +59 -0
- package/src/__tests__/conversation-queue.test.ts +55 -4
- package/src/__tests__/conversation-runtime-assembly.test.ts +134 -102
- package/src/__tests__/conversation-runtime-workspace.test.ts +14 -10
- package/src/__tests__/credential-prompt-route.test.ts +7 -10
- package/src/__tests__/custom-profile-ensure.test.ts +5 -1
- package/src/__tests__/discord-access-request-privacy.test.ts +5 -1
- package/src/__tests__/discord-requester-notice-privacy.test.ts +3 -3
- package/src/__tests__/document-append-idempotency.test.ts +233 -0
- package/src/__tests__/edit-propagation.test.ts +0 -7
- package/src/__tests__/helpers/mock-actor-context.ts +49 -0
- package/src/__tests__/helpers/mock-conversation.ts +13 -1
- package/src/__tests__/injector-chain.test.ts +63 -41
- package/src/__tests__/injector-disk-pressure.test.ts +11 -23
- package/src/__tests__/llm-context-resolution.test.ts +73 -1
- package/src/__tests__/llm-schema.test.ts +5 -2
- package/src/__tests__/mcp-list-plugin-servers.test.ts +250 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +6 -5
- package/src/__tests__/messages-read-boundary-guard.test.ts +134 -0
- package/src/__tests__/mtime-cache.test.ts +1 -1
- package/src/__tests__/non-member-access-request.test.ts +0 -20
- package/src/__tests__/outbound-slack-persistence.test.ts +40 -1
- package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -0
- package/src/__tests__/plugin-secret-pattern-contribution.test.ts +1 -1
- package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +14 -7
- package/src/__tests__/provider-commit-message-generator.test.ts +20 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +434 -105
- package/src/__tests__/scoped-approval-grants.test.ts +11 -6
- package/src/__tests__/secret-ingress-channel.test.ts +0 -1
- package/src/__tests__/skills.test.ts +32 -0
- package/src/__tests__/slack-edit-ordering-characterization.test.ts +0 -1
- package/src/__tests__/subagent-call-site-routing.test.ts +31 -19
- package/src/__tests__/subagent-spawn-and-await.test.ts +14 -10
- package/src/__tests__/turn-events-store.test.ts +43 -0
- package/src/__tests__/user-plugin-loader.test.ts +1 -1
- package/src/__tests__/visible-app-context.test.ts +16 -9
- package/src/__tests__/worker-entrypoint-guards.test.ts +54 -0
- package/src/__tests__/worker-plugin-surface.test.ts +77 -0
- package/src/__tests__/workspace-migration-142-consolidate-voice-front-door.test.ts +158 -0
- package/src/__tests__/workspace-migration-143-repair-deprecated-codex-model-id.test.ts +133 -0
- package/src/__tests__/workspace-migration-144-convert-stranded-subscription-openai-profiles.test.ts +316 -0
- package/src/__tests__/workspace-migration-145-collapse-profile-bindings-to-entries.test.ts +325 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +10 -2
- package/src/acp/__tests__/auth-required.test.ts +161 -0
- package/src/acp/acp-claude-oauth.ts +19 -2
- package/src/acp/agent-process.test.ts +100 -0
- package/src/acp/agent-process.ts +29 -26
- package/src/acp/auth-required.ts +102 -0
- package/src/acp/session-manager.test.ts +119 -0
- package/src/acp/session-manager.ts +68 -2
- package/src/api/events/acp-auth-required.ts +55 -0
- package/src/api/index.ts +7 -0
- package/src/apps/app-store.ts +3 -0
- package/src/bundler/package-resolver.ts +2 -30
- package/src/calls/__tests__/voice-session-bridge.test.ts +173 -1
- package/src/calls/__tests__/voice-triage-escalate.test.ts +94 -0
- package/src/calls/call-controller.ts +9 -2
- package/src/calls/call-setup-flow.ts +0 -1
- package/src/calls/media-stream-stt-session.ts +15 -0
- package/src/calls/voice-session-bridge.ts +71 -16
- package/src/calls/voice-triage-escalate.ts +104 -2
- package/src/channels/__tests__/plugin-channel-declarations.test.ts +161 -0
- package/src/channels/config.ts +13 -0
- package/src/channels/plugin-channel-declarations.ts +108 -0
- package/src/channels/types.ts +30 -0
- package/src/cli/AGENTS.md +5 -2
- package/src/cli/commands/credentials.help.ts +2 -2
- package/src/cli/commands/inference-providers.ts +1 -1
- package/src/cli/commands/mcp.help.ts +13 -4
- package/src/cli/commands/mcp.ts +9 -0
- package/src/cli/commands/memory/__tests__/memory-v3.test.ts +128 -5
- package/src/cli/commands/memory/index.help.ts +43 -1
- package/src/cli/commands/memory/memory-v3.ts +64 -0
- package/src/cli/commands/stt.help.ts +27 -2
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +39 -0
- package/src/cli/lib/bundled-marketplace.json +13 -0
- package/src/cli/lib/upgrade-plugin.ts +42 -0
- package/src/config/__tests__/default-profile-catalog.test.ts +34 -2
- package/src/config/__tests__/default-provider.test.ts +6 -1
- package/src/config/__tests__/profile-materialization.test.ts +75 -19
- package/src/config/bundled-skills/acp/SKILL.md +6 -7
- package/src/config/bundled-skills/document-editor/SKILL.md +2 -2
- package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +14 -4
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +22 -1
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +9 -2
- package/src/config/call-site-defaults.ts +4 -5
- package/src/config/default-profile-catalog.ts +82 -11
- package/src/config/default-profile-names.ts +4 -1
- package/src/config/default-provider-resolution.ts +4 -0
- package/src/config/llm-context-resolution.ts +11 -3
- package/src/config/llm-resolver.ts +28 -1
- package/src/config/profile-materialization.ts +70 -22
- package/src/config/schemas/__tests__/live-voice.test.ts +107 -4
- package/src/config/schemas/call-site-catalog.ts +4 -4
- package/src/config/schemas/live-voice.ts +57 -23
- package/src/config/schemas/llm.ts +59 -32
- package/src/config/schemas/mcp.ts +23 -0
- package/src/config/schemas/plugin-updates.ts +6 -2
- package/src/config/schemas/stt.ts +1 -0
- package/src/context/outbound-sanitize.ts +96 -1
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +82 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +15 -10
- package/src/daemon/conversation-agent-loop.ts +17 -6
- package/src/daemon/conversation-messaging.ts +5 -1
- package/src/daemon/conversation-notifiers.ts +9 -1
- package/src/daemon/conversation-process.ts +9 -6
- package/src/daemon/conversation-runtime-assembly.ts +3 -4
- package/src/daemon/conversation-tool-setup.ts +1 -2
- package/src/daemon/conversation.ts +48 -0
- package/src/daemon/mcp-reload-service.ts +36 -6
- package/src/daemon/process-message.ts +13 -3
- package/src/daemon/providers-setup.ts +6 -3
- package/src/daemon/trust-context-types.ts +29 -0
- package/src/daemon/wake-conversation-ops.ts +3 -2
- package/src/documents/document-store.ts +138 -5
- package/src/hooks/hook-loader.ts +3 -3
- package/src/hooks/registry.ts +50 -6
- package/src/inbound/__tests__/oauth-callback-url.test.ts +83 -0
- package/src/inbound/oauth-callback-url.ts +61 -0
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +1 -104
- package/src/live-voice/__tests__/live-voice-events.test.ts +7 -8
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +932 -0
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +115 -8
- package/src/live-voice/__tests__/live-voice-photo.test.ts +100 -0
- package/src/live-voice/__tests__/live-voice-progress.test.ts +60 -194
- package/src/live-voice/__tests__/live-voice-stt.test.ts +14 -0
- package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +29 -0
- package/src/live-voice/__tests__/live-voice-tts-session.test.ts +0 -483
- package/src/live-voice/__tests__/live-voice-vad.test.ts +0 -16
- package/src/live-voice/__tests__/progress-narration.test.ts +214 -0
- package/src/live-voice/live-voice-archive.ts +2 -0
- package/src/live-voice/live-voice-metrics.ts +57 -32
- package/src/live-voice/live-voice-photo.ts +1 -2
- package/src/live-voice/live-voice-session.ts +535 -314
- package/src/live-voice/progress-narration.ts +277 -0
- package/src/live-voice/protocol.ts +21 -1
- package/src/mcp/__tests__/effective-config.test.ts +238 -0
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
- package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +200 -0
- package/src/mcp/__tests__/mcp-oauth-provider.test.ts +9 -9
- package/src/mcp/__tests__/plugin-server-credential-isolation.test.ts +95 -0
- package/src/mcp/client.ts +16 -11
- package/src/mcp/effective-config.ts +113 -0
- package/src/mcp/manager.ts +11 -6
- package/src/mcp/mcp-auth-orchestrator.ts +13 -22
- package/src/mcp/mcp-oauth-provider.ts +205 -240
- package/src/monitoring/__tests__/plugin-auto-update.test.ts +166 -3
- package/src/monitoring/plugin-auto-update.ts +128 -24
- package/src/notifications/signal.ts +1 -0
- package/src/permissions/confirmation-guardian-request.test.ts +15 -11
- package/src/permissions/confirmation-guardian-request.ts +2 -2
- package/src/permissions/question-guardian-request.test.ts +14 -6
- package/src/permissions/question-guardian-request.ts +1 -2
- package/src/persistence/attachments-store.ts +8 -1
- package/src/persistence/bookmark-crud.ts +3 -7
- package/src/persistence/conversation-attention-store.ts +16 -45
- package/src/persistence/conversation-crud.ts +33 -4
- package/src/persistence/conversation-lineage.ts +9 -0
- package/src/persistence/conversation-queries.ts +108 -41
- package/src/persistence/delivery-crud.ts +38 -29
- package/src/persistence/external-conversation-store.ts +32 -4
- package/src/persistence/llm-request-log-store.ts +4 -10
- package/src/persistence/llm-usage-store.ts +8 -3
- package/src/persistence/message-reads.test.ts +197 -0
- package/src/persistence/message-reads.ts +211 -0
- package/src/persistence/migrations/366-chatgpt-subscription-row-identity.test.ts +120 -0
- package/src/persistence/migrations/366-chatgpt-subscription-row-identity.ts +62 -0
- package/src/persistence/real-user-turn-filter.ts +27 -3
- package/src/persistence/steps.ts +9 -0
- package/src/plugin-api/__tests__/oauth-callback-url-export.test.ts +29 -0
- package/src/plugin-api/conversation-turn.ts +168 -5
- package/src/plugin-api/index.ts +21 -5
- package/src/plugins/__tests__/mcp-servers.test.ts +371 -0
- package/src/plugins/defaults/memory/AGENTS.md +4 -0
- package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +204 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +72 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +4 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +4 -1
- package/src/plugins/defaults/memory/buffer-format.ts +165 -0
- package/src/plugins/defaults/memory/context-search/sources/conversations.ts +6 -0
- package/src/plugins/defaults/memory/graph/image-ref-utils.ts +3 -0
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -30
- package/src/plugins/defaults/memory/graph-topology/pending-buffer.test.ts +34 -0
- package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +8 -12
- package/src/plugins/defaults/memory/hooks/post-compact.ts +1 -4
- package/src/plugins/defaults/memory/indexer.ts +3 -1
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +19 -7
- package/src/plugins/defaults/memory/src/__tests__/memory-v3-gate-stats.test.ts +281 -0
- package/src/plugins/defaults/memory/src/memory-v3-routes.ts +207 -0
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +33 -0
- package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +199 -2
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +25 -18
- package/src/plugins/defaults/memory/substrate/skill-content.ts +8 -1
- package/src/plugins/defaults/memory/substrate/static-context.ts +160 -4
- package/src/plugins/defaults/memory/substrate/sweep-job.ts +2 -4
- package/src/plugins/defaults/memory/v1/graph/extraction.ts +3 -1
- package/src/plugins/defaults/memory/v3/prune.ts +2 -0
- package/src/plugins/defaults/memory/v3/selection-log-store.ts +2 -0
- package/src/plugins/defaults/memory/worker.ts +6 -3
- package/src/plugins/external-plugin-loader.ts +47 -0
- package/src/plugins/mcp-servers.ts +361 -0
- package/src/plugins/mtime-cache.ts +23 -49
- package/src/plugins/worker-plugin-surface.ts +33 -0
- package/src/providers/__tests__/connection-model-compat.test.ts +1 -1
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +214 -2
- package/src/providers/__tests__/preflight-resolved-config.test.ts +57 -0
- package/src/providers/__tests__/retry-callsite.test.ts +5 -2
- package/src/providers/call-site-routing.ts +30 -3
- package/src/providers/connection-resolution.ts +194 -11
- package/src/providers/inference/auth.ts +6 -0
- package/src/providers/inference/connection-availability.ts +24 -2
- package/src/providers/inference/connections.ts +2 -0
- package/src/providers/model-intents.ts +26 -6
- package/src/providers/openai/codex-models.ts +2 -1
- package/src/providers/provider-send-message.ts +32 -3
- package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +433 -0
- package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +620 -0
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +34 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +285 -6
- package/src/providers/speech-to-text/deepgram-flux-frames.ts +395 -0
- package/src/providers/speech-to-text/deepgram-flux-realtime.ts +719 -0
- package/src/providers/speech-to-text/provider-catalog.ts +99 -8
- package/src/providers/speech-to-text/resolve.ts +25 -2
- package/src/routes/worker.ts +17 -5
- package/src/runtime/access-request-helper.ts +9 -12
- package/src/runtime/agent-wake.ts +3 -3
- package/src/runtime/pre-first-message-gate.ts +4 -0
- package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +12 -4
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +219 -1
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +52 -0
- package/src/runtime/routes/__tests__/default-provider-routes.test.ts +61 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +44 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +102 -1
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +44 -0
- package/src/runtime/routes/__tests__/stt-routes.test.ts +25 -0
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +62 -1
- package/src/runtime/routes/channel-availability-routes.ts +32 -14
- package/src/runtime/routes/channel-route-shared.ts +0 -6
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +6 -6
- package/src/runtime/routes/conversation-list-routes.ts +112 -1
- package/src/runtime/routes/conversation-query-routes.ts +40 -27
- package/src/runtime/routes/credential-prompt-routes.ts +4 -7
- package/src/runtime/routes/default-provider-routes.ts +15 -0
- package/src/runtime/routes/inbound-message-handler.ts +17 -41
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -9
- package/src/runtime/routes/inbound-stages/admission-policy.ts +1 -17
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -3
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +1 -3
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +3 -4
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +11 -20
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +2 -3
- package/src/runtime/routes/inference-profiles-routes.ts +20 -11
- package/src/runtime/routes/inference-provider-connection-routes.ts +77 -15
- package/src/runtime/routes/log-export-routes.ts +3 -0
- package/src/runtime/routes/mcp-auth-routes.ts +148 -57
- package/src/runtime/routes/plugins-routes.ts +21 -3
- package/src/runtime/routes/stt-routes.ts +31 -25
- package/src/runtime/routes/surface-conversation-resolver.ts +3 -0
- package/src/runtime/routes/user-route-dispatcher.ts +39 -14
- package/src/runtime/routes/user-route-import.ts +108 -0
- package/src/schedule/worker.ts +6 -0
- package/src/security/oauth2.ts +6 -22
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +22 -0
- package/src/stt/__tests__/types.test.ts +94 -0
- package/src/stt/daemon-batch-transcriber.ts +10 -0
- package/src/stt/stt-stream-session.ts +8 -4
- package/src/stt/types.ts +103 -0
- package/src/subagent/manager.ts +1 -3
- package/src/subagent/types.ts +7 -6
- package/src/tools/acp/spawn.test.ts +97 -0
- package/src/tools/acp/spawn.ts +32 -0
- package/src/tools/document/document-tool.ts +12 -3
- package/src/tools/registry.ts +2 -1
- package/src/tools/workflows/run-workflow.ts +1 -2
- package/src/tts/__tests__/reasoning-tag-filter.test.ts +63 -0
- package/src/tts/reasoning-tag-filter.ts +110 -0
- package/src/workspace/byok-default-profile-ensure.ts +61 -18
- package/src/workspace/custom-profile-ensure.ts +4 -24
- package/src/workspace/migrations/142-consolidate-voice-front-door.ts +70 -0
- package/src/workspace/migrations/143-repair-deprecated-codex-model-id.ts +134 -0
- package/src/workspace/migrations/144-convert-stranded-subscription-openai-profiles.ts +265 -0
- package/src/workspace/migrations/145-collapse-profile-bindings-to-entries.ts +328 -0
- package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +0 -10
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/workspace/provider-commit-message-generator.ts +7 -5
- package/src/live-voice/__tests__/front-decision.test.ts +0 -645
- package/src/live-voice/front-decision.ts +0 -476
|
@@ -1794,3 +1794,72 @@ describe("forkConversation + memory_retrospective_state", () => {
|
|
|
1794
1794
|
expect(fork.slackContextCompactionWatermarkAt).toBe(compactedAt);
|
|
1795
1795
|
});
|
|
1796
1796
|
});
|
|
1797
|
+
|
|
1798
|
+
describe("forkConversation with unfinalized rows", () => {
|
|
1799
|
+
beforeEach(() => {
|
|
1800
|
+
resetTables();
|
|
1801
|
+
});
|
|
1802
|
+
|
|
1803
|
+
test("skips unfinalized rows and anchors the fork at the last finalized message", async () => {
|
|
1804
|
+
const source = createConversation("Mid-turn fork");
|
|
1805
|
+
await addMessage(source.id, "user", "question", { skipIndexing: true });
|
|
1806
|
+
const lastFinalized = await addMessage(
|
|
1807
|
+
source.id,
|
|
1808
|
+
"assistant",
|
|
1809
|
+
"completed reply",
|
|
1810
|
+
{ skipIndexing: true },
|
|
1811
|
+
);
|
|
1812
|
+
const streaming = await addMessage(
|
|
1813
|
+
source.id,
|
|
1814
|
+
"assistant",
|
|
1815
|
+
"partial reply still being written",
|
|
1816
|
+
{ skipIndexing: true },
|
|
1817
|
+
);
|
|
1818
|
+
rawRun(
|
|
1819
|
+
"test:markUnfinalized",
|
|
1820
|
+
"UPDATE messages SET finalized = 0 WHERE id = ?",
|
|
1821
|
+
streaming.id,
|
|
1822
|
+
);
|
|
1823
|
+
|
|
1824
|
+
const fork = forkConversation({ conversationId: source.id });
|
|
1825
|
+
const forkMessages = getMessages(fork.id);
|
|
1826
|
+
|
|
1827
|
+
// The invariant, not mere presence: exactly the finalized rows were
|
|
1828
|
+
// copied, every copy reads as finalized, no copy descends from the
|
|
1829
|
+
// unfinalized source row, and the lineage anchor is a row the fork
|
|
1830
|
+
// actually holds.
|
|
1831
|
+
expect(forkMessages).toHaveLength(2);
|
|
1832
|
+
expect(forkMessages.every((row) => row.finalized === 1)).toBe(true);
|
|
1833
|
+
const forkedFromIds = forkMessages.map(
|
|
1834
|
+
(row) =>
|
|
1835
|
+
(parseMetadata(row.metadata) as { forkSourceMessageId?: string })
|
|
1836
|
+
?.forkSourceMessageId,
|
|
1837
|
+
);
|
|
1838
|
+
expect(forkedFromIds).not.toContain(streaming.id);
|
|
1839
|
+
expect(fork.forkParentMessageId).toBe(lastFinalized.id);
|
|
1840
|
+
// The source still owns its in-flight row untouched.
|
|
1841
|
+
expect(getMessages(source.id)).toHaveLength(3);
|
|
1842
|
+
});
|
|
1843
|
+
|
|
1844
|
+
test("a stale mid-history unfinalized row is skipped without shifting the anchor", async () => {
|
|
1845
|
+
const source = createConversation("Stale row fork");
|
|
1846
|
+
await addMessage(source.id, "user", "first", { skipIndexing: true });
|
|
1847
|
+
const stale = await addMessage(source.id, "assistant", "orphaned partial", {
|
|
1848
|
+
skipIndexing: true,
|
|
1849
|
+
});
|
|
1850
|
+
const tail = await addMessage(source.id, "user", "latest", {
|
|
1851
|
+
skipIndexing: true,
|
|
1852
|
+
});
|
|
1853
|
+
rawRun(
|
|
1854
|
+
"test:markUnfinalized",
|
|
1855
|
+
"UPDATE messages SET finalized = 0 WHERE id = ?",
|
|
1856
|
+
stale.id,
|
|
1857
|
+
);
|
|
1858
|
+
|
|
1859
|
+
const fork = forkConversation({ conversationId: source.id });
|
|
1860
|
+
const forkMessages = getMessages(fork.id);
|
|
1861
|
+
|
|
1862
|
+
expect(forkMessages).toHaveLength(2);
|
|
1863
|
+
expect(fork.forkParentMessageId).toBe(tail.id);
|
|
1864
|
+
});
|
|
1865
|
+
});
|
|
@@ -350,3 +350,70 @@ describe("referential forking", () => {
|
|
|
350
350
|
expect(textOf(getMessages(source.id))).toHaveLength(4);
|
|
351
351
|
});
|
|
352
352
|
});
|
|
353
|
+
|
|
354
|
+
describe("referential forking with unfinalized rows", () => {
|
|
355
|
+
beforeEach(() => {
|
|
356
|
+
resetTables();
|
|
357
|
+
clearConfig();
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test("ancestor rows are hidden while unfinalized and appear once finalized", async () => {
|
|
361
|
+
const source = await seedSource("Launch");
|
|
362
|
+
const midRow = getMessages(source.id)[1]!;
|
|
363
|
+
getDb()
|
|
364
|
+
.update(messages)
|
|
365
|
+
.set({ finalized: 0 })
|
|
366
|
+
.where(eq(messages.id, midRow.id))
|
|
367
|
+
.run();
|
|
368
|
+
|
|
369
|
+
setForkStrategy("reference");
|
|
370
|
+
const fork = await forkConversationForRetrospective({
|
|
371
|
+
conversationId: source.id,
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// The fork's lineage read excludes the ancestor's in-flight row; the
|
|
375
|
+
// source's own read keeps it.
|
|
376
|
+
expect(textOf(getMessages(fork.id))).toEqual([
|
|
377
|
+
"draft a launch plan",
|
|
378
|
+
"tweak the timeline",
|
|
379
|
+
"updated",
|
|
380
|
+
]);
|
|
381
|
+
expect(getMessages(source.id)).toHaveLength(4);
|
|
382
|
+
|
|
383
|
+
getDb()
|
|
384
|
+
.update(messages)
|
|
385
|
+
.set({ finalized: 1 })
|
|
386
|
+
.where(eq(messages.id, midRow.id))
|
|
387
|
+
.run();
|
|
388
|
+
|
|
389
|
+
// Once the row finalizes it enters the inherited window on its own.
|
|
390
|
+
expect(textOf(getMessages(fork.id))).toEqual([
|
|
391
|
+
"draft a launch plan",
|
|
392
|
+
"here is a first pass",
|
|
393
|
+
"tweak the timeline",
|
|
394
|
+
"updated",
|
|
395
|
+
]);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
test("an unfinalized tail is not the fork anchor", async () => {
|
|
399
|
+
const source = await seedSource("Launch");
|
|
400
|
+
const tail = getMessages(source.id).at(-1)!;
|
|
401
|
+
getDb()
|
|
402
|
+
.update(messages)
|
|
403
|
+
.set({ finalized: 0 })
|
|
404
|
+
.where(eq(messages.id, tail.id))
|
|
405
|
+
.run();
|
|
406
|
+
|
|
407
|
+
setForkStrategy("reference");
|
|
408
|
+
const fork = await forkConversationForRetrospective({
|
|
409
|
+
conversationId: source.id,
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
expect(fork.forkParentMessageId).not.toBe(tail.id);
|
|
413
|
+
expect(textOf(getMessages(fork.id))).toEqual([
|
|
414
|
+
"draft a launch plan",
|
|
415
|
+
"here is a first pass",
|
|
416
|
+
"tweak the timeline",
|
|
417
|
+
]);
|
|
418
|
+
});
|
|
419
|
+
});
|
|
@@ -723,3 +723,27 @@ describe("forkConversationForRetrospective — compacted source", () => {
|
|
|
723
723
|
});
|
|
724
724
|
});
|
|
725
725
|
});
|
|
726
|
+
|
|
727
|
+
describe("forkConversationForRetrospective with unfinalized rows", () => {
|
|
728
|
+
beforeEach(() => {
|
|
729
|
+
resetTables();
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
test("skips the unfinalized tail and anchors at the last finalized message", async () => {
|
|
733
|
+
const source = await seedSource("Mid-turn retro");
|
|
734
|
+
const tail = getMessages(source.id).at(-1)!;
|
|
735
|
+
const db = getDb();
|
|
736
|
+
db.run(`UPDATE messages SET finalized = 0 WHERE id = '${tail.id}'`);
|
|
737
|
+
|
|
738
|
+
const fork = await forkConversationForRetrospective({
|
|
739
|
+
conversationId: source.id,
|
|
740
|
+
});
|
|
741
|
+
const forkMessages = getMessages(fork.id);
|
|
742
|
+
|
|
743
|
+
// Invariant: only finalized rows are copied, and the lineage anchor is a
|
|
744
|
+
// row the fork actually holds.
|
|
745
|
+
expect(forkMessages).toHaveLength(3);
|
|
746
|
+
expect(forkMessages.every((row) => row.finalized === 1)).toBe(true);
|
|
747
|
+
expect(fork.forkParentMessageId).not.toBe(tail.id);
|
|
748
|
+
});
|
|
749
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The live-call question notifier persists its row after the voice turn has
|
|
3
|
+
* settled. The per-turn trust field is not cleared at release, so at that
|
|
4
|
+
* moment it can still hold the finished turn's actor while voice cleanup has
|
|
5
|
+
* restored the conversation's resting trust. The row must carry the owner,
|
|
6
|
+
* not the stale turn actor.
|
|
7
|
+
*/
|
|
8
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
9
|
+
|
|
10
|
+
const added: Array<{ metadata?: Record<string, unknown> }> = [];
|
|
11
|
+
mock.module("../persistence/conversation-crud.js", () => ({
|
|
12
|
+
addMessage: async (
|
|
13
|
+
_conversationId: string,
|
|
14
|
+
_role: string,
|
|
15
|
+
_content: string,
|
|
16
|
+
options?: { metadata?: Record<string, unknown> },
|
|
17
|
+
) => {
|
|
18
|
+
added.push({ metadata: options?.metadata });
|
|
19
|
+
return { id: "msg-1", deduplicated: false };
|
|
20
|
+
},
|
|
21
|
+
provenanceFromTrustContext: (ctx?: { trustClass?: string }) =>
|
|
22
|
+
ctx ? { provenanceTrustClass: ctx.trustClass } : {},
|
|
23
|
+
}));
|
|
24
|
+
mock.module("../calls/call-store.js", () => ({
|
|
25
|
+
getCallSession: () => ({ toNumber: "+15550100" }),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
import { fireCallQuestionNotifier } from "../calls/call-state.js";
|
|
29
|
+
import { registerConversationNotifiers } from "../daemon/conversation-notifiers.js";
|
|
30
|
+
|
|
31
|
+
describe("live-call question provenance", () => {
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
added.length = 0;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("a row persisted after the turn settled carries the owner, not the stale turn actor", async () => {
|
|
37
|
+
registerConversationNotifiers("conv-n1", {
|
|
38
|
+
sendToClient: () => {},
|
|
39
|
+
messages: [],
|
|
40
|
+
// Voice cleanup restored the owner; the per-turn field still holds the
|
|
41
|
+
// finished voice turn's contact.
|
|
42
|
+
trustContext: {
|
|
43
|
+
trustClass: "guardian",
|
|
44
|
+
sourceChannel: "vellum",
|
|
45
|
+
},
|
|
46
|
+
currentTurnTrustContext: {
|
|
47
|
+
trustClass: "trusted_contact",
|
|
48
|
+
sourceChannel: "phone",
|
|
49
|
+
requesterExternalUserId: "caller-1",
|
|
50
|
+
},
|
|
51
|
+
} as never);
|
|
52
|
+
|
|
53
|
+
fireCallQuestionNotifier("conv-n1", "sess-1", "Which slot works?");
|
|
54
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
55
|
+
|
|
56
|
+
expect(added.length).toBe(1);
|
|
57
|
+
expect(added[0]?.metadata?.provenanceTrustClass).toBe("guardian");
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -79,10 +79,12 @@ mock.module("../persistence/conversation-crud.js", () => ({
|
|
|
79
79
|
setConversationOriginInterfaceIfUnset: () => {},
|
|
80
80
|
updateConversationContextWindow: () => {},
|
|
81
81
|
deleteMessageById: () => {},
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
// Mirrors the real mapping so provenance assertions observe what production
|
|
83
|
+
// would stamp, rather than a placeholder.
|
|
84
|
+
provenanceFromTrustContext: (ctx?: { trustClass?: string }) =>
|
|
85
|
+
ctx
|
|
86
|
+
? { provenanceTrustClass: ctx.trustClass }
|
|
87
|
+
: { provenanceTrustClass: "unknown" },
|
|
86
88
|
getConversationOriginInterface: () => null,
|
|
87
89
|
getConversationOriginChannel: () => null,
|
|
88
90
|
getMessages: () => [],
|
|
@@ -597,6 +599,55 @@ describe("Conversation message queue", () => {
|
|
|
597
599
|
await new Promise((r) => setTimeout(r, 10));
|
|
598
600
|
});
|
|
599
601
|
|
|
602
|
+
test("a drained slash exchange is stamped with its sender's provenance, not the slot's", async () => {
|
|
603
|
+
// Provenance gates memory extraction and untrusted-content wrapping, so
|
|
604
|
+
// the stamped class must be the actor whose message this is. The unknown-
|
|
605
|
+
// slash drain branch persists an exchange for the queued sender; if the
|
|
606
|
+
// guardian wrote the conversation slot while that message waited, the
|
|
607
|
+
// rows must still carry the sender's class.
|
|
608
|
+
const conversation = makeConversation();
|
|
609
|
+
await conversation.loadFromDb();
|
|
610
|
+
|
|
611
|
+
conversation.setTrustContext({
|
|
612
|
+
trustClass: "trusted_contact",
|
|
613
|
+
sourceChannel: "slack",
|
|
614
|
+
requesterExternalUserId: "U-contact",
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
const p1 = conversation.processMessage({
|
|
618
|
+
content: "msg-1",
|
|
619
|
+
attachments: [],
|
|
620
|
+
onEvent: () => {},
|
|
621
|
+
requestId: "req-1",
|
|
622
|
+
});
|
|
623
|
+
await waitForPendingRun(1);
|
|
624
|
+
|
|
625
|
+
conversation.enqueueMessage({
|
|
626
|
+
content: "/definitely-not-a-command",
|
|
627
|
+
requestId: "req-slash",
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
// The guardian moves the slot while the contact's message waits.
|
|
631
|
+
conversation.setTrustContext({
|
|
632
|
+
trustClass: "guardian",
|
|
633
|
+
sourceChannel: "vellum",
|
|
634
|
+
requesterExternalUserId: "guardian-principal",
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
capturedAddMessages.length = 0;
|
|
638
|
+
await resolveRun(0);
|
|
639
|
+
await p1;
|
|
640
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
641
|
+
|
|
642
|
+
const exchange = capturedAddMessages.filter((m) =>
|
|
643
|
+
m.content.includes("definitely-not-a-command"),
|
|
644
|
+
);
|
|
645
|
+
expect(exchange.length).toBeGreaterThan(0);
|
|
646
|
+
for (const row of exchange) {
|
|
647
|
+
expect(row.metadata?.provenanceTrustClass).toBe("trusted_contact");
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
|
|
600
651
|
test("a drained turn keeps its sender's trust once the agent loop is running", async () => {
|
|
601
652
|
// Exercises the real drain -> runAgentLoop ordering against a live
|
|
602
653
|
// Conversation. The loop re-initializes the per-turn trust snapshot on
|
|
@@ -103,6 +103,7 @@ mock.module("../daemon/date-context.js", () => ({
|
|
|
103
103
|
formatTurnTimestamp: () => FIXED_TURN_TIMESTAMP,
|
|
104
104
|
}));
|
|
105
105
|
|
|
106
|
+
import type { SurfaceDataByType } from "../api/surfaces.js";
|
|
106
107
|
import { resolveCallSiteConfig } from "../config/llm-resolver.js";
|
|
107
108
|
import { LLMSchema } from "../config/schemas/llm.js";
|
|
108
109
|
import { REFUSAL_FALLBACK_TEXT } from "../context/refusal-quarantine.js";
|
|
@@ -136,7 +137,7 @@ import {
|
|
|
136
137
|
stripInjectionsForCompaction,
|
|
137
138
|
stripNowScratchpad,
|
|
138
139
|
} from "../daemon/conversation-runtime-assembly.js";
|
|
139
|
-
import type {
|
|
140
|
+
import type { SurfaceStateEntry } from "../daemon/conversation-surface-state.js";
|
|
140
141
|
import { buildPkbReminder } from "../daemon/pkb-reminder-builder.js";
|
|
141
142
|
import type { TrustContext } from "../daemon/trust-context-types.js";
|
|
142
143
|
import {
|
|
@@ -161,6 +162,7 @@ import { wrapUntrustedContent } from "../security/untrusted-content.js";
|
|
|
161
162
|
import { getSubagentManager } from "../subagent/index.js";
|
|
162
163
|
import type { SubagentState } from "../subagent/types.js";
|
|
163
164
|
import { getWorkspacePromptPath } from "../util/platform.js";
|
|
165
|
+
import { asConversation } from "./helpers/mock-conversation.js";
|
|
164
166
|
|
|
165
167
|
// `applyRuntimeInjections` self-resolves the Slack active-thread focus block by
|
|
166
168
|
// reading the live conversation's persisted message rows, so the schema must
|
|
@@ -215,27 +217,30 @@ function seedActiveSurfaceConversation(
|
|
|
215
217
|
conversationId: string,
|
|
216
218
|
workspaceText: string,
|
|
217
219
|
surfaceId: string,
|
|
218
|
-
data:
|
|
220
|
+
data: SurfaceDataByType["dynamic_page"],
|
|
219
221
|
channelCapabilities?: ChannelCapabilities,
|
|
220
222
|
commandIntent?: { type: string; payload?: string; languageCode?: string },
|
|
221
223
|
currentTurnTemporalSnapshot?: {
|
|
222
224
|
clientTimezone: string | null;
|
|
225
|
+
timeSinceLastMessage: string | null;
|
|
223
226
|
},
|
|
224
227
|
): void {
|
|
225
|
-
setConversation(
|
|
228
|
+
setConversation(
|
|
226
229
|
conversationId,
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
230
|
+
asConversation({
|
|
231
|
+
conversationId,
|
|
232
|
+
workingDir: "/sandbox",
|
|
233
|
+
workspaceTopLevelContext: workspaceText,
|
|
234
|
+
workspaceTopLevelDirty: false,
|
|
235
|
+
currentActiveSurfaceId: surfaceId,
|
|
236
|
+
surfaceState: new Map<string, SurfaceStateEntry>([
|
|
237
|
+
[surfaceId, { surfaceType: "dynamic_page", data }],
|
|
238
|
+
]),
|
|
239
|
+
channelCapabilities: channelCapabilities ?? undefined,
|
|
240
|
+
commandIntent,
|
|
241
|
+
currentTurnTemporalSnapshot,
|
|
242
|
+
}),
|
|
243
|
+
);
|
|
239
244
|
}
|
|
240
245
|
|
|
241
246
|
function clearWorkspaceContext(): void {
|
|
@@ -252,15 +257,18 @@ function seedChannelCapabilitiesConversation(
|
|
|
252
257
|
caps: ChannelCapabilities | null,
|
|
253
258
|
transportHints?: string[],
|
|
254
259
|
): void {
|
|
255
|
-
setConversation(
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
260
|
+
setConversation(
|
|
261
|
+
"runtime-assembly-fallback",
|
|
262
|
+
asConversation({
|
|
263
|
+
conversationId: "runtime-assembly-fallback",
|
|
264
|
+
workingDir: "/sandbox",
|
|
265
|
+
workspaceTopLevelContext: "",
|
|
266
|
+
workspaceTopLevelDirty: false,
|
|
267
|
+
surfaceState: new Map(),
|
|
268
|
+
channelCapabilities: caps ?? undefined,
|
|
269
|
+
transportHints,
|
|
270
|
+
}),
|
|
271
|
+
);
|
|
264
272
|
}
|
|
265
273
|
|
|
266
274
|
// ---------------------------------------------------------------------------
|
|
@@ -986,6 +994,7 @@ describe("applyRuntimeInjections — injection mode", () => {
|
|
|
986
994
|
{ type: "start" },
|
|
987
995
|
{
|
|
988
996
|
clientTimezone: null,
|
|
997
|
+
timeSinceLastMessage: null,
|
|
989
998
|
},
|
|
990
999
|
);
|
|
991
1000
|
});
|
|
@@ -1023,15 +1032,18 @@ describe("applyRuntimeInjections — injection mode", () => {
|
|
|
1023
1032
|
// rehydration-order tests in conversation-lifecycle.test.ts). background-turn
|
|
1024
1033
|
// fires only for a background/scheduled live conversation, so register one
|
|
1025
1034
|
// under the turn's conversation id.
|
|
1026
|
-
setConversation(
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
+
setConversation(
|
|
1036
|
+
"injection-mode-conv",
|
|
1037
|
+
asConversation({
|
|
1038
|
+
conversationId: "injection-mode-conv",
|
|
1039
|
+
workingDir: "/sandbox",
|
|
1040
|
+
workspaceTopLevelContext: "",
|
|
1041
|
+
workspaceTopLevelDirty: false,
|
|
1042
|
+
surfaceState: new Map(),
|
|
1043
|
+
channelCapabilities,
|
|
1044
|
+
conversationType: "background",
|
|
1045
|
+
}),
|
|
1046
|
+
);
|
|
1035
1047
|
|
|
1036
1048
|
const { blocks } = await applyRuntimeInjections(baseMessages, fullOptions);
|
|
1037
1049
|
|
|
@@ -2227,20 +2239,24 @@ describe("applyRuntimeInjections with unifiedTurnContext", () => {
|
|
|
2227
2239
|
// context (so the injector sources the interface label to match
|
|
2228
2240
|
// `sampleOptions.interfaceName`).
|
|
2229
2241
|
function seedTemporalSnapshot(): void {
|
|
2230
|
-
setConversation(
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2242
|
+
setConversation(
|
|
2243
|
+
"runtime-assembly-fallback",
|
|
2244
|
+
asConversation({
|
|
2245
|
+
conversationId: "runtime-assembly-fallback",
|
|
2246
|
+
workingDir: "/sandbox",
|
|
2247
|
+
workspaceTopLevelContext: "",
|
|
2248
|
+
workspaceTopLevelDirty: false,
|
|
2249
|
+
surfaceState: new Map(),
|
|
2250
|
+
currentTurnTemporalSnapshot: {
|
|
2251
|
+
clientTimezone: null,
|
|
2252
|
+
timeSinceLastMessage: null,
|
|
2253
|
+
},
|
|
2254
|
+
currentTurnInterfaceContext: {
|
|
2255
|
+
userMessageInterface: "macos",
|
|
2256
|
+
assistantMessageInterface: "macos",
|
|
2257
|
+
},
|
|
2258
|
+
}),
|
|
2259
|
+
);
|
|
2244
2260
|
}
|
|
2245
2261
|
|
|
2246
2262
|
test("injects the turn-context block when the inputs are provided", async () => {
|
|
@@ -2327,17 +2343,20 @@ describe("applyRuntimeInjections timezone resolution", () => {
|
|
|
2327
2343
|
clientTimezone: string | null,
|
|
2328
2344
|
timeSinceLastMessage: string | null = null,
|
|
2329
2345
|
): void {
|
|
2330
|
-
setConversation(
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2346
|
+
setConversation(
|
|
2347
|
+
"runtime-assembly-fallback",
|
|
2348
|
+
asConversation({
|
|
2349
|
+
conversationId: "runtime-assembly-fallback",
|
|
2350
|
+
workingDir: "/sandbox",
|
|
2351
|
+
workspaceTopLevelContext: "",
|
|
2352
|
+
workspaceTopLevelDirty: false,
|
|
2353
|
+
surfaceState: new Map(),
|
|
2354
|
+
currentTurnTemporalSnapshot: {
|
|
2355
|
+
clientTimezone,
|
|
2356
|
+
timeSinceLastMessage,
|
|
2357
|
+
},
|
|
2358
|
+
}),
|
|
2359
|
+
);
|
|
2341
2360
|
}
|
|
2342
2361
|
|
|
2343
2362
|
function injectedText(result: { messages: Message[] }): string {
|
|
@@ -2491,20 +2510,24 @@ describe("applyRuntimeInjections blocks.unifiedTurnContext", () => {
|
|
|
2491
2510
|
// context (so the injector sources the interface label to match
|
|
2492
2511
|
// `sampleOptions.interfaceName`).
|
|
2493
2512
|
function seedTemporalSnapshot(): void {
|
|
2494
|
-
setConversation(
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2513
|
+
setConversation(
|
|
2514
|
+
"runtime-assembly-fallback",
|
|
2515
|
+
asConversation({
|
|
2516
|
+
conversationId: "runtime-assembly-fallback",
|
|
2517
|
+
workingDir: "/sandbox",
|
|
2518
|
+
workspaceTopLevelContext: "",
|
|
2519
|
+
workspaceTopLevelDirty: false,
|
|
2520
|
+
surfaceState: new Map(),
|
|
2521
|
+
currentTurnTemporalSnapshot: {
|
|
2522
|
+
clientTimezone: null,
|
|
2523
|
+
timeSinceLastMessage: null,
|
|
2524
|
+
},
|
|
2525
|
+
currentTurnInterfaceContext: {
|
|
2526
|
+
userMessageInterface: "macos",
|
|
2527
|
+
assistantMessageInterface: "macos",
|
|
2528
|
+
},
|
|
2529
|
+
}),
|
|
2530
|
+
);
|
|
2508
2531
|
}
|
|
2509
2532
|
|
|
2510
2533
|
test("captures unifiedTurnContext when tail is a user message", async () => {
|
|
@@ -2688,15 +2711,18 @@ describe("applyRuntimeInjections — subagent status", () => {
|
|
|
2688
2711
|
// a fallback fake carrying that delegation for the seeded children to
|
|
2689
2712
|
// surface.
|
|
2690
2713
|
beforeEach(() => {
|
|
2691
|
-
setConversation(
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2714
|
+
setConversation(
|
|
2715
|
+
FALLBACK_CONVERSATION_ID,
|
|
2716
|
+
asConversation({
|
|
2717
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
2718
|
+
workingDir: "/sandbox",
|
|
2719
|
+
workspaceTopLevelContext: "",
|
|
2720
|
+
workspaceTopLevelDirty: false,
|
|
2721
|
+
surfaceState: new Map(),
|
|
2722
|
+
getSubagentChildren: () =>
|
|
2723
|
+
getSubagentManager().getChildrenOf(FALLBACK_CONVERSATION_ID),
|
|
2724
|
+
}),
|
|
2725
|
+
);
|
|
2700
2726
|
});
|
|
2701
2727
|
|
|
2702
2728
|
afterEach(() => {
|
|
@@ -2750,14 +2776,17 @@ describe("applyRuntimeInjections — subagent status", () => {
|
|
|
2750
2776
|
|
|
2751
2777
|
test("skips subagent status when the conversation is itself a subagent", async () => {
|
|
2752
2778
|
// GIVEN the live conversation is itself a subagent (no nesting)
|
|
2753
|
-
setConversation(
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2779
|
+
setConversation(
|
|
2780
|
+
"runtime-assembly-fallback",
|
|
2781
|
+
asConversation({
|
|
2782
|
+
conversationId: "runtime-assembly-fallback",
|
|
2783
|
+
workingDir: "/sandbox",
|
|
2784
|
+
workspaceTopLevelContext: "",
|
|
2785
|
+
workspaceTopLevelDirty: false,
|
|
2786
|
+
surfaceState: new Map(),
|
|
2787
|
+
isSubagent: true,
|
|
2788
|
+
}),
|
|
2789
|
+
);
|
|
2761
2790
|
// AND a child is registered under its id
|
|
2762
2791
|
seedSubagentChild(
|
|
2763
2792
|
"runtime-assembly-fallback",
|
|
@@ -4317,19 +4346,22 @@ describe("Slack channel chronological rendering — multi-thread", () => {
|
|
|
4317
4346
|
)
|
|
4318
4347
|
.run();
|
|
4319
4348
|
}
|
|
4320
|
-
setConversation(
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4349
|
+
setConversation(
|
|
4350
|
+
"runtime-assembly-fallback",
|
|
4351
|
+
asConversation({
|
|
4352
|
+
conversationId: "runtime-assembly-fallback",
|
|
4353
|
+
workingDir: "/sandbox",
|
|
4354
|
+
workspaceTopLevelContext: "",
|
|
4355
|
+
workspaceTopLevelDirty: false,
|
|
4356
|
+
surfaceState: new Map(),
|
|
4357
|
+
channelCapabilities: caps,
|
|
4358
|
+
trustContext: { trustClass: "guardian", sourceChannel: "vellum" },
|
|
4359
|
+
slackContextCompactionWatermarkTs:
|
|
4360
|
+
compaction.slackContextCompactionWatermarkTs ?? null,
|
|
4361
|
+
contextCompactedMessageCount:
|
|
4362
|
+
compaction.contextCompactedMessageCount ?? 0,
|
|
4363
|
+
}),
|
|
4364
|
+
);
|
|
4333
4365
|
}
|
|
4334
4366
|
|
|
4335
4367
|
// Re-run a Slack-channel turn through the public assembly path. The
|