@selesai/code 0.12.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/dist/core/agent-session.d.ts +1 -1
- package/dist/core/agent-session.js +1 -1
- package/dist/core/sdk.d.ts +2 -2
- package/dist/core/sdk.js +1 -1
- package/dist/core/system-prompt.d.ts +1 -1
- package/dist/core/system-prompt.js +1 -1
- package/dist/core/usage-totals.d.ts +2 -0
- package/dist/core/usage-totals.js +10 -0
- package/dist/extensions/ascii.txt +9 -0
- package/dist/extensions/package.json +1 -2
- package/dist/extensions/pi-hermes-memory/CHANGELOG.md +371 -0
- package/dist/extensions/pi-hermes-memory/LICENSE +21 -0
- package/dist/extensions/pi-hermes-memory/README.md +643 -0
- package/dist/extensions/pi-hermes-memory/docs/0.1/TASKS.md +197 -0
- package/dist/extensions/pi-hermes-memory/docs/0.2/PLAN.md +290 -0
- package/dist/extensions/pi-hermes-memory/docs/0.2/TASKS.md +134 -0
- package/dist/extensions/pi-hermes-memory/docs/0.2/TEST-PLAN.md +216 -0
- package/dist/extensions/pi-hermes-memory/docs/0.3/PLAN.md +330 -0
- package/dist/extensions/pi-hermes-memory/docs/0.3/TASKS.md +125 -0
- package/dist/extensions/pi-hermes-memory/docs/0.4/PLAN.md +160 -0
- package/dist/extensions/pi-hermes-memory/docs/0.4/TASKS.md +113 -0
- package/dist/extensions/pi-hermes-memory/docs/0.6/CHANGELOG.md +57 -0
- package/dist/extensions/pi-hermes-memory/docs/0.6/PLAN.md +202 -0
- package/dist/extensions/pi-hermes-memory/docs/0.6/TASKS.md +144 -0
- package/dist/extensions/pi-hermes-memory/docs/0.7/PLAN.md +349 -0
- package/dist/extensions/pi-hermes-memory/docs/0.7/TASKS.md +110 -0
- package/dist/extensions/pi-hermes-memory/docs/PUBLISHING.md +149 -0
- package/dist/extensions/pi-hermes-memory/docs/ROADMAP.md +468 -0
- package/dist/extensions/pi-hermes-memory/docs/images/memory-architecture.svg +1 -0
- package/dist/extensions/pi-hermes-memory/docs/images/pi-logo.svg +22 -0
- package/dist/extensions/pi-hermes-memory/docs/images/pi_memory.png +0 -0
- package/dist/extensions/pi-hermes-memory/docs/images/pi_memory.svg +19 -0
- package/dist/extensions/pi-hermes-memory/docs/images/security-flow.svg +1 -0
- package/dist/extensions/pi-hermes-memory/docs/images/session-lifecycle.svg +1 -0
- package/dist/extensions/pi-hermes-memory/docs/images/source-architecture.svg +1 -0
- package/dist/extensions/pi-hermes-memory/docs/mermaid/memory-architecture.mmd +49 -0
- package/dist/extensions/pi-hermes-memory/docs/mermaid/security-flow.mmd +40 -0
- package/dist/extensions/pi-hermes-memory/docs/mermaid/session-lifecycle.mmd +56 -0
- package/dist/extensions/pi-hermes-memory/docs/mermaid/source-architecture.mmd +54 -0
- package/dist/extensions/pi-hermes-memory/package-lock.json +3799 -0
- package/dist/extensions/pi-hermes-memory/package.json +66 -0
- package/dist/extensions/pi-hermes-memory/scripts/check-min-sdk.mjs +99 -0
- package/dist/extensions/pi-hermes-memory/scripts/ensure-dev.mjs +57 -0
- package/dist/extensions/pi-hermes-memory/src/auto-consolidation-warning.ts +6 -0
- package/dist/extensions/pi-hermes-memory/src/config.ts +178 -0
- package/dist/extensions/pi-hermes-memory/src/constants.ts +424 -0
- package/dist/extensions/pi-hermes-memory/src/extension-root-migration.ts +639 -0
- package/dist/extensions/pi-hermes-memory/src/handlers/auto-consolidate.ts +401 -0
- package/dist/extensions/pi-hermes-memory/src/handlers/background-review.ts +297 -0
- package/dist/extensions/pi-hermes-memory/src/handlers/child-process-watchdog.mjs +90 -0
- package/dist/extensions/pi-hermes-memory/src/handlers/correction-detector.ts +300 -0
- package/dist/extensions/pi-hermes-memory/src/handlers/index-sessions.ts +86 -0
- package/dist/extensions/pi-hermes-memory/src/handlers/insights.ts +78 -0
- package/dist/extensions/pi-hermes-memory/src/handlers/interview.ts +37 -0
- package/dist/extensions/pi-hermes-memory/src/handlers/learn-memory.ts +186 -0
- package/dist/extensions/pi-hermes-memory/src/handlers/message-parts.ts +27 -0
- package/dist/extensions/pi-hermes-memory/src/handlers/pi-child-process.ts +485 -0
- package/dist/extensions/pi-hermes-memory/src/handlers/preview-context.ts +105 -0
- package/dist/extensions/pi-hermes-memory/src/handlers/review-memory-ops.ts +506 -0
- package/dist/extensions/pi-hermes-memory/src/handlers/session-backfill.ts +145 -0
- package/dist/extensions/pi-hermes-memory/src/handlers/session-flush.ts +151 -0
- package/dist/extensions/pi-hermes-memory/src/handlers/session-live-index.ts +94 -0
- package/dist/extensions/pi-hermes-memory/src/handlers/skills-command.ts +1334 -0
- package/dist/extensions/pi-hermes-memory/src/handlers/standing-pin.ts +111 -0
- package/dist/extensions/pi-hermes-memory/src/handlers/switch-project.ts +75 -0
- package/dist/extensions/pi-hermes-memory/src/handlers/sync-markdown-memories.ts +266 -0
- package/dist/extensions/pi-hermes-memory/src/index.ts +378 -0
- package/dist/extensions/pi-hermes-memory/src/lifecycle-timing.ts +54 -0
- package/dist/extensions/pi-hermes-memory/src/paths.ts +61 -0
- package/dist/extensions/pi-hermes-memory/src/project-context.ts +13 -0
- package/dist/extensions/pi-hermes-memory/src/project-memory-migration.ts +93 -0
- package/dist/extensions/pi-hermes-memory/src/project.ts +157 -0
- package/dist/extensions/pi-hermes-memory/src/prompt-context.ts +55 -0
- package/dist/extensions/pi-hermes-memory/src/store/atomic-lock-coordinator.ts +375 -0
- package/dist/extensions/pi-hermes-memory/src/store/canonical-storage-path.ts +54 -0
- package/dist/extensions/pi-hermes-memory/src/store/content-scanner.ts +101 -0
- package/dist/extensions/pi-hermes-memory/src/store/db.ts +1180 -0
- package/dist/extensions/pi-hermes-memory/src/store/fts-query.ts +101 -0
- package/dist/extensions/pi-hermes-memory/src/store/markdown-mutation-lock.ts +38 -0
- package/dist/extensions/pi-hermes-memory/src/store/memory-lookup.ts +24 -0
- package/dist/extensions/pi-hermes-memory/src/store/memory-store.ts +1191 -0
- package/dist/extensions/pi-hermes-memory/src/store/recovery-maintenance.ts +66 -0
- package/dist/extensions/pi-hermes-memory/src/store/schema.ts +118 -0
- package/dist/extensions/pi-hermes-memory/src/store/session-anchor-search.ts +472 -0
- package/dist/extensions/pi-hermes-memory/src/store/session-indexer.ts +511 -0
- package/dist/extensions/pi-hermes-memory/src/store/session-parser.ts +213 -0
- package/dist/extensions/pi-hermes-memory/src/store/session-search.ts +234 -0
- package/dist/extensions/pi-hermes-memory/src/store/skill-store.ts +950 -0
- package/dist/extensions/pi-hermes-memory/src/store/skill-utils.ts +133 -0
- package/dist/extensions/pi-hermes-memory/src/store/sqlite-memory-store.ts +1002 -0
- package/dist/extensions/pi-hermes-memory/src/store/sqlite-native.ts +241 -0
- package/dist/extensions/pi-hermes-memory/src/store/standing-instructions.ts +247 -0
- package/dist/extensions/pi-hermes-memory/src/tools/memory-search-tool.ts +95 -0
- package/dist/extensions/pi-hermes-memory/src/tools/memory-tool.ts +469 -0
- package/dist/extensions/pi-hermes-memory/src/tools/session-search-tool.ts +246 -0
- package/dist/extensions/pi-hermes-memory/src/tools/shared-output-view.ts +170 -0
- package/dist/extensions/pi-hermes-memory/src/tools/skill-tool.ts +333 -0
- package/dist/extensions/pi-hermes-memory/src/tools/tool-result-views.ts +105 -0
- package/dist/extensions/pi-hermes-memory/src/types.ts +214 -0
- package/dist/extensions/pi-hermes-memory/tests/config.test.ts +434 -0
- package/dist/extensions/pi-hermes-memory/tests/extension-root-migration.test.ts +368 -0
- package/dist/extensions/pi-hermes-memory/tests/handlers/auto-consolidate.test.ts +984 -0
- package/dist/extensions/pi-hermes-memory/tests/handlers/background-review.test.ts +950 -0
- package/dist/extensions/pi-hermes-memory/tests/handlers/correction-detector.test.ts +671 -0
- package/dist/extensions/pi-hermes-memory/tests/handlers/insights.test.ts +157 -0
- package/dist/extensions/pi-hermes-memory/tests/handlers/interview.test.ts +127 -0
- package/dist/extensions/pi-hermes-memory/tests/handlers/pi-child-process.test.ts +905 -0
- package/dist/extensions/pi-hermes-memory/tests/handlers/preview-context.test.ts +152 -0
- package/dist/extensions/pi-hermes-memory/tests/handlers/prompt-context.test.ts +166 -0
- package/dist/extensions/pi-hermes-memory/tests/handlers/resources-discover.test.ts +71 -0
- package/dist/extensions/pi-hermes-memory/tests/handlers/review-memory-ops.test.ts +614 -0
- package/dist/extensions/pi-hermes-memory/tests/handlers/session-backfill.test.ts +192 -0
- package/dist/extensions/pi-hermes-memory/tests/handlers/session-flush.test.ts +677 -0
- package/dist/extensions/pi-hermes-memory/tests/handlers/session-live-index.test.ts +225 -0
- package/dist/extensions/pi-hermes-memory/tests/handlers/skills-command.test.ts +827 -0
- package/dist/extensions/pi-hermes-memory/tests/handlers/standing-pin.test.ts +134 -0
- package/dist/extensions/pi-hermes-memory/tests/handlers/sync-markdown-memories.test.ts +491 -0
- package/dist/extensions/pi-hermes-memory/tests/handlers/system-prompt.test.ts +199 -0
- package/dist/extensions/pi-hermes-memory/tests/handlers/thinking-max.test.ts +64 -0
- package/dist/extensions/pi-hermes-memory/tests/index.test.ts +30 -0
- package/dist/extensions/pi-hermes-memory/tests/integration/flow.test.ts +126 -0
- package/dist/extensions/pi-hermes-memory/tests/lifecycle-timing.test.ts +76 -0
- package/dist/extensions/pi-hermes-memory/tests/paths.test.ts +23 -0
- package/dist/extensions/pi-hermes-memory/tests/project-memory-migration.test.ts +80 -0
- package/dist/extensions/pi-hermes-memory/tests/project-rebinding.test.ts +115 -0
- package/dist/extensions/pi-hermes-memory/tests/project.test.ts +120 -0
- package/dist/extensions/pi-hermes-memory/tests/run-all-timeout.test.ts +107 -0
- package/dist/extensions/pi-hermes-memory/tests/run-all.sh +45 -0
- package/dist/extensions/pi-hermes-memory/tests/store/atomic-lock-coordinator.test.ts +381 -0
- package/dist/extensions/pi-hermes-memory/tests/store/content-scanner.test.ts +388 -0
- package/dist/extensions/pi-hermes-memory/tests/store/db.test.ts +1046 -0
- package/dist/extensions/pi-hermes-memory/tests/store/markdown-mutation-lock.test.ts +34 -0
- package/dist/extensions/pi-hermes-memory/tests/store/memory-store.test.ts +2150 -0
- package/dist/extensions/pi-hermes-memory/tests/store/recovery-maintenance.test.ts +170 -0
- package/dist/extensions/pi-hermes-memory/tests/store/session-anchor-search.test.ts +210 -0
- package/dist/extensions/pi-hermes-memory/tests/store/session-indexer.test.ts +643 -0
- package/dist/extensions/pi-hermes-memory/tests/store/session-parser.test.ts +282 -0
- package/dist/extensions/pi-hermes-memory/tests/store/session-search.test.ts +287 -0
- package/dist/extensions/pi-hermes-memory/tests/store/skill-store.test.ts +678 -0
- package/dist/extensions/pi-hermes-memory/tests/store/skill-utils.test.ts +21 -0
- package/dist/extensions/pi-hermes-memory/tests/store/sqlite-lazy-load.test.ts +78 -0
- package/dist/extensions/pi-hermes-memory/tests/store/sqlite-memory-store.test.ts +543 -0
- package/dist/extensions/pi-hermes-memory/tests/store/sqlite-native.test.ts +164 -0
- package/dist/extensions/pi-hermes-memory/tests/store/standing-instructions.test.ts +171 -0
- package/dist/extensions/pi-hermes-memory/tests/tools/memory-search-tool.test.ts +84 -0
- package/dist/extensions/pi-hermes-memory/tests/tools/memory-tool.test.ts +709 -0
- package/dist/extensions/pi-hermes-memory/tests/tools/session-search-tool.test.ts +345 -0
- package/dist/extensions/pi-hermes-memory/tests/tools/shared-output-view.test.ts +362 -0
- package/dist/extensions/pi-hermes-memory/tests/tools/skill-tool.test.ts +478 -0
- package/dist/extensions/pi-hermes-memory/tests/tools/tool-result-renderer-wiring.test.ts +40 -0
- package/dist/extensions/pi-hermes-memory/tsconfig.json +19 -0
- package/dist/extensions/pi-subagents/.oxlintrc.json +43 -0
- package/dist/extensions/pi-subagents/AGENTS.md +5 -0
- package/dist/extensions/pi-subagents/CHANGELOG.md +157 -0
- package/dist/extensions/pi-subagents/README.md +12 -6
- package/dist/extensions/pi-subagents/VISION.md +81 -0
- package/dist/extensions/pi-subagents/agents/claude-code-writer.md +15 -0
- package/dist/extensions/pi-subagents/agents/claude-code.md +15 -0
- package/dist/extensions/pi-subagents/agents/codex-exec-writer.md +15 -0
- package/dist/extensions/pi-subagents/agents/codex-exec.md +15 -0
- package/dist/extensions/pi-subagents/agents/cursor-agent-writer.md +14 -0
- package/dist/extensions/pi-subagents/agents/cursor-agent.md +14 -0
- package/dist/extensions/pi-subagents/agents/reviewer.md +1 -3
- package/dist/extensions/pi-subagents/docs/agents.md +488 -0
- package/dist/extensions/pi-subagents/docs/configuration.md +46 -9
- package/dist/extensions/pi-subagents/docs/extension-api.md +42 -3
- package/dist/extensions/pi-subagents/docs/models.md +27 -4
- package/dist/extensions/pi-subagents/docs/observability.md +10 -9
- package/dist/extensions/pi-subagents/docs/tool-reference.md +102 -6
- package/dist/extensions/pi-subagents/docs/workflows.md +135 -0
- package/dist/extensions/pi-subagents/package-lock.json +389 -2
- package/dist/extensions/pi-subagents/package.json +3 -1
- package/dist/extensions/pi-subagents/prompts/review-loop.md +2 -2
- package/dist/extensions/pi-subagents/skills/council-mode/SKILL.md +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +3 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +7 -3
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +26 -5
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +41 -22
- package/dist/extensions/pi-subagents/src/agents/agent-refinements.ts +4 -4
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +9 -2
- package/dist/extensions/pi-subagents/src/agents/agents.ts +322 -75
- package/dist/extensions/pi-subagents/src/agents/builtin-names.ts +6 -0
- package/dist/extensions/pi-subagents/src/agents/runtime-agent-events.ts +70 -0
- package/dist/extensions/pi-subagents/src/agents/runtime-agent-registry.ts +27 -20
- package/dist/extensions/pi-subagents/src/api/agents.ts +10 -5
- package/dist/extensions/pi-subagents/src/api/background-work.ts +5 -1
- package/dist/extensions/pi-subagents/src/api/delegation.ts +0 -7
- package/dist/extensions/pi-subagents/src/api/preflight.ts +34 -12
- package/dist/extensions/pi-subagents/src/extension/config.ts +70 -0
- package/dist/extensions/pi-subagents/src/extension/doctor.ts +3 -3
- package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +5 -3
- package/dist/extensions/pi-subagents/src/extension/index.ts +83 -30
- package/dist/extensions/pi-subagents/src/extension/public-execution.ts +44 -15
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +55 -19
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +41 -15
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +28 -31
- package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +2 -1
- package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +2 -10
- package/dist/extensions/pi-subagents/src/inspectors/herdr/session-roots-codec.ts +42 -0
- package/dist/extensions/pi-subagents/src/integrations/herdr-status.ts +51 -3
- package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +2 -0
- package/dist/extensions/pi-subagents/src/profiles/profiles.ts +5 -6
- package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +77 -10
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +176 -53
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +19 -12
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +29 -27
- package/dist/extensions/pi-subagents/src/runs/background/async-retention.ts +20 -3
- package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +23 -261
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +73 -7
- package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +8 -3
- package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +74 -8
- package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +37 -21
- package/dist/extensions/pi-subagents/src/runs/background/inspect-rpc.ts +8 -8
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +184 -9
- package/dist/extensions/pi-subagents/src/runs/background/result-delivery-ownership.ts +45 -0
- package/dist/extensions/pi-subagents/src/runs/background/result-files.ts +28 -14
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +38 -15
- package/dist/extensions/pi-subagents/src/runs/background/resume-guidance.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +50 -13
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +93 -8
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +10 -4
- package/dist/extensions/pi-subagents/src/runs/background/steering.ts +4 -14
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +549 -377
- package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +58 -10
- package/dist/extensions/pi-subagents/src/runs/background/terminal-run-index.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +26 -1
- package/dist/extensions/pi-subagents/src/runs/background/wait-config.ts +23 -9
- package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +9 -2
- package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +204 -129
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +9 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +23 -1
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +773 -311
- package/dist/extensions/pi-subagents/src/runs/foreground/workflow-detach-reconcile.ts +140 -115
- package/dist/extensions/pi-subagents/src/runs/shared/abort-recovery.ts +119 -0
- package/dist/extensions/pi-subagents/src/runs/shared/async-status-projection.ts +463 -0
- package/dist/extensions/pi-subagents/src/runs/shared/child-identity.ts +19 -4
- package/dist/extensions/pi-subagents/src/runs/shared/child-launch-plan.ts +151 -0
- package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +21 -7
- package/dist/extensions/pi-subagents/src/runs/shared/claude-code-adapter.ts +129 -0
- package/dist/extensions/pi-subagents/src/runs/shared/codex-exec-adapter.ts +129 -0
- package/dist/extensions/pi-subagents/src/runs/shared/completion-evidence.ts +89 -0
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +5 -4
- package/dist/extensions/pi-subagents/src/runs/shared/cursor-agent-adapter.ts +114 -0
- package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +3 -3
- package/dist/extensions/pi-subagents/src/runs/shared/external-cli-contract.ts +167 -0
- package/dist/extensions/pi-subagents/src/runs/shared/external-cli-preflight.ts +122 -0
- package/dist/extensions/pi-subagents/src/runs/shared/external-cli-runner.ts +348 -55
- package/dist/extensions/pi-subagents/src/runs/shared/fast-mode-extension.ts +5 -5
- package/dist/extensions/pi-subagents/src/runs/shared/host-step-status.ts +230 -0
- package/dist/extensions/pi-subagents/src/runs/shared/lane-metadata.ts +105 -0
- package/dist/extensions/pi-subagents/src/runs/shared/launch-cwd.ts +16 -0
- package/dist/extensions/pi-subagents/src/runs/shared/long-running-guard.ts +2 -1
- package/dist/extensions/pi-subagents/src/runs/shared/mcp-config-sources.ts +422 -0
- package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +221 -158
- package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-grant.ts +197 -0
- package/dist/extensions/pi-subagents/src/runs/shared/model-exclusions.ts +69 -7
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +47 -6
- package/dist/extensions/pi-subagents/src/runs/shared/mutation-evidence.ts +7 -2
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +9 -3
- package/dist/extensions/pi-subagents/src/runs/shared/nested-render.ts +9 -5
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +419 -7
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +15 -1
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +81 -8
- package/dist/extensions/pi-subagents/src/runs/shared/process-signal.ts +13 -0
- package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +21 -1
- package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +44 -4
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +98 -13
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/shared/worktree-cleanup-plan.ts +847 -0
- package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +18 -0
- package/dist/extensions/pi-subagents/src/shared/child-session-name.ts +46 -0
- package/dist/extensions/pi-subagents/src/shared/extension-context.ts +24 -0
- package/dist/extensions/pi-subagents/src/shared/fork-context.ts +21 -0
- package/dist/extensions/pi-subagents/src/shared/formatters.ts +18 -3
- package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +5 -1
- package/dist/extensions/pi-subagents/src/shared/pruned-fork.ts +450 -0
- package/dist/extensions/pi-subagents/src/shared/session-file-trust.ts +19 -0
- package/dist/extensions/pi-subagents/src/shared/session-tokens.ts +14 -3
- package/dist/extensions/pi-subagents/src/shared/settings.ts +19 -105
- package/dist/extensions/pi-subagents/src/shared/shortcuts.ts +17 -0
- package/dist/extensions/pi-subagents/src/shared/types.ts +359 -40
- package/dist/extensions/pi-subagents/src/shared/utils.ts +41 -84
- package/dist/extensions/pi-subagents/src/shared/workflow-child-permit.ts +116 -0
- package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +1 -9
- package/dist/extensions/pi-subagents/src/slash/delegation-request.ts +0 -4
- package/dist/extensions/pi-subagents/src/slash/slash-bridge.ts +1 -2
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +378 -88
- package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +22 -11
- package/dist/extensions/pi-subagents/src/slash/subagents-admin.ts +3 -0
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +147 -79
- package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +11 -5
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +39 -18
- package/dist/extensions/pi-subagents/src/tui/render.ts +369 -46
- package/dist/extensions/pi-subagents/src/watchdog/turn-delta.ts +1 -1
- package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +6 -3
- package/dist/extensions/pi-subagents/src/workflows/host-command.ts +230 -0
- package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +740 -58
- package/dist/extensions/pi-subagents/src/workflows/workflow-child-summary.ts +121 -0
- package/dist/extensions/pi-subagents/src/workflows/workflow-preflight.ts +270 -0
- package/dist/extensions/pi-subagents/src/workflows/workflow-receipt.ts +195 -6
- package/dist/extensions/pi-subagents/src/workflows/workflow-settlement.ts +246 -0
- package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +1114 -260
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +34 -0
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +150 -3
- package/dist/extensions/pi-subagents/test/integration/claude-code-smoke.test.ts +52 -0
- package/dist/extensions/pi-subagents/test/integration/claude-code-writer-smoke.test.ts +55 -0
- package/dist/extensions/pi-subagents/test/integration/codex-exec-smoke.test.ts +53 -0
- package/dist/extensions/pi-subagents/test/integration/codex-exec-writer-smoke.test.ts +57 -0
- package/dist/extensions/pi-subagents/test/integration/cursor-agent-smoke.test.ts +59 -0
- package/dist/extensions/pi-subagents/test/integration/cursor-agent-writer-smoke.test.ts +62 -0
- package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +57 -1
- package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +20 -0
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +4 -3
- package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +54 -4
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +482 -4
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +157 -9
- package/dist/extensions/pi-subagents/test/integration/session-tokens.test.ts +5 -5
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1508 -291
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +423 -1
- package/dist/extensions/pi-subagents/test/integration/template-resolution.test.ts +8 -0
- package/dist/extensions/pi-subagents/test/support/cursor-smoke-inputs.ts +51 -0
- package/dist/extensions/pi-subagents/test/support/helpers.ts +2 -0
- package/dist/extensions/pi-subagents/test/support/mock-pi.ts +5 -5
- package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +6 -6
- package/dist/extensions/pi-subagents/test/unit/abort-recovery.test.ts +152 -0
- package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +48 -0
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +184 -6
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +66 -6
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +77 -0
- package/dist/extensions/pi-subagents/test/unit/agent-refinements.test.ts +18 -0
- package/dist/extensions/pi-subagents/test/unit/anti-slop-oxlint.test.ts +86 -0
- package/dist/extensions/pi-subagents/test/unit/artifacts.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +101 -1
- package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +121 -0
- package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +28 -0
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +14 -7
- package/dist/extensions/pi-subagents/test/unit/async-retention.test.ts +53 -0
- package/dist/extensions/pi-subagents/test/unit/async-status-projection.test.ts +190 -0
- package/dist/extensions/pi-subagents/test/unit/async-status-snapshot.test.ts +42 -0
- package/dist/extensions/pi-subagents/test/unit/auto-drain.test.ts +8 -4
- package/dist/extensions/pi-subagents/test/unit/background-work.test.ts +23 -0
- package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +78 -1
- package/dist/extensions/pi-subagents/test/unit/child-launch-plan.test.ts +94 -0
- package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +21 -0
- package/dist/extensions/pi-subagents/test/unit/child-session-name.test.ts +50 -0
- package/dist/extensions/pi-subagents/test/unit/claude-code-adapter.test.ts +245 -0
- package/dist/extensions/pi-subagents/test/unit/codex-exec-adapter.test.ts +192 -0
- package/dist/extensions/pi-subagents/test/unit/compaction-resume.test.ts +27 -0
- package/dist/extensions/pi-subagents/test/unit/completion-evidence.test.ts +150 -0
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +21 -0
- package/dist/extensions/pi-subagents/test/unit/cursor-agent-adapter.test.ts +259 -0
- package/dist/extensions/pi-subagents/test/unit/cursor-smoke-inputs.test.ts +77 -0
- package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +1 -4
- package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/extension-context.test.ts +27 -0
- package/dist/extensions/pi-subagents/test/unit/external-cli-runner.test.ts +223 -0
- package/dist/extensions/pi-subagents/test/unit/fast-mode-extension.test.ts +27 -0
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +208 -2
- package/dist/extensions/pi-subagents/test/unit/fleet-transcript.test.ts +29 -1
- package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +110 -1
- package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +10 -0
- package/dist/extensions/pi-subagents/test/unit/formatters.test.ts +14 -0
- package/dist/extensions/pi-subagents/test/unit/herdr-inspector.test.ts +18 -9
- package/dist/extensions/pi-subagents/test/unit/herdr-session-roots-codec.test.ts +63 -0
- package/dist/extensions/pi-subagents/test/unit/herdr-shell-command.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/herdr-status-bridge.test.ts +132 -0
- package/dist/extensions/pi-subagents/test/unit/host-command.test.ts +156 -0
- package/dist/extensions/pi-subagents/test/unit/host-step-status.test.ts +98 -0
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +178 -26
- package/dist/extensions/pi-subagents/test/unit/index-segment.test.ts +2 -1
- package/dist/extensions/pi-subagents/test/unit/inspect-rpc.test.ts +21 -0
- package/dist/extensions/pi-subagents/test/unit/mcp-direct-tool-grant.test.ts +138 -0
- package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/model-exclusions.test.ts +91 -0
- package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +72 -7
- package/dist/extensions/pi-subagents/test/unit/mutation-evidence.test.ts +21 -0
- package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +20 -2
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +129 -1
- package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +4 -0
- package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +358 -0
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +574 -21
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +109 -1
- package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +71 -1
- package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +7 -7
- package/dist/extensions/pi-subagents/test/unit/pruned-fork.test.ts +214 -0
- package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +29 -8
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +53 -0
- package/dist/extensions/pi-subagents/test/unit/result-delivery-ownership.test.ts +34 -0
- package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +101 -3
- package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +10 -8
- package/dist/extensions/pi-subagents/test/unit/run-fanout-budget.test.ts +71 -1
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +41 -7
- package/dist/extensions/pi-subagents/test/unit/runtime-agent-registration.test.ts +136 -2
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +137 -0
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +33 -12
- package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +504 -1
- package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +8 -0
- package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +12 -1
- package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +3 -5
- package/dist/extensions/pi-subagents/test/unit/steering.test.ts +5 -6
- package/dist/extensions/pi-subagents/test/unit/subagent-action-recovery.test.ts +0 -8
- package/dist/extensions/pi-subagents/test/unit/subagent-guide.test.ts +5 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +157 -2
- package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +63 -4
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +27 -23
- package/dist/extensions/pi-subagents/test/unit/wait-completions.test.ts +44 -0
- package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +7 -5
- package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +106 -0
- package/dist/extensions/pi-subagents/test/unit/workflow-detach-reconcile.test.ts +484 -9
- package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +38 -4
- package/dist/extensions/pi-subagents/test/unit/workflow-preflight.test.ts +155 -0
- package/dist/extensions/pi-subagents/test/unit/workflow-receipt.test.ts +318 -0
- package/dist/extensions/pi-subagents/test/unit/workflow-resume-hint.test.ts +365 -0
- package/dist/extensions/pi-subagents/test/unit/worktree-cleanup-plan.test.ts +349 -0
- package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +21 -0
- package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +2 -7
- package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/effect/index.ts +13 -0
- package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/effect/rules/no-service-constructor-imports.ts +52 -0
- package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/index.ts +41 -0
- package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-chained-type-assertions.ts +77 -0
- package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-conditional-empty-object-spread.ts +49 -0
- package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-known-value-widening.ts +247 -0
- package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-module-mocking.ts +91 -0
- package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-object-parameters.ts +126 -0
- package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-reflect-apply.ts +28 -0
- package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-reflect-get.ts +28 -0
- package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-runtime-typeof.ts +67 -0
- package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-shape-in-symbol-names.ts +39 -0
- package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-unknown-parameters.ts +83 -0
- package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-unknown-returns.ts +115 -0
- package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-unknown-type-aliases.ts +72 -0
- package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-unsafe-dictionary-type.ts +134 -0
- package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-widen-then-assert.ts +366 -0
- package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/require-safety-comment-for-type-assertion.ts +62 -0
- package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/shared/dictionary-types.ts +502 -0
- package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/shared/lexical-type-parameters.ts +60 -0
- package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/shared/reflect-method.ts +35 -0
- package/dist/extensions/pi-tool-display/tests/capabilities.test.ts +1 -1
- package/dist/extensions/pi-tool-display/tests/settings-inspector.test.ts +2 -2
- package/dist/extensions/pi-tool-display/tests/tool-overrides-mcp.test.ts +1 -1
- package/dist/extensions/pi-zentui/extensions/zentui/format.ts +2 -1
- package/dist/extensions/pi-zentui/test/format.test.ts +15 -1
- package/dist/modes/interactive/components/footer.js +2 -2
- package/dist/modes/interactive/interactive-mode.js +11 -11
- package/dist/skills/workflow/SKILL.md +65 -50
- package/package.json +112 -110
- package/dist/extensions/enable-readonly-tools.test.ts +0 -63
- package/dist/extensions/enable-readonly-tools.ts +0 -24
- package/dist/extensions/model-prompt-injector/config.json +0 -16
- package/dist/extensions/model-prompt-injector/index.test.ts +0 -102
- package/dist/extensions/model-prompt-injector/index.ts +0 -148
- package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +0 -98
- package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +0 -250
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project detection — determines whether the current working directory
|
|
3
|
+
* represents a project and resolves its name.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as fs from "node:fs";
|
|
7
|
+
import * as path from "node:path";
|
|
8
|
+
import * as os from "node:os";
|
|
9
|
+
import { resolveProjectsRoot } from "./paths.js";
|
|
10
|
+
|
|
11
|
+
export interface ProjectInfo {
|
|
12
|
+
/** Project name (directory basename), or null if not in a project. */
|
|
13
|
+
name: string | null;
|
|
14
|
+
/** Path to the project-scoped memory directory, or null. */
|
|
15
|
+
memoryDir: string | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ProjectSkillInfo extends ProjectInfo {
|
|
19
|
+
/** Path to the project-scoped skills directory, or null. */
|
|
20
|
+
skillsDir: string | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the repository root shared by every linked worktree of `dir`'s repo.
|
|
25
|
+
*
|
|
26
|
+
* Mirrors what `git rev-parse --git-common-dir` reports, without spawning git:
|
|
27
|
+
* a linked worktree's `.git` is a file pointing at
|
|
28
|
+
* `<main>/.git/worktrees/<name>`, and that directory carries a `commondir`
|
|
29
|
+
* file pointing back at the shared `<main>/.git`. Returns null outside a
|
|
30
|
+
* repository, or for a bare/detached layout with no obvious working root.
|
|
31
|
+
*/
|
|
32
|
+
function findGitRepoRoot(dir: string): string | null {
|
|
33
|
+
let current = path.resolve(dir);
|
|
34
|
+
|
|
35
|
+
while (true) {
|
|
36
|
+
const dotGit = path.join(current, ".git");
|
|
37
|
+
let stat: fs.Stats | undefined;
|
|
38
|
+
try {
|
|
39
|
+
stat = fs.statSync(dotGit);
|
|
40
|
+
} catch {
|
|
41
|
+
stat = undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (stat?.isDirectory()) return current;
|
|
45
|
+
|
|
46
|
+
if (stat?.isFile()) {
|
|
47
|
+
const commonDir = resolveWorktreeCommonDir(current, dotGit);
|
|
48
|
+
if (!commonDir) return current;
|
|
49
|
+
return path.basename(commonDir) === ".git" ? path.dirname(commonDir) : commonDir;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const parent = path.dirname(current);
|
|
53
|
+
if (parent === current) return null;
|
|
54
|
+
current = parent;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function resolveWorktreeCommonDir(worktreeRoot: string, dotGitFile: string): string | null {
|
|
59
|
+
let pointer: string;
|
|
60
|
+
try {
|
|
61
|
+
pointer = fs.readFileSync(dotGitFile, "utf-8");
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const match = /^gitdir:\s*(.+)$/m.exec(pointer);
|
|
67
|
+
if (!match) return null;
|
|
68
|
+
|
|
69
|
+
const gitDir = path.resolve(worktreeRoot, match[1].trim());
|
|
70
|
+
try {
|
|
71
|
+
const commonDir = fs.readFileSync(path.join(gitDir, "commondir"), "utf-8").trim();
|
|
72
|
+
if (commonDir) return path.resolve(gitDir, commonDir);
|
|
73
|
+
} catch {
|
|
74
|
+
// Not a linked worktree, or an older layout without `commondir`.
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// `<main>/.git/worktrees/<name>` — two levels up is the shared git dir.
|
|
78
|
+
const parent = path.dirname(gitDir);
|
|
79
|
+
return path.basename(parent) === "worktrees" ? path.dirname(parent) : null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const repoRootCache = new Map<string, string | null>();
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Detect project from the current working directory.
|
|
86
|
+
*
|
|
87
|
+
* A "project" is any directory that is not the user's home directory. Inside a
|
|
88
|
+
* Git repository the project name is the *repository* root's basename, so every
|
|
89
|
+
* linked worktree shares one identity instead of stranding its memory and
|
|
90
|
+
* skills under the worktree directory name (#120). Outside Git it stays the
|
|
91
|
+
* working directory's basename.
|
|
92
|
+
*
|
|
93
|
+
* An existing `projects-memory/<cwd-basename>/` directory still wins over a
|
|
94
|
+
* newly derived repository name, so upgrading never orphans memory that was
|
|
95
|
+
* written under the old cwd-basename identity.
|
|
96
|
+
*
|
|
97
|
+
* Project-scoped memory is stored at ~/.selesai/agent/<projectsMemoryDir>/<projectName>/.
|
|
98
|
+
*/
|
|
99
|
+
export function detectProject(projectsMemoryDir = "projects-memory", cwd?: string): ProjectInfo {
|
|
100
|
+
const dir = cwd ?? process.cwd();
|
|
101
|
+
const homeDir = os.homedir();
|
|
102
|
+
|
|
103
|
+
// Normalize paths for comparison
|
|
104
|
+
const resolved = path.resolve(dir);
|
|
105
|
+
const resolvedHome = path.resolve(homeDir);
|
|
106
|
+
|
|
107
|
+
if (resolved === resolvedHome || resolved === "/" || !resolved || resolved === resolvedHome + "/") {
|
|
108
|
+
return { name: null, memoryDir: null };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const cwdName = path.basename(resolved);
|
|
112
|
+
if (!cwdName || cwdName === "." || cwdName === "..") {
|
|
113
|
+
return { name: null, memoryDir: null };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const projectsRoot = resolveProjectsRoot(projectsMemoryDir);
|
|
117
|
+
const name = resolveProjectName(resolved, resolvedHome, cwdName, projectsRoot);
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
name,
|
|
121
|
+
memoryDir: path.join(projectsRoot, name),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function resolveProjectName(
|
|
126
|
+
resolved: string,
|
|
127
|
+
resolvedHome: string,
|
|
128
|
+
cwdName: string,
|
|
129
|
+
projectsRoot: string,
|
|
130
|
+
): string {
|
|
131
|
+
let repoRoot = repoRootCache.get(resolved);
|
|
132
|
+
if (repoRoot === undefined) {
|
|
133
|
+
repoRoot = findGitRepoRoot(resolved);
|
|
134
|
+
repoRootCache.set(resolved, repoRoot);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!repoRoot || repoRoot === resolved || repoRoot === resolvedHome) return cwdName;
|
|
138
|
+
|
|
139
|
+
const repoName = path.basename(repoRoot);
|
|
140
|
+
if (!repoName || repoName === cwdName) return cwdName;
|
|
141
|
+
|
|
142
|
+
// Migration bridge: a store already written under the old cwd-basename
|
|
143
|
+
// identity keeps working. Only fresh directories adopt the repository name.
|
|
144
|
+
if (!fs.existsSync(path.join(projectsRoot, repoName)) && fs.existsSync(path.join(projectsRoot, cwdName))) {
|
|
145
|
+
return cwdName;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return repoName;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function detectProjectSkills(projectsMemoryDir = "projects-memory", cwd?: string): ProjectSkillInfo {
|
|
152
|
+
const project = detectProject(projectsMemoryDir, cwd);
|
|
153
|
+
return {
|
|
154
|
+
...project,
|
|
155
|
+
skillsDir: project.memoryDir ? path.join(project.memoryDir, "skills") : null,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { MEMORY_POLICY_PROMPT, MEMORY_POLICY_PROMPT_COMPACT } from "./constants.js";
|
|
2
|
+
import type { MemoryConfig } from "./types.js";
|
|
3
|
+
import type { MemoryStore } from "./store/memory-store.js";
|
|
4
|
+
import type { StandingInstructions } from "./store/standing-instructions.js";
|
|
5
|
+
|
|
6
|
+
type MemoryPolicyConfig = Pick<MemoryConfig, "memoryPolicyStyle" | "memoryPolicyCustomText">;
|
|
7
|
+
|
|
8
|
+
export function resolveMemoryPolicyPrompt(config: MemoryPolicyConfig): string {
|
|
9
|
+
const style = config.memoryPolicyStyle ?? "full";
|
|
10
|
+
|
|
11
|
+
switch (style) {
|
|
12
|
+
case "compact":
|
|
13
|
+
return MEMORY_POLICY_PROMPT_COMPACT;
|
|
14
|
+
case "custom":
|
|
15
|
+
return config.memoryPolicyCustomText && config.memoryPolicyCustomText.trim().length > 0
|
|
16
|
+
? config.memoryPolicyCustomText
|
|
17
|
+
: MEMORY_POLICY_PROMPT_COMPACT;
|
|
18
|
+
case "none":
|
|
19
|
+
return "";
|
|
20
|
+
case "full":
|
|
21
|
+
default:
|
|
22
|
+
return MEMORY_POLICY_PROMPT;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Standing instructions are appended in *every* mode, including policy-only
|
|
28
|
+
* and policy style "none". That is the whole point of the store: a rule the
|
|
29
|
+
* user pinned must not depend on the model choosing to run memory_search
|
|
30
|
+
* before the action it forbids (#121). They go last so they read as the
|
|
31
|
+
* operative directive rather than as recalled context.
|
|
32
|
+
*/
|
|
33
|
+
export async function buildPromptContext(
|
|
34
|
+
config: Pick<MemoryConfig, "memoryMode" | "memoryPolicyStyle" | "memoryPolicyCustomText">,
|
|
35
|
+
store: MemoryStore,
|
|
36
|
+
projectStore: MemoryStore | null,
|
|
37
|
+
projectName: string,
|
|
38
|
+
standing: StandingInstructions | null = null,
|
|
39
|
+
): Promise<string> {
|
|
40
|
+
const standingBlock = standing?.formatForSystemPrompt() ?? "";
|
|
41
|
+
|
|
42
|
+
if (config.memoryMode === "policy-only") {
|
|
43
|
+
return [resolveMemoryPolicyPrompt(config), standingBlock].filter(Boolean).join("\n\n");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const memoryBlock = store.formatForSystemPrompt();
|
|
47
|
+
const projectBlock = projectStore ? projectStore.formatProjectBlock(projectName) : "";
|
|
48
|
+
|
|
49
|
+
const parts: string[] = [];
|
|
50
|
+
if (memoryBlock) parts.push(memoryBlock);
|
|
51
|
+
if (projectBlock) parts.push(projectBlock);
|
|
52
|
+
if (standingBlock) parts.push(standingBlock);
|
|
53
|
+
|
|
54
|
+
return parts.join("\n\n");
|
|
55
|
+
}
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import { isBunRuntime, loadBetterSqlite3 } from './sqlite-native.js';
|
|
7
|
+
|
|
8
|
+
type StatementLike = {
|
|
9
|
+
run: (...args: unknown[]) => unknown;
|
|
10
|
+
get: (...args: unknown[]) => unknown;
|
|
11
|
+
all: (...args: unknown[]) => unknown[];
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type DatabaseLike = {
|
|
15
|
+
prepare: (sql: string) => StatementLike;
|
|
16
|
+
exec: (sql: string) => void;
|
|
17
|
+
close: () => void;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type DatabaseCtor = new (dbPath: string) => DatabaseLike;
|
|
21
|
+
|
|
22
|
+
export interface AtomicLockOptions {
|
|
23
|
+
staleMs: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface AtomicLockLease {
|
|
27
|
+
token: string;
|
|
28
|
+
release: () => void;
|
|
29
|
+
/**
|
|
30
|
+
* Refresh the lease timestamp so a long-running-but-healthy holder is never
|
|
31
|
+
* mistaken for a wedged one. Returns false once the lease has been taken
|
|
32
|
+
* over, which is the holder's signal that it no longer owns the resource.
|
|
33
|
+
*/
|
|
34
|
+
renew: () => boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface AtomicLockCoordinatorOptions {
|
|
38
|
+
pid?: number;
|
|
39
|
+
incarnation?: string;
|
|
40
|
+
probeIncarnation?: (pid: number) => string | null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let cachedDatabaseCtor: DatabaseCtor | null = null;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Resolved on first use, never at import time — see the same note in db.ts.
|
|
47
|
+
* Compiled Pi cannot resolve better-sqlite3 at all, so Bun must take bun:sqlite.
|
|
48
|
+
*/
|
|
49
|
+
function getDatabaseCtor(): DatabaseCtor {
|
|
50
|
+
if (!cachedDatabaseCtor) {
|
|
51
|
+
const require = createRequire(import.meta.url);
|
|
52
|
+
if (isBunRuntime()) {
|
|
53
|
+
const bunSqlite = require('bun:sqlite') as { Database: DatabaseCtor };
|
|
54
|
+
cachedDatabaseCtor = bunSqlite.Database;
|
|
55
|
+
} else {
|
|
56
|
+
cachedDatabaseCtor = loadBetterSqlite3({ requireImpl: require }) as DatabaseCtor;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return cachedDatabaseCtor;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function processIsAlive(pid: number): boolean {
|
|
63
|
+
try {
|
|
64
|
+
process.kill(pid, 0);
|
|
65
|
+
return true;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
return (error as NodeJS.ErrnoException).code !== 'ESRCH';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function probeProcessIncarnation(pid: number): string | null {
|
|
72
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) return null;
|
|
73
|
+
if (process.platform === 'linux') {
|
|
74
|
+
try {
|
|
75
|
+
const stat = fs.readFileSync(`/proc/${pid}/stat`, 'utf-8');
|
|
76
|
+
const end = stat.lastIndexOf(')');
|
|
77
|
+
const fields = stat.slice(end + 2).split(' ');
|
|
78
|
+
return fields[19] || null;
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (process.platform !== 'win32') {
|
|
85
|
+
const result = spawnSync('ps', ['-o', 'lstart=', '-p', String(pid)], {
|
|
86
|
+
encoding: 'utf-8',
|
|
87
|
+
timeout: 250,
|
|
88
|
+
});
|
|
89
|
+
return result.status === 0 ? result.stdout.trim() || null : null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const result = spawnSync(
|
|
93
|
+
'powershell.exe',
|
|
94
|
+
['-NoProfile', '-NonInteractive', '-Command', `(Get-Process -Id ${pid} -ErrorAction SilentlyContinue).StartTime.ToUniversalTime().Ticks`],
|
|
95
|
+
{ encoding: 'utf-8', timeout: 500 },
|
|
96
|
+
);
|
|
97
|
+
return result.status === 0 ? result.stdout.trim() || null : null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const currentProcessIncarnation = probeProcessIncarnation(process.pid);
|
|
101
|
+
const RELEASE_ATTEMPTS = 3;
|
|
102
|
+
// Opportunistic dead-row GC: a row must be older than the grace period before
|
|
103
|
+
// a dead pid makes it collectable (guards against a pid we cannot observe, and
|
|
104
|
+
// against racing a holder that has just inserted its row), and each coordinator
|
|
105
|
+
// sweeps at most once per interval so acquisition stays cheap.
|
|
106
|
+
const DEAD_LOCK_SWEEP_GRACE_MS = 60_000;
|
|
107
|
+
const DEAD_LOCK_SWEEP_INTERVAL_MS = 60_000;
|
|
108
|
+
const pendingReleases = new Map<string, () => void>();
|
|
109
|
+
const sharedCoordinators = new Map<string, AtomicLockCoordinator>();
|
|
110
|
+
|
|
111
|
+
export class AtomicLockCoordinator {
|
|
112
|
+
private readonly pid: number;
|
|
113
|
+
private readonly incarnation: string | null;
|
|
114
|
+
private readonly probeIncarnation: (pid: number) => string | null;
|
|
115
|
+
private cachedDb: DatabaseLike | null = null;
|
|
116
|
+
private lastSweepMs = 0;
|
|
117
|
+
|
|
118
|
+
constructor(private readonly dbPath: string, options: AtomicLockCoordinatorOptions = {}) {
|
|
119
|
+
this.pid = options.pid ?? process.pid;
|
|
120
|
+
this.probeIncarnation = options.probeIncarnation
|
|
121
|
+
?? ((pid) => pid === process.pid ? currentProcessIncarnation : probeProcessIncarnation(pid));
|
|
122
|
+
this.incarnation = options.incarnation
|
|
123
|
+
?? this.probeIncarnation(this.pid)
|
|
124
|
+
?? null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
tryAcquire(key: string, options: AtomicLockOptions): AtomicLockLease | null {
|
|
128
|
+
this.retryPendingReleases(key);
|
|
129
|
+
const token = randomUUID();
|
|
130
|
+
const now = Date.now();
|
|
131
|
+
const db = this.open();
|
|
132
|
+
this.sweepDeadLocks(db, now);
|
|
133
|
+
let acquired = false;
|
|
134
|
+
|
|
135
|
+
db.exec('BEGIN IMMEDIATE');
|
|
136
|
+
try {
|
|
137
|
+
const owner = db.prepare(`
|
|
138
|
+
SELECT token, pid, incarnation, acquired_at
|
|
139
|
+
FROM locks
|
|
140
|
+
WHERE lock_key = ?
|
|
141
|
+
`).get(key) as { token: string; pid: number; incarnation: string | null; acquired_at: number } | undefined;
|
|
142
|
+
|
|
143
|
+
if (!owner) {
|
|
144
|
+
db.prepare(`
|
|
145
|
+
INSERT INTO locks (lock_key, token, pid, incarnation, acquired_at)
|
|
146
|
+
VALUES (?, ?, ?, ?, ?)
|
|
147
|
+
`).run(key, token, this.pid, this.incarnation, now);
|
|
148
|
+
acquired = true;
|
|
149
|
+
} else {
|
|
150
|
+
const observedIncarnation = this.probeIncarnation(owner.pid);
|
|
151
|
+
const alive = observedIncarnation !== null || processIsAlive(owner.pid);
|
|
152
|
+
const sameIncarnation = alive
|
|
153
|
+
&& owner.incarnation !== null
|
|
154
|
+
&& observedIncarnation !== null
|
|
155
|
+
&& owner.incarnation === observedIncarnation;
|
|
156
|
+
const unknownIncarnation = alive && (owner.incarnation === null || observedIncarnation === null);
|
|
157
|
+
// A lease is reclaimable once it has been held for longer than staleMs,
|
|
158
|
+
// regardless of whether the owning process is still alive — it may be
|
|
159
|
+
// making no progress (blocked I/O, wedged, suspended) rather than dead.
|
|
160
|
+
// This is the sole backstop for that case: liveness/incarnation checks
|
|
161
|
+
// alone cannot distinguish "alive and working" from "alive and stuck".
|
|
162
|
+
// staleMs <= 0 disables time-based takeover (liveness checks only).
|
|
163
|
+
const stale = options.staleMs > 0 && now - owner.acquired_at >= options.staleMs;
|
|
164
|
+
if (stale || (!sameIncarnation && !unknownIncarnation)) {
|
|
165
|
+
db.prepare(`
|
|
166
|
+
UPDATE locks
|
|
167
|
+
SET token = ?, pid = ?, incarnation = ?, acquired_at = ?
|
|
168
|
+
WHERE lock_key = ? AND token = ?
|
|
169
|
+
`).run(token, this.pid, this.incarnation, now, key, owner.token);
|
|
170
|
+
acquired = true;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
db.exec('COMMIT');
|
|
175
|
+
} catch (error) {
|
|
176
|
+
try {
|
|
177
|
+
db.exec('ROLLBACK');
|
|
178
|
+
} catch {
|
|
179
|
+
// The transaction is still open on a connection we are about to hand
|
|
180
|
+
// to the next caller, whose BEGIN IMMEDIATE would then fail forever.
|
|
181
|
+
// Drop the handle so open() rebuilds it.
|
|
182
|
+
this.discardCachedDb();
|
|
183
|
+
}
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!acquired) return null;
|
|
188
|
+
return {
|
|
189
|
+
token,
|
|
190
|
+
release: () => this.release(key, token),
|
|
191
|
+
renew: () => this.renew(key, token),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Fencing check for destructive operations that lack their own independent
|
|
197
|
+
* compare-and-swap (e.g. a plain fs.renameSync with no content/inode
|
|
198
|
+
* verification). A lease can be legitimately stolen from a stale-but-alive
|
|
199
|
+
* holder (see tryAcquire); a holder resuming after being stuck must verify
|
|
200
|
+
* it is still the current owner immediately before publishing, or abort.
|
|
201
|
+
* This narrows — it cannot fully close — the check-then-act race, since
|
|
202
|
+
* synchronous work between this call and the actual write is not atomic
|
|
203
|
+
* with it.
|
|
204
|
+
*/
|
|
205
|
+
isCurrentOwner(key: string, token: string): boolean {
|
|
206
|
+
const db = this.open();
|
|
207
|
+
const row = db.prepare('SELECT token FROM locks WHERE lock_key = ?').get(key) as { token: string } | undefined;
|
|
208
|
+
return row?.token === token;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Extend a held lease. A holder whose work legitimately outlives staleMs
|
|
213
|
+
* (a consolidation child can run for minutes) must beat periodically or a
|
|
214
|
+
* peer will reclaim the lease out from under it. Beating also lets staleMs
|
|
215
|
+
* stay short, so a holder that stops making progress is reclaimed in
|
|
216
|
+
* seconds instead of after its worst-case runtime.
|
|
217
|
+
*
|
|
218
|
+
* Token-fenced: a lease that has already been taken over renews nothing and
|
|
219
|
+
* returns false.
|
|
220
|
+
*/
|
|
221
|
+
renew(key: string, token: string): boolean {
|
|
222
|
+
const db = this.open();
|
|
223
|
+
db.exec('BEGIN IMMEDIATE');
|
|
224
|
+
try {
|
|
225
|
+
const owner = db.prepare('SELECT token FROM locks WHERE lock_key = ?').get(key) as { token: string } | undefined;
|
|
226
|
+
const owned = owner?.token === token;
|
|
227
|
+
if (owned) {
|
|
228
|
+
db.prepare('UPDATE locks SET acquired_at = ? WHERE lock_key = ? AND token = ?').run(Date.now(), key, token);
|
|
229
|
+
}
|
|
230
|
+
db.exec('COMMIT');
|
|
231
|
+
return owned;
|
|
232
|
+
} catch (error) {
|
|
233
|
+
try {
|
|
234
|
+
db.exec('ROLLBACK');
|
|
235
|
+
} catch {
|
|
236
|
+
this.discardCachedDb();
|
|
237
|
+
}
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
release(key: string, token: string): void {
|
|
243
|
+
const pendingKey = this.pendingReleaseKey(key, token);
|
|
244
|
+
for (let attempt = 0; attempt < RELEASE_ATTEMPTS; attempt++) {
|
|
245
|
+
try {
|
|
246
|
+
this.deleteOwnedLock(key, token);
|
|
247
|
+
pendingReleases.delete(pendingKey);
|
|
248
|
+
return;
|
|
249
|
+
} catch {
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
pendingReleases.set(pendingKey, () => this.release(key, token));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private deleteOwnedLock(key: string, token: string): void {
|
|
256
|
+
const db = this.open();
|
|
257
|
+
db.prepare('DELETE FROM locks WHERE lock_key = ? AND token = ?').run(key, token);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Delete rows whose holder process is gone.
|
|
262
|
+
*
|
|
263
|
+
* Without this, a row is only ever reclaimed by a peer that asks for the
|
|
264
|
+
* same lock key again. A key belonging to an identity that never returns
|
|
265
|
+
* (a deleted project, a one-shot `pi -p` run) leaks its row forever, and a
|
|
266
|
+
* much later session using that identity pays a spurious wait.
|
|
267
|
+
*
|
|
268
|
+
* A dead process can never release or renew its lease, so deleting its row
|
|
269
|
+
* is always safe. Probing happens outside any transaction and the DELETE is
|
|
270
|
+
* fenced on (token, acquired_at) so a row re-taken in between is left alone.
|
|
271
|
+
*/
|
|
272
|
+
private sweepDeadLocks(db: DatabaseLike, now: number): void {
|
|
273
|
+
if (now - this.lastSweepMs < DEAD_LOCK_SWEEP_INTERVAL_MS) return;
|
|
274
|
+
this.lastSweepMs = now;
|
|
275
|
+
try {
|
|
276
|
+
const rows = db
|
|
277
|
+
.prepare('SELECT lock_key, token, pid, acquired_at FROM locks WHERE acquired_at <= ?')
|
|
278
|
+
.all(now - DEAD_LOCK_SWEEP_GRACE_MS) as Array<{
|
|
279
|
+
lock_key: string;
|
|
280
|
+
token: string;
|
|
281
|
+
pid: number;
|
|
282
|
+
acquired_at: number;
|
|
283
|
+
}>;
|
|
284
|
+
const dead = rows.filter((row) => this.holderIsGone(row.pid));
|
|
285
|
+
if (dead.length === 0) return;
|
|
286
|
+
const remove = db.prepare('DELETE FROM locks WHERE lock_key = ? AND token = ? AND acquired_at = ?');
|
|
287
|
+
for (const row of dead) {
|
|
288
|
+
remove.run(row.lock_key, row.token, row.acquired_at);
|
|
289
|
+
}
|
|
290
|
+
} catch {
|
|
291
|
+
// GC is best-effort — never fail an acquisition because of it.
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** Same liveness test tryAcquire steals on, cheapest check first. */
|
|
296
|
+
private holderIsGone(pid: number): boolean {
|
|
297
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) return false;
|
|
298
|
+
if (pid === this.pid) return false;
|
|
299
|
+
if (processIsAlive(pid)) return false;
|
|
300
|
+
return this.probeIncarnation(pid) === null;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
private retryPendingReleases(key: string): void {
|
|
304
|
+
const prefix = `${path.resolve(this.dbPath)}\0${key}\0`;
|
|
305
|
+
for (const [pendingKey, release] of [...pendingReleases.entries()]) {
|
|
306
|
+
if (pendingKey.startsWith(prefix)) release();
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private pendingReleaseKey(key: string, token: string): string {
|
|
311
|
+
return `${path.resolve(this.dbPath)}\0${key}\0${token}`;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
private discardCachedDb(): void {
|
|
315
|
+
const db = this.cachedDb;
|
|
316
|
+
this.cachedDb = null;
|
|
317
|
+
if (db) {
|
|
318
|
+
try { db.close(); } catch {}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
private open(): DatabaseLike {
|
|
323
|
+
if (this.cachedDb) return this.cachedDb;
|
|
324
|
+
fs.mkdirSync(path.dirname(this.dbPath), { recursive: true });
|
|
325
|
+
const existed = fs.existsSync(this.dbPath);
|
|
326
|
+
const db = new (getDatabaseCtor())(this.dbPath);
|
|
327
|
+
try {
|
|
328
|
+
db.exec(`
|
|
329
|
+
PRAGMA busy_timeout = 5000;
|
|
330
|
+
PRAGMA journal_mode = WAL;
|
|
331
|
+
CREATE TABLE IF NOT EXISTS locks (
|
|
332
|
+
lock_key TEXT PRIMARY KEY,
|
|
333
|
+
token TEXT NOT NULL,
|
|
334
|
+
pid INTEGER NOT NULL,
|
|
335
|
+
incarnation TEXT,
|
|
336
|
+
acquired_at INTEGER NOT NULL
|
|
337
|
+
);
|
|
338
|
+
`);
|
|
339
|
+
const columns = db.prepare('PRAGMA table_info(locks)').all() as Array<{ name: string }>;
|
|
340
|
+
if (!columns.some(({ name }) => name === 'incarnation')) {
|
|
341
|
+
try {
|
|
342
|
+
db.exec('ALTER TABLE locks ADD COLUMN incarnation TEXT');
|
|
343
|
+
} catch (error) {
|
|
344
|
+
const refreshed = db.prepare('PRAGMA table_info(locks)').all() as Array<{ name: string }>;
|
|
345
|
+
if (!refreshed.some(({ name }) => name === 'incarnation')) throw error;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (!existed) fs.chmodSync(this.dbPath, 0o600);
|
|
349
|
+
this.cachedDb = db;
|
|
350
|
+
return db;
|
|
351
|
+
} catch (error) {
|
|
352
|
+
db.close();
|
|
353
|
+
throw error;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Process-wide coordinator for `dbPath`.
|
|
359
|
+
*
|
|
360
|
+
* Each instance now pins its SQLite connection for its own lifetime, so a
|
|
361
|
+
* caller that constructs a fresh coordinator per operation would leak one
|
|
362
|
+
* open WAL connection per call. Every default-options caller must share.
|
|
363
|
+
* The option-carrying constructor stays public for tests, which pass a
|
|
364
|
+
* synthetic pid/incarnation that a dbPath-keyed cache would silently ignore.
|
|
365
|
+
*/
|
|
366
|
+
static shared(dbPath: string): AtomicLockCoordinator {
|
|
367
|
+
const key = path.resolve(dbPath);
|
|
368
|
+
let coordinator = sharedCoordinators.get(key);
|
|
369
|
+
if (!coordinator) {
|
|
370
|
+
coordinator = new AtomicLockCoordinator(dbPath);
|
|
371
|
+
sharedCoordinators.set(key, coordinator);
|
|
372
|
+
}
|
|
373
|
+
return coordinator;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
const MAX_SYMLINK_DEPTH = 40;
|
|
5
|
+
|
|
6
|
+
function pathParts(absolutePath: string): { root: string; parts: string[] } {
|
|
7
|
+
const root = path.parse(absolutePath).root;
|
|
8
|
+
return {
|
|
9
|
+
root,
|
|
10
|
+
parts: absolutePath.slice(root.length).split(path.sep).filter(Boolean),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function canonicalStoragePathSync(filePath: string): string {
|
|
15
|
+
const input = path.resolve(filePath);
|
|
16
|
+
let { root, parts } = pathParts(input);
|
|
17
|
+
let current = root;
|
|
18
|
+
let depth = 0;
|
|
19
|
+
|
|
20
|
+
while (parts.length > 0) {
|
|
21
|
+
const part = parts.shift()!;
|
|
22
|
+
const candidate = path.join(current, part);
|
|
23
|
+
let state: fs.Stats;
|
|
24
|
+
try {
|
|
25
|
+
state = fs.lstatSync(candidate);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
28
|
+
return path.join(fs.realpathSync.native(current), part, ...parts);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!state.isSymbolicLink()) {
|
|
32
|
+
current = candidate;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (depth++ >= MAX_SYMLINK_DEPTH) {
|
|
37
|
+
const error = new Error(`Symbolic link loop detected while resolving ${input}`) as NodeJS.ErrnoException;
|
|
38
|
+
error.code = "ELOOP";
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
const target = fs.readlinkSync(candidate);
|
|
42
|
+
const targetPath = path.resolve(path.dirname(candidate), target);
|
|
43
|
+
const targetParts = pathParts(targetPath);
|
|
44
|
+
root = targetParts.root;
|
|
45
|
+
current = root;
|
|
46
|
+
parts = [...targetParts.parts, ...parts];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return fs.realpathSync.native(current);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function canonicalStoragePath(filePath: string): Promise<string> {
|
|
53
|
+
return canonicalStoragePathSync(filePath);
|
|
54
|
+
}
|