@vellumai/assistant 0.6.0 → 0.6.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/AGENTS.md +4 -0
- package/ARCHITECTURE.md +68 -15
- package/Dockerfile +2 -2
- package/bun.lock +6 -2
- package/docker-entrypoint.sh +42 -1
- package/docs/architecture/integrations.md +1 -1
- package/docs/architecture/memory.md +21 -24
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +7 -9
- package/openapi.yaml +539 -4
- package/package.json +5 -1
- package/src/__tests__/anthropic-provider.test.ts +160 -95
- package/src/__tests__/app-dir-path-guard.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +47 -1
- package/src/__tests__/app-source-watcher.test.ts +159 -0
- package/src/__tests__/assistant-event-hub.test.ts +30 -0
- package/src/__tests__/checker.test.ts +138 -172
- package/src/__tests__/cli-command-risk-guard.test.ts +1 -1
- package/src/__tests__/config-schema.test.ts +5 -0
- package/src/__tests__/context-overflow-approval.test.ts +5 -5
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -6
- package/src/__tests__/conversation-agent-loop.test.ts +4 -51
- package/src/__tests__/conversation-analysis-routes.test.ts +169 -0
- package/src/__tests__/conversation-directories-parse.test.ts +105 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +653 -832
- package/src/__tests__/conversation-runtime-workspace.test.ts +1 -93
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +17 -4
- package/src/__tests__/conversation-wipe.test.ts +2 -6
- package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -12
- package/src/__tests__/conversation-workspace-injection.test.ts +25 -26
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
- package/src/__tests__/copy-composer-tc-templates.test.ts +335 -0
- package/src/__tests__/credential-execution-approval-bridge.test.ts +0 -2
- package/src/__tests__/date-context.test.ts +76 -210
- package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -1
- package/src/__tests__/file-list-tool.test.ts +219 -0
- package/src/__tests__/first-greeting.test.ts +1 -1
- package/src/__tests__/heartbeat-service.test.ts +180 -3
- package/src/__tests__/identity-routes.test.ts +328 -0
- package/src/__tests__/init-feature-flag-overrides.test.ts +167 -0
- package/src/__tests__/injection-block.test.ts +24 -0
- package/src/__tests__/inline-command-runner.test.ts +7 -5
- package/src/__tests__/install-skill-routing.test.ts +7 -6
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +15 -14
- package/src/__tests__/list-messages-tool-merge.test.ts +300 -0
- package/src/__tests__/llm-context-normalization.test.ts +18 -18
- package/src/__tests__/llm-context-route-provider.test.ts +101 -0
- package/src/__tests__/llm-request-log-turn-query.test.ts +162 -0
- package/src/__tests__/log-export-workspace.test.ts +257 -100
- package/src/__tests__/managed-credential-catalog-cli.test.ts +12 -14
- package/src/__tests__/mcp-abort-signal.test.ts +5 -0
- package/src/__tests__/mcp-client-auth.test.ts +5 -0
- package/src/__tests__/memory-recall-log-store.test.ts +132 -0
- package/src/__tests__/migration-export-streaming.test.ts +304 -0
- package/src/__tests__/migration-import-commit-http.test.ts +11 -10
- package/src/__tests__/mock-fetch.ts +87 -0
- package/src/__tests__/navigate-settings-tab.test.ts +14 -1
- package/src/__tests__/notification-broadcaster.test.ts +65 -0
- package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
- package/src/__tests__/onboarding-template-contract.test.ts +63 -14
- package/src/__tests__/parser.test.ts +32 -0
- package/src/__tests__/permission-checker-host-gate.test.ts +452 -0
- package/src/__tests__/permission-controls-v2-flag.test.ts +55 -0
- package/src/__tests__/permission-mode-sse.test.ts +418 -0
- package/src/__tests__/permission-mode-store.test.ts +277 -0
- package/src/__tests__/permission-mode.test.ts +101 -0
- package/src/__tests__/pkb-autoinject.test.ts +96 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +359 -0
- package/src/__tests__/profiler-routes.test.ts +502 -0
- package/src/__tests__/profiler-run-store.test.ts +441 -0
- package/src/__tests__/proxy-approval-callback.test.ts +4 -75
- package/src/__tests__/registry.test.ts +1 -1
- package/src/__tests__/require-fresh-approval.test.ts +0 -2
- package/src/__tests__/sandbox-diagnostics.test.ts +1 -32
- package/src/__tests__/sandbox-host-parity.test.ts +5 -4
- package/src/__tests__/scheduler-reuse-conversation.test.ts +368 -0
- package/src/__tests__/scrub-corrupted-image-attachments.test.ts +278 -0
- package/src/__tests__/search-skills-unified.test.ts +4 -3
- package/src/__tests__/send-endpoint-busy.test.ts +42 -3
- package/src/__tests__/set-permission-mode.test.ts +274 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +12 -0
- package/src/__tests__/skill-memory.test.ts +2 -783
- package/src/__tests__/strip-memory-injections.test.ts +187 -0
- package/src/__tests__/subagent-detail.test.ts +84 -0
- package/src/__tests__/subagent-disposal.test.ts +308 -0
- package/src/__tests__/subagent-manager-notify.test.ts +19 -10
- package/src/__tests__/subagent-notify-parent.test.ts +390 -0
- package/src/__tests__/subagent-role-registry.test.ts +108 -0
- package/src/__tests__/subagent-tool-filtering.test.ts +71 -0
- package/src/__tests__/subagent-tools.test.ts +464 -4
- package/src/__tests__/system-prompt-ask-mode.test.ts +139 -0
- package/src/__tests__/task-memory-cleanup.test.ts +12 -12
- package/src/__tests__/terminal-sandbox.test.ts +1 -1
- package/src/__tests__/terminal-tools.test.ts +16 -29
- package/src/__tests__/test-preload.ts +18 -0
- package/src/__tests__/tool-domain-event-publisher.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -8
- package/src/__tests__/tool-executor.test.ts +4 -27
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
- package/src/__tests__/top-level-renderer.test.ts +10 -13
- package/src/__tests__/transport-hints-queue.test.ts +77 -0
- package/src/__tests__/trust-store.test.ts +4 -4
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +116 -2
- package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +387 -0
- package/src/__tests__/workspace-migration-030-seed-pkb-autoinject.test.ts +168 -0
- package/src/__tests__/workspace-policy.test.ts +2 -7
- package/src/agent/loop.ts +6 -29
- package/src/approvals/guardian-request-resolvers.ts +24 -0
- package/src/avatar/traits-png-sync.ts +3 -3
- package/src/channels/types.ts +5 -0
- package/src/cli/__tests__/run-assistant-command.ts +56 -0
- package/src/cli/__tests__/unknown-command.test.ts +33 -0
- package/src/cli/commands/__tests__/email-download.test.ts +245 -0
- package/src/cli/commands/__tests__/email-list.test.ts +192 -0
- package/src/cli/commands/__tests__/email-register.test.ts +186 -0
- package/src/cli/commands/__tests__/email-send.test.ts +291 -0
- package/src/cli/commands/__tests__/email-status.test.ts +181 -0
- package/src/cli/commands/__tests__/email-unregister.test.ts +139 -0
- package/src/cli/commands/__tests__/routes.test.ts +562 -0
- package/src/cli/commands/conversations.ts +1 -8
- package/src/cli/commands/default-action.ts +68 -1
- package/src/cli/commands/email.ts +584 -835
- package/src/cli/commands/memory.ts +1 -34
- package/src/cli/commands/notifications.ts +7 -2
- package/src/cli/commands/oauth/__tests__/connect.test.ts +27 -0
- package/src/cli/commands/oauth/connect.ts +25 -5
- package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +1 -1
- package/src/cli/commands/routes.ts +396 -0
- package/src/cli/commands/skills.ts +130 -20
- package/src/cli/program.ts +11 -2
- package/src/cli.ts +1 -120
- package/src/config/assistant-feature-flags.ts +59 -55
- package/src/config/bundled-skills/app-builder/SKILL.md +91 -5
- package/src/config/bundled-skills/gmail/SKILL.md +13 -8
- package/src/config/bundled-skills/gmail/TOOLS.json +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -1
- package/src/config/bundled-skills/messaging/SKILL.md +7 -0
- package/src/config/bundled-skills/schedule/SKILL.md +22 -2
- package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
- package/src/config/bundled-skills/settings/TOOLS.json +1 -1
- package/src/config/bundled-skills/settings/tools/avatar-get.ts +3 -13
- package/src/config/bundled-skills/settings/tools/avatar-remove.ts +2 -4
- package/src/config/bundled-skills/settings/tools/avatar-update.ts +5 -2
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +8 -3
- package/src/config/bundled-skills/slack/SKILL.md +2 -0
- package/src/config/bundled-skills/subagent/SKILL.md +43 -3
- package/src/config/bundled-skills/subagent/TOOLS.json +29 -4
- package/src/config/env-registry.ts +63 -0
- package/src/config/feature-flag-registry.json +17 -1
- package/src/config/schema.ts +8 -0
- package/src/config/schemas/filing.ts +51 -0
- package/src/config/schemas/heartbeat.ts +15 -12
- package/src/config/schemas/memory-lifecycle.ts +12 -0
- package/src/config/schemas/security.ts +14 -0
- package/src/config/schemas/services.ts +8 -0
- package/src/credential-execution/approval-bridge.ts +0 -1
- package/src/credential-execution/managed-catalog.ts +3 -7
- package/src/daemon/app-source-watcher.ts +93 -0
- package/src/daemon/config-watcher.ts +85 -3
- package/src/daemon/context-overflow-approval.ts +0 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +20 -0
- package/src/daemon/conversation-agent-loop.ts +179 -65
- package/src/daemon/conversation-attachments.ts +0 -1
- package/src/daemon/conversation-history.ts +4 -19
- package/src/daemon/conversation-lifecycle.ts +8 -14
- package/src/daemon/conversation-messaging.ts +3 -0
- package/src/daemon/conversation-process.ts +30 -8
- package/src/daemon/conversation-queue-manager.ts +8 -0
- package/src/daemon/conversation-runtime-assembly.ts +359 -308
- package/src/daemon/conversation-surfaces.ts +65 -0
- package/src/daemon/conversation-tool-setup.ts +44 -17
- package/src/daemon/conversation-workspace.ts +1 -2
- package/src/daemon/conversation.ts +19 -3
- package/src/daemon/date-context.ts +26 -53
- package/src/daemon/first-greeting.ts +1 -1
- package/src/daemon/handlers/conversations.ts +5 -7
- package/src/daemon/handlers/shared.test.ts +143 -0
- package/src/daemon/handlers/shared.ts +70 -5
- package/src/daemon/handlers/skills.ts +11 -18
- package/src/daemon/lifecycle.ts +220 -158
- package/src/daemon/message-types/conversations.ts +29 -6
- package/src/daemon/message-types/messages.ts +9 -2
- package/src/daemon/message-types/notifications.ts +12 -0
- package/src/daemon/message-types/schedules.ts +1 -0
- package/src/daemon/message-types/settings.ts +18 -0
- package/src/daemon/profiler-run-store.ts +557 -0
- package/src/daemon/server.ts +87 -10
- package/src/daemon/shutdown-handlers.ts +5 -0
- package/src/daemon/tool-side-effects.ts +23 -3
- package/src/daemon/transport-hints.ts +33 -0
- package/src/export/transcript-formatter.ts +148 -0
- package/src/filing/filing-service.ts +228 -0
- package/src/heartbeat/heartbeat-service.ts +96 -7
- package/src/index.ts +1 -1
- package/src/mcp/client.ts +6 -0
- package/src/mcp/mcp-oauth-provider.ts +149 -27
- package/src/memory/admin.ts +33 -32
- package/src/memory/app-store.ts +69 -0
- package/src/memory/conversation-bootstrap.ts +1 -1
- package/src/memory/conversation-crud.ts +151 -117
- package/src/memory/conversation-directories.ts +39 -0
- package/src/memory/conversation-group-migration.ts +66 -6
- package/src/memory/conversation-queries.ts +58 -12
- package/src/memory/conversation-title-service.ts +1 -0
- package/src/memory/db-init.ts +182 -376
- package/src/memory/embedding-local.ts +1 -1
- package/src/memory/graph/bootstrap.ts +75 -66
- package/src/memory/graph/capability-seed.ts +167 -17
- package/src/memory/graph/consolidation.ts +38 -4
- package/src/memory/graph/conversation-graph-memory.ts +133 -104
- package/src/memory/graph/extraction-job.ts +9 -4
- package/src/memory/graph/extraction.ts +66 -23
- package/src/memory/graph/graph-memory-state-store.ts +37 -0
- package/src/memory/graph/graph-search.ts +29 -15
- package/src/memory/graph/injection.ts +38 -8
- package/src/memory/graph/inspect.ts +12 -3
- package/src/memory/graph/retriever.ts +365 -262
- package/src/memory/graph/store.test.ts +48 -0
- package/src/memory/graph/store.ts +150 -11
- package/src/memory/graph/tool-handlers.ts +84 -209
- package/src/memory/graph/tools.ts +8 -52
- package/src/memory/graph/types.ts +24 -0
- package/src/memory/group-crud.ts +25 -9
- package/src/memory/job-handlers/cleanup.ts +44 -1
- package/src/memory/jobs-store.ts +70 -60
- package/src/memory/jobs-worker.ts +44 -28
- package/src/memory/llm-request-log-store.ts +96 -12
- package/src/memory/memory-recall-log-store.ts +49 -5
- package/src/memory/migrations/203-drop-memory-items-tables.ts +33 -1
- package/src/memory/migrations/206-memory-graph-node-edits.ts +19 -0
- package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +131 -0
- package/src/memory/migrations/207-conversation-graph-memory-state.ts +20 -0
- package/src/memory/migrations/208-conversations-last-message-at.ts +35 -0
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +85 -0
- package/src/memory/migrations/210-schedule-reuse-conversation.ts +13 -0
- package/src/memory/migrations/211-memory-recall-logs-query-context.ts +21 -0
- package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +19 -0
- package/src/memory/migrations/index.ts +8 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/conversations.ts +14 -0
- package/src/memory/schema/infrastructure.ts +8 -1
- package/src/memory/schema/memory-core.ts +0 -51
- package/src/memory/schema/memory-graph.ts +15 -0
- package/src/memory/task-memory-cleanup.ts +30 -11
- package/src/messaging/provider.ts +1 -1
- package/src/notifications/broadcaster.ts +6 -0
- package/src/notifications/conversation-pairing.ts +12 -4
- package/src/notifications/copy-composer.ts +86 -0
- package/src/notifications/decision-engine.ts +35 -0
- package/src/notifications/emit-signal.ts +14 -0
- package/src/notifications/signal.ts +11 -0
- package/src/oauth/platform-connection.test.ts +2 -2
- package/src/oauth/seed-providers.ts +1 -0
- package/src/permissions/checker.ts +15 -4
- package/src/permissions/defaults.ts +7 -8
- package/src/permissions/permission-mode-store.ts +180 -0
- package/src/permissions/permission-mode.ts +31 -0
- package/src/permissions/prompter.ts +0 -2
- package/src/permissions/workspace-policy.ts +9 -0
- package/src/platform/client.ts +1 -1
- package/src/prompts/system-prompt.ts +59 -7
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
- package/src/prompts/templates/BOOTSTRAP.md +76 -162
- package/src/prompts/templates/HEARTBEAT.md +3 -1
- package/src/prompts/templates/SOUL.md +30 -9
- package/src/prompts/templates/UPDATES.md +8 -0
- package/src/providers/anthropic/client.ts +107 -219
- package/src/runtime/assistant-event-hub.ts +22 -0
- package/src/runtime/auth/route-policy.ts +23 -0
- package/src/runtime/auth/token-service.ts +8 -0
- package/src/runtime/http-server.ts +32 -2
- package/src/runtime/http-types.ts +12 -1
- package/src/runtime/migrations/vbundle-builder.ts +389 -3
- package/src/runtime/migrations/vbundle-importer.ts +8 -6
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +378 -0
- package/src/runtime/routes/app-management-routes.ts +1 -11
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +26 -0
- package/src/runtime/routes/archive-utils.ts +29 -0
- package/src/runtime/routes/avatar-routes.ts +2 -9
- package/src/runtime/routes/btw-routes.ts +14 -1
- package/src/runtime/routes/conversation-analysis-routes.ts +185 -0
- package/src/runtime/routes/conversation-management-routes.ts +1 -14
- package/src/runtime/routes/conversation-query-routes.ts +49 -3
- package/src/runtime/routes/conversation-routes.ts +270 -44
- package/src/runtime/routes/group-routes.ts +22 -8
- package/src/runtime/routes/heartbeat-routes.ts +4 -10
- package/src/runtime/routes/identity-routes.ts +53 -18
- package/src/runtime/routes/llm-context-normalization.ts +14 -10
- package/src/runtime/routes/log-export/AGENTS.md +104 -0
- package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +103 -0
- package/src/runtime/routes/log-export/__tests__/workspace-allowlist.test.ts +716 -0
- package/src/runtime/routes/log-export/workspace-allowlist.ts +458 -0
- package/src/runtime/routes/log-export-routes.ts +41 -278
- package/src/runtime/routes/memory-item-routes.test.ts +168 -233
- package/src/runtime/routes/migration-routes.ts +18 -7
- package/src/runtime/routes/profiler-routes.ts +350 -0
- package/src/runtime/routes/schedule-routes.ts +27 -12
- package/src/runtime/routes/settings-routes.ts +95 -8
- package/src/runtime/routes/subagents-routes.ts +28 -7
- package/src/runtime/routes/user-route-dispatcher.ts +223 -0
- package/src/runtime/routes/user-routes.ts +41 -0
- package/src/runtime/routes/workspace-routes.ts +0 -1
- package/src/schedule/schedule-store.ts +30 -0
- package/src/schedule/scheduler.ts +45 -18
- package/src/skills/catalog-install.ts +10 -2
- package/src/skills/inline-command-runner.ts +12 -14
- package/src/skills/managed-store.ts +2 -2
- package/src/skills/skill-memory.ts +1 -293
- package/src/subagent/index.ts +13 -3
- package/src/subagent/manager.ts +308 -29
- package/src/subagent/types.ts +68 -0
- package/src/tasks/task-runner.ts +4 -4
- package/src/tools/apps/executors.ts +29 -4
- package/src/tools/filesystem/list.ts +93 -0
- package/src/tools/permission-checker.ts +78 -18
- package/src/tools/registry.ts +4 -0
- package/src/tools/schedule/create.ts +3 -0
- package/src/tools/schedule/list.ts +1 -0
- package/src/tools/schedule/update.ts +6 -0
- package/src/tools/secret-detection-handler.ts +0 -1
- package/src/tools/shared/filesystem/errors.ts +5 -0
- package/src/tools/shared/filesystem/file-ops-service.ts +90 -2
- package/src/tools/shared/filesystem/types.ts +17 -0
- package/src/tools/shared/shell-output.ts +31 -2
- package/src/tools/skills/sandbox-runner.ts +3 -6
- package/src/tools/subagent/abort.ts +12 -2
- package/src/tools/subagent/message.ts +9 -2
- package/src/tools/subagent/notify-parent.ts +79 -0
- package/src/tools/subagent/read.ts +29 -8
- package/src/tools/subagent/resolve.ts +21 -0
- package/src/tools/subagent/spawn.ts +2 -0
- package/src/tools/subagent/status.ts +11 -1
- package/src/tools/system/avatar-generator.ts +3 -3
- package/src/tools/system/register.ts +23 -0
- package/src/tools/system/set-permission-mode.ts +103 -0
- package/src/tools/terminal/parser.ts +30 -5
- package/src/tools/terminal/safe-env.ts +16 -1
- package/src/tools/terminal/sandbox-diagnostics.ts +4 -4
- package/src/tools/terminal/sandbox.ts +4 -1
- package/src/tools/terminal/shell.ts +3 -5
- package/src/tools/tool-manifest.ts +6 -0
- package/src/tools/types.ts +2 -3
- package/src/util/logger.ts +1 -1
- package/src/util/platform.ts +50 -17
- package/src/watcher/provider-types.ts +1 -1
- package/src/workspace/migrations/023-move-config-files-to-workspace.ts +2 -2
- package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +2 -2
- package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +270 -0
- package/src/workspace/migrations/029-seed-pkb.ts +85 -0
- package/src/workspace/migrations/030-seed-pkb-autoinject.ts +73 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/workspace/top-level-renderer.ts +5 -9
- package/src/__tests__/cli-memory.test.ts +0 -377
- package/src/__tests__/clipboard.test.ts +0 -88
- package/src/cli/cli-memory.ts +0 -179
- package/src/util/clipboard.ts +0 -34
|
@@ -44,10 +44,7 @@ import {
|
|
|
44
44
|
updateConversationTitle,
|
|
45
45
|
updateMessageMetadata,
|
|
46
46
|
} from "../memory/conversation-crud.js";
|
|
47
|
-
import {
|
|
48
|
-
rebuildConversationDiskViewFromDbState,
|
|
49
|
-
syncMessageToDisk,
|
|
50
|
-
} from "../memory/conversation-disk-view.js";
|
|
47
|
+
import { syncMessageToDisk } from "../memory/conversation-disk-view.js";
|
|
51
48
|
import {
|
|
52
49
|
isReplaceableTitle,
|
|
53
50
|
queueGenerateConversationTitle,
|
|
@@ -91,30 +88,32 @@ import {
|
|
|
91
88
|
classifyConversationError,
|
|
92
89
|
isUserCancellation,
|
|
93
90
|
} from "./conversation-error.js";
|
|
94
|
-
import { consolidateAssistantMessages } from "./conversation-history.js";
|
|
95
91
|
import { raceWithTimeout } from "./conversation-media-retry.js";
|
|
96
92
|
import type { MessageQueue } from "./conversation-queue-manager.js";
|
|
97
93
|
import type { QueueDrainReason } from "./conversation-queue-manager.js";
|
|
98
94
|
import type {
|
|
99
95
|
ActiveSurfaceContext,
|
|
100
96
|
ChannelCapabilities,
|
|
101
|
-
ChannelTurnContextParams,
|
|
102
97
|
InboundActorContext,
|
|
103
98
|
InjectionMode,
|
|
104
|
-
InterfaceTurnContextParams,
|
|
105
99
|
TrustContext,
|
|
106
100
|
} from "./conversation-runtime-assembly.js";
|
|
107
101
|
import {
|
|
108
102
|
applyRuntimeInjections,
|
|
103
|
+
buildUnifiedTurnContextBlock,
|
|
104
|
+
findLastInjectedNowContent,
|
|
105
|
+
findLastInjectedPkbContent,
|
|
109
106
|
inboundActorContextFromTrust,
|
|
110
107
|
inboundActorContextFromTrustContext,
|
|
111
108
|
readNowScratchpad,
|
|
112
|
-
|
|
109
|
+
readPkbContext,
|
|
110
|
+
stripInjectionsForCompaction,
|
|
113
111
|
} from "./conversation-runtime-assembly.js";
|
|
114
112
|
import type { SkillProjectionCache } from "./conversation-skill-tools.js";
|
|
113
|
+
import { markSurfaceCompleted } from "./conversation-surfaces.js";
|
|
115
114
|
import { resolveTrustClass } from "./conversation-tool-setup.js";
|
|
116
115
|
import { recordUsage } from "./conversation-usage.js";
|
|
117
|
-
import {
|
|
116
|
+
import { formatTurnTimestamp } from "./date-context.js";
|
|
118
117
|
import { deepRepairHistory, repairHistory } from "./history-repair.js";
|
|
119
118
|
import type {
|
|
120
119
|
DynamicPageSurfaceData,
|
|
@@ -123,6 +122,7 @@ import type {
|
|
|
123
122
|
SurfaceType,
|
|
124
123
|
UsageStats,
|
|
125
124
|
} from "./message-protocol.js";
|
|
125
|
+
import type { MemoryRecalled } from "./message-types/memory.js";
|
|
126
126
|
import type { TraceEmitter } from "./trace-emitter.js";
|
|
127
127
|
|
|
128
128
|
const log = getLogger("conversation-agent-loop");
|
|
@@ -440,6 +440,7 @@ export async function runAgentLoopImpl(
|
|
|
440
440
|
surfaceId,
|
|
441
441
|
summary: "Dismissed",
|
|
442
442
|
});
|
|
443
|
+
markSurfaceCompleted(ctx, surfaceId, "Dismissed");
|
|
443
444
|
ctx.pendingSurfaceActions.delete(surfaceId);
|
|
444
445
|
}
|
|
445
446
|
}
|
|
@@ -502,6 +503,7 @@ export async function runAgentLoopImpl(
|
|
|
502
503
|
}
|
|
503
504
|
|
|
504
505
|
const isFirstMessage = ctx.messages.length === 1;
|
|
506
|
+
let shouldInjectWorkspace = isFirstMessage;
|
|
505
507
|
|
|
506
508
|
const compactCheck = ctx.contextWindowManager.shouldCompact(ctx.messages);
|
|
507
509
|
if (compactCheck.needed) {
|
|
@@ -556,6 +558,7 @@ export async function runAgentLoopImpl(
|
|
|
556
558
|
compacted.summaryCacheReadInputTokens ?? 0,
|
|
557
559
|
collapseRawResponses(compacted.summaryRawResponses),
|
|
558
560
|
);
|
|
561
|
+
shouldInjectWorkspace = true;
|
|
559
562
|
}
|
|
560
563
|
|
|
561
564
|
const state = createEventHandlerState();
|
|
@@ -599,8 +602,8 @@ export async function runAgentLoopImpl(
|
|
|
599
602
|
|
|
600
603
|
let runMessages = ctx.messages;
|
|
601
604
|
|
|
602
|
-
// Memory graph retrieval — dispatches to context-load / per-turn
|
|
603
|
-
//
|
|
605
|
+
// Memory graph retrieval — dispatches to context-load / per-turn based on
|
|
606
|
+
// conversation state.
|
|
604
607
|
const isTrustedActor = resolveTrustClass(ctx.trustContext) === "guardian";
|
|
605
608
|
if (isTrustedActor) {
|
|
606
609
|
const graphResult = await ctx.graphMemory.prepareMemory(
|
|
@@ -627,26 +630,65 @@ export async function runAgentLoopImpl(
|
|
|
627
630
|
}
|
|
628
631
|
}
|
|
629
632
|
|
|
633
|
+
const m = graphResult.metrics;
|
|
634
|
+
|
|
630
635
|
try {
|
|
631
636
|
recordMemoryRecallLog({
|
|
632
637
|
conversationId: ctx.conversationId,
|
|
633
638
|
enabled: true,
|
|
634
639
|
degraded: false,
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
640
|
+
provider: m?.embeddingProvider ?? undefined,
|
|
641
|
+
model: m?.embeddingModel ?? undefined,
|
|
642
|
+
semanticHits: m?.semanticHits ?? 0,
|
|
643
|
+
mergedCount: m?.mergedCount ?? 0,
|
|
644
|
+
selectedCount: m?.selectedCount ?? 0,
|
|
645
|
+
tier1Count: m?.tier1Count ?? 0,
|
|
646
|
+
tier2Count: m?.tier2Count ?? 0,
|
|
647
|
+
hybridSearchLatencyMs: m?.hybridSearchLatencyMs ?? 0,
|
|
648
|
+
sparseVectorUsed: m?.sparseVectorUsed ?? false,
|
|
642
649
|
injectedTokens: graphResult.injectedTokens,
|
|
643
650
|
latencyMs: graphResult.latencyMs,
|
|
644
|
-
topCandidatesJson: []
|
|
651
|
+
topCandidatesJson: (m?.topCandidates ?? []).map((c) => ({
|
|
652
|
+
key: c.nodeId,
|
|
653
|
+
type: c.type,
|
|
654
|
+
kind: "graph",
|
|
655
|
+
finalScore: c.score,
|
|
656
|
+
semantic: c.semanticSimilarity,
|
|
657
|
+
recency: c.recencyBoost,
|
|
658
|
+
})),
|
|
659
|
+
injectedText: graphResult.injectedBlockText ?? undefined,
|
|
645
660
|
reason: `graph:${graphResult.mode}`,
|
|
661
|
+
queryContext: m?.queryContext ?? undefined,
|
|
646
662
|
});
|
|
647
663
|
} catch (err) {
|
|
648
664
|
log.warn({ err }, "Failed to persist memory recall log (non-fatal)");
|
|
649
665
|
}
|
|
666
|
+
|
|
667
|
+
if (m) {
|
|
668
|
+
const memoryRecalledEvent: MemoryRecalled = {
|
|
669
|
+
type: "memory_recalled",
|
|
670
|
+
provider: m.embeddingProvider ?? "unknown",
|
|
671
|
+
model: m.embeddingModel ?? "unknown",
|
|
672
|
+
semanticHits: m.semanticHits,
|
|
673
|
+
mergedCount: m.mergedCount,
|
|
674
|
+
selectedCount: m.selectedCount,
|
|
675
|
+
tier1Count: m.tier1Count,
|
|
676
|
+
tier2Count: m.tier2Count,
|
|
677
|
+
hybridSearchLatencyMs: m.hybridSearchLatencyMs,
|
|
678
|
+
sparseVectorUsed: m.sparseVectorUsed,
|
|
679
|
+
injectedTokens: graphResult.injectedTokens,
|
|
680
|
+
latencyMs: graphResult.latencyMs,
|
|
681
|
+
topCandidates: m.topCandidates.map((c) => ({
|
|
682
|
+
key: c.nodeId,
|
|
683
|
+
type: c.type,
|
|
684
|
+
kind: "graph",
|
|
685
|
+
finalScore: c.score,
|
|
686
|
+
semantic: c.semanticSimilarity,
|
|
687
|
+
recency: c.recencyBoost,
|
|
688
|
+
})),
|
|
689
|
+
};
|
|
690
|
+
onEvent(memoryRecalledEvent);
|
|
691
|
+
}
|
|
650
692
|
}
|
|
651
693
|
|
|
652
694
|
// Build active surface context
|
|
@@ -678,37 +720,20 @@ export async function runAgentLoopImpl(
|
|
|
678
720
|
|
|
679
721
|
ctx.refreshWorkspaceTopLevelContextIfNeeded();
|
|
680
722
|
|
|
681
|
-
// Compute fresh
|
|
723
|
+
// Compute fresh turn timestamp for date grounding.
|
|
682
724
|
// Absolute "now" is always anchored to assistant host clock, while local
|
|
683
725
|
// date semantics prefer configured user timezone, then recalled memory.
|
|
684
726
|
const hostTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
685
727
|
const configuredUserTimeZone = getConfig().ui.userTimezone ?? null;
|
|
686
728
|
const recalledUserTimeZone = null;
|
|
687
|
-
const
|
|
729
|
+
const timestamp = formatTurnTimestamp({
|
|
688
730
|
hostTimeZone,
|
|
689
731
|
configuredUserTimeZone,
|
|
690
732
|
userTimeZone: recalledUserTimeZone,
|
|
691
733
|
});
|
|
692
734
|
|
|
693
|
-
//
|
|
694
|
-
//
|
|
695
|
-
// message, even if a newer message from a different channel arrived since.
|
|
696
|
-
const channelTurnContext: ChannelTurnContextParams = {
|
|
697
|
-
turnContext: capturedTurnChannelContext,
|
|
698
|
-
conversationOriginChannel: getConversationOriginChannel(
|
|
699
|
-
ctx.conversationId,
|
|
700
|
-
),
|
|
701
|
-
};
|
|
702
|
-
|
|
703
|
-
const interfaceTurnContext: InterfaceTurnContextParams = {
|
|
704
|
-
turnContext: capturedTurnInterfaceContext,
|
|
705
|
-
conversationOriginInterface: getConversationOriginInterface(
|
|
706
|
-
ctx.conversationId,
|
|
707
|
-
),
|
|
708
|
-
};
|
|
709
|
-
|
|
710
|
-
// Resolve the inbound actor context for the model's <inbound_actor_context>
|
|
711
|
-
// block. When the conversation carries enough identity info, use the unified
|
|
735
|
+
// Resolve the inbound actor context for the unified <turn_context> block.
|
|
736
|
+
// When the conversation carries enough identity info, use the unified
|
|
712
737
|
// actor trust resolver so member status/policy and guardian binding details
|
|
713
738
|
// are fresh for this turn. The conversation runtime context remains the source
|
|
714
739
|
// for policy gating; this block is model-facing grounding metadata.
|
|
@@ -729,22 +754,61 @@ export async function runAgentLoopImpl(
|
|
|
729
754
|
}
|
|
730
755
|
}
|
|
731
756
|
|
|
757
|
+
// Build unified turn context block that replaces the separate temporal,
|
|
758
|
+
// channel, interface, and actor context blocks.
|
|
759
|
+
const interfaceName =
|
|
760
|
+
capturedTurnInterfaceContext.userMessageInterface ?? undefined;
|
|
761
|
+
const channelName =
|
|
762
|
+
capturedTurnChannelContext?.userMessageChannel ?? undefined;
|
|
763
|
+
const isGuardian =
|
|
764
|
+
resolvedInboundActorContext?.trustClass === "guardian" ||
|
|
765
|
+
!resolvedInboundActorContext;
|
|
766
|
+
const unifiedTurnContextStr = buildUnifiedTurnContextBlock(
|
|
767
|
+
isGuardian
|
|
768
|
+
? { timestamp, interfaceName, channelName }
|
|
769
|
+
: {
|
|
770
|
+
timestamp,
|
|
771
|
+
interfaceName,
|
|
772
|
+
channelName,
|
|
773
|
+
actorContext: resolvedInboundActorContext,
|
|
774
|
+
},
|
|
775
|
+
);
|
|
776
|
+
|
|
732
777
|
// The `remember` tool handles scratchpad-style memory writes directly to the graph.
|
|
733
778
|
|
|
734
779
|
const isInteractiveResolved =
|
|
735
780
|
options?.isInteractive ?? (!ctx.hasNoClient && !ctx.headlessLock);
|
|
736
781
|
|
|
782
|
+
// Only inject NOW.md if it changed since the last injection in the
|
|
783
|
+
// conversation. Keeping the previous injection in place avoids mutating
|
|
784
|
+
// historical user messages and preserves the cached prefix.
|
|
785
|
+
const currentNowContent = readNowScratchpad();
|
|
786
|
+
const lastInjectedNow = findLastInjectedNowContent(ctx.messages);
|
|
787
|
+
const nowScratchpad =
|
|
788
|
+
currentNowContent !== lastInjectedNow ? currentNowContent : null;
|
|
789
|
+
|
|
790
|
+
// Only inject PKB if it changed since the last injection in the
|
|
791
|
+
// conversation. Keeping the previous injection in place avoids mutating
|
|
792
|
+
// historical user messages and preserves the cached prefix.
|
|
793
|
+
// Note: injectPkbContext escapes </pkb> sequences before writing to history,
|
|
794
|
+
// so we must apply the same escaping before comparing to avoid false mismatches.
|
|
795
|
+
const currentPkbContent = readPkbContext();
|
|
796
|
+
const lastInjectedPkb = findLastInjectedPkbContent(ctx.messages);
|
|
797
|
+
const escapedCurrentPkb = currentPkbContent?.replace(/<\/pkb\s*>/gi, "</pkb>") ?? null;
|
|
798
|
+
const pkbContext =
|
|
799
|
+
escapedCurrentPkb !== lastInjectedPkb ? currentPkbContent : null;
|
|
800
|
+
|
|
737
801
|
// Shared injection options — reused whenever we need to re-inject after reduction.
|
|
738
802
|
const injectionOpts = {
|
|
739
803
|
activeSurface,
|
|
740
|
-
workspaceTopLevelContext:
|
|
804
|
+
workspaceTopLevelContext: shouldInjectWorkspace
|
|
805
|
+
? ctx.workspaceTopLevelContext
|
|
806
|
+
: null,
|
|
741
807
|
channelCapabilities: ctx.channelCapabilities ?? null,
|
|
742
808
|
channelCommandContext: ctx.commandIntent ?? null,
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
temporalContext,
|
|
747
|
-
nowScratchpad: readNowScratchpad(),
|
|
809
|
+
unifiedTurnContext: unifiedTurnContextStr,
|
|
810
|
+
pkbContext,
|
|
811
|
+
nowScratchpad,
|
|
748
812
|
voiceCallControlPrompt: ctx.voiceCallControlPrompt ?? null,
|
|
749
813
|
transportHints: ctx.transportHints ?? null,
|
|
750
814
|
isNonInteractive: !isInteractiveResolved,
|
|
@@ -860,11 +924,20 @@ export async function runAgentLoopImpl(
|
|
|
860
924
|
ctx.graphMemory.onCompacted(
|
|
861
925
|
step.compactionResult.compactedPersistedMessages,
|
|
862
926
|
);
|
|
927
|
+
shouldInjectWorkspace = true;
|
|
863
928
|
}
|
|
864
929
|
|
|
865
|
-
// Re-inject with potentially downgraded injection mode
|
|
930
|
+
// Re-inject with potentially downgraded injection mode.
|
|
931
|
+
// When compaction ran it strips existing NOW.md / PKB blocks, so we
|
|
932
|
+
// must re-inject the current content. Otherwise rely on the deduplicated
|
|
933
|
+
// value from injectionOpts to avoid duplicate injection.
|
|
866
934
|
runMessages = applyRuntimeInjections(ctx.messages, {
|
|
867
935
|
...injectionOpts,
|
|
936
|
+
...(step.compactionResult?.compacted && { pkbContext: currentPkbContent }),
|
|
937
|
+
...(step.compactionResult?.compacted && { nowScratchpad: currentNowContent }),
|
|
938
|
+
workspaceTopLevelContext: shouldInjectWorkspace
|
|
939
|
+
? ctx.workspaceTopLevelContext
|
|
940
|
+
: null,
|
|
868
941
|
mode: currentInjectionMode,
|
|
869
942
|
});
|
|
870
943
|
if (isTrustedActor && currentInjectionMode !== "minimal") {
|
|
@@ -994,7 +1067,7 @@ export async function runAgentLoopImpl(
|
|
|
994
1067
|
|
|
995
1068
|
// Strip injected context from updated history before compacting,
|
|
996
1069
|
// so we compact the "raw" persistent messages.
|
|
997
|
-
const rawHistory =
|
|
1070
|
+
const rawHistory = stripInjectionsForCompaction(updatedHistory);
|
|
998
1071
|
ctx.messages = rawHistory;
|
|
999
1072
|
|
|
1000
1073
|
ctx.emitActivityState(
|
|
@@ -1048,14 +1121,21 @@ export async function runAgentLoopImpl(
|
|
|
1048
1121
|
midLoopCompact.summaryCacheReadInputTokens ?? 0,
|
|
1049
1122
|
collapseRawResponses(midLoopCompact.summaryRawResponses),
|
|
1050
1123
|
);
|
|
1051
|
-
ctx.graphMemory.onCompacted(
|
|
1052
|
-
|
|
1053
|
-
);
|
|
1124
|
+
ctx.graphMemory.onCompacted(midLoopCompact.compactedPersistedMessages);
|
|
1125
|
+
shouldInjectWorkspace = true;
|
|
1054
1126
|
}
|
|
1055
1127
|
|
|
1056
|
-
// Re-inject runtime context and re-enter the agent loop
|
|
1128
|
+
// Re-inject runtime context and re-enter the agent loop.
|
|
1129
|
+
// stripInjectionsForCompaction() unconditionally removed the existing
|
|
1130
|
+
// NOW.md block from ctx.messages above, so we must always re-inject
|
|
1131
|
+
// the current content regardless of whether compaction actually ran.
|
|
1057
1132
|
runMessages = applyRuntimeInjections(ctx.messages, {
|
|
1058
1133
|
...injectionOpts,
|
|
1134
|
+
pkbContext: currentPkbContent,
|
|
1135
|
+
nowScratchpad: currentNowContent,
|
|
1136
|
+
workspaceTopLevelContext: shouldInjectWorkspace
|
|
1137
|
+
? ctx.workspaceTopLevelContext
|
|
1138
|
+
: null,
|
|
1059
1139
|
mode: currentInjectionMode,
|
|
1060
1140
|
});
|
|
1061
1141
|
if (isTrustedActor && currentInjectionMode !== "minimal") {
|
|
@@ -1133,8 +1213,16 @@ export async function runAgentLoopImpl(
|
|
|
1133
1213
|
// limit), incorporate those new messages into ctx.messages so the
|
|
1134
1214
|
// convergence loop operates on the full (larger) history.
|
|
1135
1215
|
if (state.contextTooLargeDetected) {
|
|
1216
|
+
// Track whether ctx.messages was actually stripped so we know if
|
|
1217
|
+
// NOW.md (and other injections) need to be re-injected. When the
|
|
1218
|
+
// provider rejects before adding any messages, the strip is skipped
|
|
1219
|
+
// and ctx.messages still contains the previous injection — blindly
|
|
1220
|
+
// re-injecting would duplicate the NOW.md block.
|
|
1221
|
+
let convergenceStripped = false;
|
|
1222
|
+
|
|
1136
1223
|
if (updatedHistory.length > preRunHistoryLength) {
|
|
1137
|
-
ctx.messages =
|
|
1224
|
+
ctx.messages = stripInjectionsForCompaction(updatedHistory);
|
|
1225
|
+
convergenceStripped = true;
|
|
1138
1226
|
preRepairMessages = updatedHistory;
|
|
1139
1227
|
preRunHistoryLength = updatedHistory.length;
|
|
1140
1228
|
}
|
|
@@ -1254,10 +1342,19 @@ export async function runAgentLoopImpl(
|
|
|
1254
1342
|
ctx.graphMemory.onCompacted(
|
|
1255
1343
|
step.compactionResult.compactedPersistedMessages,
|
|
1256
1344
|
);
|
|
1345
|
+
shouldInjectWorkspace = true;
|
|
1257
1346
|
}
|
|
1258
1347
|
|
|
1348
|
+
// Only re-inject NOW.md when ctx.messages was actually stripped;
|
|
1349
|
+
// otherwise the existing NOW.md block is still present and
|
|
1350
|
+
// re-injecting would duplicate it.
|
|
1259
1351
|
runMessages = applyRuntimeInjections(ctx.messages, {
|
|
1260
1352
|
...injectionOpts,
|
|
1353
|
+
pkbContext: currentPkbContent,
|
|
1354
|
+
nowScratchpad: convergenceStripped ? currentNowContent : null,
|
|
1355
|
+
workspaceTopLevelContext: shouldInjectWorkspace
|
|
1356
|
+
? ctx.workspaceTopLevelContext
|
|
1357
|
+
: null,
|
|
1261
1358
|
mode: currentInjectionMode,
|
|
1262
1359
|
});
|
|
1263
1360
|
if (isTrustedActor && currentInjectionMode !== "minimal") {
|
|
@@ -1295,7 +1392,8 @@ export async function runAgentLoopImpl(
|
|
|
1295
1392
|
// tier operates on up-to-date history instead of stale
|
|
1296
1393
|
// pre-rerun messages.
|
|
1297
1394
|
if (updatedHistory.length > preRunHistoryLength) {
|
|
1298
|
-
ctx.messages =
|
|
1395
|
+
ctx.messages = stripInjectionsForCompaction(updatedHistory);
|
|
1396
|
+
convergenceStripped = true;
|
|
1299
1397
|
preRepairMessages = updatedHistory;
|
|
1300
1398
|
preRunHistoryLength = updatedHistory.length;
|
|
1301
1399
|
}
|
|
@@ -1368,14 +1466,23 @@ export async function runAgentLoopImpl(
|
|
|
1368
1466
|
ctx.graphMemory.onCompacted(
|
|
1369
1467
|
emergencyCompact.compactedPersistedMessages,
|
|
1370
1468
|
);
|
|
1469
|
+
shouldInjectWorkspace = true;
|
|
1371
1470
|
}
|
|
1372
1471
|
|
|
1472
|
+
// Only re-inject NOW.md when ctx.messages was actually stripped;
|
|
1473
|
+
// otherwise the existing block is still present.
|
|
1373
1474
|
runMessages = applyRuntimeInjections(ctx.messages, {
|
|
1374
1475
|
...injectionOpts,
|
|
1476
|
+
pkbContext: currentPkbContent,
|
|
1477
|
+
nowScratchpad: convergenceStripped ? currentNowContent : null,
|
|
1478
|
+
workspaceTopLevelContext: shouldInjectWorkspace
|
|
1479
|
+
? ctx.workspaceTopLevelContext
|
|
1480
|
+
: null,
|
|
1375
1481
|
mode: currentInjectionMode,
|
|
1376
1482
|
});
|
|
1377
1483
|
if (isTrustedActor && currentInjectionMode !== "minimal") {
|
|
1378
|
-
const memResult =
|
|
1484
|
+
const memResult =
|
|
1485
|
+
ctx.graphMemory.reinjectCachedMemory(runMessages);
|
|
1379
1486
|
runMessages = memResult.runMessages;
|
|
1380
1487
|
}
|
|
1381
1488
|
preRepairMessages = runMessages;
|
|
@@ -1479,10 +1586,18 @@ export async function runAgentLoopImpl(
|
|
|
1479
1586
|
ctx.graphMemory.onCompacted(
|
|
1480
1587
|
emergencyCompact.compactedPersistedMessages,
|
|
1481
1588
|
);
|
|
1589
|
+
shouldInjectWorkspace = true;
|
|
1482
1590
|
}
|
|
1483
1591
|
|
|
1592
|
+
// Only re-inject NOW.md when ctx.messages was actually stripped;
|
|
1593
|
+
// otherwise the existing block is still present.
|
|
1484
1594
|
runMessages = applyRuntimeInjections(ctx.messages, {
|
|
1485
1595
|
...injectionOpts,
|
|
1596
|
+
pkbContext: currentPkbContent,
|
|
1597
|
+
nowScratchpad: convergenceStripped ? currentNowContent : null,
|
|
1598
|
+
workspaceTopLevelContext: shouldInjectWorkspace
|
|
1599
|
+
? ctx.workspaceTopLevelContext
|
|
1600
|
+
: null,
|
|
1486
1601
|
mode: currentInjectionMode,
|
|
1487
1602
|
});
|
|
1488
1603
|
if (isTrustedActor && currentInjectionMode !== "minimal") {
|
|
@@ -1626,7 +1741,11 @@ export async function runAgentLoopImpl(
|
|
|
1626
1741
|
{ providerName: ctx.provider.name, toolTokenBudget },
|
|
1627
1742
|
);
|
|
1628
1743
|
|
|
1629
|
-
|
|
1744
|
+
// Persist injections in history: runtime-injected context stays on
|
|
1745
|
+
// historical user messages so the conversation prefix is stable for
|
|
1746
|
+
// Anthropic's prefix caching. Stripping only happens during
|
|
1747
|
+
// compaction/overflow recovery (where a cache miss is expected).
|
|
1748
|
+
ctx.messages = restoredHistory;
|
|
1630
1749
|
|
|
1631
1750
|
emitUsage(
|
|
1632
1751
|
ctx,
|
|
@@ -1877,15 +1996,10 @@ export async function runAgentLoopImpl(
|
|
|
1877
1996
|
// Clear at turn end so they never leak into subsequent unrelated messages.
|
|
1878
1997
|
ctx.commandIntent = undefined;
|
|
1879
1998
|
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
);
|
|
1885
|
-
if (didMutateHistory) {
|
|
1886
|
-
rebuildConversationDiskViewFromDbState(ctx.conversationId);
|
|
1887
|
-
}
|
|
1888
|
-
}
|
|
1999
|
+
// Consolidation deferred to compaction: keeping assistant + tool_result
|
|
2000
|
+
// messages unconsolidated preserves the exact message structure sent to
|
|
2001
|
+
// the API, enabling stable prefix caching across turns. Compaction
|
|
2002
|
+
// consolidates when it summarizes old messages (cache miss is expected).
|
|
1889
2003
|
|
|
1890
2004
|
ctx.drainQueue(yieldedForHandoff ? "checkpoint_handoff" : "loop_complete");
|
|
1891
2005
|
|
|
@@ -68,14 +68,13 @@ export function findLastUndoableUserMessageIndex(messages: Message[]): number {
|
|
|
68
68
|
// ── Qdrant Vector Cleanup ────────────────────────────────────────────
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
|
-
* Delete Qdrant vector entries for the given segment
|
|
71
|
+
* Delete Qdrant vector entries for the given segment IDs.
|
|
72
72
|
* Individual deletion failures are logged and enqueued as retry jobs
|
|
73
73
|
* to prevent silently orphaned vectors.
|
|
74
74
|
*/
|
|
75
75
|
export async function cleanupQdrantVectors(
|
|
76
76
|
conversationId: string,
|
|
77
77
|
segmentIds: string[],
|
|
78
|
-
orphanedItemIds: string[],
|
|
79
78
|
): Promise<void> {
|
|
80
79
|
let qdrant: ReturnType<typeof getQdrantClient>;
|
|
81
80
|
try {
|
|
@@ -84,15 +83,12 @@ export async function cleanupQdrantVectors(
|
|
|
84
83
|
return; // Qdrant not initialized — nothing to clean up.
|
|
85
84
|
}
|
|
86
85
|
|
|
87
|
-
if (segmentIds.length === 0
|
|
86
|
+
if (segmentIds.length === 0) return;
|
|
88
87
|
|
|
89
88
|
const targets: Array<{ targetType: string; targetId: string }> = [];
|
|
90
89
|
for (const segId of segmentIds) {
|
|
91
90
|
targets.push({ targetType: "segment", targetId: segId });
|
|
92
91
|
}
|
|
93
|
-
for (const itemId of orphanedItemIds) {
|
|
94
|
-
targets.push({ targetType: "item", targetId: itemId });
|
|
95
|
-
}
|
|
96
92
|
|
|
97
93
|
const results = await Promise.allSettled(
|
|
98
94
|
targets.map((t) =>
|
|
@@ -124,7 +120,6 @@ export async function cleanupQdrantVectors(
|
|
|
124
120
|
succeeded,
|
|
125
121
|
failed,
|
|
126
122
|
segments: segmentIds.length,
|
|
127
|
-
items: orphanedItemIds.length,
|
|
128
123
|
},
|
|
129
124
|
"Cleaned up Qdrant vectors after regenerate",
|
|
130
125
|
);
|
|
@@ -187,20 +182,17 @@ export function consolidateAssistantMessages(
|
|
|
187
182
|
// Still delete internal tool_result messages even if only one assistant message,
|
|
188
183
|
// and collect IDs for vector cleanup
|
|
189
184
|
const allSegmentIds: string[] = [];
|
|
190
|
-
const allOrphanedItemIds: string[] = [];
|
|
191
185
|
for (const id of messagesToDelete) {
|
|
192
186
|
const deleted = deleteMessageById(id);
|
|
193
187
|
didMutate = true;
|
|
194
188
|
allSegmentIds.push(...deleted.segmentIds);
|
|
195
|
-
allOrphanedItemIds.push(...deleted.orphanedItemIds);
|
|
196
189
|
}
|
|
197
190
|
|
|
198
191
|
// Clean up Qdrant vectors (fire-and-forget)
|
|
199
|
-
if (allSegmentIds.length > 0
|
|
192
|
+
if (allSegmentIds.length > 0) {
|
|
200
193
|
cleanupQdrantVectors(
|
|
201
194
|
conversationId,
|
|
202
195
|
allSegmentIds,
|
|
203
|
-
allOrphanedItemIds,
|
|
204
196
|
).catch((err) => {
|
|
205
197
|
log.warn(
|
|
206
198
|
{ err, conversationId },
|
|
@@ -322,24 +314,20 @@ export function consolidateAssistantMessages(
|
|
|
322
314
|
// Delete the other assistant messages and internal tool_result messages,
|
|
323
315
|
// and collect IDs for vector cleanup
|
|
324
316
|
const allSegmentIds: string[] = [];
|
|
325
|
-
const allOrphanedItemIds: string[] = [];
|
|
326
317
|
for (let i = 1; i < messagesToConsolidate.length; i++) {
|
|
327
318
|
const deleted = deleteMessageById(messagesToConsolidate[i].id);
|
|
328
319
|
allSegmentIds.push(...deleted.segmentIds);
|
|
329
|
-
allOrphanedItemIds.push(...deleted.orphanedItemIds);
|
|
330
320
|
}
|
|
331
321
|
for (const id of messagesToDelete) {
|
|
332
322
|
const deleted = deleteMessageById(id);
|
|
333
323
|
allSegmentIds.push(...deleted.segmentIds);
|
|
334
|
-
allOrphanedItemIds.push(...deleted.orphanedItemIds);
|
|
335
324
|
}
|
|
336
325
|
|
|
337
326
|
// Clean up Qdrant vectors (fire-and-forget)
|
|
338
|
-
if (allSegmentIds.length > 0
|
|
327
|
+
if (allSegmentIds.length > 0) {
|
|
339
328
|
cleanupQdrantVectors(
|
|
340
329
|
conversationId,
|
|
341
330
|
allSegmentIds,
|
|
342
|
-
allOrphanedItemIds,
|
|
343
331
|
).catch((err) => {
|
|
344
332
|
log.warn(
|
|
345
333
|
{ err, conversationId },
|
|
@@ -539,18 +527,15 @@ export async function regenerate(
|
|
|
539
527
|
|
|
540
528
|
// Delete each message via deleteMessageById and collect IDs for Qdrant cleanup.
|
|
541
529
|
const allSegmentIds: string[] = [];
|
|
542
|
-
const allOrphanedItemIds: string[] = [];
|
|
543
530
|
for (const msg of messagesToDelete) {
|
|
544
531
|
const deleted = deleteMessageById(msg.id);
|
|
545
532
|
allSegmentIds.push(...deleted.segmentIds);
|
|
546
|
-
allOrphanedItemIds.push(...deleted.orphanedItemIds);
|
|
547
533
|
}
|
|
548
534
|
|
|
549
535
|
// Clean up Qdrant vectors (fire-and-forget).
|
|
550
536
|
cleanupQdrantVectors(
|
|
551
537
|
conversation.conversationId,
|
|
552
538
|
allSegmentIds,
|
|
553
|
-
allOrphanedItemIds,
|
|
554
539
|
).catch((err) => {
|
|
555
540
|
log.warn(
|
|
556
541
|
{ err, conversationId: conversation.conversationId },
|
|
@@ -9,7 +9,6 @@ import type { EventBus } from "../events/bus.js";
|
|
|
9
9
|
import type { AssistantDomainEvents } from "../events/domain-events.js";
|
|
10
10
|
import type { ToolProfiler } from "../events/tool-profiling-listener.js";
|
|
11
11
|
import { getHookManager } from "../hooks/manager.js";
|
|
12
|
-
import { getMemoryCheckpoint } from "../memory/checkpoints.js";
|
|
13
12
|
import {
|
|
14
13
|
getConversation,
|
|
15
14
|
getMessages,
|
|
@@ -151,6 +150,10 @@ export interface DisposeContext extends AbortContext {
|
|
|
151
150
|
lastSurfaceAction: Map<string, unknown>;
|
|
152
151
|
workspaceTopLevelContext: string | null;
|
|
153
152
|
trustContext?: { trustClass: TrustClass };
|
|
153
|
+
/** Active memory node IDs snapshotted from the conversation's InContextTracker before disposal. */
|
|
154
|
+
activeContextNodeIds?: string[];
|
|
155
|
+
/** Memory scope for extraction — defaults to "default" if omitted. */
|
|
156
|
+
memoryScopeId?: string;
|
|
154
157
|
abort(): void;
|
|
155
158
|
}
|
|
156
159
|
|
|
@@ -289,19 +292,6 @@ export function disposeConversation(ctx: DisposeContext): void {
|
|
|
289
292
|
conversationId: ctx.conversationId,
|
|
290
293
|
});
|
|
291
294
|
|
|
292
|
-
// Trigger batch extraction for any remaining unextracted messages
|
|
293
|
-
try {
|
|
294
|
-
const pendingKey = `batch_extract:${ctx.conversationId}:pending_count`;
|
|
295
|
-
const pending = getMemoryCheckpoint(pendingKey);
|
|
296
|
-
if (pending && parseInt(pending, 10) > 0) {
|
|
297
|
-
enqueueMemoryJob("batch_extract", {
|
|
298
|
-
conversationId: ctx.conversationId,
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
} catch {
|
|
302
|
-
// Best-effort — don't block conversation disposal
|
|
303
|
-
}
|
|
304
|
-
|
|
305
295
|
// Trigger graph extraction for end-of-conversation sweep.
|
|
306
296
|
// Only extract from guardian conversations to preserve the memory trust
|
|
307
297
|
// boundary — untrusted content must not influence future memory retrieval.
|
|
@@ -309,6 +299,10 @@ export function disposeConversation(ctx: DisposeContext): void {
|
|
|
309
299
|
try {
|
|
310
300
|
enqueueMemoryJob("graph_extract", {
|
|
311
301
|
conversationId: ctx.conversationId,
|
|
302
|
+
scopeId: ctx.memoryScopeId ?? "default",
|
|
303
|
+
...(ctx.activeContextNodeIds?.length
|
|
304
|
+
? { activeContextNodeIds: ctx.activeContextNodeIds }
|
|
305
|
+
: {}),
|
|
312
306
|
});
|
|
313
307
|
} catch {
|
|
314
308
|
// Best-effort — don't block conversation disposal
|
|
@@ -41,6 +41,7 @@ import type {
|
|
|
41
41
|
ServerMessage,
|
|
42
42
|
UserMessageAttachment,
|
|
43
43
|
} from "./message-protocol.js";
|
|
44
|
+
import type { ConversationTransportMetadata } from "./message-types/conversations.js";
|
|
44
45
|
|
|
45
46
|
const log = getLogger("conversation-messaging");
|
|
46
47
|
|
|
@@ -222,6 +223,7 @@ export function enqueueMessage(
|
|
|
222
223
|
metadata?: Record<string, unknown>,
|
|
223
224
|
options?: { isInteractive?: boolean },
|
|
224
225
|
displayContent?: string,
|
|
226
|
+
transport?: ConversationTransportMetadata,
|
|
225
227
|
): { queued: boolean; requestId: string; rejected?: boolean } {
|
|
226
228
|
if (!ctx.processing) {
|
|
227
229
|
return { queued: false, requestId };
|
|
@@ -246,6 +248,7 @@ export function enqueueMessage(
|
|
|
246
248
|
turnChannelContext,
|
|
247
249
|
turnInterfaceContext,
|
|
248
250
|
isInteractive: options?.isInteractive,
|
|
251
|
+
transport,
|
|
249
252
|
displayContent,
|
|
250
253
|
sentAt: Date.now(),
|
|
251
254
|
});
|