@selesai/code 0.13.15 → 0.13.18
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/CHANGELOG.md +37 -0
- package/README.md +2 -14
- package/dist/bun/cli.d.ts +3 -1
- package/dist/bun/cli.js +3 -9
- package/dist/bun/{register-bedrock.js → runtime-setup.js} +5 -0
- package/dist/bun/sandbox-env-setup.d.ts +1 -0
- package/dist/bun/sandbox-env-setup.js +3 -0
- package/dist/cli/auth-command.d.ts +23 -0
- package/dist/cli/auth-command.js +102 -0
- package/dist/cli/config-selector.js +2 -1
- package/dist/cli/setup.d.ts +1 -0
- package/dist/cli/setup.js +11 -0
- package/dist/cli.js +2 -14
- package/dist/config.d.ts +3 -2
- package/dist/config.js +9 -4
- package/dist/core/agent-session-runtime.js +15 -7
- package/dist/core/agent-session.d.ts +2 -6
- package/dist/core/agent-session.js +15 -4
- package/dist/core/compaction/branch-summarization.d.ts +1 -1
- package/dist/core/compaction/branch-summarization.js +2 -1
- package/dist/core/extensions/loader.js +3 -0
- package/dist/core/extensions/types.d.ts +16 -2
- package/dist/core/http-dispatcher.js +2 -0
- package/dist/core/keybindings.d.ts +5 -0
- package/dist/core/keybindings.js +4 -0
- package/dist/core/messages.d.ts +1 -1
- package/dist/core/model-config.d.ts +15 -0
- package/dist/core/model-config.js +3 -0
- package/dist/core/model-runtime.d.ts +1 -0
- package/dist/core/model-runtime.js +5 -2
- package/dist/core/provider-composer.js +8 -2
- package/dist/core/session-export.d.ts +3 -0
- package/dist/core/session-export.js +31 -0
- package/dist/core/session-manager.d.ts +3 -2
- package/dist/core/session-manager.js +40 -13
- package/dist/core/skills.d.ts +1 -1
- package/dist/core/skills.js +4 -2
- package/dist/core/system-prompt.js +10 -10
- package/dist/core/tools/bash.js +3 -117
- package/dist/core/tools/edit.d.ts +1 -12
- package/dist/core/tools/edit.js +5 -163
- package/dist/core/tools/find.js +4 -55
- package/dist/core/tools/grep.js +4 -60
- package/dist/core/tools/ls.js +4 -49
- package/dist/core/tools/read.d.ts +4 -0
- package/dist/core/tools/read.js +10 -122
- package/dist/core/tools/renderers/bash.d.ts +11 -0
- package/dist/core/tools/renderers/bash.js +129 -0
- package/dist/core/tools/renderers/edit.d.ts +22 -0
- package/dist/core/tools/renderers/edit.js +166 -0
- package/dist/core/tools/renderers/find.d.ts +9 -0
- package/dist/core/tools/renderers/find.js +63 -0
- package/dist/core/tools/renderers/grep.d.ts +9 -0
- package/dist/core/tools/renderers/grep.js +68 -0
- package/dist/core/tools/renderers/index.d.ts +27 -0
- package/dist/core/tools/renderers/index.js +46 -0
- package/dist/core/tools/renderers/ls.d.ts +9 -0
- package/dist/core/tools/renderers/ls.js +57 -0
- package/dist/core/tools/renderers/read.d.ts +10 -0
- package/dist/core/tools/renderers/read.js +128 -0
- package/dist/core/tools/renderers/write.d.ts +9 -0
- package/dist/core/tools/renderers/write.js +152 -0
- package/dist/core/tools/write.js +5 -146
- package/dist/extensions/capability-gateway/index.ts +12 -1
- package/dist/extensions/capability-gateway/integration.test.ts +23 -0
- package/dist/extensions/pi-hermes-memory/CHANGELOG.md +18 -0
- package/dist/extensions/pi-hermes-memory/README.md +16 -12
- package/dist/extensions/pi-hermes-memory/docs/ROADMAP.md +1 -1
- package/dist/extensions/pi-hermes-memory/package-lock.json +587 -62
- package/dist/extensions/pi-hermes-memory/package.json +4 -4
- package/dist/extensions/pi-hermes-memory/src/config.ts +32 -0
- package/dist/extensions/pi-hermes-memory/src/constants.ts +12 -2
- package/dist/extensions/pi-hermes-memory/src/handlers/background-review.ts +109 -51
- package/dist/extensions/pi-hermes-memory/src/handlers/index-sessions.ts +9 -3
- package/dist/extensions/pi-hermes-memory/src/handlers/review-memory-ops.ts +257 -98
- package/dist/extensions/pi-hermes-memory/src/handlers/session-backfill.ts +9 -2
- package/dist/extensions/pi-hermes-memory/src/handlers/sync-markdown-memories.ts +5 -0
- package/dist/extensions/pi-hermes-memory/src/index.ts +22 -2
- package/dist/extensions/pi-hermes-memory/src/paths.ts +1 -1
- package/dist/extensions/pi-hermes-memory/src/store/db.ts +6 -0
- package/dist/extensions/pi-hermes-memory/src/store/fts-query.ts +58 -1
- package/dist/extensions/pi-hermes-memory/src/store/memory-store.ts +60 -8
- package/dist/extensions/pi-hermes-memory/src/store/session-indexer.ts +169 -7
- package/dist/extensions/pi-hermes-memory/src/store/session-search.ts +7 -21
- package/dist/extensions/pi-hermes-memory/src/store/sqlite-memory-store.ts +134 -15
- package/dist/extensions/pi-hermes-memory/src/tools/memory-search-tool.ts +4 -2
- package/dist/extensions/pi-hermes-memory/src/tools/memory-tool.ts +24 -9
- package/dist/extensions/pi-hermes-memory/src/types.ts +11 -0
- package/dist/extensions/pi-hermes-memory/tests/config.test.ts +42 -0
- package/dist/extensions/pi-hermes-memory/tests/handlers/background-review.test.ts +159 -3
- package/dist/extensions/pi-hermes-memory/tests/handlers/review-memory-ops.test.ts +452 -0
- package/dist/extensions/pi-hermes-memory/tests/handlers/session-backfill.test.ts +39 -0
- package/dist/extensions/pi-hermes-memory/tests/integration/corruption-recovery-integration.test.ts +91 -0
- package/dist/extensions/pi-hermes-memory/tests/store/db.test.ts +29 -0
- package/dist/extensions/pi-hermes-memory/tests/store/fts-query.test.ts +116 -0
- package/dist/extensions/pi-hermes-memory/tests/store/memory-store.test.ts +163 -0
- package/dist/extensions/pi-hermes-memory/tests/store/session-indexer.test.ts +233 -0
- package/dist/extensions/pi-hermes-memory/tests/store/session-search.test.ts +12 -0
- package/dist/extensions/pi-hermes-memory/tests/store/sqlite-memory-store.test.ts +175 -0
- package/dist/extensions/pi-hermes-memory/tests/store/sqlite-native.test.ts +2 -2
- package/dist/extensions/pi-hermes-memory/tests/tools/memory-search-tool.test.ts +22 -0
- package/dist/extensions/pi-hermes-memory/tests/tools/memory-tool.test.ts +37 -1
- package/dist/extensions/pi-intercom/CHANGELOG.md +13 -0
- package/dist/extensions/pi-intercom/README.md +16 -2
- package/dist/extensions/pi-intercom/broker/spawn.test.ts +19 -2
- package/dist/extensions/pi-intercom/broker/spawn.ts +4 -4
- package/dist/extensions/pi-intercom/index.ts +1 -1
- package/dist/extensions/pi-intercom/intercom.integration.test.ts +121 -2
- package/dist/extensions/pi-intercom/package.json +1 -1
- package/dist/extensions/pi-intercom/project-agent.test.ts +1 -1
- package/dist/extensions/pi-intercom/project-agent.ts +1 -6
- package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +7 -6
- package/dist/extensions/pi-rewind-hook/index.test.ts +7 -0
- package/dist/extensions/pi-subagents/.github/workflows/test.yml +36 -4
- package/dist/extensions/pi-subagents/CHANGELOG.md +120 -0
- package/dist/extensions/pi-subagents/README.md +3 -3
- package/dist/extensions/pi-subagents/agents/researcher.md +23 -13
- package/dist/extensions/pi-subagents/agents/reviewer.md +1 -1
- package/dist/extensions/pi-subagents/agents/scout.md +1 -1
- package/dist/extensions/pi-subagents/docs/agents.md +32 -16
- package/dist/extensions/pi-subagents/docs/configuration.md +41 -15
- package/dist/extensions/pi-subagents/docs/extension-api.md +109 -6
- package/dist/extensions/pi-subagents/docs/missions.md +2 -0
- package/dist/extensions/pi-subagents/docs/models.md +58 -1
- package/dist/extensions/pi-subagents/docs/observability.md +50 -12
- package/dist/extensions/pi-subagents/docs/tool-reference.md +17 -10
- package/dist/extensions/pi-subagents/docs/watchdog.md +1 -1
- package/dist/extensions/pi-subagents/docs/workflows.md +26 -9
- package/dist/extensions/pi-subagents/package-lock.json +747 -177
- package/dist/extensions/pi-subagents/package.json +6 -4
- package/dist/extensions/pi-subagents/runner-server-preload.mjs +13 -0
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +2 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +21 -4
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/multi-lane-orchestration.md +2 -0
- package/dist/extensions/pi-subagents/src/agents/advertised-agent-prompt.ts +63 -0
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +61 -16
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +2 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +8 -0
- package/dist/extensions/pi-subagents/src/api/capability-ceiling.ts +0 -1
- package/dist/extensions/pi-subagents/src/api/{pi-args.ts → child-tool-plan.ts} +1 -1
- package/dist/extensions/pi-subagents/src/api/preflight.ts +7 -4
- package/dist/extensions/pi-subagents/src/api/shared-types.ts +2 -2
- package/dist/extensions/pi-subagents/src/api/workflow-resources.ts +6 -0
- package/dist/extensions/pi-subagents/src/extension/config.ts +4 -2
- package/dist/extensions/pi-subagents/src/extension/doctor.ts +2 -10
- package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +9 -11
- package/dist/extensions/pi-subagents/src/extension/index.ts +99 -10
- package/dist/extensions/pi-subagents/src/extension/public-execution.ts +13 -0
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +7 -23
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +11 -8
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +25 -7
- package/dist/extensions/pi-subagents/src/integrations/pi-web-session-liveness.ts +73 -0
- package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +228 -136
- package/dist/extensions/pi-subagents/src/intercom/supervisor-ui.ts +244 -0
- package/dist/extensions/pi-subagents/src/missions/workflow-state.ts +37 -16
- package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +18 -18
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +51 -25
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +46 -3
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +14 -3
- package/dist/extensions/pi-subagents/src/runs/background/async-retention.ts +9 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +10 -12
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +18 -15
- package/dist/extensions/pi-subagents/src/runs/background/auto-drain.ts +40 -29
- package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +8 -0
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +79 -247
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +88 -12
- package/dist/extensions/pi-subagents/src/runs/background/owned-process-tree.ts +6 -6
- package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +24 -24
- package/dist/extensions/pi-subagents/src/runs/background/retained-nested-route-tracker.ts +96 -0
- package/dist/extensions/pi-subagents/src/runs/background/run-child-session.ts +642 -0
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +75 -6
- package/dist/extensions/pi-subagents/src/runs/background/runner-aliases.ts +163 -0
- package/dist/extensions/pi-subagents/src/runs/background/runner-child-launch.ts +86 -0
- package/dist/extensions/pi-subagents/src/runs/background/runner-child-sessions.ts +31 -0
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +18 -4
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +3 -1
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +597 -1092
- package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +3 -0
- package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +4 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +20 -17
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +296 -394
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +4 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +3 -1
- package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +9 -5
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +630 -283
- package/dist/extensions/pi-subagents/src/runs/foreground/workflow-detach-reconcile.ts +8 -5
- package/dist/extensions/pi-subagents/src/runs/foreground/workflow-foreground-steering.ts +78 -98
- package/dist/extensions/pi-subagents/src/runs/shared/abort-recovery.ts +3 -3
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +16 -3
- package/dist/extensions/pi-subagents/src/runs/shared/agent-contract.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/shared/async-status-projection.ts +47 -47
- package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +1 -2
- package/dist/extensions/pi-subagents/src/runs/shared/child-hooks.ts +174 -0
- package/dist/extensions/pi-subagents/src/runs/shared/child-identity.ts +14 -3
- package/dist/extensions/pi-subagents/src/runs/shared/child-launch.ts +319 -0
- package/dist/extensions/pi-subagents/src/runs/shared/child-lifecycle.ts +25 -0
- package/dist/extensions/pi-subagents/src/runs/shared/child-runtime-config.ts +126 -0
- package/dist/extensions/pi-subagents/src/runs/shared/child-session.ts +373 -0
- package/dist/extensions/pi-subagents/src/runs/shared/child-tool-plan.ts +530 -0
- package/dist/extensions/pi-subagents/src/runs/shared/completion-evidence.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +2 -1
- package/dist/extensions/pi-subagents/src/runs/shared/external-cli-preflight.ts +16 -0
- package/dist/extensions/pi-subagents/src/runs/shared/host-step-status.ts +11 -11
- package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +30 -20
- package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +5 -4
- package/dist/extensions/pi-subagents/src/runs/shared/model-exclusions.ts +84 -15
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +88 -12
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +37 -53
- package/dist/extensions/pi-subagents/src/runs/shared/nested-path.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/shared/orca-progress-tabs.ts +18 -7
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +57 -12
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +3 -4
- package/dist/extensions/pi-subagents/src/runs/shared/permissions.ts +0 -11
- package/dist/extensions/pi-subagents/src/runs/shared/process-signal.ts +4 -1
- package/dist/extensions/pi-subagents/src/runs/shared/readonly-drain-observation.ts +42 -0
- package/dist/extensions/pi-subagents/src/runs/shared/readonly-model-continuation.ts +69 -0
- package/dist/extensions/pi-subagents/src/runs/shared/readonly-session-evidence.ts +307 -0
- package/dist/extensions/pi-subagents/src/runs/shared/run-fanout-budget.ts +8 -21
- package/dist/extensions/pi-subagents/src/runs/shared/runtime-acknowledged-extensions.ts +3 -30
- package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +17 -4
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +99 -386
- package/dist/extensions/pi-subagents/src/runs/shared/tool-availability.ts +18 -62
- package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +0 -14
- package/dist/extensions/pi-subagents/src/runs/shared/worktree-cleanup-plan.ts +31 -9
- package/dist/extensions/pi-subagents/src/runs/shared/worktree-setup-command.ts +190 -0
- package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +506 -226
- package/dist/extensions/pi-subagents/src/shared/child-session-name.ts +1 -1
- package/dist/extensions/pi-subagents/src/shared/jsonl-writer.ts +11 -0
- package/dist/extensions/pi-subagents/src/shared/model-response-aliases.ts +13 -0
- package/dist/extensions/pi-subagents/src/shared/thinking-ceiling.ts +0 -6
- package/dist/extensions/pi-subagents/src/shared/types.ts +145 -89
- package/dist/extensions/pi-subagents/src/shared/utils.ts +13 -6
- package/dist/extensions/pi-subagents/src/shared/watch-strategy.ts +2 -0
- package/dist/extensions/pi-subagents/src/shared/workflow-child-permit.ts +18 -13
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +0 -6
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +1 -1
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +11 -6
- package/dist/extensions/pi-subagents/src/tui/render.ts +277 -46
- package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +0 -1
- package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +12 -12
- package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +3 -3
- package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +116 -15
- package/dist/extensions/pi-subagents/src/workflows/workflow-checklist.ts +15 -18
- package/dist/extensions/pi-subagents/src/workflows/workflow-child-summary.ts +57 -8
- package/dist/extensions/pi-subagents/src/workflows/workflow-preflight.ts +19 -19
- package/dist/extensions/pi-subagents/src/workflows/workflow-receipt.ts +3 -3
- package/dist/extensions/pi-subagents/src/workflows/workflow-resources.ts +97 -22
- package/dist/extensions/pi-subagents/src/workflows/workflow-settlement.ts +3 -0
- package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +336 -21
- package/dist/extensions/pi-subagents/test/integration/async-execution.part-1.test.ts +1181 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.part-2.test.ts +2240 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.part-3.test.ts +2268 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.part-4.test.ts +1512 -0
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +231 -16
- package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +4 -18
- package/dist/extensions/pi-subagents/test/integration/foreign-workflow-steering.test.ts +95 -0
- package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +9 -15
- package/dist/extensions/pi-subagents/test/integration/in-process-child.test.ts +374 -0
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +20 -10
- package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +18 -6
- package/dist/extensions/pi-subagents/test/integration/recovered-assistant-error.test.ts +151 -0
- package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +42 -1
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +150 -25
- package/dist/extensions/pi-subagents/test/integration/result-publication.test.ts +354 -0
- package/dist/extensions/pi-subagents/test/integration/shared-cwd-terminal-evidence.test.ts +117 -0
- package/dist/extensions/pi-subagents/test/integration/single-execution.part-1.test.ts +3678 -0
- package/dist/extensions/pi-subagents/test/integration/{single-execution.test.ts → single-execution.part-2.test.ts} +500 -4207
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +1 -2
- package/dist/extensions/pi-subagents/test/integration/workflow-result-publication.test.ts +127 -0
- package/dist/extensions/pi-subagents/test/integration/workflow-steer-inbox.test.ts +365 -0
- package/dist/extensions/pi-subagents/test/smoke/pi085-child.ts +31 -0
- package/dist/extensions/pi-subagents/test/smoke/pi085-clean-install.mjs +74 -0
- package/dist/extensions/pi-subagents/test/smoke/pi085-extension.ts +16 -0
- package/dist/extensions/pi-subagents/test/support/async-execution-fixture.ts +699 -0
- package/dist/extensions/pi-subagents/test/support/fake-child-session.ts +418 -0
- package/dist/extensions/pi-subagents/test/support/foreign-workflow-steer-host.mjs +13 -0
- package/dist/extensions/pi-subagents/test/support/mock-pi.ts +30 -76
- package/dist/extensions/pi-subagents/test/support/notify-diagnostics-fixture.ts +84 -0
- package/dist/extensions/pi-subagents/test/support/result-publication-capacity-preload.mjs +70 -0
- package/dist/extensions/pi-subagents/test/support/runner-child-session-factory.ts +17 -0
- package/dist/extensions/pi-subagents/test/support/single-execution-fixture.ts +390 -0
- package/dist/extensions/pi-subagents/test/unit/acceptance-compaction.test.ts +120 -0
- package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +24 -3
- package/dist/extensions/pi-subagents/test/unit/advertised-agent-prompt.test.ts +87 -0
- package/dist/extensions/pi-subagents/test/unit/advertised-agent-refresh.test.ts +163 -0
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +70 -31
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +78 -3
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +2 -8
- package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +73 -66
- package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +51 -1
- package/dist/extensions/pi-subagents/test/unit/async-retention.test.ts +49 -1
- package/dist/extensions/pi-subagents/test/unit/async-spawn-preload.test.ts +103 -0
- package/dist/extensions/pi-subagents/test/unit/async-status-projection.test.ts +3 -3
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +9 -8
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +10 -7
- package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +48 -6
- package/dist/extensions/pi-subagents/test/unit/child-runtime-config.test.ts +90 -0
- package/dist/extensions/pi-subagents/test/unit/{pi-args-permission-system.test.ts → child-tool-plan-permission-system.test.ts} +48 -86
- package/dist/extensions/pi-subagents/test/unit/child-tool-plan.test.ts +33 -0
- package/dist/extensions/pi-subagents/test/unit/compaction-resume.test.ts +18 -18
- package/dist/extensions/pi-subagents/test/unit/completion-evidence.test.ts +9 -9
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +8 -7
- package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +225 -65
- package/dist/extensions/pi-subagents/test/unit/extension-bindings.test.ts +17 -22
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +11 -1
- package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +47 -3
- package/dist/extensions/pi-subagents/test/unit/foreign-workflow-steering.test.ts +74 -0
- package/dist/extensions/pi-subagents/test/unit/fork-cache-key.test.ts +23 -29
- package/dist/extensions/pi-subagents/test/unit/host-peer-runtime-imports.test.ts +96 -4
- package/dist/extensions/pi-subagents/test/unit/host-step-status.test.ts +4 -4
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +116 -55
- package/dist/extensions/pi-subagents/test/unit/index-segment.test.ts +58 -1
- package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +104 -3
- package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +10 -1
- package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +31 -1
- package/dist/extensions/pi-subagents/test/unit/model-exclusions.test.ts +102 -51
- package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +196 -6
- package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +181 -38
- package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +62 -70
- package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +58 -53
- package/dist/extensions/pi-subagents/test/unit/notify-diagnostics.test.ts +42 -0
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +84 -0
- package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +13 -5
- package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +21 -5
- package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +5 -4
- package/dist/extensions/pi-subagents/test/unit/permissions.test.ts +2 -6
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +28 -2
- package/dist/extensions/pi-subagents/test/unit/pi-web-session-liveness.test.ts +209 -0
- package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +38 -1
- package/dist/extensions/pi-subagents/test/unit/prompt-audit-optional-peers.test.ts +43 -0
- package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +24 -2
- package/dist/extensions/pi-subagents/test/unit/readonly-drain-observation.test.ts +174 -0
- package/dist/extensions/pi-subagents/test/unit/readonly-model-continuation.test.ts +94 -0
- package/dist/extensions/pi-subagents/test/unit/readonly-session-evidence.test.ts +1329 -0
- package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +29 -80
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +11 -8
- package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/retained-nested-route-tracker.test.ts +175 -0
- package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +38 -12
- package/dist/extensions/pi-subagents/test/unit/run-fanout-budget.test.ts +2 -5
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +89 -1
- package/dist/extensions/pi-subagents/test/unit/runtime-acknowledged-extensions.test.ts +2 -16
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +27 -3
- package/dist/extensions/pi-subagents/test/unit/scheduled-supervisor-demand.test.ts +215 -0
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +15 -11
- package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +237 -1
- package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +2 -5
- package/dist/extensions/pi-subagents/test/unit/streamed-progress-bounds.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/subagent-guide.test.ts +14 -1
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +130 -739
- package/dist/extensions/pi-subagents/test/unit/supervisor-ask-registration.test.ts +682 -0
- package/dist/extensions/pi-subagents/test/unit/supervisor-ui.test.ts +137 -0
- package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +1 -11
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +23 -3
- package/dist/extensions/pi-subagents/test/unit/wait-completions.test.ts +8 -1
- package/dist/extensions/pi-subagents/test/unit/watchdog-tool-actions.test.ts +19 -1
- package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +72 -4
- package/dist/extensions/pi-subagents/test/unit/workflow-checklist.test.ts +17 -3
- package/dist/extensions/pi-subagents/test/unit/workflow-child-summary.test.ts +34 -0
- package/dist/extensions/pi-subagents/test/unit/workflow-detach-reconcile.test.ts +12 -2
- package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +46 -6
- package/dist/extensions/pi-subagents/test/unit/workflow-receipt.test.ts +3 -3
- package/dist/extensions/pi-subagents/test/unit/workflow-resources.test.ts +133 -1
- package/dist/extensions/pi-subagents/test/unit/workflow-resume-hint.test.ts +53 -2
- package/dist/extensions/pi-subagents/test/unit/worktree-cleanup-plan.test.ts +222 -14
- package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +526 -85
- package/dist/extensions/pi-web-agent/src/backends/config.ts +1 -0
- package/dist/extensions/pi-web-agent/src/backends/settings-reader.ts +5 -3
- package/dist/extensions/pi-web-agent/src/search/tokenin.ts +7 -3
- package/dist/extensions/pi-web-agent/src/types.ts +1 -1
- package/dist/extensions/pi-zentui/README.md +17 -0
- package/dist/extensions/pi-zentui/assets/screenshots/footer-status-hyperlink.png +0 -0
- package/dist/extensions/pi-zentui/docs/configuration.md +71 -2
- package/dist/extensions/pi-zentui/extensions/zentui/config.ts +11 -0
- package/dist/extensions/pi-zentui/extensions/zentui/editor-transfer.ts +14 -0
- package/dist/extensions/pi-zentui/extensions/zentui/extension-status.ts +29 -13
- package/dist/extensions/pi-zentui/extensions/zentui/footer-layout.ts +5 -4
- package/dist/extensions/pi-zentui/extensions/zentui/footer-text.ts +11 -0
- package/dist/extensions/pi-zentui/extensions/zentui/footer.ts +11 -10
- package/dist/extensions/pi-zentui/extensions/zentui/index.ts +77 -3
- package/dist/extensions/pi-zentui/extensions/zentui/presets.ts +77 -0
- package/dist/extensions/pi-zentui/extensions/zentui/settings-command.ts +103 -2
- package/dist/extensions/pi-zentui/extensions/zentui/working-line-extension-segments.ts +115 -0
- package/dist/extensions/pi-zentui/extensions/zentui/working-line.ts +87 -5
- package/dist/extensions/pi-zentui/package-lock.json +277 -517
- package/dist/extensions/pi-zentui/package.json +7 -4
- package/dist/extensions/pi-zentui/test/config-presets.test.ts +238 -0
- package/dist/extensions/pi-zentui/test/editor-transfer.test.ts +24 -1
- package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +570 -2
- package/dist/extensions/pi-zentui/test/extension-status.test.ts +90 -0
- package/dist/extensions/pi-zentui/test/footer-hyperlink.test.ts +243 -0
- package/dist/extensions/pi-zentui/test/package-contents.mjs +1 -0
- package/dist/extensions/pi-zentui/test/presets.test.ts +121 -0
- package/dist/extensions/pi-zentui/test/settings-command.test.ts +205 -1
- package/dist/extensions/pi-zentui/test/working-line-extension-segments.test.ts +139 -0
- package/dist/extensions/pi-zentui/test/working-line-lifecycle.integration.test.ts +237 -3
- package/dist/extensions/pi-zentui/test/working-line.test.ts +94 -2
- package/dist/index.d.ts +1 -1
- package/dist/main.js +4 -1
- package/dist/modes/interactive/chat-viewport.d.ts +19 -0
- package/dist/modes/interactive/chat-viewport.js +27 -0
- package/dist/modes/interactive/components/assistant-message.d.ts +6 -2
- package/dist/modes/interactive/components/assistant-message.js +30 -13
- package/dist/modes/interactive/components/custom-editor.d.ts +10 -1
- package/dist/modes/interactive/components/custom-editor.js +39 -1
- package/dist/modes/interactive/components/first-time-setup.js +1 -1
- package/dist/modes/interactive/components/index.d.ts +1 -1
- package/dist/modes/interactive/components/markdown-transform.d.ts +2 -0
- package/dist/modes/interactive/components/markdown-transform.js +18 -0
- package/dist/modes/interactive/components/model-selector.js +19 -17
- package/dist/modes/interactive/components/scoped-models-selector.js +21 -23
- package/dist/modes/interactive/components/settings-selector.d.ts +7 -1
- package/dist/modes/interactive/components/settings-selector.js +128 -20
- package/dist/modes/interactive/components/settings-submenu.d.ts +71 -0
- package/dist/modes/interactive/components/settings-submenu.js +164 -0
- package/dist/modes/interactive/components/status-indicator.d.ts +3 -1
- package/dist/modes/interactive/components/status-indicator.js +10 -3
- package/dist/modes/interactive/components/thinking-selector.d.ts +15 -3
- package/dist/modes/interactive/components/thinking-selector.js +75 -18
- package/dist/modes/interactive/components/tool-execution.d.ts +21 -4
- package/dist/modes/interactive/components/tool-execution.js +35 -33
- package/dist/modes/interactive/components/trust-selector.js +2 -2
- package/dist/modes/interactive/interactive-mode.d.ts +13 -15
- package/dist/modes/interactive/interactive-mode.js +144 -80
- package/dist/modes/interactive/session-share.d.ts +15 -0
- package/dist/modes/interactive/session-share.js +183 -0
- package/dist/modes/interactive/theme/dark.json +2 -1
- package/dist/modes/interactive/theme/light.json +2 -1
- package/dist/modes/interactive/theme/theme-controller.d.ts +1 -0
- package/dist/modes/interactive/theme/theme-controller.js +5 -0
- package/dist/modes/interactive/theme/theme-json.d.ts +83 -0
- package/dist/modes/interactive/theme/theme-json.js +129 -0
- package/dist/modes/interactive/theme/theme-schema.json +9 -1
- package/dist/modes/interactive/theme/theme.d.ts +14 -5
- package/dist/modes/interactive/theme/theme.js +18 -119
- package/dist/modes/interactive/tui-renderer.d.ts +20 -0
- package/dist/modes/interactive/tui-renderer.js +65 -0
- package/dist/utils/exif-orientation.js +2 -3
- package/dist/utils/management-http.d.ts +24 -0
- package/dist/utils/management-http.js +53 -0
- package/dist/utils/text.d.ts +7 -0
- package/dist/utils/text.js +8 -0
- package/dist/utils/tools-manager.d.ts +1 -0
- package/dist/utils/tools-manager.js +43 -21
- package/docs/docs.json +0 -4
- package/package.json +4 -4
- package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +0 -415
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +0 -1059
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +0 -116
- package/dist/extensions/pi-subagents/src/shared/post-exit-stdio-guard.ts +0 -85
- package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +0 -224
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +0 -6986
- package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +0 -447
- package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +0 -185
- package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +0 -311
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling-pi-args.test.ts +0 -84
- package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +0 -202
- package/dist/extensions/pi-subagents/test/unit/close-grace-timer.test.ts +0 -101
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +0 -2347
- package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +0 -104
- package/dist/extensions/pi-subagents/test/unit/windows-hide-spawn.test.ts +0 -49
- package/dist/skills/imagegen-frontend-mobile/SKILL.md +0 -1465
- package/dist/skills/imagegen-frontend-web/SKILL.md +0 -987
- package/dist/skills/implanger/SKILL.md +0 -70
- package/dist/skills/output/SKILL.md +0 -49
- package/dist/skills/planger/SKILL.md +0 -167
- package/dist/skills/workflow/SKILL.md +0 -95
- package/docs/workflows.md +0 -18
- /package/dist/bun/{register-bedrock.d.ts → runtime-setup.d.ts} +0 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {
|
|
4
|
+
normalizeFts5Query,
|
|
5
|
+
buildFallbackFts5Query,
|
|
6
|
+
hasExplicitFts5Operator,
|
|
7
|
+
normalizeNaturalLanguageFts5Query,
|
|
8
|
+
buildNaturalLanguageFallbackQuery,
|
|
9
|
+
isFts5QueryError,
|
|
10
|
+
} from '../../src/store/fts-query.js';
|
|
11
|
+
|
|
12
|
+
describe('fts-query', () => {
|
|
13
|
+
describe('stop word filtering', () => {
|
|
14
|
+
it('filters filler words but keeps meaningful terms', () => {
|
|
15
|
+
const q = normalizeFts5Query('how to fix the authentication bug');
|
|
16
|
+
assert.ok(q.includes('"authentication"'), 'keeps meaningful term');
|
|
17
|
+
assert.ok(q.includes('"bug"'), 'keeps meaningful term');
|
|
18
|
+
assert.ok(!q.includes('"how"'), 'filters "how"');
|
|
19
|
+
assert.ok(!q.includes('"to"'), 'filters "to"');
|
|
20
|
+
assert.ok(!q.includes('"the"'), 'filters "the"');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('returns empty string when every term is a stop word', () => {
|
|
24
|
+
assert.strictEqual(normalizeFts5Query('the a is was were'), '');
|
|
25
|
+
assert.strictEqual(normalizeFts5Query('for in on'), '');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('keeps quoted phrases intact even when they contain stop words', () => {
|
|
29
|
+
const q = normalizeNaturalLanguageFts5Query('"the quick brown fox" jumps');
|
|
30
|
+
assert.ok(q.includes('"the quick brown fox"'));
|
|
31
|
+
assert.ok(q.includes('"jumps"'));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('normalizes case-insensitively against the stop word list', () => {
|
|
35
|
+
const q = normalizeFts5Query('The QUICK brown Fox');
|
|
36
|
+
assert.ok(q.includes('"QUICK"'));
|
|
37
|
+
assert.ok(q.includes('"Fox"'));
|
|
38
|
+
assert.ok(!q.includes('"The"'));
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('fallback queries', () => {
|
|
43
|
+
it('builds an OR fallback for multi-term natural language queries', () => {
|
|
44
|
+
const fb = buildFallbackFts5Query('authentication bug fix');
|
|
45
|
+
assert.ok(fb);
|
|
46
|
+
assert.ok(fb.includes('OR'));
|
|
47
|
+
assert.ok(fb.includes('"authentication"'));
|
|
48
|
+
assert.ok(fb.includes('"bug"'));
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('returns null when there are not enough meaningful terms', () => {
|
|
52
|
+
assert.strictEqual(buildFallbackFts5Query('auth'), null);
|
|
53
|
+
assert.strictEqual(buildFallbackFts5Query('the a'), null);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('returns null for explicit operator queries', () => {
|
|
57
|
+
assert.strictEqual(buildFallbackFts5Query('auth OR database'), null);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('operator handling', () => {
|
|
62
|
+
it('detects explicit FTS5 operators', () => {
|
|
63
|
+
assert.ok(hasExplicitFts5Operator('auth OR database'));
|
|
64
|
+
assert.ok(hasExplicitFts5Operator('"auth bug" AND database'));
|
|
65
|
+
assert.ok(!hasExplicitFts5Operator('auth database'));
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('preserves explicit operator queries verbatim in normalizeFts5Query', () => {
|
|
69
|
+
assert.strictEqual(normalizeFts5Query('auth OR database'), 'auth OR database');
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('natural language recovery', () => {
|
|
74
|
+
it('normalizes uppercase operator words as natural language', () => {
|
|
75
|
+
// 'DO' is a stop word and 'NOT' a natural-language connector, so only
|
|
76
|
+
// the two meaningful terms survive, quoted for implicit AND matching.
|
|
77
|
+
assert.strictEqual(normalizeNaturalLanguageFts5Query('DO NOT USE FIND'), '"USE" "FIND"');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('keeps intent-bearing words out of the stop list (can/need/off/like/get)', () => {
|
|
81
|
+
// These words frequently carry the actual search intent ("can the app
|
|
82
|
+
// start", "need to restart", "like this", "get the build passing"), so
|
|
83
|
+
// they must survive normalization rather than be silently dropped.
|
|
84
|
+
assert.strictEqual(
|
|
85
|
+
normalizeFts5Query('can need off like get'),
|
|
86
|
+
'"can" "need" "off" "like" "get"',
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('builds a natural language OR fallback ignoring operator detection', () => {
|
|
91
|
+
const fb = buildNaturalLanguageFallbackQuery('DO NOT USE FIND');
|
|
92
|
+
assert.ok(fb && fb.includes('OR'));
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('isFts5QueryError', () => {
|
|
97
|
+
it('detects genuine FTS5 query/index failure messages', () => {
|
|
98
|
+
assert.ok(isFts5QueryError(new Error('fts5: syntax error near "AND"')), 'fts5');
|
|
99
|
+
assert.ok(isFts5QueryError(new Error('fts5: unterminated string')), 'unterminated string');
|
|
100
|
+
assert.ok(isFts5QueryError(new Error('unterminated string constant in FTS5 query')), 'unterminated string constant');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('rejects corruption signals so DatabaseManager recovery still runs (#186)', () => {
|
|
104
|
+
// These are corruption/recovery signals, NOT recoverable FTS query errors.
|
|
105
|
+
// Swallowing them would suppress quarantine/rebuild of a corrupt database.
|
|
106
|
+
assert.ok(!isFts5QueryError(new Error('database disk image is malformed')), 'malformed is corruption, not FTS');
|
|
107
|
+
assert.ok(!isFts5QueryError(new Error('SQL logic error')), 'sql logic error is too broad to swallow');
|
|
108
|
+
assert.ok(!isFts5QueryError(new Error('file is not a database')), 'notadb is corruption, not FTS');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('rejects unrelated errors', () => {
|
|
112
|
+
assert.ok(!isFts5QueryError(new Error('disk full')));
|
|
113
|
+
assert.ok(!isFts5QueryError('string, not an Error'));
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -117,6 +117,9 @@ describe("MemoryStore", { concurrency: 1 }, () => {
|
|
|
117
117
|
await removeFile(memoryPath);
|
|
118
118
|
await removeFile(userPath);
|
|
119
119
|
await removeFile(failurePath);
|
|
120
|
+
const artifactNames = (await fs.readdir(MEMORY_DIR))
|
|
121
|
+
.filter((name) => name.includes(".recovery-") || name.includes(".retired-") || name.includes(".conflict-"));
|
|
122
|
+
await Promise.all(artifactNames.map((name) => removeFile(path.join(MEMORY_DIR, name))));
|
|
120
123
|
await new Promise((r) => setTimeout(r, 50));
|
|
121
124
|
}
|
|
122
125
|
|
|
@@ -184,6 +187,48 @@ describe("MemoryStore", { concurrency: 1 }, () => {
|
|
|
184
187
|
assert.ok(result.error!.includes("chars"));
|
|
185
188
|
});
|
|
186
189
|
|
|
190
|
+
it("bypasses the Markdown cap for memory, user, failure, and project stores in policy-only mode", async () => {
|
|
191
|
+
const config = makeConfig({
|
|
192
|
+
memoryMode: "policy-only",
|
|
193
|
+
memoryCharLimit: 1,
|
|
194
|
+
userCharLimit: 1,
|
|
195
|
+
projectCharLimit: 1,
|
|
196
|
+
memoryOverflowStrategy: "auto-consolidate",
|
|
197
|
+
autoConsolidate: true,
|
|
198
|
+
});
|
|
199
|
+
const store = new MemoryStore(config);
|
|
200
|
+
const projectStore = new MemoryStore({
|
|
201
|
+
...config,
|
|
202
|
+
memoryDir: path.join(MEMORY_DIR, "project"),
|
|
203
|
+
});
|
|
204
|
+
let consolidatorCalls = 0;
|
|
205
|
+
const consolidator = async () => {
|
|
206
|
+
consolidatorCalls++;
|
|
207
|
+
return { consolidated: true };
|
|
208
|
+
};
|
|
209
|
+
store.setConsolidator(consolidator);
|
|
210
|
+
projectStore.setConsolidator(consolidator);
|
|
211
|
+
await store.loadFromDisk();
|
|
212
|
+
await projectStore.loadFromDisk();
|
|
213
|
+
|
|
214
|
+
const results = await Promise.all([
|
|
215
|
+
store.add("memory", `${TEST_MARKER} policy-only memory ${"x".repeat(100)}`),
|
|
216
|
+
store.add("user", `${TEST_MARKER} policy-only user ${"x".repeat(100)}`),
|
|
217
|
+
store.addFailure(`${TEST_MARKER} policy-only failure ${"x".repeat(100)}`, { category: "failure" }),
|
|
218
|
+
projectStore.add("memory", `${TEST_MARKER} policy-only project ${"x".repeat(100)}`),
|
|
219
|
+
]);
|
|
220
|
+
|
|
221
|
+
for (const result of results) {
|
|
222
|
+
assert.equal(result.success, true, result.error);
|
|
223
|
+
}
|
|
224
|
+
assert.equal(consolidatorCalls, 0);
|
|
225
|
+
assert.equal(store.getRawEntriesForSync("memory").length, 1);
|
|
226
|
+
assert.equal(store.getRawEntriesForSync("user").length, 1);
|
|
227
|
+
assert.equal(store.getRawEntriesForSync("failure").length, 1);
|
|
228
|
+
assert.equal(projectStore.getRawEntriesForSync("memory").length, 1);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
|
|
187
232
|
it("rejects without consolidation when memoryOverflowStrategy is reject", async () => {
|
|
188
233
|
let consolidatorCalled = false;
|
|
189
234
|
const store = new MemoryStore(makeConfig({
|
|
@@ -603,6 +648,45 @@ describe("MemoryStore", { concurrency: 1 }, () => {
|
|
|
603
648
|
// ─── replace() tests ───
|
|
604
649
|
|
|
605
650
|
describe("replace()", () => {
|
|
651
|
+
it("bypasses the Markdown cap for replacements in policy-only mode", async () => {
|
|
652
|
+
const config = makeConfig({
|
|
653
|
+
memoryMode: "policy-only",
|
|
654
|
+
memoryCharLimit: 1,
|
|
655
|
+
userCharLimit: 1,
|
|
656
|
+
projectCharLimit: 1,
|
|
657
|
+
});
|
|
658
|
+
const store = new MemoryStore(config);
|
|
659
|
+
const projectStore = new MemoryStore({
|
|
660
|
+
...config,
|
|
661
|
+
memoryDir: path.join(MEMORY_DIR, "project-replace"),
|
|
662
|
+
});
|
|
663
|
+
let consolidatorCalls = 0;
|
|
664
|
+
const consolidator = async () => {
|
|
665
|
+
consolidatorCalls++;
|
|
666
|
+
return { consolidated: true };
|
|
667
|
+
};
|
|
668
|
+
store.setConsolidator(consolidator);
|
|
669
|
+
projectStore.setConsolidator(consolidator);
|
|
670
|
+
await store.loadFromDisk();
|
|
671
|
+
await projectStore.loadFromDisk();
|
|
672
|
+
await store.add("memory", `${TEST_MARKER} replace memory seed`);
|
|
673
|
+
await store.add("user", `${TEST_MARKER} replace user seed`);
|
|
674
|
+
await store.addFailure(`${TEST_MARKER} replace failure seed`, { category: "failure" });
|
|
675
|
+
await projectStore.add("memory", `${TEST_MARKER} replace project seed`);
|
|
676
|
+
|
|
677
|
+
const replacement = `${TEST_MARKER} ${"x".repeat(100)}`;
|
|
678
|
+
const results = await Promise.all([
|
|
679
|
+
store.replace("memory", "replace memory seed", replacement),
|
|
680
|
+
store.replace("user", "replace user seed", replacement),
|
|
681
|
+
store.replace("failure", "replace failure seed", replacement),
|
|
682
|
+
projectStore.replace("memory", "replace project seed", replacement),
|
|
683
|
+
]);
|
|
684
|
+
|
|
685
|
+
for (const result of results) {
|
|
686
|
+
assert.equal(result.success, true, result.error);
|
|
687
|
+
}
|
|
688
|
+
assert.equal(consolidatorCalls, 0);
|
|
689
|
+
});
|
|
606
690
|
it("updates entry in file", async () => {
|
|
607
691
|
const store = new MemoryStore(makeConfig());
|
|
608
692
|
await store.loadFromDisk();
|
|
@@ -1318,6 +1402,52 @@ describe("MemoryStore", { concurrency: 1 }, () => {
|
|
|
1318
1402
|
assert.equal(await readRaw(memoryPath), beforeDisk);
|
|
1319
1403
|
}
|
|
1320
1404
|
});
|
|
1405
|
+
it("bypasses the Markdown cap for batch mutations in policy-only mode", async () => {
|
|
1406
|
+
const config = makeConfig({
|
|
1407
|
+
memoryMode: "policy-only",
|
|
1408
|
+
memoryCharLimit: 1,
|
|
1409
|
+
userCharLimit: 1,
|
|
1410
|
+
projectCharLimit: 1,
|
|
1411
|
+
});
|
|
1412
|
+
const store = new MemoryStore(config);
|
|
1413
|
+
const projectStore = new MemoryStore({
|
|
1414
|
+
...config,
|
|
1415
|
+
memoryDir: path.join(MEMORY_DIR, "project-batch"),
|
|
1416
|
+
});
|
|
1417
|
+
let consolidatorCalls = 0;
|
|
1418
|
+
const consolidator = async () => {
|
|
1419
|
+
consolidatorCalls++;
|
|
1420
|
+
return { consolidated: true };
|
|
1421
|
+
};
|
|
1422
|
+
store.setConsolidator(consolidator);
|
|
1423
|
+
projectStore.setConsolidator(consolidator);
|
|
1424
|
+
await store.loadFromDisk();
|
|
1425
|
+
await projectStore.loadFromDisk();
|
|
1426
|
+
|
|
1427
|
+
const results = await Promise.all([
|
|
1428
|
+
store.applyMutationPlan("memory", [
|
|
1429
|
+
{ action: "add", content: `${TEST_MARKER} batch memory ${"x".repeat(100)}` },
|
|
1430
|
+
{ action: "add", content: `${TEST_MARKER} batch memory second ${"x".repeat(100)}` },
|
|
1431
|
+
]),
|
|
1432
|
+
store.applyMutationPlan("user", [
|
|
1433
|
+
{ action: "add", content: `${TEST_MARKER} batch user ${"x".repeat(100)}` },
|
|
1434
|
+
{ action: "add", content: `${TEST_MARKER} batch user second ${"x".repeat(100)}` },
|
|
1435
|
+
]),
|
|
1436
|
+
store.applyMutationPlan("failure", [
|
|
1437
|
+
{ action: "add", content: `${TEST_MARKER} batch failure ${"x".repeat(100)}`, category: "failure" },
|
|
1438
|
+
{ action: "add", content: `${TEST_MARKER} batch failure second ${"x".repeat(100)}`, category: "failure" },
|
|
1439
|
+
]),
|
|
1440
|
+
projectStore.applyMutationPlan("memory", [
|
|
1441
|
+
{ action: "add", content: `${TEST_MARKER} batch project ${"x".repeat(100)}` },
|
|
1442
|
+
{ action: "add", content: `${TEST_MARKER} batch project second ${"x".repeat(100)}` },
|
|
1443
|
+
]),
|
|
1444
|
+
]);
|
|
1445
|
+
|
|
1446
|
+
for (const result of results) {
|
|
1447
|
+
assert.equal(result.success, true, result.error);
|
|
1448
|
+
}
|
|
1449
|
+
assert.equal(consolidatorCalls, 0);
|
|
1450
|
+
});
|
|
1321
1451
|
});
|
|
1322
1452
|
|
|
1323
1453
|
describe("external file changes", () => {
|
|
@@ -1710,6 +1840,39 @@ describe("MemoryStore", { concurrency: 1 }, () => {
|
|
|
1710
1840
|
assert.match(raw, /later add/);
|
|
1711
1841
|
});
|
|
1712
1842
|
|
|
1843
|
+
it("reuses one recent recovery snapshot during rapid successive writes", async () => {
|
|
1844
|
+
const store = new MemoryStore(makeConfig());
|
|
1845
|
+
await store.loadFromDisk();
|
|
1846
|
+
await store.add("memory", `${TEST_MARKER} recovery seed`);
|
|
1847
|
+
|
|
1848
|
+
for (let index = 0; index < 10; index++) {
|
|
1849
|
+
await store.add("memory", `${TEST_MARKER} rapid overwrite ${index}`);
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
const recoveryFiles = (await fs.readdir(MEMORY_DIR))
|
|
1853
|
+
.filter((name) => name.startsWith(`.${MEMORY_FILE}.recovery-`));
|
|
1854
|
+
assert.ok(recoveryFiles.length <= 2);
|
|
1855
|
+
});
|
|
1856
|
+
|
|
1857
|
+
it("takes a new recovery snapshot after the reuse interval", async () => {
|
|
1858
|
+
const store = new MemoryStore(makeConfig());
|
|
1859
|
+
await store.loadFromDisk();
|
|
1860
|
+
await store.add("memory", `${TEST_MARKER} recovery seed`);
|
|
1861
|
+
await store.add("memory", `${TEST_MARKER} first overwrite`);
|
|
1862
|
+
|
|
1863
|
+
const initialRecoveryFiles = (await fs.readdir(MEMORY_DIR))
|
|
1864
|
+
.filter((name) => name.startsWith(`.${MEMORY_FILE}.recovery-`));
|
|
1865
|
+
assert.equal(initialRecoveryFiles.length, 1);
|
|
1866
|
+
const old = new Date(Date.now() - 60 * 60 * 1000 - 1_000);
|
|
1867
|
+
await fs.utimes(path.join(MEMORY_DIR, initialRecoveryFiles[0]), old, old);
|
|
1868
|
+
|
|
1869
|
+
await store.add("memory", `${TEST_MARKER} overwrite after interval`);
|
|
1870
|
+
|
|
1871
|
+
const recoveryFiles = (await fs.readdir(MEMORY_DIR))
|
|
1872
|
+
.filter((name) => name.startsWith(`.${MEMORY_FILE}.recovery-`));
|
|
1873
|
+
assert.equal(recoveryFiles.length, 2);
|
|
1874
|
+
});
|
|
1875
|
+
|
|
1713
1876
|
it("prunes expired recovery files but retains recently active ones", async () => {
|
|
1714
1877
|
const pathStore = new MemoryStore(makeConfig());
|
|
1715
1878
|
const expiredPath = (pathStore as any).recoveryPathFor(memoryPath) as string;
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
truncateMessageContent,
|
|
20
20
|
upsertSessionFileMetadata,
|
|
21
21
|
pruneEphemeralReviewSessions,
|
|
22
|
+
pruneOldSessions,
|
|
22
23
|
} from '../../src/store/session-indexer.js';
|
|
23
24
|
import { DEFAULT_MAX_MESSAGE_CONTENT_LENGTH } from '../../src/constants.js';
|
|
24
25
|
import { parseSessionFile, type ParsedSession } from '../../src/store/session-parser.js';
|
|
@@ -221,6 +222,44 @@ describe('session-indexer', () => {
|
|
|
221
222
|
const result = indexAllSessions(dbManager, '/nonexistent/path');
|
|
222
223
|
assert.strictEqual(result.sessionsProcessed, 0);
|
|
223
224
|
});
|
|
225
|
+
|
|
226
|
+
it('skips files outside the retention window and reports expiredSkipped', () => {
|
|
227
|
+
const sessionsDir = path.join(tmpDir, 'sessions');
|
|
228
|
+
const projDir = path.join(sessionsDir, 'test-project');
|
|
229
|
+
fs.mkdirSync(projDir, { recursive: true });
|
|
230
|
+
|
|
231
|
+
const write = (fileName: string, sessionId: string) => {
|
|
232
|
+
const lines = [
|
|
233
|
+
JSON.stringify({ type: 'session', id: sessionId, timestamp: '2026-05-03T00:00:00Z', cwd: '/test' }),
|
|
234
|
+
JSON.stringify({
|
|
235
|
+
type: 'message',
|
|
236
|
+
id: `${sessionId}-m1`,
|
|
237
|
+
parentId: null,
|
|
238
|
+
timestamp: '2026-05-03T00:01:00Z',
|
|
239
|
+
message: { role: 'user', content: [{ type: 'text', text: 'Hello' }], timestamp: Date.now() },
|
|
240
|
+
}),
|
|
241
|
+
];
|
|
242
|
+
fs.writeFileSync(path.join(projDir, fileName), lines.join('\n'));
|
|
243
|
+
};
|
|
244
|
+
write('recent.jsonl', 'recent');
|
|
245
|
+
write('expired.jsonl', 'expired');
|
|
246
|
+
|
|
247
|
+
// Age the second file beyond the retention window.
|
|
248
|
+
const old = new Date('2026-01-01T00:00:00Z');
|
|
249
|
+
fs.utimesSync(path.join(projDir, 'expired.jsonl'), old, old);
|
|
250
|
+
|
|
251
|
+
const cutoff = Date.parse('2026-03-01T00:00:00Z');
|
|
252
|
+
const result = indexAllSessions(dbManager, sessionsDir, undefined, cutoff);
|
|
253
|
+
assert.strictEqual(result.sessionsIndexed, 1);
|
|
254
|
+
assert.strictEqual(result.expiredSkipped, 1);
|
|
255
|
+
assert.strictEqual(dbManager.getStats().sessions, 1);
|
|
256
|
+
|
|
257
|
+
// Without a cutoff the same directory indexes both files.
|
|
258
|
+
const result2 = indexAllSessions(dbManager, sessionsDir);
|
|
259
|
+
assert.strictEqual(result2.sessionsIndexed, 1);
|
|
260
|
+
assert.strictEqual(result2.expiredSkipped, undefined);
|
|
261
|
+
assert.strictEqual(dbManager.getStats().sessions, 2);
|
|
262
|
+
});
|
|
224
263
|
});
|
|
225
264
|
|
|
226
265
|
describe('indexChangedSessions', () => {
|
|
@@ -565,6 +604,75 @@ describe('session-indexer', () => {
|
|
|
565
604
|
assert.strictEqual(needsBackfill(dbManager, sessionsDir, new Date('2026-05-03T01:00:00Z')), true);
|
|
566
605
|
});
|
|
567
606
|
|
|
607
|
+
it('needsBackfill returns no work when retention is enabled and every file is expired, even without a backfill timestamp', () => {
|
|
608
|
+
const sessionsDir = path.join(tmpDir, 'sessions');
|
|
609
|
+
writeJsonlSession(sessionsDir, 'project-a', 's1');
|
|
610
|
+
const filePath = path.join(sessionsDir, 'project-a', 's1.jsonl');
|
|
611
|
+
|
|
612
|
+
// Age the file beyond the retention window.
|
|
613
|
+
const old = new Date('2026-01-01T00:00:00Z');
|
|
614
|
+
fs.utimesSync(filePath, old, old);
|
|
615
|
+
|
|
616
|
+
const now = new Date('2026-05-03T01:00:00Z');
|
|
617
|
+
const cutoff = Date.parse('2026-03-01T00:00:00Z');
|
|
618
|
+
assert.ok(old.getTime() < cutoff, 'sanity: the file must be outside the window');
|
|
619
|
+
|
|
620
|
+
// Fresh DB: no indexed rows and no backfill timestamp, so the periodic
|
|
621
|
+
// timestamp check alone would demand a backfill. With retention enabled
|
|
622
|
+
// and no retained files, there is no work to do — this must return
|
|
623
|
+
// false instead of scheduling an empty backfill on every startup.
|
|
624
|
+
assert.strictEqual(needsBackfill(dbManager, sessionsDir, now, cutoff), false);
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
it('needsBackfill ignores expired files instead of using a blind file-count shortcut', () => {
|
|
628
|
+
const sessionsDir = path.join(tmpDir, 'sessions');
|
|
629
|
+
// Two files on disk, zero indexed rows: a file-count-based shortcut
|
|
630
|
+
// would return true here. With retention enabled and every file
|
|
631
|
+
// expired, there is nothing to backfill.
|
|
632
|
+
writeJsonlSession(sessionsDir, 'project-a', 's1');
|
|
633
|
+
writeJsonlSession(sessionsDir, 'project-b', 's2');
|
|
634
|
+
const old = new Date('2026-01-01T00:00:00Z');
|
|
635
|
+
fs.utimesSync(path.join(sessionsDir, 'project-a', 's1.jsonl'), old, old);
|
|
636
|
+
fs.utimesSync(path.join(sessionsDir, 'project-b', 's2.jsonl'), old, old);
|
|
637
|
+
|
|
638
|
+
const cutoff = Date.parse('2026-03-01T00:00:00Z');
|
|
639
|
+
assert.strictEqual(needsBackfill(dbManager, sessionsDir, new Date('2026-05-03T01:00:00Z'), cutoff), false);
|
|
640
|
+
|
|
641
|
+
// A retained file with no stored metadata still demands a backfill,
|
|
642
|
+
// even though the file count alone (2 files, 0 rows) is unchanged.
|
|
643
|
+
const recent = new Date('2026-04-15T00:00:00Z');
|
|
644
|
+
fs.utimesSync(path.join(sessionsDir, 'project-b', 's2.jsonl'), recent, recent);
|
|
645
|
+
assert.strictEqual(needsBackfill(dbManager, sessionsDir, new Date('2026-05-03T01:00:00Z'), cutoff), true);
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
it('needsBackfill still schedules when a retained file exists without a backfill timestamp', () => {
|
|
649
|
+
const sessionsDir = path.join(tmpDir, 'sessions');
|
|
650
|
+
writeJsonlSession(sessionsDir, 'project-a', 's1');
|
|
651
|
+
const filePath = path.join(sessionsDir, 'project-a', 's1.jsonl');
|
|
652
|
+
|
|
653
|
+
// File stays inside the retention window.
|
|
654
|
+
const recent = new Date('2026-04-15T00:00:00Z');
|
|
655
|
+
fs.utimesSync(filePath, recent, recent);
|
|
656
|
+
|
|
657
|
+
const now = new Date('2026-05-03T01:00:00Z');
|
|
658
|
+
const cutoff = Date.parse('2026-03-01T00:00:00Z');
|
|
659
|
+
assert.ok(recent.getTime() >= cutoff, 'sanity: the file must be inside the window');
|
|
660
|
+
|
|
661
|
+
assert.strictEqual(needsBackfill(dbManager, sessionsDir, now, cutoff), true);
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
it('needsBackfill keeps the periodic check active when retention is disabled', () => {
|
|
665
|
+
const sessionsDir = path.join(tmpDir, 'sessions');
|
|
666
|
+
writeJsonlSession(sessionsDir, 'project-a', 's1');
|
|
667
|
+
const filePath = path.join(sessionsDir, 'project-a', 's1.jsonl');
|
|
668
|
+
|
|
669
|
+
// An old file is still eligible when retention is disabled (cutoff 0).
|
|
670
|
+
const old = new Date('2026-01-01T00:00:00Z');
|
|
671
|
+
fs.utimesSync(filePath, old, old);
|
|
672
|
+
|
|
673
|
+
assert.strictEqual(needsBackfill(dbManager, sessionsDir, new Date('2026-05-03T01:00:00Z')), true);
|
|
674
|
+
});
|
|
675
|
+
|
|
568
676
|
it('touchBackfillTimestamp upserts the metadata row', () => {
|
|
569
677
|
touchBackfillTimestamp(dbManager, new Date('2026-05-03T00:00:00Z'));
|
|
570
678
|
touchBackfillTimestamp(dbManager, new Date('2026-05-03T01:00:00Z'));
|
|
@@ -602,6 +710,131 @@ describe('session-indexer', () => {
|
|
|
602
710
|
});
|
|
603
711
|
});
|
|
604
712
|
|
|
713
|
+
describe('retention and backfill alignment', () => {
|
|
714
|
+
/** Write a session JSONL file and force its mtime to `ms` (ms epoch). */
|
|
715
|
+
function writeJsonlSessionAt(sessionsDir: string, sessionId: string, ms: number): string {
|
|
716
|
+
const filePath = path.join(sessionsDir, 'project-a', `${sessionId}.jsonl`);
|
|
717
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
718
|
+
const lines = [
|
|
719
|
+
JSON.stringify({ type: 'session', id: sessionId, timestamp: new Date(ms).toISOString(), cwd: `/test/${sessionId}` }),
|
|
720
|
+
JSON.stringify({ type: 'message', id: `${sessionId}-m1`, parentId: null, timestamp: new Date(ms + 1000).toISOString(), message: { role: 'user', content: [{ type: 'text', text: `Hello ${sessionId}` }], timestamp: Date.now() } }),
|
|
721
|
+
];
|
|
722
|
+
fs.writeFileSync(filePath, lines.join('\n'));
|
|
723
|
+
fs.utimesSync(filePath, new Date(ms), new Date(ms));
|
|
724
|
+
return filePath;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
const DAY = 24 * 60 * 60 * 1000;
|
|
728
|
+
const now = () => Date.now();
|
|
729
|
+
const cutoffFor = (days: number) => now() - days * DAY;
|
|
730
|
+
|
|
731
|
+
it('does not re-index a session file outside the retention cutoff after pruning', () => {
|
|
732
|
+
const sessionsDir = path.join(tmpDir, 'sessions');
|
|
733
|
+
// A file last modified 60 days ago; retention window of 30 days => expired.
|
|
734
|
+
writeJsonlSessionAt(sessionsDir, 's1', cutoffFor(60));
|
|
735
|
+
const cutoff = cutoffFor(30);
|
|
736
|
+
|
|
737
|
+
// Fresh index (no cutoff) mirrors the pre-retention state, then retention
|
|
738
|
+
// pruning removes the session row; the on-disk file stays.
|
|
739
|
+
indexAllSessions(dbManager, sessionsDir);
|
|
740
|
+
const pruned = pruneOldSessions(dbManager, 30);
|
|
741
|
+
assert.strictEqual(pruned.sessionsRemoved, 1);
|
|
742
|
+
assert.strictEqual(dbManager.getStats().sessions, 0);
|
|
743
|
+
|
|
744
|
+
// A later startup backfill, given the same retention cutoff, must NOT
|
|
745
|
+
// re-queue the expired file -- otherwise the pruned session returns.
|
|
746
|
+
const result = indexChangedSessions(dbManager, sessionsDir, { retentionCutoffMs: cutoff });
|
|
747
|
+
assert.strictEqual(result.sessionsProcessed, 0);
|
|
748
|
+
assert.strictEqual(result.sessionsIndexed, 0);
|
|
749
|
+
assert.strictEqual(result.sessionsSkipped, 0);
|
|
750
|
+
assert.strictEqual(dbManager.getStats().sessions, 0);
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
it('does not schedule a startup backfill when only expired files remain', () => {
|
|
754
|
+
const sessionsDir = path.join(tmpDir, 'sessions');
|
|
755
|
+
writeJsonlSessionAt(sessionsDir, 's1', cutoffFor(60));
|
|
756
|
+
const cutoff = cutoffFor(30);
|
|
757
|
+
indexAllSessions(dbManager, sessionsDir);
|
|
758
|
+
pruneOldSessions(dbManager, 30);
|
|
759
|
+
touchBackfillTimestamp(dbManager);
|
|
760
|
+
|
|
761
|
+
// With every file outside the window, needsBackfill must be false so no
|
|
762
|
+
// backfill is scheduled on the next startup (no repeated re-indexing).
|
|
763
|
+
assert.strictEqual(needsBackfill(dbManager, sessionsDir, new Date(), cutoff), false);
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
it('still indexes files within the retention cutoff even when others are expired', () => {
|
|
767
|
+
const sessionsDir = path.join(tmpDir, 'sessions');
|
|
768
|
+
writeJsonlSessionAt(sessionsDir, 'old', cutoffFor(60));
|
|
769
|
+
writeJsonlSessionAt(sessionsDir, 'fresh', now());
|
|
770
|
+
const cutoff = cutoffFor(30);
|
|
771
|
+
|
|
772
|
+
const result = indexChangedSessions(dbManager, sessionsDir, { retentionCutoffMs: cutoff });
|
|
773
|
+
// Only the fresh file is eligible; the expired one is skipped.
|
|
774
|
+
assert.strictEqual(result.sessionsProcessed, 1);
|
|
775
|
+
assert.strictEqual(result.sessionsIndexed, 1);
|
|
776
|
+
const stats = getSessionStats(dbManager);
|
|
777
|
+
assert.deepStrictEqual(stats.projects[0].sessions, 1); // only the retained session
|
|
778
|
+
assert.strictEqual(needsBackfill(dbManager, sessionsDir, new Date(), cutoff), true);
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
it('prunes only sessions whose file mtime is outside the retention window', () => {
|
|
782
|
+
const sessionsDir = path.join(tmpDir, 'sessions');
|
|
783
|
+
writeJsonlSessionAt(sessionsDir, 'old', cutoffFor(60));
|
|
784
|
+
writeJsonlSessionAt(sessionsDir, 'recent', now());
|
|
785
|
+
indexAllSessions(dbManager, sessionsDir);
|
|
786
|
+
|
|
787
|
+
const pruned = pruneOldSessions(dbManager, 30);
|
|
788
|
+
// 'old' has an expired file mtime and is pruned; 'recent' has a fresh
|
|
789
|
+
// file mtime and is kept, even though both were indexed.
|
|
790
|
+
assert.strictEqual(pruned.sessionsRemoved, 1);
|
|
791
|
+
const stats = getSessionStats(dbManager);
|
|
792
|
+
assert.strictEqual(stats.totalSessions, 1);
|
|
793
|
+
const remaining = dbManager.getDb().prepare('SELECT id FROM sessions').all() as { id: string }[];
|
|
794
|
+
assert.deepStrictEqual(remaining.map((r) => r.id), ['recent']);
|
|
795
|
+
});
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
describe('pruneOldSessions', () => {
|
|
799
|
+
it('removes nothing when the database is empty', () => {
|
|
800
|
+
const result = pruneOldSessions(dbManager, 30);
|
|
801
|
+
assert.deepStrictEqual(result, { sessionsRemoved: 0, messagesRemoved: 0 });
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
it('removes sessions older than the retention window and their messages', () => {
|
|
805
|
+
indexSession(dbManager, createTestSession({ id: 'old', startedAt: '2020-01-01T00:00:00Z' }));
|
|
806
|
+
indexSession(dbManager, createTestSession({ id: 'recent', startedAt: new Date().toISOString() }));
|
|
807
|
+
|
|
808
|
+
const result = pruneOldSessions(dbManager, 30);
|
|
809
|
+
assert.strictEqual(result.sessionsRemoved, 1);
|
|
810
|
+
assert.strictEqual(result.messagesRemoved, 2); // old session has 2 messages
|
|
811
|
+
|
|
812
|
+
const stats = getSessionStats(dbManager);
|
|
813
|
+
assert.strictEqual(stats.totalSessions, 1);
|
|
814
|
+
assert.strictEqual(stats.totalMessages, 2);
|
|
815
|
+
const remaining = dbManager.getDb().prepare('SELECT id FROM sessions').all() as { id: string }[];
|
|
816
|
+
assert.deepStrictEqual(remaining.map((r) => r.id), ['recent']);
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
it('leaves sessions exactly at the retention boundary (not strictly older) untouched', () => {
|
|
820
|
+
const now = Date.now();
|
|
821
|
+
const justWithin = new Date(now - 10 * 24 * 60 * 60 * 1000).toISOString(); // 10 days ago
|
|
822
|
+
indexSession(dbManager, createTestSession({ id: 'within', startedAt: justWithin }));
|
|
823
|
+
|
|
824
|
+
const result = pruneOldSessions(dbManager, 30);
|
|
825
|
+
assert.strictEqual(result.sessionsRemoved, 0);
|
|
826
|
+
assert.strictEqual(dbManager.getStats().sessions, 1);
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
it('is idempotent across repeated calls', () => {
|
|
830
|
+
indexSession(dbManager, createTestSession({ id: 'old', startedAt: '2020-01-01T00:00:00Z' }));
|
|
831
|
+
|
|
832
|
+
pruneOldSessions(dbManager, 30);
|
|
833
|
+
const again = pruneOldSessions(dbManager, 30);
|
|
834
|
+
assert.deepStrictEqual(again, { sessionsRemoved: 0, messagesRemoved: 0 });
|
|
835
|
+
});
|
|
836
|
+
});
|
|
837
|
+
|
|
605
838
|
describe('getSessionStats', () => {
|
|
606
839
|
it('should return zero counts for empty database', () => {
|
|
607
840
|
const stats = getSessionStats(dbManager);
|
|
@@ -50,6 +50,18 @@ describe('session-search', () => {
|
|
|
50
50
|
assert.ok(results.length > 0);
|
|
51
51
|
assert.ok(results.some(r => r.content.includes('Prisma')));
|
|
52
52
|
});
|
|
53
|
+
it('degrades an all-stop-word query to the LIKE fallback instead of []', () => {
|
|
54
|
+
// "the" is a stop word and "and"/"or" connectors, so FTS5 normalization
|
|
55
|
+
// leaves nothing to match. The query must fall through to the scoped
|
|
56
|
+
// literal fallback (which still searches the raw terms) rather than
|
|
57
|
+
// hard-return an empty result set.
|
|
58
|
+
indexSession(dbManager, createTestSession());
|
|
59
|
+
|
|
60
|
+
const results = searchSessions(dbManager, 'the and or');
|
|
61
|
+
assert.ok(results.length > 0, 'all-stop-word query must reach the LIKE fallback, not []');
|
|
62
|
+
assert.ok(results.some(r => r.content.includes('the')));
|
|
63
|
+
});
|
|
64
|
+
|
|
53
65
|
it('finds pure CJK substrings with the trigram tokenizer', () => {
|
|
54
66
|
indexSession(dbManager, createTestSession({ id: 'cjk-trigram-session', messages: [
|
|
55
67
|
{ id: 'cjk-trigram-session-msg-1', role: 'assistant', content: '设备清单包含 NAS 和备份策略', timestamp: '2026-05-03T00:01:00Z' },
|