@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
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Memory buffer file: the two writers of `memory/buffer.md`
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
//
|
|
5
|
+
// `memory/buffer.md` has exactly two writers, and both live here so the
|
|
6
|
+
// protocol between them has one home. `remember()` and the sweep job APPEND
|
|
7
|
+
// entries through `appendBufferAndArchive`. The consolidation job CONSUMES
|
|
8
|
+
// the entries it handed to a run through `consumeBufferEntries`, removing
|
|
9
|
+
// exactly those entries and nothing else. Everything else in the plugin
|
|
10
|
+
// reads the file. The entry format itself is owned by `buffer-format.ts`.
|
|
11
|
+
//
|
|
12
|
+
// The writers run in different processes: user turns append from the
|
|
13
|
+
// daemon, while consolidation (and its consume) runs in the memory jobs
|
|
14
|
+
// worker, and the sweep appends from the worker too. No in-process lock can
|
|
15
|
+
// order them, and the buffer must never be rewritten from a stale read: an
|
|
16
|
+
// entry appended between a reader's read and its write would be destroyed
|
|
17
|
+
// without ever being filed. The consumer therefore never trusts a read for
|
|
18
|
+
// longer than one synchronous stretch and never lets an append land where
|
|
19
|
+
// it cannot see it:
|
|
20
|
+
//
|
|
21
|
+
// 1. Open the file and keep the descriptor: it pins the inode the path
|
|
22
|
+
// names at that moment (call it O).
|
|
23
|
+
// 2. Read O through that descriptor, remove the consumed entries from what
|
|
24
|
+
// was read, write the remainder to a sibling temp file, and rename it
|
|
25
|
+
// over the path. Steps 1 and 2 run synchronously, with no await, so
|
|
26
|
+
// nothing in this process interleaves. From the rename on, the path
|
|
27
|
+
// names a new inode (N) and every appender that opens the path lands
|
|
28
|
+
// on N, where nothing is ever rewritten.
|
|
29
|
+
// 3. Drain: an appender that opened O before the rename still writes to O,
|
|
30
|
+
// which the held descriptor keeps alive. Compare O's size against the
|
|
31
|
+
// bytes read; copy anything beyond them onto the path (an append to N,
|
|
32
|
+
// the same operation the appenders use). Drain once immediately, then
|
|
33
|
+
// once more after a short grace window, and only then close O.
|
|
34
|
+
//
|
|
35
|
+
// What can still be lost: an appender whose `open` preceded the rename and
|
|
36
|
+
// whose `write` lands after the final drain. Both calls sit inside one
|
|
37
|
+
// synchronous `appendFileSync`, so that needs the appending process to be
|
|
38
|
+
// stopped between two adjacent syscalls for longer than the grace window (a
|
|
39
|
+
// debugger, SIGSTOP, or a multi-second scheduler stall). Machine sleep
|
|
40
|
+
// freezes both processes together and does not count. The other way out is
|
|
41
|
+
// the drain's own copy failing (disk full, an I/O error) on every retry;
|
|
42
|
+
// the descriptor cannot be held forever, so those bytes are then gone from
|
|
43
|
+
// the buffer and reported as unrecovered, and the entries survive only in
|
|
44
|
+
// the daily archive the same append wrote. Nothing after the rename throws:
|
|
45
|
+
// once the rename has committed the pass is consumed, and the result says
|
|
46
|
+
// so. If either residual ever proves reachable, the escalation is a
|
|
47
|
+
// short-held `wx` lock file taken by both writers around their synchronous
|
|
48
|
+
// critical sections (the idiom `substrate/consolidation-lock.ts` uses);
|
|
49
|
+
// the consume shape above does not change, only its critical section gains
|
|
50
|
+
// a guard.
|
|
51
|
+
//
|
|
52
|
+
// Ordering: consumption removes entries and copies the rest verbatim, so
|
|
53
|
+
// the file stays in append order except for a drained late append, which
|
|
54
|
+
// lands after any entry appended to N in the meantime. Readers only rely
|
|
55
|
+
// on the tail being recent, never on strict order, so that is harmless.
|
|
56
|
+
// The same holds if a late append's bytes were split across the two
|
|
57
|
+
// drains (a partial `write()` on a regular file, well below the residual
|
|
58
|
+
// above): the halves land in order on N, possibly with another appender's
|
|
59
|
+
// entry between them, so the entry is split rather than lost, and the
|
|
60
|
+
// next pass's snapshot selection treats an unterminated tail as
|
|
61
|
+
// in-flight.
|
|
62
|
+
|
|
63
|
+
import {
|
|
64
|
+
appendFileSync,
|
|
65
|
+
closeSync,
|
|
66
|
+
existsSync,
|
|
67
|
+
fstatSync,
|
|
68
|
+
mkdirSync,
|
|
69
|
+
openSync,
|
|
70
|
+
readFileSync,
|
|
71
|
+
readSync,
|
|
72
|
+
renameSync,
|
|
73
|
+
unlinkSync,
|
|
74
|
+
writeFileSync,
|
|
75
|
+
writeSync,
|
|
76
|
+
} from "node:fs";
|
|
77
|
+
import { dirname, join } from "node:path";
|
|
78
|
+
|
|
79
|
+
import {
|
|
80
|
+
type BufferEntryLines,
|
|
81
|
+
bufferEntryText,
|
|
82
|
+
joinBufferEntries,
|
|
83
|
+
splitBufferContent,
|
|
84
|
+
} from "./buffer-format.js";
|
|
85
|
+
import { getLogger } from "./logging.js";
|
|
86
|
+
|
|
87
|
+
const log = getLogger("memory-buffer-file");
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* How long the consumer keeps the replaced inode open after the rename
|
|
91
|
+
* before its final drain. Only an appender stalled between its `open` and
|
|
92
|
+
* its `write` for longer than this can lose an entry; a healthy appender
|
|
93
|
+
* completes both within microseconds.
|
|
94
|
+
*/
|
|
95
|
+
const LATE_APPEND_GRACE_MS = 500;
|
|
96
|
+
|
|
97
|
+
/** Attempts to copy late-appended bytes back before giving them up. */
|
|
98
|
+
const LATE_APPEND_COPY_ATTEMPTS = 3;
|
|
99
|
+
/** Wait between those attempts; disk pressure and I/O errors are often brief. */
|
|
100
|
+
const LATE_APPEND_COPY_RETRY_MS = 200;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Append `entry` to `<rootDir>/buffer.md` and `<rootDir>/archive/<today>.md`,
|
|
104
|
+
* creating the archive directory and seeding the archive header if missing.
|
|
105
|
+
*
|
|
106
|
+
* Returns the absolute paths of both files so callers can fan out follow-up
|
|
107
|
+
* work.
|
|
108
|
+
*
|
|
109
|
+
* Shared by `remember()` and the background jobs (`sweep`, future LLM-driven
|
|
110
|
+
* extractors) so every appender writes exactly the same format and
|
|
111
|
+
* downstream consumers (consolidation, search) cannot tell them apart.
|
|
112
|
+
*/
|
|
113
|
+
export function appendBufferAndArchive(args: {
|
|
114
|
+
rootDir: string;
|
|
115
|
+
entry: string;
|
|
116
|
+
now: Date;
|
|
117
|
+
}): { bufferPath: string; archivePath: string } {
|
|
118
|
+
const { rootDir, entry, now } = args;
|
|
119
|
+
const archiveDir = join(rootDir, "archive");
|
|
120
|
+
mkdirSync(archiveDir, { recursive: true });
|
|
121
|
+
|
|
122
|
+
const bufferPath = join(rootDir, "buffer.md");
|
|
123
|
+
appendFileSync(bufferPath, entry, "utf-8");
|
|
124
|
+
|
|
125
|
+
const yyyy = now.getFullYear();
|
|
126
|
+
const mm = String(now.getMonth() + 1).padStart(2, "0");
|
|
127
|
+
const dd = String(now.getDate()).padStart(2, "0");
|
|
128
|
+
const archivePath = join(archiveDir, `${yyyy}-${mm}-${dd}.md`);
|
|
129
|
+
if (!existsSync(archivePath)) {
|
|
130
|
+
const month = now.toLocaleString("en-US", { month: "short" });
|
|
131
|
+
appendFileSync(
|
|
132
|
+
archivePath,
|
|
133
|
+
`# ${month} ${now.getDate()}, ${yyyy}\n\n`,
|
|
134
|
+
"utf-8",
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
appendFileSync(archivePath, entry, "utf-8");
|
|
138
|
+
|
|
139
|
+
return { bufferPath, archivePath };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface ConsumeBufferEntriesResult {
|
|
143
|
+
/** Consumed entries found in the live file and removed. */
|
|
144
|
+
removed: number;
|
|
145
|
+
/**
|
|
146
|
+
* Consumed entries not present in the live file. Something other than an
|
|
147
|
+
* appender rewrote the file during the run; the caller decides whether
|
|
148
|
+
* that is worth a warning.
|
|
149
|
+
*/
|
|
150
|
+
alreadyAbsent: number;
|
|
151
|
+
/** Bytes appended to the replaced inode after the read and copied back. */
|
|
152
|
+
lateAppendBytesRecovered: number;
|
|
153
|
+
/**
|
|
154
|
+
* Bytes appended to the replaced inode that could not be copied back
|
|
155
|
+
* after every retry. The pass is consumed regardless (the rename had
|
|
156
|
+
* committed); these entries remain only in the daily archive.
|
|
157
|
+
*/
|
|
158
|
+
unrecoveredLateAppendBytes: number;
|
|
159
|
+
/**
|
|
160
|
+
* The replaced inode could not be inspected or read after the rename, so
|
|
161
|
+
* any append that landed on it is unaccounted for (not even counted in
|
|
162
|
+
* `unrecoveredLateAppendBytes`). The pass is consumed regardless.
|
|
163
|
+
*/
|
|
164
|
+
lateAppendDrainFailed: boolean;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Remove `consumed` from `bufferPath`, leaving every other entry in place.
|
|
169
|
+
* Never throws once the rename has committed; see the result's
|
|
170
|
+
* `unrecoveredLateAppendBytes`.
|
|
171
|
+
*
|
|
172
|
+
* Each consumed entry is matched by its exact text ({@link bufferEntryText})
|
|
173
|
+
* against the live file, first occurrence, once per consumed entry, so an
|
|
174
|
+
* identical fact appended during the run survives when the snapshot held
|
|
175
|
+
* one copy. An entry that is absent from the live file is counted, never
|
|
176
|
+
* searched for elsewhere. A missing file consumes nothing.
|
|
177
|
+
*
|
|
178
|
+
* The critical section (read through a pinned descriptor, rewrite via temp
|
|
179
|
+
* file + rename) is synchronous; the drain of late appends to the replaced
|
|
180
|
+
* inode follows, with a second drain after `lateAppendGraceMs`. See the
|
|
181
|
+
* module header for why this is lossless and what the residual is.
|
|
182
|
+
*
|
|
183
|
+
* I/O failures other than a missing file throw: the caller (the
|
|
184
|
+
* consolidation job) treats a failed consume as no progress and leaves the
|
|
185
|
+
* buffer for the next pass rather than guessing at its state.
|
|
186
|
+
*/
|
|
187
|
+
export async function consumeBufferEntries(
|
|
188
|
+
bufferPath: string,
|
|
189
|
+
consumed: readonly BufferEntryLines[],
|
|
190
|
+
options: { lateAppendGraceMs?: number } = {},
|
|
191
|
+
): Promise<ConsumeBufferEntriesResult> {
|
|
192
|
+
const graceMs = options.lateAppendGraceMs ?? LATE_APPEND_GRACE_MS;
|
|
193
|
+
const pending = consumed.map(bufferEntryText);
|
|
194
|
+
|
|
195
|
+
let fd: number;
|
|
196
|
+
try {
|
|
197
|
+
fd = openSync(bufferPath, "r");
|
|
198
|
+
} catch (err) {
|
|
199
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
200
|
+
return {
|
|
201
|
+
removed: 0,
|
|
202
|
+
alreadyAbsent: pending.length,
|
|
203
|
+
lateAppendBytesRecovered: 0,
|
|
204
|
+
unrecoveredLateAppendBytes: 0,
|
|
205
|
+
lateAppendDrainFailed: false,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
throw err;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
// Synchronous critical section: nothing in this process runs between
|
|
213
|
+
// the read and the rename.
|
|
214
|
+
const before = readFileSync(fd);
|
|
215
|
+
let drainedTo = before.length;
|
|
216
|
+
const live = splitBufferContent(before.toString("utf-8"));
|
|
217
|
+
const remaining: BufferEntryLines[] = [];
|
|
218
|
+
let removed = 0;
|
|
219
|
+
for (const entry of live) {
|
|
220
|
+
const index = pending.indexOf(bufferEntryText(entry));
|
|
221
|
+
if (index === -1) {
|
|
222
|
+
remaining.push(entry);
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
pending.splice(index, 1);
|
|
226
|
+
removed += 1;
|
|
227
|
+
}
|
|
228
|
+
const tmpPath = join(
|
|
229
|
+
dirname(bufferPath),
|
|
230
|
+
`.buffer.md.${process.pid}.${Date.now()}.tmp`,
|
|
231
|
+
);
|
|
232
|
+
writeFileSync(tmpPath, joinBufferEntries(remaining), "utf-8");
|
|
233
|
+
try {
|
|
234
|
+
renameSync(tmpPath, bufferPath);
|
|
235
|
+
} catch (err) {
|
|
236
|
+
unlinkSafely(tmpPath);
|
|
237
|
+
throw err;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Drain: appends that landed on the replaced inode after the read. The
|
|
241
|
+
// rename has committed, so from here nothing throws: a copy that fails
|
|
242
|
+
// is retried, bytes that cannot be copied are reported, and a replaced
|
|
243
|
+
// inode that cannot even be read is reported, all without raising.
|
|
244
|
+
let lateAppendBytesRecovered = 0;
|
|
245
|
+
let unrecoveredLateAppendBytes = 0;
|
|
246
|
+
let lateAppendDrainFailed = false;
|
|
247
|
+
// Copies through an explicit write loop so a retry resumes from the
|
|
248
|
+
// first unwritten byte: an append that wrote part of the bytes before
|
|
249
|
+
// failing (disk pressure) must not have its prefix appended twice.
|
|
250
|
+
const copyBack = async (bytes: Buffer): Promise<void> => {
|
|
251
|
+
let written = 0;
|
|
252
|
+
for (let attempt = 1; ; attempt++) {
|
|
253
|
+
try {
|
|
254
|
+
const out = openSync(bufferPath, "a");
|
|
255
|
+
try {
|
|
256
|
+
while (written < bytes.length) {
|
|
257
|
+
const n = writeSync(out, bytes, written, bytes.length - written);
|
|
258
|
+
if (n <= 0) {
|
|
259
|
+
// A regular file never returns a zero-byte write without an
|
|
260
|
+
// error, but a spin here would wedge the worker with the
|
|
261
|
+
// consolidation lock held; treat it as a failed attempt.
|
|
262
|
+
throw new Error("writeSync made no progress");
|
|
263
|
+
}
|
|
264
|
+
written += n;
|
|
265
|
+
}
|
|
266
|
+
} finally {
|
|
267
|
+
closeSync(out);
|
|
268
|
+
}
|
|
269
|
+
lateAppendBytesRecovered += bytes.length;
|
|
270
|
+
return;
|
|
271
|
+
} catch (err) {
|
|
272
|
+
if (attempt >= LATE_APPEND_COPY_ATTEMPTS) {
|
|
273
|
+
log.error(
|
|
274
|
+
{ err, bufferPath, bytes: bytes.length, written, attempt },
|
|
275
|
+
"buffer consume: could not copy entries appended during the rewrite back into the buffer; giving up the rest",
|
|
276
|
+
);
|
|
277
|
+
lateAppendBytesRecovered += written;
|
|
278
|
+
unrecoveredLateAppendBytes += bytes.length - written;
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
log.warn(
|
|
282
|
+
{ err, bufferPath, bytes: bytes.length, written, attempt },
|
|
283
|
+
"buffer consume: copying late-appended entries failed; retrying from the first unwritten byte",
|
|
284
|
+
);
|
|
285
|
+
await new Promise((resolve) =>
|
|
286
|
+
setTimeout(resolve, LATE_APPEND_COPY_RETRY_MS),
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
const drain = async (): Promise<void> => {
|
|
292
|
+
let late: Buffer;
|
|
293
|
+
try {
|
|
294
|
+
const size = fstatSync(fd).size;
|
|
295
|
+
if (size <= drainedTo) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
late = Buffer.alloc(size - drainedTo);
|
|
299
|
+
const read = readSync(fd, late, 0, late.length, drainedTo);
|
|
300
|
+
late = late.subarray(0, read);
|
|
301
|
+
drainedTo += read;
|
|
302
|
+
} catch (err) {
|
|
303
|
+
log.error(
|
|
304
|
+
{ err, bufferPath },
|
|
305
|
+
"buffer consume: could not read the replaced inode for entries appended during the rewrite",
|
|
306
|
+
);
|
|
307
|
+
lateAppendDrainFailed = true;
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
await copyBack(late);
|
|
311
|
+
};
|
|
312
|
+
await drain();
|
|
313
|
+
if (graceMs > 0) {
|
|
314
|
+
await new Promise((resolve) => setTimeout(resolve, graceMs));
|
|
315
|
+
}
|
|
316
|
+
if (!lateAppendDrainFailed) {
|
|
317
|
+
await drain();
|
|
318
|
+
}
|
|
319
|
+
if (lateAppendBytesRecovered > 0) {
|
|
320
|
+
log.info(
|
|
321
|
+
{ bufferPath, lateAppendBytesRecovered },
|
|
322
|
+
"buffer consume: recovered entries appended during the rewrite",
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
removed,
|
|
328
|
+
alreadyAbsent: pending.length,
|
|
329
|
+
lateAppendBytesRecovered,
|
|
330
|
+
unrecoveredLateAppendBytes,
|
|
331
|
+
lateAppendDrainFailed,
|
|
332
|
+
};
|
|
333
|
+
} finally {
|
|
334
|
+
// After the rename, a failing close must not surface as a failure of
|
|
335
|
+
// the consume: the pass is consumed and the drain has reported. Before
|
|
336
|
+
// the rename, the original error is already propagating.
|
|
337
|
+
try {
|
|
338
|
+
closeSync(fd);
|
|
339
|
+
} catch (err) {
|
|
340
|
+
log.error(
|
|
341
|
+
{ err, bufferPath },
|
|
342
|
+
"buffer consume: closing the replaced buffer inode failed",
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function unlinkSafely(path: string): void {
|
|
349
|
+
try {
|
|
350
|
+
unlinkSync(path);
|
|
351
|
+
} catch {
|
|
352
|
+
// best-effort cleanup of a temp file the rename never claimed
|
|
353
|
+
}
|
|
354
|
+
}
|
|
@@ -154,6 +154,46 @@ export function splitBufferEntries(
|
|
|
154
154
|
return groups;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Split a whole buffer FILE into entries. The file is newline-terminated
|
|
159
|
+
* (every append ends in `\n`), so the terminator ends the last line rather
|
|
160
|
+
* than opening an empty one: an entry's lines are the same whether it is
|
|
161
|
+
* currently last in the file or has since been followed by more entries.
|
|
162
|
+
* That stability is what lets a consumer match an entry it snapshotted
|
|
163
|
+
* earlier against the live file by exact text.
|
|
164
|
+
*/
|
|
165
|
+
export function splitBufferContent(content: string): BufferEntryLines[] {
|
|
166
|
+
if (content.length === 0) {
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
const lines = content.split("\n");
|
|
170
|
+
if (lines[lines.length - 1] === "") {
|
|
171
|
+
lines.pop();
|
|
172
|
+
}
|
|
173
|
+
return splitBufferEntries(lines);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* One entry's verbatim text: its lines joined, without a terminating
|
|
178
|
+
* newline. The identity a consumer matches on.
|
|
179
|
+
*/
|
|
180
|
+
export function bufferEntryText(entry: BufferEntryLines): string {
|
|
181
|
+
return entry.lines.join("\n");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Inverse of {@link splitBufferContent}: entries back to newline-terminated
|
|
186
|
+
* file content, or the empty string when there are none.
|
|
187
|
+
*/
|
|
188
|
+
export function joinBufferEntries(
|
|
189
|
+
entries: readonly BufferEntryLines[],
|
|
190
|
+
): string {
|
|
191
|
+
if (entries.length === 0) {
|
|
192
|
+
return "";
|
|
193
|
+
}
|
|
194
|
+
return `${entries.map(bufferEntryText).join("\n")}\n`;
|
|
195
|
+
}
|
|
196
|
+
|
|
157
197
|
/** One entry's verbatim lines, as returned by {@link splitBufferEntries}. */
|
|
158
198
|
export interface BufferEntryLines {
|
|
159
199
|
/** `null` for the headless group before the first entry opening. */
|
|
@@ -4,10 +4,9 @@ import type {
|
|
|
4
4
|
ProviderResponse,
|
|
5
5
|
ToolUseContent,
|
|
6
6
|
} from "@vellumai/plugin-api";
|
|
7
|
-
import { getConfiguredProvider } from "@vellumai/plugin-api";
|
|
7
|
+
import { getConfiguredProvider, safeStringSlice } from "@vellumai/plugin-api";
|
|
8
8
|
|
|
9
9
|
import { redactSecrets } from "../../../../security/secret-scanner.js";
|
|
10
|
-
import { safeStringSlice } from "../host-utils.js";
|
|
11
10
|
import {
|
|
12
11
|
buildRecallAgentPromptBundle,
|
|
13
12
|
FINISH_RECALL_TOOL_DEFINITION,
|
|
@@ -26,8 +26,9 @@
|
|
|
26
26
|
import { readdir, readFile, realpath, stat } from "node:fs/promises";
|
|
27
27
|
import { extname, join } from "node:path";
|
|
28
28
|
|
|
29
|
+
import { safeStringSlice } from "@vellumai/plugin-api";
|
|
30
|
+
|
|
29
31
|
import { embedWithRetry } from "../../../../../persistence/embeddings/embed.js";
|
|
30
|
-
import { safeStringSlice } from "../../host-utils.js";
|
|
31
32
|
import { getLogger } from "../../logging.js";
|
|
32
33
|
import { isPathInsideRoot } from "../../path-containment.js";
|
|
33
34
|
import { getEdgeIndex } from "../../substrate/edge-index.js";
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { readdir, readFile, realpath, stat } from "node:fs/promises";
|
|
2
2
|
import { extname, isAbsolute, join, relative, sep } from "node:path";
|
|
3
3
|
|
|
4
|
-
import { safeStringSlice } from "
|
|
4
|
+
import { safeStringSlice } from "@vellumai/plugin-api";
|
|
5
|
+
|
|
5
6
|
import { isPathInsideRoot } from "../../path-containment.js";
|
|
6
7
|
import type {
|
|
7
8
|
RecallEvidence,
|
|
@@ -16,6 +16,10 @@ export const CONVERSATION_KEYED_MEMORY_TABLES: readonly string[] = [
|
|
|
16
16
|
"memory_recall_logs",
|
|
17
17
|
"memory_v3_selections",
|
|
18
18
|
"memory_v3_pools",
|
|
19
|
+
// The pool input capture keys its turn rows here; its companion texts
|
|
20
|
+
// table is content-addressed and page-derived, so it has no conversation
|
|
21
|
+
// rows to purge.
|
|
22
|
+
"memory_v3_pool_inputs",
|
|
19
23
|
"activation_sessions",
|
|
20
24
|
"activation_state",
|
|
21
25
|
"conversation_graph_memory_state",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// semantic retrieval.
|
|
6
6
|
// ---------------------------------------------------------------------------
|
|
7
7
|
|
|
8
|
-
import { CLI_COMMAND_HELP } from "@vellumai/plugin-api";
|
|
8
|
+
import { CLI_COMMAND_HELP, safeStringSlice } from "@vellumai/plugin-api";
|
|
9
9
|
import { and, eq, like, sql } from "drizzle-orm";
|
|
10
10
|
|
|
11
11
|
import { isAssistantFeatureFlagEnabled } from "../../../../config/assistant-feature-flags.js";
|
|
@@ -26,7 +26,6 @@ import {
|
|
|
26
26
|
getCachedCatalogSync,
|
|
27
27
|
getCatalog,
|
|
28
28
|
} from "../../../../skills/catalog-cache.js";
|
|
29
|
-
import { safeStringSlice } from "../host-utils.js";
|
|
30
29
|
import { getLogger } from "../logging.js";
|
|
31
30
|
import { memoryDbOrNull } from "../memory-db.js";
|
|
32
31
|
import type { SkillCapabilityInput } from "../substrate/skill-content.js";
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
// into concept pages.
|
|
7
7
|
// ---------------------------------------------------------------------------
|
|
8
8
|
|
|
9
|
-
import { appendFileSync, existsSync, mkdirSync } from "node:fs";
|
|
10
9
|
import { join } from "node:path";
|
|
11
10
|
|
|
12
11
|
import {
|
|
@@ -15,6 +14,7 @@ import {
|
|
|
15
14
|
} from "../../../../config/memory-v3-gate.js";
|
|
16
15
|
import type { AssistantConfig } from "../../../../config/types.js";
|
|
17
16
|
import { enqueueMemoryJob } from "../../../../persistence/jobs-store.js";
|
|
17
|
+
import { appendBufferAndArchive } from "../buffer-file.js";
|
|
18
18
|
import { formatRememberEntry } from "../buffer-format.js";
|
|
19
19
|
import { getWorkspaceDir } from "../paths.js";
|
|
20
20
|
import type { GraphStats } from "./store.js";
|
|
@@ -99,47 +99,6 @@ export function handleRemember(
|
|
|
99
99
|
return { success: true, message };
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
/**
|
|
103
|
-
* Append `entry` to `<rootDir>/buffer.md` and `<rootDir>/archive/<today>.md`,
|
|
104
|
-
* creating the archive directory and seeding the archive header if missing.
|
|
105
|
-
*
|
|
106
|
-
* Returns the absolute paths of both files so callers can fan out follow-up
|
|
107
|
-
* work.
|
|
108
|
-
*
|
|
109
|
-
* Exported so background jobs (`sweep`, future LLM-driven extractors) can
|
|
110
|
-
* append to `memory/buffer.md` + `memory/archive/<today>.md` with exactly the
|
|
111
|
-
* same format `remember()` produces, keeping the two write paths
|
|
112
|
-
* byte-compatible for downstream consumers (consolidation, search).
|
|
113
|
-
*/
|
|
114
|
-
export function appendBufferAndArchive(args: {
|
|
115
|
-
rootDir: string;
|
|
116
|
-
entry: string;
|
|
117
|
-
now: Date;
|
|
118
|
-
}): { bufferPath: string; archivePath: string } {
|
|
119
|
-
const { rootDir, entry, now } = args;
|
|
120
|
-
const archiveDir = join(rootDir, "archive");
|
|
121
|
-
mkdirSync(archiveDir, { recursive: true });
|
|
122
|
-
|
|
123
|
-
const bufferPath = join(rootDir, "buffer.md");
|
|
124
|
-
appendFileSync(bufferPath, entry, "utf-8");
|
|
125
|
-
|
|
126
|
-
const yyyy = now.getFullYear();
|
|
127
|
-
const mm = String(now.getMonth() + 1).padStart(2, "0");
|
|
128
|
-
const dd = String(now.getDate()).padStart(2, "0");
|
|
129
|
-
const archivePath = join(archiveDir, `${yyyy}-${mm}-${dd}.md`);
|
|
130
|
-
if (!existsSync(archivePath)) {
|
|
131
|
-
const month = now.toLocaleString("en-US", { month: "short" });
|
|
132
|
-
appendFileSync(
|
|
133
|
-
archivePath,
|
|
134
|
-
`# ${month} ${now.getDate()}, ${yyyy}\n\n`,
|
|
135
|
-
"utf-8",
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
appendFileSync(archivePath, entry, "utf-8");
|
|
139
|
-
|
|
140
|
-
return { bufferPath, archivePath };
|
|
141
|
-
}
|
|
142
|
-
|
|
143
102
|
// ---------------------------------------------------------------------------
|
|
144
103
|
// Shared helpers for delete / update / list
|
|
145
104
|
// ---------------------------------------------------------------------------
|
|
@@ -25,7 +25,6 @@ import type { SqliteRetryOptions } from "../../../util/sqlite-retry.js";
|
|
|
25
25
|
import * as hostSqliteRetry from "../../../util/sqlite-retry.js";
|
|
26
26
|
import * as hostStripCommentLines from "../../../util/strip-comment-lines.js";
|
|
27
27
|
import * as hostTruncate from "../../../util/truncate.js";
|
|
28
|
-
import * as hostUnicode from "../../../util/unicode.js";
|
|
29
28
|
import * as hostWorkerCompute from "../../../util/worker-compute.js";
|
|
30
29
|
import * as hostWorkerMemory from "../../../util/worker-memory.js";
|
|
31
30
|
|
|
@@ -77,15 +76,6 @@ export function truncate(str: string, maxLen: number, suffix?: string): string {
|
|
|
77
76
|
: hostTruncate.truncate(str, maxLen, suffix);
|
|
78
77
|
}
|
|
79
78
|
|
|
80
|
-
/** `String.prototype.slice` that never cuts a UTF-16 surrogate pair in half. */
|
|
81
|
-
export function safeStringSlice(
|
|
82
|
-
str: string,
|
|
83
|
-
start?: number,
|
|
84
|
-
end?: number,
|
|
85
|
-
): string {
|
|
86
|
-
return hostUnicode.safeStringSlice(str, start, end);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
79
|
export function workerMemoryEnv(): Record<string, string | undefined> {
|
|
90
80
|
return hostWorkerMemory.workerMemoryEnv();
|
|
91
81
|
}
|
|
@@ -374,9 +374,10 @@ const memoryV2StaticInjector: Injector = {
|
|
|
374
374
|
if (mode !== "full") {
|
|
375
375
|
return null;
|
|
376
376
|
}
|
|
377
|
-
// The consolidation
|
|
378
|
-
//
|
|
379
|
-
//
|
|
377
|
+
// The consolidation run already carries its pass's buffer entries in
|
|
378
|
+
// the prompt itself, and the job removes them from memory/buffer.md
|
|
379
|
+
// after the run; injecting the buffer section here would duplicate the
|
|
380
|
+
// pass and hand the agent entries that are not its to file.
|
|
380
381
|
const content = readGatedMemoryV2Static(ctx.trust, {
|
|
381
382
|
excludeBuffer: ctx.callSite === "memoryV2Consolidation",
|
|
382
383
|
});
|