@vellumai/assistant 0.11.3 → 0.11.4-dev.202608190019.b94dbf2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +9 -3
- package/ARCHITECTURE.md +32 -10
- package/docs/architecture/memory.md +43 -5
- package/docs/architecture/security.md +96 -152
- package/docs/architecture/turn-actor.md +70 -0
- package/docs/browser-use-architecture-phase2.md +128 -56
- package/docs/flux-turn-detection-spike.md +248 -0
- package/docs/guardian-request-flow.md +35 -0
- package/docs/stt-provider-onboarding.md +3 -1
- package/knip.json +3 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +34 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +166 -1
- package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
- package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/openapi.yaml +289 -113
- package/package.json +1 -1
- package/scripts/voice-ttft-spike.ts +3 -3
- package/scripts/write-plugin-api-shim.ts +10 -0
- package/src/__tests__/app-compiler.test.ts +38 -3
- package/src/__tests__/app-control-flow.test.ts +1 -0
- package/src/__tests__/approval-routes-http.test.ts +2 -2
- package/src/__tests__/assistant-feature-flag-guard.test.ts +25 -3
- package/src/__tests__/attachments-store.test.ts +21 -12
- package/src/__tests__/byok-default-profile-ensure.test.ts +17 -0
- package/src/__tests__/call-setup-flow-name-capture.test.ts +0 -1
- package/src/__tests__/call-site-routing-provider.test.ts +1 -1
- package/src/__tests__/channel-availability-routes.test.ts +14 -1
- package/src/__tests__/channel-capabilities-dedupe.test.ts +214 -0
- package/src/__tests__/channel-delivery-store.test.ts +14 -14
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +4 -4
- package/src/__tests__/channel-setup-panel-ack.test.ts +1 -1
- package/src/__tests__/checker.test.ts +20 -314
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
- package/src/__tests__/compaction-events.test.ts +8 -10
- package/src/__tests__/config-loader-backfill.test.ts +3 -3
- package/src/__tests__/config-schema.test.ts +25 -10
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +8 -11
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +8 -11
- package/src/__tests__/conversation-agent-loop.test.ts +32 -21
- package/src/__tests__/conversation-attachments.test.ts +0 -1
- package/src/__tests__/conversation-attention-store.test.ts +63 -0
- package/src/__tests__/conversation-confirmation-signals.test.ts +112 -0
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -4
- package/src/__tests__/conversation-fork-crud.test.ts +69 -0
- package/src/__tests__/conversation-fork-referential.test.ts +67 -0
- package/src/__tests__/conversation-fork-retrospective.test.ts +24 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +209 -0
- package/src/__tests__/conversation-notifiers-provenance.test.ts +59 -0
- package/src/__tests__/conversation-queue.test.ts +154 -4
- package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-routes-enabled-plugins.test.ts +1 -1
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +9 -9
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +1 -1
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +187 -102
- package/src/__tests__/conversation-runtime-workspace.test.ts +14 -10
- package/src/__tests__/conversation-slash-queue.test.ts +3 -0
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-app-open.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +21 -14
- package/src/__tests__/conversation-surfaces-queued-emit.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-standalone.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-state-update.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-table-action.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -1
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +1 -1
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +1 -1
- package/src/__tests__/credential-prompt-route.test.ts +7 -10
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/cu-unified-flow.test.ts +1 -0
- package/src/__tests__/custom-profile-ensure.test.ts +5 -1
- package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
- package/src/__tests__/discord-access-request-privacy.test.ts +5 -1
- package/src/__tests__/discord-requester-notice-privacy.test.ts +3 -3
- package/src/__tests__/document-append-idempotency.test.ts +233 -0
- package/src/__tests__/document-sync-tags.test.ts +0 -75
- package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
- package/src/__tests__/edit-propagation.test.ts +107 -11
- package/src/__tests__/file-ops-service.test.ts +163 -30
- package/src/__tests__/filesystem-tools.test.ts +23 -24
- package/src/__tests__/gateway-only-guard.test.ts +2 -5
- package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
- package/src/__tests__/helpers/mock-actor-context.ts +49 -0
- package/src/__tests__/helpers/mock-conversation.ts +13 -1
- package/src/__tests__/host-file-read-tool.test.ts +16 -19
- package/src/__tests__/http-user-message-parity.test.ts +1 -1
- package/src/__tests__/init-feature-flag-overrides.test.ts +49 -0
- package/src/__tests__/injector-chain.test.ts +63 -41
- package/src/__tests__/injector-disk-pressure.test.ts +11 -23
- package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
- package/src/__tests__/llm-context-resolution.test.ts +73 -1
- package/src/__tests__/llm-schema.test.ts +5 -2
- package/src/__tests__/managed-skill-lifecycle.test.ts +7 -0
- package/src/__tests__/mcp-list-plugin-servers.test.ts +250 -0
- package/src/__tests__/media-generate-image.test.ts +131 -21
- package/src/__tests__/memory-retrieval-hook.test.ts +100 -7
- package/src/__tests__/messages-read-boundary-guard.test.ts +134 -0
- package/src/__tests__/mtime-cache.test.ts +3 -1
- package/src/__tests__/non-member-access-request.test.ts +0 -20
- package/src/__tests__/outbound-slack-persistence.test.ts +40 -1
- package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
- package/src/__tests__/plugin-api-store-credential.test.ts +261 -0
- package/src/__tests__/plugin-api-webhook-url.test.ts +10 -7
- package/src/__tests__/plugin-disabled-state.test.ts +2 -0
- package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
- package/src/__tests__/plugin-execution-context.test.ts +73 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -1
- package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +7 -4
- package/src/__tests__/plugin-secret-pattern-contribution.test.ts +1 -1
- package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +14 -7
- package/src/__tests__/provider-commit-message-generator.test.ts +20 -0
- package/src/__tests__/proxy-approval-callback.test.ts +1 -0
- package/src/__tests__/qdrant-manager.test.ts +14 -1
- package/src/__tests__/reaction-persistence.test.ts +200 -7
- package/src/__tests__/require-fresh-approval.test.ts +0 -4
- package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +460 -105
- package/src/__tests__/run-due-schedules.test.ts +21 -0
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +187 -18
- package/src/__tests__/schedule-routes.test.ts +23 -0
- package/src/__tests__/schedule-store.test.ts +17 -0
- package/src/__tests__/scoped-approval-grants.test.ts +11 -6
- package/src/__tests__/secret-ingress-channel.test.ts +0 -1
- package/src/__tests__/secret-ingress-http.test.ts +1 -1
- package/src/__tests__/send-endpoint-busy.test.ts +3 -3
- package/src/__tests__/skills.test.ts +32 -0
- package/src/__tests__/slack-edit-ordering-characterization.test.ts +0 -1
- package/src/__tests__/starter-task-flow.test.ts +5 -4
- package/src/__tests__/subagent-call-site-routing.test.ts +31 -19
- package/src/__tests__/subagent-fork-prompt-role.test.ts +1 -1
- package/src/__tests__/subagent-spawn-and-await.test.ts +18 -17
- package/src/__tests__/subagent-tool-gate-mode.test.ts +1 -1
- package/src/__tests__/subagent-tools.test.ts +81 -101
- package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +1 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
- package/src/__tests__/tool-executor.test.ts +68 -39
- package/src/__tests__/turn-events-store.test.ts +43 -0
- package/src/__tests__/ui-choice-copy-surfaces.test.ts +1 -1
- package/src/__tests__/ui-shape-teaching.test.ts +33 -0
- package/src/__tests__/ui-visual-surface.test.ts +1 -1
- package/src/__tests__/ui-voice-picker-surface.test.ts +128 -0
- package/src/__tests__/ui-work-result-surface.test.ts +1 -1
- package/src/__tests__/user-plugin-loader.test.ts +3 -1
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
- package/src/__tests__/visible-app-context.test.ts +16 -9
- package/src/__tests__/voice-config-update.test.ts +40 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +5 -3
- package/src/__tests__/voice-session-bridge.test.ts +85 -29
- package/src/__tests__/worker-entrypoint-guards.test.ts +54 -0
- package/src/__tests__/worker-plugin-surface.test.ts +77 -0
- package/src/__tests__/workspace-migration-142-consolidate-voice-front-door.test.ts +158 -0
- package/src/__tests__/workspace-migration-143-repair-deprecated-codex-model-id.test.ts +133 -0
- package/src/__tests__/workspace-migration-144-convert-stranded-subscription-openai-profiles.test.ts +316 -0
- package/src/__tests__/workspace-migration-145-collapse-profile-bindings-to-entries.test.ts +325 -0
- package/src/__tests__/workspace-migration-146-repair-retired-fireworks-deepseek-flash-model-id.test.ts +235 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +10 -2
- package/src/acp/__tests__/auth-required.test.ts +161 -0
- package/src/acp/acp-claude-oauth.ts +19 -2
- package/src/acp/agent-process.test.ts +100 -0
- package/src/acp/agent-process.ts +29 -26
- package/src/acp/auth-required.ts +102 -0
- package/src/acp/session-manager.test.ts +119 -0
- package/src/acp/session-manager.ts +76 -3
- package/src/api/events/acp-auth-required.ts +55 -0
- package/src/api/events/host-file.ts +2 -2
- package/src/api/index.ts +7 -0
- package/src/api/surfaces.ts +12 -3
- package/src/apps/app-store.ts +3 -0
- package/src/bundler/package-resolver.ts +2 -30
- package/src/calls/__tests__/voice-session-bridge.test.ts +194 -11
- package/src/calls/__tests__/voice-triage-escalate.test.ts +102 -0
- package/src/calls/call-controller.ts +19 -3
- package/src/calls/call-setup-flow.ts +0 -1
- package/src/calls/media-stream-stt-session.ts +15 -0
- package/src/calls/voice-session-bridge.ts +115 -41
- package/src/calls/voice-triage-escalate.ts +105 -2
- package/src/channels/__tests__/plugin-channel-declarations.test.ts +161 -0
- package/src/channels/config.ts +13 -0
- package/src/channels/plugin-channel-declarations.ts +108 -0
- package/src/channels/types.ts +30 -0
- package/src/cli/AGENTS.md +5 -2
- package/src/cli/bundled-modules.ts +29 -0
- package/src/cli/commands/credentials.help.ts +2 -2
- package/src/cli/commands/db/repair.ts +4 -8
- package/src/cli/commands/domain.ts +6 -3
- package/src/cli/commands/email.ts +6 -3
- package/src/cli/commands/inference-providers.ts +1 -1
- package/src/cli/commands/keys.ts +8 -3
- package/src/cli/commands/mcp.help.ts +13 -4
- package/src/cli/commands/mcp.ts +9 -0
- package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
- package/src/cli/commands/memory/__tests__/memory-v3.test.ts +128 -5
- package/src/cli/commands/memory/index.help.ts +84 -28
- package/src/cli/commands/memory/index.ts +2 -0
- package/src/cli/commands/memory/memory-v2.ts +58 -54
- package/src/cli/commands/memory/memory-v3.ts +64 -0
- package/src/cli/commands/memory/memory-validate.ts +18 -0
- package/src/cli/commands/plugins.ts +85 -36
- package/src/cli/commands/schedules.ts +35 -1
- package/src/cli/commands/stt.help.ts +27 -2
- package/src/cli/commands/trust.ts +3 -14
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +39 -0
- package/src/cli/lib/bundled-marketplace.json +14 -1
- package/src/cli/lib/upgrade-plugin.ts +42 -0
- package/src/config/__tests__/balanced-model-experiment.test.ts +278 -0
- package/src/config/__tests__/default-profile-catalog.test.ts +34 -2
- package/src/config/__tests__/default-provider.test.ts +6 -1
- package/src/config/__tests__/profile-materialization.test.ts +75 -19
- package/src/config/assistant-feature-flags.ts +36 -15
- package/src/config/balanced-model-experiment.ts +35 -0
- package/src/config/bundled-skills/acp/SKILL.md +6 -7
- package/src/config/bundled-skills/document-editor/SKILL.md +2 -2
- package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
- package/src/config/bundled-skills/image-studio/SKILL.md +5 -4
- package/src/config/bundled-skills/image-studio/TOOLS.json +1 -1
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +101 -0
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +14 -4
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.test.ts +65 -0
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +7 -1
- package/src/config/bundled-skills/settings/tools/shared.ts +16 -0
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +9 -3
- package/src/config/bundled-skills/subagent/SKILL.md +17 -12
- package/src/config/bundled-skills/subagent/TOOLS.json +4 -4
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +22 -1
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +9 -2
- package/src/config/call-site-defaults.ts +11 -5
- package/src/config/default-profile-catalog.ts +179 -16
- package/src/config/default-profile-names.ts +4 -1
- package/src/config/default-provider-resolution.ts +4 -0
- package/src/config/feature-flag-registry.json +12 -12
- package/src/config/llm-context-resolution.ts +11 -3
- package/src/config/llm-resolver.ts +28 -1
- package/src/config/profile-materialization.ts +70 -22
- package/src/config/schemas/__tests__/live-voice.test.ts +107 -4
- package/src/config/schemas/call-site-catalog.ts +4 -4
- package/src/config/schemas/live-voice.ts +57 -23
- package/src/config/schemas/llm.ts +59 -32
- package/src/config/schemas/mcp.ts +23 -0
- package/src/config/schemas/plugin-updates.ts +6 -2
- package/src/config/schemas/stt.ts +1 -0
- package/src/config/skills.ts +9 -2
- package/src/context/outbound-sanitize.ts +96 -1
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +1 -1
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +82 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +15 -10
- package/src/daemon/conversation-agent-loop.ts +31 -19
- package/src/daemon/conversation-messaging.ts +5 -1
- package/src/daemon/conversation-notifiers.ts +20 -10
- package/src/daemon/conversation-process.ts +20 -6
- package/src/daemon/conversation-runtime-assembly.ts +12 -6
- package/src/daemon/conversation-store.ts +14 -4
- package/src/daemon/conversation-surfaces.ts +75 -26
- package/src/daemon/conversation-tool-setup.ts +3 -6
- package/src/daemon/conversation.ts +154 -51
- package/src/daemon/doordash-steps.ts +2 -2
- package/src/daemon/lifecycle.ts +14 -1
- package/src/daemon/mcp-reload-service.ts +36 -6
- package/src/daemon/process-message.ts +13 -26
- package/src/daemon/providers-setup.ts +6 -3
- package/src/daemon/trust-context-types.ts +29 -0
- package/src/daemon/trust-context.ts +12 -7
- package/src/daemon/wake-conversation-ops.ts +3 -2
- package/src/daemon/windows-compiled-entry.ts +4 -0
- package/src/documents/document-store.ts +143 -240
- package/src/hooks/hook-loader.ts +3 -3
- package/src/hooks/registry.ts +103 -45
- package/src/hooks/types.ts +5 -0
- package/src/inbound/__tests__/oauth-callback-url.test.ts +83 -0
- package/src/inbound/oauth-callback-url.ts +61 -0
- package/src/ipc/gateway-client.test.ts +1 -1
- package/src/ipc/gateway-client.ts +14 -21
- package/src/ipc/gateway-flag-listener.ts +17 -3
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +1 -104
- package/src/live-voice/__tests__/live-voice-events.test.ts +7 -8
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +1050 -0
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +115 -8
- package/src/live-voice/__tests__/live-voice-photo.test.ts +100 -0
- package/src/live-voice/__tests__/live-voice-progress.test.ts +60 -194
- package/src/live-voice/__tests__/live-voice-stt.test.ts +14 -0
- package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +29 -0
- package/src/live-voice/__tests__/live-voice-tts-session.test.ts +0 -483
- package/src/live-voice/__tests__/live-voice-vad.test.ts +0 -16
- package/src/live-voice/__tests__/progress-narration.test.ts +214 -0
- package/src/live-voice/live-voice-archive.ts +2 -0
- package/src/live-voice/live-voice-manager.ts +16 -3
- package/src/live-voice/live-voice-metrics.ts +57 -32
- package/src/live-voice/live-voice-photo.ts +1 -2
- package/src/live-voice/live-voice-session.ts +598 -323
- package/src/live-voice/progress-narration.ts +277 -0
- package/src/live-voice/protocol.ts +21 -1
- package/src/live-voice/windows-compiled-live-voice.ts +4 -0
- package/src/mcp/__tests__/effective-config.test.ts +238 -0
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
- package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +200 -0
- package/src/mcp/__tests__/mcp-oauth-provider.test.ts +9 -9
- package/src/mcp/__tests__/plugin-server-credential-isolation.test.ts +95 -0
- package/src/mcp/client.ts +16 -11
- package/src/mcp/effective-config.ts +113 -0
- package/src/mcp/manager.ts +11 -6
- package/src/mcp/mcp-auth-orchestrator.ts +13 -22
- package/src/mcp/mcp-oauth-provider.ts +205 -240
- package/src/monitoring/__tests__/plugin-auto-update.test.ts +166 -3
- package/src/monitoring/control.ts +1 -0
- package/src/monitoring/db-integrity-sample.ts +4 -5
- package/src/monitoring/plugin-auto-update.ts +128 -24
- package/src/notifications/AGENTS.md +2 -0
- package/src/notifications/approval-card-data.ts +33 -0
- package/src/notifications/signal.ts +1 -0
- package/src/permissions/AGENTS.md +16 -0
- package/src/permissions/checker.test.ts +75 -122
- package/src/permissions/checker.ts +44 -560
- package/src/permissions/confirmation-guardian-request.test.ts +15 -11
- package/src/permissions/confirmation-guardian-request.ts +2 -2
- package/src/permissions/prompter.ts +1 -5
- package/src/permissions/question-guardian-request.test.ts +14 -6
- package/src/permissions/question-guardian-request.ts +1 -2
- package/src/persistence/attachments-store.ts +8 -1
- package/src/persistence/bookmark-crud.ts +3 -7
- package/src/persistence/conversation-attention-store.ts +16 -45
- package/src/persistence/conversation-crud.ts +33 -4
- package/src/persistence/conversation-lineage.ts +9 -0
- package/src/persistence/conversation-queries.ts +182 -52
- package/src/persistence/delivery-crud.ts +150 -36
- package/src/persistence/embeddings/qdrant-manager.ts +84 -49
- package/src/persistence/external-conversation-store.ts +32 -4
- package/src/persistence/llm-request-log-store.ts +4 -10
- package/src/persistence/llm-usage-store.ts +8 -3
- package/src/persistence/message-reads.test.ts +197 -0
- package/src/persistence/message-reads.ts +211 -0
- package/src/persistence/migrations/360-add-document-workspace-path.ts +5 -14
- package/src/persistence/migrations/366-chatgpt-subscription-row-identity.test.ts +120 -0
- package/src/persistence/migrations/366-chatgpt-subscription-row-identity.ts +62 -0
- package/src/persistence/real-user-turn-filter.ts +27 -3
- package/src/persistence/schema/documents.ts +4 -4
- package/src/persistence/steps.ts +9 -0
- package/src/plugin-api/__tests__/oauth-callback-url-export.test.ts +29 -0
- package/src/plugin-api/constants.ts +8 -0
- package/src/plugin-api/conversation-turn.ts +195 -5
- package/src/plugin-api/index.ts +38 -6
- package/src/plugin-api/resolve-credential.ts +3 -2
- package/src/plugin-api/store-credential.ts +146 -0
- package/src/plugin-api/vision-support.test.ts +1 -1
- package/src/plugin-api/webhook-url.ts +13 -11
- package/src/plugins/__tests__/mcp-servers.test.ts +371 -0
- package/src/plugins/defaults/main.ts +21 -7
- package/src/plugins/defaults/memory/AGENTS.md +15 -3
- package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +204 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +72 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +4 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +4 -1
- package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
- package/src/plugins/defaults/memory/buffer-format.ts +165 -0
- package/src/plugins/defaults/memory/context-search/sources/conversations.ts +6 -0
- package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +77 -0
- package/src/plugins/defaults/memory/graph/conversation-graph-memory.ts +24 -6
- package/src/plugins/defaults/memory/graph/image-ref-utils.ts +3 -0
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -30
- package/src/plugins/defaults/memory/graph-topology/pending-buffer.test.ts +34 -0
- package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +17 -24
- package/src/plugins/defaults/memory/hooks/post-compact.ts +1 -4
- package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +53 -5
- package/src/plugins/defaults/memory/indexer.ts +3 -1
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +19 -7
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +4 -4
- package/src/plugins/defaults/memory/src/__tests__/memory-v3-gate-stats.test.ts +281 -0
- package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
- package/src/plugins/defaults/memory/src/memory-v3-routes.ts +207 -0
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +147 -0
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
- package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
- package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
- package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
- package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
- package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
- package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +195 -58
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +93 -24
- package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
- package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
- package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
- package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
- package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
- package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
- package/src/plugins/defaults/memory/substrate/skill-content.ts +8 -1
- package/src/plugins/defaults/memory/substrate/static-context.ts +160 -33
- package/src/plugins/defaults/memory/substrate/sweep-job.ts +2 -4
- package/src/plugins/defaults/memory/v1/graph/extraction.ts +3 -1
- package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +18 -0
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +66 -1
- package/src/plugins/defaults/memory/v3/card.ts +9 -9
- package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
- package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
- package/src/plugins/defaults/memory/v3/edge.ts +13 -57
- package/src/plugins/defaults/memory/v3/injector.ts +8 -0
- package/src/plugins/defaults/memory/v3/prune.ts +2 -0
- package/src/plugins/defaults/memory/v3/selection-log-store.ts +2 -0
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +23 -9
- package/src/plugins/defaults/memory/worker-control.ts +1 -0
- package/src/plugins/defaults/memory/worker.ts +6 -3
- package/src/plugins/defaults/worker-entrypoints.ts +3 -0
- package/src/plugins/external-plugin-loader.ts +47 -0
- package/src/plugins/mcp-servers.ts +361 -0
- package/src/plugins/mtime-cache.ts +37 -46
- package/src/plugins/pipeline.ts +14 -7
- package/src/plugins/plugin-execution-context.ts +35 -11
- package/src/plugins/worker-plugin-surface.ts +33 -0
- package/src/prompts/templates/system-sections.ts +0 -7
- package/src/providers/__tests__/connection-model-compat.test.ts +1 -1
- package/src/providers/__tests__/context-overflow-error.test.ts +24 -0
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +214 -2
- package/src/providers/__tests__/preflight-resolved-config.test.ts +57 -0
- package/src/providers/__tests__/retry-callsite.test.ts +25 -2
- package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
- package/src/providers/anthropic/client.ts +332 -241
- package/src/providers/call-site-routing.ts +30 -3
- package/src/providers/connection-resolution.ts +194 -11
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +4 -0
- package/src/providers/inference/adapter-factory.ts +3 -0
- package/src/providers/inference/auth.ts +6 -0
- package/src/providers/inference/connection-availability.ts +24 -2
- package/src/providers/inference/connections.ts +2 -0
- package/src/providers/model-catalog.ts +3 -3
- package/src/providers/model-intents.ts +28 -8
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +163 -40
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
- package/src/providers/openai/chat-completions-provider.ts +133 -36
- package/src/providers/openai/codex-models.ts +2 -1
- package/src/providers/provider-send-message.ts +32 -3
- package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +433 -0
- package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +625 -0
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +34 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +285 -6
- package/src/providers/speech-to-text/deepgram-flux-frames.ts +395 -0
- package/src/providers/speech-to-text/deepgram-flux-realtime.ts +679 -0
- package/src/providers/speech-to-text/provider-catalog.ts +99 -8
- package/src/providers/speech-to-text/resolve.ts +25 -2
- package/src/routes/control.ts +1 -0
- package/src/routes/route-host-client.ts +1 -0
- package/src/routes/route-host-protocol.ts +7 -0
- package/src/routes/worker.ts +48 -15
- package/src/runtime/AGENTS.md +16 -17
- package/src/runtime/access-request-helper.ts +9 -12
- package/src/runtime/agent-wake.ts +18 -15
- package/src/runtime/effective-capabilities.test.ts +43 -0
- package/src/runtime/effective-capabilities.ts +27 -0
- package/src/runtime/guardian-reply-router.ts +6 -2
- package/src/runtime/pre-first-message-gate.ts +4 -0
- package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +12 -4
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +522 -2
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +52 -0
- package/src/runtime/routes/__tests__/default-provider-routes.test.ts +61 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +44 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +102 -1
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +132 -35
- package/src/runtime/routes/__tests__/schedule-routes-disarm-reason.test.ts +215 -0
- package/src/runtime/routes/__tests__/stt-routes.test.ts +25 -0
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
- package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +133 -1
- package/src/runtime/routes/channel-availability-routes.ts +32 -14
- package/src/runtime/routes/channel-route-shared.ts +0 -6
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +6 -6
- package/src/runtime/routes/conversation-list-routes.ts +181 -23
- package/src/runtime/routes/conversation-management-routes.ts +17 -12
- package/src/runtime/routes/conversation-query-routes.ts +40 -27
- package/src/runtime/routes/conversation-routes.ts +43 -29
- package/src/runtime/routes/credential-prompt-routes.ts +4 -7
- package/src/runtime/routes/credential-routes.ts +26 -82
- package/src/runtime/routes/default-provider-routes.ts +15 -0
- package/src/runtime/routes/documents-routes.ts +3 -222
- package/src/runtime/routes/inbound-message-handler.ts +21 -45
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -9
- package/src/runtime/routes/inbound-stages/admission-policy.ts +1 -17
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -3
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -79
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +12 -7
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -8
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -79
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +2 -3
- package/src/runtime/routes/inference-profiles-routes.ts +20 -11
- package/src/runtime/routes/inference-provider-connection-routes.ts +77 -15
- package/src/runtime/routes/log-export-routes.ts +3 -0
- package/src/runtime/routes/mcp-auth-routes.ts +148 -57
- package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +2 -0
- package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +3 -0
- package/src/runtime/routes/playground/inject-failures.ts +2 -2
- package/src/runtime/routes/playground/reset-circuit.ts +1 -1
- package/src/runtime/routes/plugins-routes.ts +38 -8
- package/src/runtime/routes/schedule-routes.ts +94 -7
- package/src/runtime/routes/settings-routes.ts +6 -6
- package/src/runtime/routes/stt-routes.ts +31 -25
- package/src/runtime/routes/surface-action-routes.ts +20 -19
- package/src/runtime/routes/surface-conversation-resolver.ts +3 -0
- package/src/runtime/routes/user-route-dispatcher.ts +69 -15
- package/src/runtime/routes/user-route-import.ts +108 -0
- package/src/runtime/routes/user-route-resolution.ts +18 -1
- package/src/runtime/routes/workspace-routes.ts +0 -9
- package/src/runtime/routes/workspace-utils.ts +3 -13
- package/src/runtime/services/conversation-serializer.ts +7 -2
- package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +68 -5
- package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +81 -0
- package/src/schedule/plugin-schedule-availability.ts +58 -0
- package/src/schedule/plugin-schedule-declarations.ts +23 -27
- package/src/schedule/plugin-schedule-reconciler.ts +12 -3
- package/src/schedule/schedule-store.ts +5 -1
- package/src/schedule/scheduler.ts +9 -4
- package/src/schedule/worker-control.ts +1 -0
- package/src/schedule/worker.ts +6 -0
- package/src/security/oauth2.ts +6 -22
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +22 -0
- package/src/stt/__tests__/types.test.ts +94 -0
- package/src/stt/daemon-batch-transcriber.ts +10 -0
- package/src/stt/stt-stream-session.ts +8 -4
- package/src/stt/types.ts +103 -0
- package/src/subagent/__tests__/consult-prompt.test.ts +26 -15
- package/src/subagent/consult-context.ts +11 -11
- package/src/subagent/consult-prompt.ts +26 -35
- package/src/subagent/manager.ts +21 -40
- package/src/subagent/notify.ts +7 -1
- package/src/subagent/types.ts +15 -12
- package/src/tools/__tests__/tool-input-schemas.test.ts +7 -7
- package/src/tools/acp/spawn.test.ts +97 -0
- package/src/tools/acp/spawn.ts +38 -4
- package/src/tools/credentials/ref-parse.ts +35 -0
- package/src/tools/credentials/resolve.ts +4 -10
- package/src/tools/credentials/store.ts +168 -0
- package/src/tools/document/document-tool.ts +12 -3
- package/src/tools/executor.ts +53 -15
- package/src/tools/filesystem/read.ts +27 -10
- package/src/tools/host-filesystem/read.ts +27 -15
- package/src/tools/network/url-safety.ts +9 -16
- package/src/tools/permission-checker.ts +43 -52
- package/src/tools/registry.ts +2 -1
- package/src/tools/shared/filesystem/file-ops-service.ts +63 -35
- package/src/tools/shared/filesystem/legacy-read-args.ts +22 -0
- package/src/tools/shared/filesystem/types.ts +5 -5
- package/src/tools/skills/scaffold-managed.ts +25 -7
- package/src/tools/subagent/spawn.ts +28 -88
- package/src/tools/tool-types.ts +26 -13
- package/src/tools/types.ts +6 -6
- package/src/tools/ui-surface/surface-shape-docs.ts +12 -1
- package/src/tools/workflows/run-workflow.ts +1 -2
- package/src/tts/__tests__/reasoning-tag-filter.test.ts +78 -0
- package/src/tts/reasoning-tag-filter.ts +89 -0
- package/src/util/__tests__/worker-process-command.test.ts +37 -0
- package/src/util/logger.ts +16 -0
- package/src/util/think-tag-stream.ts +95 -0
- package/src/util/worker-process.ts +37 -4
- package/src/windows-compiled-cli.ts +32 -0
- package/src/windows-compiled-entry.ts +4 -0
- package/src/windows-compiled-logger.ts +6 -0
- package/src/windows-compiled-worker-entry.ts +29 -0
- package/src/workspace/byok-default-profile-ensure.ts +76 -24
- package/src/workspace/custom-profile-ensure.ts +4 -24
- package/src/workspace/migrations/142-consolidate-voice-front-door.ts +70 -0
- package/src/workspace/migrations/143-repair-deprecated-codex-model-id.ts +134 -0
- package/src/workspace/migrations/144-convert-stranded-subscription-openai-profiles.ts +265 -0
- package/src/workspace/migrations/145-collapse-profile-bindings-to-entries.ts +328 -0
- package/src/workspace/migrations/146-repair-retired-fireworks-deepseek-flash-model-id.ts +195 -0
- package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +0 -10
- package/src/workspace/migrations/registry.ts +10 -0
- package/src/workspace/provider-commit-message-generator.ts +7 -5
- package/src/__tests__/document-workspace-file.test.ts +0 -467
- package/src/live-voice/__tests__/front-decision.test.ts +0 -645
- package/src/live-voice/front-decision.ts +0 -476
- package/src/permissions/ipc-risk-types.ts +0 -143
- package/src/permissions/risk-types.ts +0 -76
- package/src/subagent/__tests__/consult-transcript.test.ts +0 -184
- package/src/subagent/consult-transcript.ts +0 -90
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for `substrate/page-links.ts`: the single owner of `links:` entry and
|
|
3
|
+
* `[[wikilink]]` parsing, plus the dangling-link finder every write-side
|
|
4
|
+
* report uses.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, expect, test } from "bun:test";
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
extractWikilinkTargets,
|
|
11
|
+
findDanglingLinks,
|
|
12
|
+
LINK_SEPARATOR,
|
|
13
|
+
parseLinkEntry,
|
|
14
|
+
WIKILINK_REGEX,
|
|
15
|
+
wikilinkTarget,
|
|
16
|
+
} from "../page-links.js";
|
|
17
|
+
import { isValidSlug } from "../page-store.js";
|
|
18
|
+
|
|
19
|
+
/** Build a `"<slug> <sep> <why>"` links entry without spelling the separator. */
|
|
20
|
+
const link = (target: string, why?: string): string =>
|
|
21
|
+
why === undefined ? target : `${target}${LINK_SEPARATOR}${why}`;
|
|
22
|
+
|
|
23
|
+
describe("LINK_SEPARATOR", () => {
|
|
24
|
+
test("is space, U+2014 EM DASH, space", () => {
|
|
25
|
+
expect(LINK_SEPARATOR).toHaveLength(3);
|
|
26
|
+
expect(LINK_SEPARATOR.codePointAt(1)).toBe(0x2014);
|
|
27
|
+
expect(LINK_SEPARATOR[0]).toBe(" ");
|
|
28
|
+
expect(LINK_SEPARATOR[2]).toBe(" ");
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("parseLinkEntry", () => {
|
|
33
|
+
test("splits target and description on the first separator", () => {
|
|
34
|
+
expect(parseLinkEntry(link("alice", "the principal"))).toEqual({
|
|
35
|
+
target: "alice",
|
|
36
|
+
description: "the principal",
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("keeps later separators inside the description", () => {
|
|
41
|
+
const why = `why${LINK_SEPARATOR}with a second dash`;
|
|
42
|
+
expect(parseLinkEntry(link("alice", why))).toEqual({
|
|
43
|
+
target: "alice",
|
|
44
|
+
description: why,
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("a bare slug carries no description; whitespace is trimmed", () => {
|
|
49
|
+
expect(parseLinkEntry(" alice ")).toEqual({
|
|
50
|
+
target: "alice",
|
|
51
|
+
description: undefined,
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("an empty annotation after the separator reads as no description", () => {
|
|
56
|
+
expect(parseLinkEntry(`alice${LINK_SEPARATOR} `)).toEqual({
|
|
57
|
+
target: "alice",
|
|
58
|
+
description: undefined,
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("wikilinkTarget / extractWikilinkTargets", () => {
|
|
64
|
+
test("strips |display and #anchor suffixes and trims", () => {
|
|
65
|
+
expect(wikilinkTarget("alice")).toBe("alice");
|
|
66
|
+
expect(wikilinkTarget("alice|Alice B.")).toBe("alice");
|
|
67
|
+
expect(wikilinkTarget("alice#role")).toBe("alice");
|
|
68
|
+
expect(wikilinkTarget(" tools/vs-code#setup|editor ")).toBe(
|
|
69
|
+
"tools/vs-code",
|
|
70
|
+
);
|
|
71
|
+
expect(wikilinkTarget("|label")).toBe("");
|
|
72
|
+
expect(wikilinkTarget("#anchor")).toBe("");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("extracts every target in order, dropping empty ones, keeping duplicates", () => {
|
|
76
|
+
const body =
|
|
77
|
+
"See [[alice]] and [[bob|Bob]] then [[alice#role]] but not [[|x]] or [[#y]].";
|
|
78
|
+
expect(extractWikilinkTargets(body)).toEqual(["alice", "bob", "alice"]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("the regex is global and captures the raw inner text", () => {
|
|
82
|
+
const matches = [..."[[a|A]] [[b#c]]".matchAll(WIKILINK_REGEX)].map(
|
|
83
|
+
(m) => m[1],
|
|
84
|
+
);
|
|
85
|
+
expect(matches).toEqual(["a|A", "b#c"]);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("findDanglingLinks", () => {
|
|
90
|
+
const known = new Set(["alice", "bob", "hub", "skills/deploy"]);
|
|
91
|
+
|
|
92
|
+
test("reports links:, wikilink, and edges: targets absent from the known set", () => {
|
|
93
|
+
const result = findDanglingLinks(
|
|
94
|
+
[
|
|
95
|
+
{
|
|
96
|
+
slug: "hub",
|
|
97
|
+
frontmatter: {
|
|
98
|
+
links: [link("alice", "principal"), link("atl-1291", "ticket")],
|
|
99
|
+
edges: ["bob", "ghost-edge"],
|
|
100
|
+
},
|
|
101
|
+
body: "Lead mentions [[alice]] and [[ghost-wiki|Ghost]].",
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
known,
|
|
105
|
+
isValidSlug,
|
|
106
|
+
);
|
|
107
|
+
expect(result).toEqual([
|
|
108
|
+
{ from: "hub", to: "atl-1291", kind: "links" },
|
|
109
|
+
{ from: "hub", to: "ghost-edge", kind: "edges" },
|
|
110
|
+
{ from: "hub", to: "ghost-wiki", kind: "wikilink" },
|
|
111
|
+
]);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("resolves against synthetic slugs too when the caller counts them as known", () => {
|
|
115
|
+
expect(
|
|
116
|
+
findDanglingLinks(
|
|
117
|
+
[
|
|
118
|
+
{
|
|
119
|
+
slug: "alice",
|
|
120
|
+
frontmatter: { links: [link("skills/deploy")] },
|
|
121
|
+
body: "",
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
known,
|
|
125
|
+
isValidSlug,
|
|
126
|
+
),
|
|
127
|
+
).toEqual([]);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("ignores self-references and empty targets, dedupes per (from, to, kind)", () => {
|
|
131
|
+
const result = findDanglingLinks(
|
|
132
|
+
[
|
|
133
|
+
{
|
|
134
|
+
slug: "alice",
|
|
135
|
+
frontmatter: {
|
|
136
|
+
links: [
|
|
137
|
+
link("alice", "self"),
|
|
138
|
+
link("ghost"),
|
|
139
|
+
link("ghost", "twice"),
|
|
140
|
+
],
|
|
141
|
+
},
|
|
142
|
+
body: "[[alice]] [[ghost]] [[ghost#sec]] [[|nothing]]",
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
known,
|
|
146
|
+
isValidSlug,
|
|
147
|
+
);
|
|
148
|
+
expect(result).toEqual([
|
|
149
|
+
{ from: "alice", to: "ghost", kind: "links" },
|
|
150
|
+
{ from: "alice", to: "ghost", kind: "wikilink" },
|
|
151
|
+
]);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("output is sorted by (from, to, kind) regardless of page order", () => {
|
|
155
|
+
const pages = [
|
|
156
|
+
{ slug: "zed", frontmatter: { links: [link("nope")] }, body: "[[nope]]" },
|
|
157
|
+
{ slug: "amy", frontmatter: {}, body: "[[zzz]] [[aaa]]" },
|
|
158
|
+
];
|
|
159
|
+
const forward = findDanglingLinks(pages, known, isValidSlug);
|
|
160
|
+
const reversed = findDanglingLinks(
|
|
161
|
+
[...pages].reverse(),
|
|
162
|
+
known,
|
|
163
|
+
isValidSlug,
|
|
164
|
+
);
|
|
165
|
+
expect(forward).toEqual(reversed);
|
|
166
|
+
expect(forward.map((d) => `${d.from}>${d.to}>${d.kind}`)).toEqual([
|
|
167
|
+
"amy>aaa>wikilink",
|
|
168
|
+
"amy>zzz>wikilink",
|
|
169
|
+
"zed>nope>links",
|
|
170
|
+
"zed>nope>wikilink",
|
|
171
|
+
]);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("targets that are not slug-shaped are not references at all", () => {
|
|
175
|
+
// Shell tests, code, and prose in double brackets must not be reported
|
|
176
|
+
// (and so never reach the repair step that would unwrap them).
|
|
177
|
+
expect(
|
|
178
|
+
findDanglingLinks(
|
|
179
|
+
[
|
|
180
|
+
{
|
|
181
|
+
slug: "procs/deploy",
|
|
182
|
+
frontmatter: { links: ["Not A Slug"] },
|
|
183
|
+
body: "Run `if [[ -f foo ]]; then` and [[ Some Prose ]] then [[real-missing]].",
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
known,
|
|
187
|
+
isValidSlug,
|
|
188
|
+
),
|
|
189
|
+
).toEqual([{ from: "procs/deploy", to: "real-missing", kind: "wikilink" }]);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("a fully resolved corpus yields an empty list", () => {
|
|
193
|
+
expect(
|
|
194
|
+
findDanglingLinks(
|
|
195
|
+
[
|
|
196
|
+
{
|
|
197
|
+
slug: "alice",
|
|
198
|
+
frontmatter: { links: [link("bob", "peer")] },
|
|
199
|
+
body: "[[hub]]",
|
|
200
|
+
},
|
|
201
|
+
{ slug: "bob", frontmatter: { edges: ["alice"] }, body: "" },
|
|
202
|
+
],
|
|
203
|
+
known,
|
|
204
|
+
isValidSlug,
|
|
205
|
+
),
|
|
206
|
+
).toEqual([]);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
@@ -66,8 +66,10 @@ const {
|
|
|
66
66
|
CORE_PAGES_CONSOLIDATION_SECTION,
|
|
67
67
|
CORE_PAGES_PLACEHOLDER,
|
|
68
68
|
CUTOFF_PLACEHOLDER,
|
|
69
|
+
DANGLING_LINKS_PLACEHOLDER,
|
|
69
70
|
PARSE_FAILURES_PLACEHOLDER,
|
|
70
71
|
renderConsolidationPrompt,
|
|
72
|
+
renderDanglingLinksSection,
|
|
71
73
|
renderParseFailuresSection,
|
|
72
74
|
resolveConsolidationPrompt,
|
|
73
75
|
} = await import("../prompts/consolidation.js");
|
|
@@ -107,7 +109,15 @@ const bundledPrompt = (includeCorePagesSection = false): string =>
|
|
|
107
109
|
? (CORE_PAGES_CONSOLIDATION_SECTION as string)
|
|
108
110
|
: "",
|
|
109
111
|
)
|
|
110
|
-
.replaceAll(PARSE_FAILURES_PLACEHOLDER, "")
|
|
112
|
+
.replaceAll(PARSE_FAILURES_PLACEHOLDER, "")
|
|
113
|
+
.replaceAll(DANGLING_LINKS_PLACEHOLDER, "");
|
|
114
|
+
|
|
115
|
+
/** Sample dangling links for the repair-section tests. */
|
|
116
|
+
const SAMPLE_DANGLING = [
|
|
117
|
+
{ from: "atl-1290", to: "atl-1291", kind: "links" as const },
|
|
118
|
+
{ from: "people/alice", to: "ghost-page", kind: "wikilink" as const },
|
|
119
|
+
{ from: "arcs/day", to: "old-slug", kind: "edges" as const },
|
|
120
|
+
];
|
|
111
121
|
|
|
112
122
|
beforeEach(() => {
|
|
113
123
|
warnCalls.length = 0;
|
|
@@ -316,6 +326,153 @@ describe("resolveConsolidationPrompt — parse-failures repair section", () => {
|
|
|
316
326
|
});
|
|
317
327
|
});
|
|
318
328
|
|
|
329
|
+
describe("resolveConsolidationPrompt: dangling-links repair section", () => {
|
|
330
|
+
test("no dangling links: no section and no placeholder residue, both article shapes", () => {
|
|
331
|
+
expect(renderDanglingLinksSection([])).toBe("");
|
|
332
|
+
for (const articleShape of ["v2", "v3"] as const) {
|
|
333
|
+
const result = renderConsolidationPrompt(CUTOFF, {
|
|
334
|
+
includeCorePagesSection: false,
|
|
335
|
+
articleShape,
|
|
336
|
+
});
|
|
337
|
+
expect(result).not.toContain("resolve dangling links");
|
|
338
|
+
expect(result).not.toContain(DANGLING_LINKS_PLACEHOLDER);
|
|
339
|
+
expect(result).toContain("# The work\n\n## 1. Read the buffer");
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test("with dangling links: section renders once, before step 1, one line per reference with its kind", () => {
|
|
344
|
+
const result = renderConsolidationPrompt(CUTOFF, {
|
|
345
|
+
...NO_CORE,
|
|
346
|
+
danglingLinks: SAMPLE_DANGLING,
|
|
347
|
+
});
|
|
348
|
+
expect(result.split("resolve dangling links").length - 1).toBe(1);
|
|
349
|
+
const sectionAt = result.indexOf("## 0. FIRST: resolve dangling links");
|
|
350
|
+
expect(sectionAt).toBeGreaterThan(result.indexOf("# The work"));
|
|
351
|
+
expect(sectionAt).toBeLessThan(result.indexOf("## 1. Read the buffer"));
|
|
352
|
+
expect(result).toContain(
|
|
353
|
+
"- `memory/concepts/atl-1290.md` → `atl-1291` (frontmatter `links:` entry)",
|
|
354
|
+
);
|
|
355
|
+
expect(result).toContain(
|
|
356
|
+
"- `memory/concepts/people/alice.md` → `ghost-page` (inline `[[wikilink]]` in the body)",
|
|
357
|
+
);
|
|
358
|
+
expect(result).toContain(
|
|
359
|
+
"- `memory/concepts/arcs/day.md` → `old-slug` (frontmatter `edges:` entry)",
|
|
360
|
+
);
|
|
361
|
+
// The three resolutions the agent must choose between.
|
|
362
|
+
expect(result).toContain("spawn it, a stub is fine, and keep the link");
|
|
363
|
+
expect(result).toContain("repoint the reference at the surviving slug");
|
|
364
|
+
expect(result).toContain("Prose stays prose");
|
|
365
|
+
expect(result).not.toContain("more, reported next pass");
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
test("renders after the parse-failures section when both are present", () => {
|
|
369
|
+
const result = renderConsolidationPrompt(CUTOFF, {
|
|
370
|
+
...NO_CORE,
|
|
371
|
+
parseFailures: SAMPLE_FAILURES,
|
|
372
|
+
danglingLinks: SAMPLE_DANGLING,
|
|
373
|
+
});
|
|
374
|
+
expect(result.indexOf("repair unreadable pages")).toBeLessThan(
|
|
375
|
+
result.indexOf("resolve dangling links"),
|
|
376
|
+
);
|
|
377
|
+
expect(result.indexOf("resolve dangling links")).toBeLessThan(
|
|
378
|
+
result.indexOf("## 1. Read the buffer"),
|
|
379
|
+
);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
test("caps the rendered list at 40 and counts the remainder", () => {
|
|
383
|
+
const many = Array.from({ length: 45 }, (_, i) => ({
|
|
384
|
+
from: `page-${String(i).padStart(2, "0")}`,
|
|
385
|
+
to: `missing-${i}`,
|
|
386
|
+
kind: "links" as const,
|
|
387
|
+
}));
|
|
388
|
+
const section = renderDanglingLinksSection(many);
|
|
389
|
+
const listLines = section
|
|
390
|
+
.split("\n")
|
|
391
|
+
.filter((line) => line.startsWith("- `memory/concepts/"));
|
|
392
|
+
expect(listLines).toHaveLength(40);
|
|
393
|
+
expect(section).toContain("`memory/concepts/page-39.md`");
|
|
394
|
+
expect(section).not.toContain("`memory/concepts/page-40.md`");
|
|
395
|
+
expect(section).toContain("...and 5 more, reported next pass");
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
test("sanitizes injection-shaped slugs and targets: newlines, backticks, oversize", () => {
|
|
399
|
+
// Both ends of a dangling link are page content: the source is a
|
|
400
|
+
// filename, the target is whatever the page wrote inside `[[...]]` or
|
|
401
|
+
// `links:`. Raw, either could break out of the list item.
|
|
402
|
+
const section = renderDanglingLinksSection([
|
|
403
|
+
{
|
|
404
|
+
from: "evil\n\n## Injected heading\n- `nested`",
|
|
405
|
+
to: "target`code`\n## also injected",
|
|
406
|
+
kind: "wikilink",
|
|
407
|
+
},
|
|
408
|
+
{ from: "s".repeat(500), to: "t".repeat(500), kind: "links" },
|
|
409
|
+
]);
|
|
410
|
+
const listLines = section
|
|
411
|
+
.split("\n")
|
|
412
|
+
.filter((line) => line.startsWith("- `memory/concepts/"));
|
|
413
|
+
expect(listLines).toHaveLength(2);
|
|
414
|
+
for (const line of listLines) {
|
|
415
|
+
// Source path, target, and the kind label's own inline-code span:
|
|
416
|
+
// six template backticks, no injected ones.
|
|
417
|
+
expect((line.match(/`/g) ?? []).length).toBe(6);
|
|
418
|
+
}
|
|
419
|
+
const headingLines = section
|
|
420
|
+
.split("\n")
|
|
421
|
+
.filter((line) => line.startsWith("## "));
|
|
422
|
+
expect(headingLines).toHaveLength(1);
|
|
423
|
+
expect(section).toContain("memory/concepts/evil ");
|
|
424
|
+
expect(section).toContain(`${"s".repeat(199)}…`);
|
|
425
|
+
expect(section).toContain(`${"t".repeat(199)}…`);
|
|
426
|
+
expect(section).not.toContain("s".repeat(200));
|
|
427
|
+
expect(section).not.toContain("t".repeat(200));
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
test("override containing the placeholder: substituted in place", () => {
|
|
431
|
+
const path = join(tmpWorkspace, "custom-prompt.md");
|
|
432
|
+
writeFileSync(path, "Top\n{{DANGLING_LINKS_SECTION}}Bottom\n");
|
|
433
|
+
|
|
434
|
+
const result = resolveConsolidationPrompt(path, CUTOFF, {
|
|
435
|
+
...NO_CORE,
|
|
436
|
+
danglingLinks: SAMPLE_DANGLING,
|
|
437
|
+
});
|
|
438
|
+
expect(result).toContain("## 0. FIRST: resolve dangling links");
|
|
439
|
+
expect(result.indexOf("resolve dangling links")).toBeLessThan(
|
|
440
|
+
result.indexOf("Bottom"),
|
|
441
|
+
);
|
|
442
|
+
expect(result).not.toContain(DANGLING_LINKS_PLACEHOLDER);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
test("override without either placeholder: both non-empty repair sections are appended, in order", () => {
|
|
446
|
+
const path = join(tmpWorkspace, "no-placeholder.md");
|
|
447
|
+
writeFileSync(path, "My custom prompt {{CUTOFF}}\n");
|
|
448
|
+
|
|
449
|
+
const result = resolveConsolidationPrompt(path, CUTOFF, {
|
|
450
|
+
...NO_CORE,
|
|
451
|
+
parseFailures: SAMPLE_FAILURES,
|
|
452
|
+
danglingLinks: SAMPLE_DANGLING,
|
|
453
|
+
});
|
|
454
|
+
expect(result.startsWith(`My custom prompt ${CUTOFF}`)).toBe(true);
|
|
455
|
+
const parseAt = result.indexOf("repair unreadable pages");
|
|
456
|
+
const danglingAt = result.indexOf("resolve dangling links");
|
|
457
|
+
expect(parseAt).toBeGreaterThan(0);
|
|
458
|
+
expect(danglingAt).toBeGreaterThan(parseAt);
|
|
459
|
+
expect(result).toContain("`memory/concepts/atl-1290.md`");
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
test("override with only the parse placeholder + dangling links: dangling section appended", () => {
|
|
463
|
+
const path = join(tmpWorkspace, "custom-prompt.md");
|
|
464
|
+
writeFileSync(path, "Top\n{{PARSE_FAILURES_SECTION}}Bottom\n");
|
|
465
|
+
|
|
466
|
+
const result = resolveConsolidationPrompt(path, CUTOFF, {
|
|
467
|
+
...NO_CORE,
|
|
468
|
+
danglingLinks: SAMPLE_DANGLING,
|
|
469
|
+
});
|
|
470
|
+
expect(result.indexOf("Bottom")).toBeLessThan(
|
|
471
|
+
result.indexOf("resolve dangling links"),
|
|
472
|
+
);
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
|
|
319
476
|
describe("resolveConsolidationPrompt — with override", () => {
|
|
320
477
|
test("loads an absolute path verbatim and substitutes {{CUTOFF}}", () => {
|
|
321
478
|
const path = join(tmpWorkspace, "custom-prompt.md");
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* - Reads the four files in canonical order and joins them under headings.
|
|
6
6
|
* - Skips empty / missing files.
|
|
7
7
|
* - Returns null when every file is empty or missing.
|
|
8
|
+
* - Caps the Buffer section at `consolidation_max_buffer_lines`.
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
11
|
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
@@ -33,17 +34,23 @@ mock.module("../../../../../util/logger.js", () => ({
|
|
|
33
34
|
|
|
34
35
|
let configMemoryV2Enabled = true;
|
|
35
36
|
let configMemoryEnabled = true;
|
|
37
|
+
// Mirrors the `memory.v2.consolidation_max_buffer_lines` schema default; the
|
|
38
|
+
// Buffer-cap tests move it.
|
|
39
|
+
let configMaxBufferLines: number | null = 100;
|
|
36
40
|
|
|
37
|
-
// static-context reads its gates
|
|
41
|
+
// static-context reads its gates and the substrate tuning through the
|
|
42
|
+
// plugin's own config accessor.
|
|
38
43
|
mock.module("../../config.js", () => ({
|
|
39
44
|
getMemoryConfig: () => ({
|
|
40
45
|
enabled: configMemoryEnabled,
|
|
41
|
-
v2: {
|
|
46
|
+
v2: {
|
|
47
|
+
enabled: configMemoryV2Enabled,
|
|
48
|
+
consolidation_max_buffer_lines: configMaxBufferLines,
|
|
49
|
+
},
|
|
42
50
|
}),
|
|
43
51
|
}));
|
|
44
52
|
|
|
45
|
-
const { readMemoryV2StaticContent
|
|
46
|
-
await import("../static-context.js");
|
|
53
|
+
const { readMemoryV2StaticContent } = await import("../static-context.js");
|
|
47
54
|
|
|
48
55
|
const MEMORY_FILES = [
|
|
49
56
|
"essentials.md",
|
|
@@ -70,6 +77,7 @@ describe("readMemoryV2StaticContent", () => {
|
|
|
70
77
|
mkdirSync(TEST_DIR, { recursive: true });
|
|
71
78
|
configMemoryV2Enabled = true;
|
|
72
79
|
configMemoryEnabled = true;
|
|
80
|
+
configMaxBufferLines = 100;
|
|
73
81
|
});
|
|
74
82
|
|
|
75
83
|
afterEach(() => {
|
|
@@ -165,61 +173,190 @@ describe("readMemoryV2StaticContent", () => {
|
|
|
165
173
|
});
|
|
166
174
|
});
|
|
167
175
|
|
|
168
|
-
describe("
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
isTrustedActor: false,
|
|
183
|
-
}),
|
|
184
|
-
).toBe(true);
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
test("allows turns with no trust context (work-item task runs, internal background)", () => {
|
|
188
|
-
expect(
|
|
189
|
-
shouldExposePersonalMemory({
|
|
190
|
-
sourceChannel: undefined,
|
|
191
|
-
isTrustedActor: false,
|
|
192
|
-
}),
|
|
193
|
-
).toBe(true);
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
const REMOTE_CHANNELS = [
|
|
197
|
-
"phone",
|
|
198
|
-
"slack",
|
|
199
|
-
"telegram",
|
|
200
|
-
"whatsapp",
|
|
201
|
-
"email",
|
|
202
|
-
] as const;
|
|
203
|
-
|
|
204
|
-
test("allows guardian-trusted remote channels (user's own phone/Slack)", () => {
|
|
205
|
-
for (const channel of REMOTE_CHANNELS) {
|
|
206
|
-
expect(
|
|
207
|
-
shouldExposePersonalMemory({
|
|
208
|
-
sourceChannel: channel,
|
|
209
|
-
isTrustedActor: true,
|
|
210
|
-
}),
|
|
211
|
-
).toBe(true);
|
|
212
|
-
}
|
|
176
|
+
describe("readMemoryV2StaticContent Buffer cap", () => {
|
|
177
|
+
/** `n` timestamped buffer entries, oldest first, in `remember()`'s shape. */
|
|
178
|
+
function bufferEntries(n: number): string {
|
|
179
|
+
return Array.from(
|
|
180
|
+
{ length: n },
|
|
181
|
+
(_, i) => `- [Jan 1, 9:00 AM] entry-${i}`,
|
|
182
|
+
).join("\n");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
beforeEach(() => {
|
|
186
|
+
mkdirSync(TEST_DIR, { recursive: true });
|
|
187
|
+
configMemoryV2Enabled = true;
|
|
188
|
+
configMemoryEnabled = true;
|
|
189
|
+
configMaxBufferLines = 10;
|
|
213
190
|
});
|
|
214
191
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
192
|
+
afterEach(() => {
|
|
193
|
+
cleanupMemoryDir();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("passes a buffer at or under the cap through byte-identically", () => {
|
|
197
|
+
const buffer = bufferEntries(10);
|
|
198
|
+
writeMemoryFile("buffer.md", buffer);
|
|
199
|
+
|
|
200
|
+
expect(readMemoryV2StaticContent()).toBe(`## Buffer\n\n${buffer}`);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("keeps the newest entries and drops the oldest when over the cap", () => {
|
|
204
|
+
writeMemoryFile("buffer.md", bufferEntries(30));
|
|
205
|
+
|
|
206
|
+
const text = readMemoryV2StaticContent()!;
|
|
207
|
+
expect(text).toContain(
|
|
208
|
+
"(Older entries trimmed. Read memory/buffer.md for the full backlog.)",
|
|
209
|
+
);
|
|
210
|
+
// The cap counts non-empty lines, matching the scheduler's
|
|
211
|
+
// `countBufferLines`, so exactly the newest 10 entries survive.
|
|
212
|
+
const kept = text
|
|
213
|
+
.split("\n")
|
|
214
|
+
.filter((line) => line.startsWith("- ["))
|
|
215
|
+
.map((line) => line.slice(line.lastIndexOf(" ") + 1));
|
|
216
|
+
expect(kept).toEqual(
|
|
217
|
+
Array.from({ length: 10 }, (_, i) => `entry-${i + 20}`),
|
|
218
|
+
);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("never opens mid-entry when the cap lands inside a multiline fact", () => {
|
|
222
|
+
// The 5-line fact straddles the cap: counting back 10 non-empty lines
|
|
223
|
+
// lands on one of its continuation lines. Injecting from there would show
|
|
224
|
+
// orphan bullets with no timestamp and no opening clause.
|
|
225
|
+
const multiline = [
|
|
226
|
+
"- [Jan 1, 9:00 AM] the straddling fact",
|
|
227
|
+
" - [ ] a checklist item inside the fact",
|
|
228
|
+
" continuation prose",
|
|
229
|
+
" - another bullet",
|
|
230
|
+
" closing line",
|
|
231
|
+
].join("\n");
|
|
232
|
+
writeMemoryFile(
|
|
233
|
+
"buffer.md",
|
|
234
|
+
`${bufferEntries(20)}\n${multiline}\n${bufferEntries(8)}`,
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
const text = readMemoryV2StaticContent()!;
|
|
238
|
+
const body = text.slice(text.indexOf("buffer.md for the full backlog.)\n"));
|
|
239
|
+
const firstLine = body.split("\n")[1]!;
|
|
240
|
+
expect(firstLine).toMatch(/^- \[Jan 1, 9:00 AM\]/);
|
|
241
|
+
// The partial entry is dropped whole: none of its continuation lines
|
|
242
|
+
// survive on their own.
|
|
243
|
+
expect(text).not.toContain("continuation prose");
|
|
244
|
+
expect(text).not.toContain("the straddling fact");
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test("keeps the newest entry attributable when its body alone exceeds the cap", () => {
|
|
248
|
+
// The newest entry has no successor to fall back to: counting back 10
|
|
249
|
+
// non-empty lines lands inside its body and there is no later timestamped
|
|
250
|
+
// entry to open on. Dropping it would leave the section with no facts at
|
|
251
|
+
// all, so the entry's opening line is kept and its head elided.
|
|
252
|
+
writeMemoryFile(
|
|
253
|
+
"buffer.md",
|
|
254
|
+
[
|
|
255
|
+
...Array.from(
|
|
256
|
+
{ length: 20 },
|
|
257
|
+
(_, i) => `- [Jan 1, 9:00 AM] entry-${i}`,
|
|
258
|
+
),
|
|
259
|
+
"- [Jan 2, 9:00 AM] the oversized fact",
|
|
260
|
+
...Array.from({ length: 14 }, (_, i) => ` body line ${i}`),
|
|
261
|
+
].join("\n"),
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
const text = readMemoryV2StaticContent()!;
|
|
265
|
+
const body = text
|
|
266
|
+
.slice(text.indexOf("full backlog.)\n") + "full backlog.)\n".length)
|
|
267
|
+
.split("\n")
|
|
268
|
+
.filter((line) => line.trim().length > 0);
|
|
269
|
+
|
|
270
|
+
// Opens on the entry's own timestamped line, never on an orphan body line.
|
|
271
|
+
expect(body[0]).toBe("- [Jan 2, 9:00 AM] the oversized fact");
|
|
272
|
+
expect(body[1]).toBe(
|
|
273
|
+
"(This entry's body was trimmed. Read memory/buffer.md for the rest of it.)",
|
|
274
|
+
);
|
|
275
|
+
// The retained tail is the newest end of the body, not its head.
|
|
276
|
+
expect(body.at(-1)).toBe(" body line 13");
|
|
277
|
+
expect(body).toContain(" body line 4");
|
|
278
|
+
expect(body).not.toContain(" body line 3");
|
|
279
|
+
// Bounded at the cap plus exactly the opening line and the marker, so the
|
|
280
|
+
// oversized entry cannot reintroduce an unbounded injection.
|
|
281
|
+
expect(body).toHaveLength(12);
|
|
282
|
+
// The older entries are still dropped.
|
|
283
|
+
expect(text).not.toContain("entry-19");
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test("does not claim a trim when the newest entry's body exactly fills the cap", () => {
|
|
287
|
+
// The cut lands on the line right after the opening, so nothing of the
|
|
288
|
+
// entry was elided and the marker would be a lie.
|
|
289
|
+
writeMemoryFile(
|
|
290
|
+
"buffer.md",
|
|
291
|
+
[
|
|
292
|
+
...Array.from(
|
|
293
|
+
{ length: 20 },
|
|
294
|
+
(_, i) => `- [Jan 1, 9:00 AM] entry-${i}`,
|
|
295
|
+
),
|
|
296
|
+
"- [Jan 2, 9:00 AM] the exact-fit fact",
|
|
297
|
+
...Array.from({ length: 10 }, (_, i) => ` body line ${i}`),
|
|
298
|
+
].join("\n"),
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
const text = readMemoryV2StaticContent()!;
|
|
302
|
+
const body = text
|
|
303
|
+
.slice(text.indexOf("full backlog.)\n") + "full backlog.)\n".length)
|
|
304
|
+
.split("\n")
|
|
305
|
+
.filter((line) => line.trim().length > 0);
|
|
306
|
+
|
|
307
|
+
expect(body[0]).toBe("- [Jan 2, 9:00 AM] the exact-fit fact");
|
|
308
|
+
expect(text).not.toContain("This entry's body was trimmed");
|
|
309
|
+
// The entry survives whole: opening line plus its full 10-line body.
|
|
310
|
+
expect(body).toHaveLength(11);
|
|
311
|
+
expect(body.at(-1)).toBe(" body line 9");
|
|
312
|
+
expect(body).toContain(" body line 0");
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
test("keeps a multiline entry intact when it sits fully inside the cap", () => {
|
|
316
|
+
const multiline = [
|
|
317
|
+
"- [Jan 1, 9:00 AM] a fact with a body",
|
|
318
|
+
" second line of the same fact",
|
|
319
|
+
].join("\n");
|
|
320
|
+
writeMemoryFile("buffer.md", `${bufferEntries(20)}\n${multiline}`);
|
|
321
|
+
|
|
322
|
+
const text = readMemoryV2StaticContent()!;
|
|
323
|
+
expect(text).toContain("a fact with a body");
|
|
324
|
+
expect(text).toContain("second line of the same fact");
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test("falls back to the line cut for a buffer with no timestamped entries", () => {
|
|
328
|
+
// A hand-written buffer that never went through `remember()` has no entry
|
|
329
|
+
// structure to preserve, so the line-based cut stands rather than dropping
|
|
330
|
+
// everything.
|
|
331
|
+
const handWritten = Array.from(
|
|
332
|
+
{ length: 30 },
|
|
333
|
+
(_, i) => `just a line ${i}`,
|
|
334
|
+
).join("\n");
|
|
335
|
+
writeMemoryFile("buffer.md", handWritten);
|
|
336
|
+
|
|
337
|
+
const text = readMemoryV2StaticContent()!;
|
|
338
|
+
expect(text).toContain("just a line 29");
|
|
339
|
+
expect(text).not.toContain("just a line 0");
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
test("leaves the buffer unbounded when the size trigger is disabled", () => {
|
|
343
|
+
configMaxBufferLines = null;
|
|
344
|
+
const buffer = bufferEntries(30);
|
|
345
|
+
writeMemoryFile("buffer.md", buffer);
|
|
346
|
+
|
|
347
|
+
expect(readMemoryV2StaticContent()).toBe(`## Buffer\n\n${buffer}`);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
test("caps only the Buffer section, never the curated views", () => {
|
|
351
|
+
const long = Array.from({ length: 30 }, (_, i) => `line-${i}`).join("\n");
|
|
352
|
+
writeMemoryFile("essentials.md", long);
|
|
353
|
+
writeMemoryFile("threads.md", long);
|
|
354
|
+
writeMemoryFile("recent.md", long);
|
|
355
|
+
writeMemoryFile("buffer.md", bufferEntries(30));
|
|
356
|
+
|
|
357
|
+
const lines = readMemoryV2StaticContent()!.split("\n");
|
|
358
|
+
expect(lines.filter((line) => line === "line-0")).toHaveLength(3);
|
|
359
|
+
expect(lines.filter((line) => line === "line-29")).toHaveLength(3);
|
|
360
|
+
expect(lines.some((line) => line.endsWith("entry-0"))).toBe(false);
|
|
224
361
|
});
|
|
225
362
|
});
|