@pugi/cli 0.1.0-beta.7 → 0.1.0-beta.87
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 +96 -0
- package/THIRD_PARTY_NOTICES.md +40 -0
- package/assets/pugi-prozr2-mascot.ansi +9 -0
- package/bin/run.js +33 -1
- package/dist/commands/deploy.js +40 -40
- package/dist/commands/flatten.js +191 -0
- package/dist/commands/jobs-watch.js +201 -0
- package/dist/commands/jobs.js +42 -27
- package/dist/commands/smoke.js +133 -0
- package/dist/core/agent-progress/cleanup.js +134 -0
- package/dist/core/agent-progress/schema.js +144 -0
- package/dist/core/agent-progress/writer.js +101 -0
- package/dist/core/agents/adaptive-router.js +330 -0
- package/dist/core/agents/query-decomposer.js +297 -0
- package/dist/core/agents/registry.js +2 -2
- package/dist/core/approvals/shortcut-resolver.js +98 -0
- package/dist/core/artifact-chain/dispatcher.js +148 -0
- package/dist/core/artifact-chain/exporter.js +164 -0
- package/dist/core/artifact-chain/state.js +243 -0
- package/dist/core/artifact-chain/steps.js +169 -0
- package/dist/core/ask-user/question.js +92 -0
- package/dist/core/audit/audit-trail.js +275 -0
- package/dist/core/auth/ensure-authenticated.js +129 -0
- package/dist/core/auth/env-provider.js +238 -0
- package/dist/core/auto-open-browser.js +4 -4
- package/dist/core/auto-update/channels.js +122 -0
- package/dist/core/auto-update/checker.js +241 -0
- package/dist/core/auto-update/state.js +235 -0
- package/dist/core/bare-mode/index.js +107 -0
- package/dist/core/bash/redirect.js +281 -0
- package/dist/core/bash-classifier.js +436 -40
- package/dist/core/checkpoint/resumer.js +149 -0
- package/dist/core/checkpoint/rewinder.js +291 -0
- package/dist/core/checkpoints/shadow-git.js +670 -0
- package/dist/core/citations/parser.js +109 -0
- package/dist/core/classifier/yolo-classifier.js +88 -0
- package/dist/core/codegraph/decision-store.js +248 -0
- package/dist/core/codegraph/detect-repo.js +459 -0
- package/dist/core/codegraph/install.js +134 -0
- package/dist/core/codegraph/offer-hook.js +220 -0
- package/dist/core/compact/auto-trigger.js +96 -0
- package/dist/core/compact/buffer-rewriter.js +115 -0
- package/dist/core/compact/summarizer.js +208 -0
- package/dist/core/compact/token-counter.js +108 -0
- package/dist/core/consensus/anvil-fanout.js +25 -25
- package/dist/core/consensus/diff-capture.js +121 -12
- package/dist/core/consensus/rubric.js +21 -21
- package/dist/core/context/builder.js +6 -6
- package/dist/core/context/compaction-events.js +8 -8
- package/dist/core/context/compaction.js +31 -31
- package/dist/core/context/index.js +15 -8
- package/dist/core/context/invariants.js +51 -51
- package/dist/core/context/markdown-loader.js +28 -10
- package/dist/core/context/markdown-traverse.js +255 -0
- package/dist/core/context/pugiignore.js +41 -41
- package/dist/core/context/repo-skeleton.js +37 -37
- package/dist/core/context/tool-eviction.js +55 -0
- package/dist/core/context/watcher.js +32 -32
- package/dist/core/context/working-set.js +23 -23
- package/dist/core/coordinator/agent-tools.js +77 -0
- package/dist/core/coordinator/agent-toolset.js +65 -0
- package/dist/core/coordinator/fsm.js +73 -0
- package/dist/core/coordinator/mode-fsm.js +70 -0
- package/dist/core/cost/rate-card.js +129 -0
- package/dist/core/cost/tracker.js +221 -0
- package/dist/core/credentials.js +12 -12
- package/dist/core/cron/scheduler.js +138 -0
- package/dist/core/denial-tracking/index.js +8 -0
- package/dist/core/denial-tracking/state.js +264 -0
- package/dist/core/diagnostics/probe-runner.js +93 -0
- package/dist/core/diagnostics/probes/api.js +46 -0
- package/dist/core/diagnostics/probes/auth.js +93 -0
- package/dist/core/diagnostics/probes/bare-mode.js +42 -0
- package/dist/core/diagnostics/probes/cli-version.js +127 -0
- package/dist/core/diagnostics/probes/config.js +72 -0
- package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
- package/dist/core/diagnostics/probes/disk.js +81 -0
- package/dist/core/diagnostics/probes/engine-live.js +46 -0
- package/dist/core/diagnostics/probes/git.js +65 -0
- package/dist/core/diagnostics/probes/hooks.js +118 -0
- package/dist/core/diagnostics/probes/mcp.js +75 -0
- package/dist/core/diagnostics/probes/node.js +59 -0
- package/dist/core/diagnostics/probes/pnpm.js +36 -0
- package/dist/core/diagnostics/probes/pugi-md.js +89 -0
- package/dist/core/diagnostics/probes/sandbox.js +40 -0
- package/dist/core/diagnostics/probes/session.js +74 -0
- package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
- package/dist/core/diagnostics/probes/workspace.js +63 -0
- package/dist/core/diagnostics/types.js +70 -0
- package/dist/core/dispatch/cache-cleanup.js +197 -0
- package/dist/core/dispatch/cache-handoff.js +295 -0
- package/dist/core/edits/apply-patch-layer-e.js +189 -0
- package/dist/core/edits/dispatch.js +293 -7
- package/dist/core/edits/format-matrix.js +26 -0
- package/dist/core/edits/fuzzy-ladder.js +650 -0
- package/dist/core/edits/index.js +3 -1
- package/dist/core/edits/journal.js +199 -0
- package/dist/core/edits/layer-a-apply.js +15 -15
- package/dist/core/edits/layer-a-fuzzy-apply.js +198 -0
- package/dist/core/edits/layer-b-apply.js +9 -9
- package/dist/core/edits/layer-c-apply.js +6 -6
- package/dist/core/edits/layer-d-ast.js +557 -14
- package/dist/core/edits/marker-parser.js +12 -12
- package/dist/core/edits/security-gate.js +27 -27
- package/dist/core/edits/verify-hook.js +273 -0
- package/dist/core/edits/worktree.js +322 -0
- package/dist/core/engine/anvil-client.js +140 -26
- package/dist/core/engine/auto-compact.js +179 -0
- package/dist/core/engine/budgets.js +186 -0
- package/dist/core/engine/context-prefix.js +155 -0
- package/dist/core/engine/index.js +1 -1
- package/dist/core/engine/intensity.js +158 -0
- package/dist/core/engine/intent.js +260 -0
- package/dist/core/engine/native-pugi.js +1295 -227
- package/dist/core/engine/prompts.js +134 -16
- package/dist/core/engine/strip-internal-fields.js +124 -0
- package/dist/core/engine/tool-bridge.js +1295 -59
- package/dist/core/evaluation/golden-dataset.js +293 -0
- package/dist/core/feedback/queue.js +177 -0
- package/dist/core/feedback/submitter.js +145 -0
- package/dist/core/file-cache.js +113 -1
- package/dist/core/flatten/flatten-repo.js +439 -0
- package/dist/core/format/osc8-link.js +28 -0
- package/dist/core/hook-chains.js +392 -0
- package/dist/core/hooks/citation-verify-hook.js +138 -0
- package/dist/core/hooks/citation-verify.js +112 -0
- package/dist/core/hooks/events.js +44 -0
- package/dist/core/hooks/index.js +15 -0
- package/dist/core/hooks/registry.js +213 -0
- package/dist/core/hooks/runner.js +236 -0
- package/dist/core/hooks/v2/event-emitter.js +115 -0
- package/dist/core/hooks/v2/executor.js +282 -0
- package/dist/core/hooks/v2/index.js +25 -0
- package/dist/core/hooks/v2/lifecycle.js +104 -0
- package/dist/core/hooks/v2/loader.js +216 -0
- package/dist/core/hooks/v2/matcher.js +125 -0
- package/dist/core/hooks/v2/trust.js +143 -0
- package/dist/core/hooks/v2/types.js +86 -0
- package/dist/core/image/renderer.js +71 -0
- package/dist/core/init/detector.js +582 -0
- package/dist/core/init/template-renderer.js +242 -0
- package/dist/core/jobs/registry.js +18 -18
- package/dist/core/ledger/results-tsv.js +142 -0
- package/dist/core/log-discipline/stdout-redirect.js +51 -0
- package/dist/core/lsp/cache.js +105 -0
- package/dist/core/lsp/client.js +776 -0
- package/dist/core/lsp/language-detect.js +66 -0
- package/dist/core/lsp/post-edit-diagnostics.js +171 -0
- package/dist/core/lsp/symbol-tools.js +372 -0
- package/dist/core/mcp/client.js +97 -28
- package/dist/core/mcp/http-server.js +553 -0
- package/dist/core/mcp/orchestrator-tools.js +662 -0
- package/dist/core/mcp/permission.js +190 -0
- package/dist/core/mcp/registry.js +39 -17
- package/dist/core/mcp/server-tools.js +219 -0
- package/dist/core/mcp/server.js +397 -0
- package/dist/core/mcp/trust.js +10 -10
- package/dist/core/memory/dual-write.js +416 -0
- package/dist/core/memory/passive-extract.js +130 -0
- package/dist/core/memory/phase1-kinds.js +20 -0
- package/dist/core/memory/secret-scanner.js +304 -0
- package/dist/core/memory-sync/queue.js +170 -0
- package/dist/core/metrics/extract.js +113 -0
- package/dist/core/modes/roo-modes.js +68 -0
- package/dist/core/onboarding/ensure-initialized.js +133 -0
- package/dist/core/onboarding/marker.js +111 -0
- package/dist/core/onboarding/telemetry-state.js +108 -0
- package/dist/core/output-style/presets.js +176 -0
- package/dist/core/output-style/state.js +185 -0
- package/dist/core/path-security.js +287 -5
- package/dist/core/permission.js +82 -22
- package/dist/core/permissions/auto-classifier.js +124 -0
- package/dist/core/permissions/bash-parser.js +371 -0
- package/dist/core/permissions/circuit-breaker.js +83 -0
- package/dist/core/permissions/constrained-edit.js +91 -0
- package/dist/core/permissions/gate.js +278 -0
- package/dist/core/permissions/index.js +20 -0
- package/dist/core/permissions/mode.js +174 -0
- package/dist/core/permissions/network-egress.js +137 -0
- package/dist/core/permissions/state.js +241 -0
- package/dist/core/permissions/tool-class.js +93 -0
- package/dist/core/plan-mode/ui-state.js +51 -0
- package/dist/core/plans/plan-artifact.js +721 -0
- package/dist/core/policy-limits/etag-store.js +122 -0
- package/dist/core/prd-check/parser.js +215 -0
- package/dist/core/prd-check/reporter.js +127 -0
- package/dist/core/prd-check/session-review.js +557 -0
- package/dist/core/prd-check/verifiers.js +223 -0
- package/dist/core/prompt-cache/client-cache.js +99 -0
- package/dist/core/prompts/assembly.js +29 -0
- package/dist/core/prompts/registry.js +364 -0
- package/dist/core/pugi-md/cc-compat-rules.js +735 -0
- package/dist/core/pugi-md/context-injector.js +76 -0
- package/dist/core/pugi-md/walk-up.js +207 -0
- package/dist/core/python/uv-installer.js +270 -0
- package/dist/core/python/uv-resolver.js +83 -0
- package/dist/core/rate-limit/narrator.js +146 -0
- package/dist/core/recipes/cli-types.js +20 -0
- package/dist/core/recipes/loader.js +103 -0
- package/dist/core/recipes/runner.js +345 -0
- package/dist/core/recipes/schema.js +587 -0
- package/dist/core/release-notes/parser.js +241 -0
- package/dist/core/release-notes/state.js +116 -0
- package/dist/core/repl/ask.js +37 -37
- package/dist/core/repl/cancellation.js +26 -26
- package/dist/core/repl/cap-warning.js +4 -4
- package/dist/core/repl/clipboard-read.js +11 -11
- package/dist/core/repl/dispatch-fsm.js +12 -12
- package/dist/core/repl/history-search.js +15 -15
- package/dist/core/repl/history.js +28 -18
- package/dist/core/repl/kill-ring.js +5 -5
- package/dist/core/repl/model-pricing.js +135 -0
- package/dist/core/repl/privacy-banner.js +22 -22
- package/dist/core/repl/session.js +2157 -214
- package/dist/core/repl/slash-commands.js +533 -40
- package/dist/core/repl/store/index.js +1 -1
- package/dist/core/repl/store/jsonl-log.js +22 -22
- package/dist/core/repl/store/lockfile.js +10 -10
- package/dist/core/repl/store/session-store.js +136 -107
- package/dist/core/repl/store/types.js +15 -15
- package/dist/core/repl/store/uuid-v7.js +12 -12
- package/dist/core/repl/workspace-context.js +43 -21
- package/dist/core/repo-map/build.js +125 -0
- package/dist/core/repo-map/cache.js +185 -0
- package/dist/core/repo-map/extractor.js +254 -0
- package/dist/core/repo-map/formatter.js +145 -0
- package/dist/core/repo-map/page-rank.js +105 -0
- package/dist/core/repo-map/scanner.js +211 -0
- package/dist/core/retry-budget/budget.js +284 -0
- package/dist/core/retry-budget/index.js +5 -0
- package/dist/core/retry-budget/retry-cap.js +74 -0
- package/dist/core/routing/lead-worker.js +43 -0
- package/dist/core/routing/pre-flight-estimator.js +108 -0
- package/dist/core/runs/run-tree.js +103 -0
- package/dist/core/security/injection-scanner.js +367 -0
- package/dist/core/security/output-filter.js +418 -0
- package/dist/core/session/env-file.js +105 -0
- package/dist/core/session/section-budgets.js +140 -0
- package/dist/core/session.js +92 -0
- package/dist/core/settings.js +286 -5
- package/dist/core/share/formatter.js +271 -0
- package/dist/core/share/redactor.js +221 -0
- package/dist/core/share/uploader.js +267 -0
- package/dist/core/skills/defaults.js +457 -0
- package/dist/core/skills/loader.js +22 -22
- package/dist/core/skills/sources.js +27 -27
- package/dist/core/smoke/headless-driver.js +174 -0
- package/dist/core/smoke/orchestrator.js +194 -0
- package/dist/core/smoke/runner.js +238 -0
- package/dist/core/smoke/scenario-parser.js +316 -0
- package/dist/core/statusline.js +99 -0
- package/dist/core/subagents/dispatcher-real.js +600 -0
- package/dist/core/subagents/dispatcher.js +132 -43
- package/dist/core/subagents/index.js +19 -6
- package/dist/core/subagents/isolation-matrix.js +213 -0
- package/dist/core/subagents/spawn.js +19 -4
- package/dist/core/telemetry/emitter.js +229 -0
- package/dist/core/telemetry/queue.js +251 -0
- package/dist/core/theme/context.js +91 -0
- package/dist/core/theme/presets.js +228 -0
- package/dist/core/theme/state.js +181 -0
- package/dist/core/todos/invariant.js +10 -0
- package/dist/core/todos/state.js +177 -0
- package/dist/core/tool-schema/compressor.js +89 -0
- package/dist/core/transport/version-interceptor.js +166 -0
- package/dist/core/trust.js +2 -2
- package/dist/core/tui/thinking-block.js +64 -0
- package/dist/core/vim/keymap.js +288 -0
- package/dist/core/vim/state.js +92 -0
- package/dist/core/watch-markers/marker-watcher.js +133 -0
- package/dist/core/worktree-manager/cleanup.js +123 -0
- package/dist/core/worktree-manager/manager.js +303 -0
- package/dist/index.js +28 -0
- package/dist/runtime/bootstrap.js +190 -0
- package/dist/runtime/cli.js +4162 -488
- package/dist/runtime/commands/agents.js +30 -30
- package/dist/runtime/commands/budget.js +5 -5
- package/dist/runtime/commands/cancel.js +231 -0
- package/dist/runtime/commands/chain.js +489 -0
- package/dist/runtime/commands/codegraph-status.js +227 -0
- package/dist/runtime/commands/compact.js +297 -0
- package/dist/runtime/commands/config.js +32 -32
- package/dist/runtime/commands/cost.js +199 -0
- package/dist/runtime/commands/delegate.js +244 -13
- package/dist/runtime/commands/dispatch.js +126 -0
- package/dist/runtime/commands/doctor.js +579 -0
- package/dist/runtime/commands/feedback.js +184 -0
- package/dist/runtime/commands/hooks.js +184 -0
- package/dist/runtime/commands/init.js +254 -0
- package/dist/runtime/commands/lsp.js +368 -0
- package/dist/runtime/commands/mcp.js +879 -0
- package/dist/runtime/commands/memory.js +582 -0
- package/dist/runtime/commands/model.js +237 -0
- package/dist/runtime/commands/onboarding.js +275 -0
- package/dist/runtime/commands/patch.js +128 -0
- package/dist/runtime/commands/permissions.js +112 -0
- package/dist/runtime/commands/plan.js +143 -0
- package/dist/runtime/commands/prd-check.js +285 -0
- package/dist/runtime/commands/privacy.js +17 -17
- package/dist/runtime/commands/recipe.js +325 -0
- package/dist/runtime/commands/redo-blob-store.js +92 -0
- package/dist/runtime/commands/redo.js +361 -0
- package/dist/runtime/commands/release-notes.js +229 -0
- package/dist/runtime/commands/repo-map.js +95 -0
- package/dist/runtime/commands/report.js +299 -0
- package/dist/runtime/commands/resume.js +118 -0
- package/dist/runtime/commands/review-consensus.js +68 -53
- package/dist/runtime/commands/rewind.js +333 -0
- package/dist/runtime/commands/roster.js +14 -14
- package/dist/runtime/commands/sessions.js +163 -0
- package/dist/runtime/commands/share.js +316 -0
- package/dist/runtime/commands/skills.js +31 -31
- package/dist/runtime/commands/status.js +186 -0
- package/dist/runtime/commands/stickers.js +82 -0
- package/dist/runtime/commands/style.js +194 -0
- package/dist/runtime/commands/theme.js +196 -0
- package/dist/runtime/commands/undo.js +54 -22
- package/dist/runtime/commands/update.js +289 -0
- package/dist/runtime/commands/vim.js +140 -0
- package/dist/runtime/commands/worktree.js +177 -0
- package/dist/runtime/commands/worktrees.js +155 -0
- package/dist/runtime/headless-repl.js +195 -0
- package/dist/runtime/headless.js +543 -0
- package/dist/runtime/load-hooks-or-exit.js +71 -0
- package/dist/runtime/plan-decompose.js +531 -0
- package/dist/runtime/update-check.js +28 -28
- package/dist/runtime/version.js +65 -0
- package/dist/skills/bundled/batch.js +617 -0
- package/dist/skills/bundled/index.js +45 -0
- package/dist/skills/bundled/loop.js +358 -0
- package/dist/skills/bundled/remember.js +383 -0
- package/dist/skills/bundled/simplify.js +289 -0
- package/dist/skills/bundled/skillify.js +373 -0
- package/dist/skills/bundled/stuck.js +558 -0
- package/dist/skills/bundled/verify.js +439 -0
- package/dist/testing/vcr.js +486 -0
- package/dist/tools/agent-tool.js +229 -0
- package/dist/tools/apply-patch.js +556 -0
- package/dist/tools/ask-user-question.js +222 -0
- package/dist/tools/ask-user.js +115 -0
- package/dist/tools/bash.js +623 -45
- package/dist/tools/brief.js +224 -0
- package/dist/tools/enter-worktree.js +250 -0
- package/dist/tools/exit-worktree.js +147 -0
- package/dist/tools/file-tools.js +161 -44
- package/dist/tools/lsp-tools.js +189 -0
- package/dist/tools/mcp-tool.js +260 -0
- package/dist/tools/multi-edit.js +361 -0
- package/dist/tools/powershell.js +268 -0
- package/dist/tools/registry.js +85 -0
- package/dist/tools/skill-tool.js +96 -0
- package/dist/tools/sleep.js +99 -0
- package/dist/tools/synthetic-output.js +133 -0
- package/dist/tools/tasks.js +208 -0
- package/dist/tools/todo-write.js +184 -0
- package/dist/tools/verify-plan-execution.js +295 -0
- package/dist/tools/web-fetch-injection-scanner.js +207 -0
- package/dist/tools/web-fetch.js +195 -10
- package/dist/tools/web-search.js +458 -0
- package/dist/tui/agent-progress-card.js +111 -0
- package/dist/tui/agent-tree.js +11 -1
- package/dist/tui/ask-modal.js +14 -14
- package/dist/tui/ask-user-question-prompt.js +203 -0
- package/dist/tui/compact-banner.js +81 -0
- package/dist/tui/conversation-pane.js +85 -11
- package/dist/tui/cost-table.js +111 -0
- package/dist/tui/device-flow.js +2 -2
- package/dist/tui/doctor-table.js +46 -0
- package/dist/tui/feedback-prompt.js +156 -0
- package/dist/tui/input-box.js +247 -32
- package/dist/tui/login-picker.js +3 -3
- package/dist/tui/markdown-render.js +6 -6
- package/dist/tui/onboarding-wizard.js +240 -0
- package/dist/tui/permissions-picker.js +86 -0
- package/dist/tui/render.js +35 -0
- package/dist/tui/repl-render.js +332 -54
- package/dist/tui/repl-splash-art.js +16 -16
- package/dist/tui/repl-splash-mascot.js +48 -24
- package/dist/tui/repl-splash.js +22 -22
- package/dist/tui/repl.js +124 -44
- package/dist/tui/slash-palette.js +6 -6
- package/dist/tui/splash.js +2 -2
- package/dist/tui/status-bar.js +109 -31
- package/dist/tui/status-table.js +7 -0
- package/dist/tui/stickers-art.js +136 -0
- package/dist/tui/style-table.js +28 -0
- package/dist/tui/theme-table.js +29 -0
- package/dist/tui/thinking-spinner.js +123 -0
- package/dist/tui/tool-stream-pane.js +53 -4
- package/dist/tui/update-banner.js +27 -2
- package/dist/tui/vim-input.js +267 -0
- package/dist/tui/welcome-banner.js +107 -0
- package/dist/tui/welcome-data.js +293 -0
- package/dist/tui/workspace-context.js +2 -2
- package/docs/examples/codegraph.mcp.json +10 -0
- package/package.json +23 -6
- package/test/scenarios/codegen-create-file.scenario.txt +13 -0
- package/test/scenarios/compact-force.scenario.txt +11 -0
- package/test/scenarios/identity.scenario.txt +11 -0
- package/test/scenarios/persona-handoff.scenario.txt +11 -0
- package/test/scenarios/walkback.scenario.txt +12 -0
- package/dist/core/engine/compaction-hook.js +0 -154
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WORKSPACE probe — verifies `.pugi/` exists, is writable, and is not
|
|
3
|
+
* littered with stale lock files. Optional NDJSON session log presence
|
|
4
|
+
* is reported as additional context but never the basis for a verdict
|
|
5
|
+
* change (it is created on first dispatch, not at init time).
|
|
6
|
+
*
|
|
7
|
+
* The probe owns its fs surface so the spec can run in a tmp sandbox.
|
|
8
|
+
*/
|
|
9
|
+
export function probeWorkspace(ctx, fs) {
|
|
10
|
+
const pugiDir = `${ctx.cwd}/.pugi`;
|
|
11
|
+
if (!fs.existsSync(pugiDir)) {
|
|
12
|
+
return {
|
|
13
|
+
name: 'WORKSPACE',
|
|
14
|
+
status: 'warn',
|
|
15
|
+
detail: `.pugi/ not initialised in ${ctx.cwd}`,
|
|
16
|
+
remediation: 'Run `pugi init` to scaffold the workspace',
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
let isDir = false;
|
|
20
|
+
try {
|
|
21
|
+
isDir = fs.statSync(pugiDir).isDirectory();
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return {
|
|
25
|
+
name: 'WORKSPACE',
|
|
26
|
+
status: 'error',
|
|
27
|
+
detail: `.pugi/ stat failed in ${ctx.cwd}`,
|
|
28
|
+
remediation: 'Re-create the directory: `rm -rf .pugi && pugi init`',
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (!isDir) {
|
|
32
|
+
return {
|
|
33
|
+
name: 'WORKSPACE',
|
|
34
|
+
status: 'error',
|
|
35
|
+
detail: `${pugiDir} exists but is not a directory`,
|
|
36
|
+
remediation: 'Remove the file at .pugi and run `pugi init`',
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
fs.accessSync(pugiDir, fs.W_OK);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return {
|
|
44
|
+
name: 'WORKSPACE',
|
|
45
|
+
status: 'error',
|
|
46
|
+
detail: `.pugi/ is not writable for the current user`,
|
|
47
|
+
remediation: `chown / chmod the directory so the current user can write it`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// Best-effort: report session log presence as detail context. Absence
|
|
51
|
+
// is normal (events.jsonl is created lazily) so it never moves the
|
|
52
|
+
// verdict.
|
|
53
|
+
const eventLogPresent = fs.existsSync(`${pugiDir}/events.jsonl`);
|
|
54
|
+
const detail = eventLogPresent
|
|
55
|
+
? `.pugi/ writable, events.jsonl present`
|
|
56
|
+
: `.pugi/ writable (events.jsonl created on first dispatch)`;
|
|
57
|
+
return {
|
|
58
|
+
name: 'WORKSPACE',
|
|
59
|
+
status: 'ok',
|
|
60
|
+
detail,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=workspace.js.map
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* — `pugi doctor` diagnostics types.
|
|
3
|
+
*
|
|
4
|
+
* The doctor command probes the local environment + remote API +
|
|
5
|
+
* workspace state and produces a structured health report. Each probe
|
|
6
|
+
* runs independently; one probe's failure NEVER cascades to another.
|
|
7
|
+
*
|
|
8
|
+
* Status semantics:
|
|
9
|
+
* - `ok` : probe verified the expected state.
|
|
10
|
+
* - `warn` : non-blocking signal (stale CLI, low-but-not-empty disk,
|
|
11
|
+
* missing optional config, etc.). Overall verdict still
|
|
12
|
+
* passes the gate.
|
|
13
|
+
* - `error` : a real problem the operator must fix before Pugi will
|
|
14
|
+
* work end-to-end (auth missing, API unreachable, .pugi/
|
|
15
|
+
* unwritable, Node version below floor, disk full).
|
|
16
|
+
* - `skipped` : prerequisite for the probe is absent (e.g. MCP probe
|
|
17
|
+
* when no mcp.json exists). Does NOT count against the
|
|
18
|
+
* overall verdict.
|
|
19
|
+
*
|
|
20
|
+
* Layered design: this module owns NO I/O. Individual probe files own
|
|
21
|
+
* their I/O surface. The runner orchestrates them in parallel with a
|
|
22
|
+
* timeout + fail-isolation wrapper. The doctor command formats the
|
|
23
|
+
* results for human + JSON consumers.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Helper for the runner to compute the overall verdict from a probe
|
|
27
|
+
* set without leaking the algorithm into the doctor command. Any error
|
|
28
|
+
* → 'error'; any warn (no errors) → 'warning'; otherwise 'healthy'.
|
|
29
|
+
* Skipped probes do NOT influence the verdict.
|
|
30
|
+
*/
|
|
31
|
+
export function computeOverall(probes) {
|
|
32
|
+
let hasError = false;
|
|
33
|
+
let hasWarn = false;
|
|
34
|
+
for (const probe of probes) {
|
|
35
|
+
if (probe.status === 'error')
|
|
36
|
+
hasError = true;
|
|
37
|
+
else if (probe.status === 'warn')
|
|
38
|
+
hasWarn = true;
|
|
39
|
+
}
|
|
40
|
+
if (hasError)
|
|
41
|
+
return 'error';
|
|
42
|
+
if (hasWarn)
|
|
43
|
+
return 'warning';
|
|
44
|
+
return 'healthy';
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Compute the per-status counts in a single pass so renderers do not
|
|
48
|
+
* have to re-iterate. Surfaces in both the trailer line and the JSON
|
|
49
|
+
* envelope so downstream consumers can render a one-line summary
|
|
50
|
+
* without re-walking the probe array.
|
|
51
|
+
*/
|
|
52
|
+
export function countProbes(probes) {
|
|
53
|
+
const counts = { ok: 0, warn: 0, error: 0, skipped: 0 };
|
|
54
|
+
for (const probe of probes)
|
|
55
|
+
counts[probe.status] += 1;
|
|
56
|
+
return counts;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Exit-code map. Exposed for both the CLI handler and the spec so the
|
|
60
|
+
* contract stays in one place.
|
|
61
|
+
* 0 — healthy OR warnings only.
|
|
62
|
+
* 1 — internal crash (unhandled throw in the runner itself).
|
|
63
|
+
* 2 — at least one probe reported `error`.
|
|
64
|
+
*/
|
|
65
|
+
export function exitCodeFor(overall) {
|
|
66
|
+
if (overall === 'error')
|
|
67
|
+
return 2;
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fork-subagent cache-ref cleanup (—).
|
|
3
|
+
*
|
|
4
|
+
* GC pass for `.pugi/cache-refs/`. Two surfaces use this:
|
|
5
|
+
*
|
|
6
|
+
* 1. `pugi dispatch clear-cache-refs --older-than <duration>` — operator
|
|
7
|
+
* runs this after a long session to evict stale handles. The flag
|
|
8
|
+
* accepts an `--older-than` window (e.g. `1h`, `24h`, `7d`); refs
|
|
9
|
+
* whose `createdAt` falls outside the window are removed.
|
|
10
|
+
*
|
|
11
|
+
* 2. Background sweep on REPL boot — when `cleanupStaleCacheRefs` is
|
|
12
|
+
* called on session start with a default 24h window, the dispatcher
|
|
13
|
+
* auto-prunes refs left over from crashed processes. Idempotent
|
|
14
|
+
* and silent (no logs unless `verbose: true`).
|
|
15
|
+
*
|
|
16
|
+
* Design notes:
|
|
17
|
+
*
|
|
18
|
+
* - Cleanup is path-scoped to `.pugi/cache-refs/` only. The function
|
|
19
|
+
* refuses to traverse outside that directory; a malicious symlink
|
|
20
|
+
* pointing at `~/.ssh/` would not cause damage because we only
|
|
21
|
+
* unlink files directly under cacheRefDir().
|
|
22
|
+
*
|
|
23
|
+
* - Cleanup is best-effort. A ref file with a corrupted JSON body
|
|
24
|
+
* has no `createdAt` to compare against — the policy is to treat
|
|
25
|
+
* such files as "stale" because they cannot be reused anyway.
|
|
26
|
+
* They get evicted alongside out-of-window refs.
|
|
27
|
+
*
|
|
28
|
+
* - The function returns a structured summary (counts + paths) so
|
|
29
|
+
* the CLI can render a deterministic report (`X refs removed,
|
|
30
|
+
* Y refs kept`).
|
|
31
|
+
*/
|
|
32
|
+
import { readdirSync, readFileSync, statSync, rmSync } from 'node:fs';
|
|
33
|
+
import { join } from 'node:path';
|
|
34
|
+
import { cacheRefDir, cacheRefSchema } from './cache-handoff.js';
|
|
35
|
+
const DEFAULT_OLDER_THAN_MS = 24 * 60 * 60 * 1000;
|
|
36
|
+
/**
|
|
37
|
+
* Sweep `.pugi/cache-refs/` for stale refs.
|
|
38
|
+
*
|
|
39
|
+
* Returns a CleanupResult with three buckets:
|
|
40
|
+
* - `removed`: refs that were unlinked (either older than window
|
|
41
|
+
* OR corrupt + therefore unusable).
|
|
42
|
+
* - `kept`: refs that remain on disk after the sweep.
|
|
43
|
+
* - `corrupt`: refs that failed schema validation; these are also
|
|
44
|
+
* present in `removed` (the corrupt array is a sub-view for
|
|
45
|
+
* diagnostic surfaces).
|
|
46
|
+
*
|
|
47
|
+
* The function does not throw on individual file errors — it tracks
|
|
48
|
+
* the failure (silent skip for unreadable files) and continues so a
|
|
49
|
+
* single broken ref cannot block the rest of the sweep.
|
|
50
|
+
*/
|
|
51
|
+
export function cleanupStaleCacheRefs(workspaceRoot, options = {}) {
|
|
52
|
+
const dir = cacheRefDir(workspaceRoot);
|
|
53
|
+
const now = options.now ?? Date.now;
|
|
54
|
+
const olderThanMs = options.olderThanMs ?? DEFAULT_OLDER_THAN_MS;
|
|
55
|
+
const verbose = options.verbose ?? false;
|
|
56
|
+
const cutoff = now() - olderThanMs;
|
|
57
|
+
let entries;
|
|
58
|
+
try {
|
|
59
|
+
entries = readdirSync(dir).filter((name) => name.endsWith('.json')).sort();
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// No directory yet — nothing to clean. Return empty buckets so the
|
|
63
|
+
// caller's report says "0 refs removed" cleanly.
|
|
64
|
+
return {
|
|
65
|
+
removed: [],
|
|
66
|
+
kept: [],
|
|
67
|
+
corrupt: [],
|
|
68
|
+
removedCount: 0,
|
|
69
|
+
keptCount: 0,
|
|
70
|
+
corruptCount: 0,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const removed = [];
|
|
74
|
+
const kept = [];
|
|
75
|
+
const corrupt = [];
|
|
76
|
+
for (const name of entries) {
|
|
77
|
+
const full = join(dir, name);
|
|
78
|
+
let raw;
|
|
79
|
+
try {
|
|
80
|
+
raw = readFileSync(full, 'utf8');
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Unreadable — treat as corrupt and remove. mtime fallback path
|
|
84
|
+
// would be unreliable since we already failed to read the file.
|
|
85
|
+
removed.push(full);
|
|
86
|
+
corrupt.push(full);
|
|
87
|
+
tryUnlink(full, verbose);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
let parsed;
|
|
91
|
+
try {
|
|
92
|
+
parsed = JSON.parse(raw);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
removed.push(full);
|
|
96
|
+
corrupt.push(full);
|
|
97
|
+
tryUnlink(full, verbose);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const validated = cacheRefSchema.safeParse(parsed);
|
|
101
|
+
if (!validated.success) {
|
|
102
|
+
removed.push(full);
|
|
103
|
+
corrupt.push(full);
|
|
104
|
+
tryUnlink(full, verbose);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const createdAtMs = Date.parse(validated.data.createdAt);
|
|
108
|
+
// Date.parse returns NaN on invalid strings. Treat as stale.
|
|
109
|
+
const ageMs = Number.isFinite(createdAtMs)
|
|
110
|
+
? createdAtMs
|
|
111
|
+
: tryStatMtime(full);
|
|
112
|
+
if (ageMs < cutoff) {
|
|
113
|
+
removed.push(full);
|
|
114
|
+
tryUnlink(full, verbose);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
kept.push(full);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
removed,
|
|
122
|
+
kept,
|
|
123
|
+
corrupt,
|
|
124
|
+
removedCount: removed.length,
|
|
125
|
+
keptCount: kept.length,
|
|
126
|
+
corruptCount: corrupt.length,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/* ------------------------------------------------------------------ */
|
|
130
|
+
/* Duration parsing */
|
|
131
|
+
/* ------------------------------------------------------------------ */
|
|
132
|
+
/**
|
|
133
|
+
* Parse a human-readable duration string (e.g. `1h`, `30m`, `7d`) into
|
|
134
|
+
* milliseconds. Used by the `--older-than` CLI flag handler. Returns
|
|
135
|
+
* null on parse failure so the CLI can surface a usage error.
|
|
136
|
+
*
|
|
137
|
+
* Accepted suffixes:
|
|
138
|
+
* - `ms` — milliseconds
|
|
139
|
+
* - `s` — seconds
|
|
140
|
+
* - `m` — minutes
|
|
141
|
+
* - `h` — hours
|
|
142
|
+
* - `d` — days
|
|
143
|
+
*
|
|
144
|
+
* A bare number with no suffix is rejected (the CLI requires explicit
|
|
145
|
+
* units to avoid the "is 60 seconds or 60 minutes?" trap).
|
|
146
|
+
*/
|
|
147
|
+
export function parseDuration(input) {
|
|
148
|
+
const trimmed = input.trim().toLowerCase();
|
|
149
|
+
if (!trimmed)
|
|
150
|
+
return null;
|
|
151
|
+
const match = trimmed.match(/^([0-9]+(?:\.[0-9]+)?)(ms|s|m|h|d)$/);
|
|
152
|
+
if (!match)
|
|
153
|
+
return null;
|
|
154
|
+
const value = Number(match[1]);
|
|
155
|
+
if (!Number.isFinite(value) || value < 0)
|
|
156
|
+
return null;
|
|
157
|
+
switch (match[2]) {
|
|
158
|
+
case 'ms':
|
|
159
|
+
return value;
|
|
160
|
+
case 's':
|
|
161
|
+
return value * 1000;
|
|
162
|
+
case 'm':
|
|
163
|
+
return value * 60 * 1000;
|
|
164
|
+
case 'h':
|
|
165
|
+
return value * 60 * 60 * 1000;
|
|
166
|
+
case 'd':
|
|
167
|
+
return value * 24 * 60 * 60 * 1000;
|
|
168
|
+
default:
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/* ------------------------------------------------------------------ */
|
|
173
|
+
/* Internals */
|
|
174
|
+
/* ------------------------------------------------------------------ */
|
|
175
|
+
function tryUnlink(path, verbose) {
|
|
176
|
+
try {
|
|
177
|
+
rmSync(path, { force: false });
|
|
178
|
+
if (verbose) {
|
|
179
|
+
process.stderr.write(`pugi-cache-cleanup: removed ${path}\n`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
// Concurrent cleanup raced us. Treat as success — the file is
|
|
184
|
+
// gone, which is what we wanted.
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function tryStatMtime(path) {
|
|
188
|
+
try {
|
|
189
|
+
return statSync(path).mtimeMs;
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
// If we can't stat either, treat the ref as ancient so it gets
|
|
193
|
+
// evicted in the same pass.
|
|
194
|
+
return 0;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=cache-cleanup.js.map
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fork-subagent prompt-cache inheritance (—).
|
|
3
|
+
*
|
|
4
|
+
* the upstream tool's leaked sub-agent spawn pattern: when a parent agent
|
|
5
|
+
* dispatches a child via the Task tool, the child boots with the parent's
|
|
6
|
+
* prompt cache REFERENCE inherited — not the conversation transcript
|
|
7
|
+
* verbatim, but a provider-native cache handle that lets the child reuse
|
|
8
|
+
* the parent's system prompt + cached tool definitions without re-paying
|
|
9
|
+
* the prompt-prefix tokens on the first turn.
|
|
10
|
+
*
|
|
11
|
+
* Anthropic's `cache_control` block in their messages API exposes this
|
|
12
|
+
* directly via cache breakpoints. OpenAI / xAI / Gemini have similar
|
|
13
|
+
* primitives at different granularities; the wire payload our Anvil
|
|
14
|
+
* proxy speaks is provider-agnostic, so we forward a generic
|
|
15
|
+
* `parent_cache_id` hint and Anvil routes it onto the underlying
|
|
16
|
+
* provider's cache primitive (or silently drops it if the provider
|
|
17
|
+
* doesn't support cache inheritance — graceful degrade).
|
|
18
|
+
*
|
|
19
|
+
* What this module does:
|
|
20
|
+
*
|
|
21
|
+
* 1. `inheritCacheContext(parentSessionId, childAgentId)` —
|
|
22
|
+
* synthesises a cache handle for a child dispatch. Persists the
|
|
23
|
+
* handle to `.pugi/cache-refs/<child-agent-id>.json` so:
|
|
24
|
+
*
|
|
25
|
+
* a. The child's own boot path can read it (e.g. a child engine
|
|
26
|
+
* loop running in a worktree subshell where the in-memory
|
|
27
|
+
* DispatcherContext isn't reachable).
|
|
28
|
+
* b. `pugi dispatch list-cache-refs` can surface active refs
|
|
29
|
+
* for debugging "why is my cache hit rate so low".
|
|
30
|
+
* c. `pugi dispatch clear-cache-refs --older-than 1h` can
|
|
31
|
+
* garbage-collect stale refs from crashed/killed children.
|
|
32
|
+
*
|
|
33
|
+
* 2. `readCacheRef(workspaceRoot, childAgentId)` — child-side read.
|
|
34
|
+
* Returns the persisted handle so the child's first engine loop
|
|
35
|
+
* turn can include the parent_cache_id hint in its request.
|
|
36
|
+
*
|
|
37
|
+
* 3. `cacheHandoffHookForRequest(handle)` — produces the wire payload
|
|
38
|
+
* shape that the Anvil bridge can splice into the
|
|
39
|
+
* `engineLoopServerRequest` body. Lives here (not in the bridge)
|
|
40
|
+
* so the cache-control schema is in one place.
|
|
41
|
+
*
|
|
42
|
+
* What this module does NOT do:
|
|
43
|
+
*
|
|
44
|
+
* - It does not replay the parent's conversation transcript into the
|
|
45
|
+
* child. That would defeat the cyber-zoo isolation contract
|
|
46
|
+
* (`shared_fs_readonly` etc.). The child gets a CACHE HINT — the
|
|
47
|
+
* provider may use it to skip re-tokenising shared prompt prefix,
|
|
48
|
+
* but the LOGICAL conversation always starts fresh from the child's
|
|
49
|
+
* own system prompt + brief.
|
|
50
|
+
*
|
|
51
|
+
* - It does not assume any provider honours the hint. Cache-miss is
|
|
52
|
+
* the default path. If Anvil routes the request to a model whose
|
|
53
|
+
* provider doesn't expose cache inheritance, the request still
|
|
54
|
+
* succeeds — just at full prompt-prefix cost.
|
|
55
|
+
*
|
|
56
|
+
* - It does not rotate or invalidate the parent's cache on the
|
|
57
|
+
* parent's side. Parent cache lifecycle is owned by the parent
|
|
58
|
+
* engine loop; the child holds a read-only reference.
|
|
59
|
+
*
|
|
60
|
+
* Cross-reference:
|
|
61
|
+
* - apps/pugi-cli/src/core/subagents/dispatcher-real.ts — where the
|
|
62
|
+
* child engine loop is driven; the cache_id from this module is
|
|
63
|
+
* forwarded onto Anvil via the `extensions` field of the engine
|
|
64
|
+
* loop server request (β2 forward-compat slot).
|
|
65
|
+
* - packages/pugi-sdk/src/engine-loop.ts — engineLoopServerRequest
|
|
66
|
+
* schema; cache-handoff field is OPTIONAL and additive.
|
|
67
|
+
*
|
|
68
|
+
* Leak research §L10 (the upstream tool sub-agent spawn fork pattern):
|
|
69
|
+
* docs/research/2026-05-27-leak-parity-sprint.md.
|
|
70
|
+
*/
|
|
71
|
+
import { mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
72
|
+
import { join, resolve as resolvePath } from 'node:path';
|
|
73
|
+
import { randomUUID } from 'node:crypto';
|
|
74
|
+
import { z } from 'zod';
|
|
75
|
+
/* ------------------------------------------------------------------ */
|
|
76
|
+
/* Types + schema */
|
|
77
|
+
/* ------------------------------------------------------------------ */
|
|
78
|
+
/**
|
|
79
|
+
* Persisted cache reference. The shape is intentionally minimal —
|
|
80
|
+
* provider-specific cache tokens are opaque strings so the file format
|
|
81
|
+
* does not couple to any one provider's API.
|
|
82
|
+
*
|
|
83
|
+
* - `cacheId` — opaque provider hint forwarded to Anvil.
|
|
84
|
+
* Synthesized client-side as `pugi-cache-<uuid>`
|
|
85
|
+
* so the server has a stable correlation key
|
|
86
|
+
* even when the underlying provider's cache
|
|
87
|
+
* object is not yet provisioned.
|
|
88
|
+
* - `contextRef` — logical reference key the parent uses to tag
|
|
89
|
+
* which prompt segments to re-use. Same value
|
|
90
|
+
* as cacheId for the v1 wire (single breakpoint
|
|
91
|
+
* per parent); reserved for future multi-breakpoint
|
|
92
|
+
* schemes (split system + tools + first-user
|
|
93
|
+
* segments).
|
|
94
|
+
* - `parentSessionId` — debugging / GC scoping. `list-cache-refs`
|
|
95
|
+
* can group by parent session.
|
|
96
|
+
* - `childAgentId` — the dispatch this ref is scoped to.
|
|
97
|
+
* - `createdAt` — ISO timestamp for `--older-than` cleanup.
|
|
98
|
+
* - `schemaVersion` — pinned to 1; bumped on breaking shape change.
|
|
99
|
+
*/
|
|
100
|
+
export const cacheRefSchema = z.object({
|
|
101
|
+
schemaVersion: z.literal(1),
|
|
102
|
+
cacheId: z.string().min(1).max(256),
|
|
103
|
+
contextRef: z.string().min(1).max(256),
|
|
104
|
+
parentSessionId: z.string().min(1).max(256),
|
|
105
|
+
childAgentId: z.string().min(1).max(256),
|
|
106
|
+
createdAt: z.string().min(1),
|
|
107
|
+
});
|
|
108
|
+
/* ------------------------------------------------------------------ */
|
|
109
|
+
/* Path resolution */
|
|
110
|
+
/* ------------------------------------------------------------------ */
|
|
111
|
+
/**
|
|
112
|
+
* Resolve the directory under `.pugi/` where cache refs live. The
|
|
113
|
+
* directory is created on first write — readers tolerate its absence
|
|
114
|
+
* (no refs = empty list, not an error).
|
|
115
|
+
*/
|
|
116
|
+
export function cacheRefDir(workspaceRoot) {
|
|
117
|
+
return resolvePath(workspaceRoot, '.pugi', 'cache-refs');
|
|
118
|
+
}
|
|
119
|
+
function cacheRefPath(workspaceRoot, childAgentId) {
|
|
120
|
+
// Sanitise the child id — directory traversal via crafted child id
|
|
121
|
+
// would let a dispatch target write outside the cache-refs dir. The
|
|
122
|
+
// child id format is owned by spawn.ts (`subagent-<uuid>`) but we
|
|
123
|
+
// defend in depth.
|
|
124
|
+
const safe = childAgentId.replace(/[^A-Za-z0-9_-]/g, '_').slice(0, 200);
|
|
125
|
+
return join(cacheRefDir(workspaceRoot), `${safe}.json`);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Synthesize a cache handle for a child dispatch and persist it to
|
|
129
|
+
* `.pugi/cache-refs/<childAgentId>.json`. Returns the in-memory handle
|
|
130
|
+
* the dispatcher feeds into the Anvil wire request.
|
|
131
|
+
*
|
|
132
|
+
* Idempotent: calling twice with the same `childAgentId` overwrites
|
|
133
|
+
* the existing ref. The dispatcher's `taskId` is a UUID per dispatch,
|
|
134
|
+
* so collisions are not expected, but the overwrite semantic is safer
|
|
135
|
+
* than failing — a stale ref from a previous run (e.g. process killed
|
|
136
|
+
* between spawn and run) should not block a fresh dispatch.
|
|
137
|
+
*/
|
|
138
|
+
export function inheritCacheContext(parentSessionId, childAgentId, options) {
|
|
139
|
+
if (!parentSessionId) {
|
|
140
|
+
throw new Error('inheritCacheContext: parentSessionId must be non-empty');
|
|
141
|
+
}
|
|
142
|
+
if (!childAgentId) {
|
|
143
|
+
throw new Error('inheritCacheContext: childAgentId must be non-empty');
|
|
144
|
+
}
|
|
145
|
+
const now = options.now ?? defaultNow;
|
|
146
|
+
const cacheIdFactory = options.cacheIdFactory ?? defaultCacheId;
|
|
147
|
+
const cacheId = cacheIdFactory();
|
|
148
|
+
const ref = {
|
|
149
|
+
schemaVersion: 1,
|
|
150
|
+
cacheId,
|
|
151
|
+
contextRef: cacheId,
|
|
152
|
+
parentSessionId,
|
|
153
|
+
childAgentId,
|
|
154
|
+
createdAt: now(),
|
|
155
|
+
};
|
|
156
|
+
const dir = cacheRefDir(options.workspaceRoot);
|
|
157
|
+
mkdirSync(dir, { recursive: true });
|
|
158
|
+
const path = cacheRefPath(options.workspaceRoot, childAgentId);
|
|
159
|
+
writeFileSync(path, JSON.stringify(ref, null, 2), 'utf8');
|
|
160
|
+
return {
|
|
161
|
+
cacheId: ref.cacheId,
|
|
162
|
+
contextRef: ref.contextRef,
|
|
163
|
+
persistedPath: path,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Child-side read. Returns null when the ref is missing or malformed
|
|
168
|
+
* (the child boots without cache inheritance — degrade is silent so a
|
|
169
|
+
* corrupted ref does not block a dispatch).
|
|
170
|
+
*
|
|
171
|
+
* Malformed ref files are NOT auto-deleted by this read path — that's
|
|
172
|
+
* the cleanup command's job. A failed parse here just returns null so
|
|
173
|
+
* the dispatch proceeds at full prompt-prefix cost.
|
|
174
|
+
*/
|
|
175
|
+
export function readCacheRef(workspaceRoot, childAgentId) {
|
|
176
|
+
const path = cacheRefPath(workspaceRoot, childAgentId);
|
|
177
|
+
let raw;
|
|
178
|
+
try {
|
|
179
|
+
raw = readFileSync(path, 'utf8');
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
let parsed;
|
|
185
|
+
try {
|
|
186
|
+
parsed = JSON.parse(raw);
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
const validated = cacheRefSchema.safeParse(parsed);
|
|
192
|
+
if (!validated.success)
|
|
193
|
+
return null;
|
|
194
|
+
return validated.data;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* List every persisted cache ref under `.pugi/cache-refs/`. Used by
|
|
198
|
+
* `pugi dispatch list-cache-refs` and by GC sweeps in
|
|
199
|
+
* `cache-cleanup.ts`. Returns refs in deterministic
|
|
200
|
+
* (filename-ascending) order so the CLI output is stable.
|
|
201
|
+
*
|
|
202
|
+
* Malformed ref files are silently skipped — the cleanup command can
|
|
203
|
+
* surface them via a separate `--show-corrupt` flag if needed.
|
|
204
|
+
*/
|
|
205
|
+
export function listCacheRefs(workspaceRoot) {
|
|
206
|
+
const dir = cacheRefDir(workspaceRoot);
|
|
207
|
+
let entries;
|
|
208
|
+
try {
|
|
209
|
+
entries = readdirSync(dir).filter((name) => name.endsWith('.json')).sort();
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return [];
|
|
213
|
+
}
|
|
214
|
+
const refs = [];
|
|
215
|
+
for (const entry of entries) {
|
|
216
|
+
const full = join(dir, entry);
|
|
217
|
+
let raw;
|
|
218
|
+
try {
|
|
219
|
+
raw = readFileSync(full, 'utf8');
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
let parsed;
|
|
225
|
+
try {
|
|
226
|
+
parsed = JSON.parse(raw);
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const validated = cacheRefSchema.safeParse(parsed);
|
|
232
|
+
if (!validated.success)
|
|
233
|
+
continue;
|
|
234
|
+
refs.push(validated.data);
|
|
235
|
+
}
|
|
236
|
+
return refs;
|
|
237
|
+
}
|
|
238
|
+
/* ------------------------------------------------------------------ */
|
|
239
|
+
/* Wire-format hook */
|
|
240
|
+
/* ------------------------------------------------------------------ */
|
|
241
|
+
/**
|
|
242
|
+
* Project the in-memory handle (or persisted ref) onto the wire-format
|
|
243
|
+
* hint object that the Anvil bridge merges into the engine-loop
|
|
244
|
+
* request body. Pulled out so callers (dispatcher-real, future bridge
|
|
245
|
+
* adapters) all speak the same shape.
|
|
246
|
+
*/
|
|
247
|
+
export function cacheHandoffHookForRequest(source) {
|
|
248
|
+
return {
|
|
249
|
+
parent_cache_id: source.cacheId,
|
|
250
|
+
cache_context_ref: source.contextRef,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
/* ------------------------------------------------------------------ */
|
|
254
|
+
/* Internals */
|
|
255
|
+
/* ------------------------------------------------------------------ */
|
|
256
|
+
function defaultNow() {
|
|
257
|
+
return new Date().toISOString();
|
|
258
|
+
}
|
|
259
|
+
function defaultCacheId() {
|
|
260
|
+
return `pugi-cache-${randomUUID()}`;
|
|
261
|
+
}
|
|
262
|
+
/* ------------------------------------------------------------------ */
|
|
263
|
+
/* Stat helper (used by cache-cleanup.ts) */
|
|
264
|
+
/* ------------------------------------------------------------------ */
|
|
265
|
+
/**
|
|
266
|
+
* Exposed for cache-cleanup.ts so the GC sweep can read mtime without
|
|
267
|
+
* re-implementing the path-resolution logic. Returns null when the
|
|
268
|
+
* file is gone (a concurrent cleanup may have raced us — silent miss).
|
|
269
|
+
*/
|
|
270
|
+
export function cacheRefMtime(workspaceRoot, childAgentId) {
|
|
271
|
+
try {
|
|
272
|
+
const stat = statSync(cacheRefPath(workspaceRoot, childAgentId));
|
|
273
|
+
return stat.mtime;
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Delete a single cache ref. Returns true when the file existed and
|
|
281
|
+
* was removed, false when it was already gone. Used by both the
|
|
282
|
+
* `clear-cache-refs` CLI and by post-dispatch cleanup (a successful
|
|
283
|
+
* subagent run does not need the cache ref to outlive its dispatch).
|
|
284
|
+
*/
|
|
285
|
+
export function deleteCacheRef(workspaceRoot, childAgentId) {
|
|
286
|
+
const path = cacheRefPath(workspaceRoot, childAgentId);
|
|
287
|
+
try {
|
|
288
|
+
rmSync(path, { force: false });
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
//# sourceMappingURL=cache-handoff.js.map
|