gentle-pi 3.7.0 → 4.1.0
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/README.md +155 -10
- package/assets/agents/gentle-ai-explore.md +13 -6
- package/assets/agents/gentle-ai-verify.md +27 -6
- package/assets/agents/gentle-ai-worker.md +13 -15
- package/assets/install-wizard/index.html +47 -0
- package/assets/install-wizard/wizard.css +1279 -0
- package/assets/install-wizard/wizard.js +951 -0
- package/assets/orchestrator-delegation.md +75 -169
- package/assets/orchestrator-memory.md +12 -26
- package/assets/orchestrator-prompts.md +48 -0
- package/assets/orchestrator-skills.md +1 -1
- package/assets/orchestrator-tracking.md +53 -0
- package/assets/orchestrator-verification.md +28 -0
- package/assets/orchestrator-writer.md +38 -0
- package/assets/orchestrator.md +30 -38
- package/assets/sounds/LICENSE.md +52 -0
- package/assets/sounds/attention.wav +0 -0
- package/assets/sounds/error.wav +0 -0
- package/assets/sounds/success.wav +0 -0
- package/assets/support/strict-tdd-verify.md +4 -266
- package/assets/support/strict-tdd.md +8 -360
- package/bin/engram-guard +9 -0
- package/bin/gentle-shell-install.mjs +302 -0
- package/bin/gentle-shell.mjs +956 -50
- package/contracts/review-provider-contract-mirror/provider-contract.lock.json +7 -7
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/manifest.json +2 -2
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/orchestration/pi.md +16 -4
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-capabilities.baseline.json +2 -2
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-roles.baseline.json +1 -1
- package/docs/bundled-install.md +397 -0
- package/docs/delegated-verification.md +27 -4
- package/docs/gentle-agents-activity.md +537 -0
- package/docs/gentle-shell-uninstall.md +136 -0
- package/docs/gentle-shell.md +165 -51
- package/docs/install-wizard.md +2040 -0
- package/docs/native-authority-architecture.md +3 -3
- package/docs/permission-modes.md +82 -0
- package/docs/prompt-history.md +280 -0
- package/docs/readme-reference.md +364 -231
- package/docs/render-cache-performance.md +28 -0
- package/docs/sound-notifications-proposal.md +191 -0
- package/docs/sound-notifications.md +114 -0
- package/docs/telemetry.md +1 -1
- package/docs/yolo-mode.md +86 -0
- package/extensions/child-context.ts +35 -0
- package/extensions/child-safety.ts +25 -0
- package/extensions/codegraph-tools.ts +18 -5
- package/extensions/gentle-agents.ts +1093 -326
- package/extensions/gentle-ai.ts +1636 -890
- package/extensions/gentle-herdr-activity.ts +81 -0
- package/extensions/gentle-notifications.ts +104 -0
- package/extensions/gentle-shell.ts +1619 -136
- package/extensions/gentle-stats.ts +101 -0
- package/extensions/gentle-todo.ts +126 -20
- package/extensions/history/atomic-write.ts +38 -0
- package/extensions/history/hide-prompts.ts +183 -0
- package/extensions/history/index.ts +1424 -0
- package/extensions/history/load-shared-history.ts +39 -0
- package/extensions/history/selector-helpers.ts +538 -0
- package/extensions/history/session-scan.ts +233 -0
- package/extensions/history/store.ts +1119 -0
- package/extensions/nan-provider.ts +6 -0
- package/extensions/pi-pretty.ts +39 -3
- package/extensions/quiet-tools.ts +254 -92
- package/extensions/resume-hint.ts +60 -0
- package/extensions/skill-registry.ts +191 -58
- package/extensions/startup-banner.ts +71 -33
- package/lib/agent-assets.ts +604 -0
- package/lib/agent-home.ts +10 -1
- package/lib/agent-model-resolution.ts +80 -0
- package/lib/agent-profile-pin.ts +12 -0
- package/lib/agent-profiles.ts +13 -3
- package/lib/agents-completion-delivery.ts +11 -3
- package/lib/agents-message-delivery.ts +181 -0
- package/lib/agents-messaging.ts +1 -1
- package/lib/agents-protocol.ts +99 -0
- package/lib/agents-runner.ts +180 -107
- package/lib/agents-view.ts +26 -3
- package/lib/agents-widget.ts +16 -9
- package/lib/append-system-prompt.ts +21 -0
- package/lib/auto-permission-classifier.ts +280 -0
- package/lib/auto-permission-runtime.ts +268 -0
- package/lib/background-jobs-tools.ts +202 -0
- package/lib/background-jobs.ts +253 -0
- package/lib/background-monitor.ts +154 -0
- package/lib/bounded-writer-admission.ts +220 -0
- package/lib/builtin-codemode-optout.ts +166 -0
- package/lib/card-content-policy.ts +85 -0
- package/lib/card-style-policy.ts +60 -0
- package/lib/child-context-files.ts +166 -0
- package/lib/child-package-injection.ts +62 -0
- package/lib/codemode-renderer.ts +195 -0
- package/lib/command-palette-catalog.ts +5 -10
- package/lib/command-palette.ts +25 -14
- package/lib/context-threshold.ts +26 -0
- package/lib/destructive-command-guard.ts +144 -0
- package/lib/gentle-ai-binary.ts +109 -4
- package/lib/gentle-ai-elapsed-store.ts +87 -0
- package/lib/gentle-ai-renderer.ts +196 -39
- package/lib/gentle-shell-bundled.ts +517 -0
- package/lib/gentle-shell-launcher.ts +89 -27
- package/lib/gentle-shell-resume-hint.ts +176 -0
- package/lib/gentle-shell-startup-flow.ts +54 -0
- package/lib/gentle-shell-startup-update.ts +92 -0
- package/lib/gentle-shell-uninstall.ts +840 -0
- package/lib/gentle-shell-update-panel.ts +231 -0
- package/lib/herdr-activity.ts +183 -0
- package/lib/history-capture-policy.ts +95 -0
- package/lib/inprocess-reviewer.ts +3 -0
- package/lib/jobs-sidebar-state.ts +20 -0
- package/lib/jobs-view.ts +161 -0
- package/lib/model-price-ratio.ts +83 -0
- package/lib/model-routing-authority.ts +5 -1
- package/lib/nan-provider.ts +249 -0
- package/lib/native-review-cli.ts +242 -180
- package/lib/notification-audio-native.ts +162 -0
- package/lib/notification-audio-windows.ts +399 -0
- package/lib/notification-audio.ts +332 -0
- package/lib/notification-customize.ts +241 -0
- package/lib/notification-events.ts +113 -0
- package/lib/notification-policy.ts +148 -0
- package/lib/notification-pulse-client.ts +367 -0
- package/lib/notification-pulse-protocol.ts +305 -0
- package/lib/notification-pulse-stream.ts +213 -0
- package/lib/notification-pulse-worker.ts +66 -0
- package/lib/notification-scheduler.ts +173 -0
- package/lib/notification-service.ts +202 -0
- package/lib/notification-ui.ts +87 -0
- package/lib/odd-phase-inference.ts +231 -0
- package/lib/odd-phase.ts +141 -0
- package/lib/orchestrator-alias.ts +20 -0
- package/lib/orchestrator-catalog.ts +79 -0
- package/lib/orchestrator-consultation.ts +61 -0
- package/lib/orchestrator-discovery.ts +49 -0
- package/lib/orchestrator-helper-consent.ts +122 -0
- package/lib/orchestrator-helper.ts +167 -0
- package/lib/orchestrator-message-card.ts +105 -0
- package/lib/orchestrator-presence.ts +115 -14
- package/lib/orchestrator-scope.ts +80 -0
- package/lib/orchestrator-state.ts +107 -0
- package/lib/orchestrator-tool-card.ts +280 -0
- package/lib/orchestrator-work-search.ts +218 -0
- package/lib/orchestrator-work.ts +79 -0
- package/lib/overlay-repaint.ts +26 -0
- package/lib/permission-mode-policy.ts +59 -0
- package/lib/pi-tui-keys.ts +53 -0
- package/lib/review-candidate-view-owner.ts +73 -21
- package/lib/review-candidate-view.ts +115 -27
- package/lib/review-compact-contract.ts +2 -2
- package/lib/review-host-relay.ts +7 -2
- package/lib/review-integration-v2.ts +100 -22
- package/lib/review-publication-gate.ts +1 -1
- package/lib/review-reminder-receipt.ts +48 -8
- package/lib/review-risk-assessment.ts +227 -56
- package/lib/review-sidebar-state.ts +224 -0
- package/lib/review-transaction.ts +72 -3
- package/lib/runtime-metrics-children.ts +1 -1
- package/lib/runtime-metrics-native.ts +3 -2
- package/lib/selection-engine.ts +527 -0
- package/lib/session-change-capture.ts +5 -4
- package/lib/session-changes.ts +27 -22
- package/lib/session-messaging-grants.ts +135 -0
- package/lib/session-profile-binding.ts +83 -0
- package/lib/session-profile-freeze.ts +266 -0
- package/lib/session-worktree-registry.ts +87 -9
- package/lib/shell-bar.ts +215 -30
- package/lib/shell-card.ts +287 -18
- package/lib/shell-changes-view.ts +42 -14
- package/lib/shell-changes.ts +86 -71
- package/lib/shell-prompt.ts +102 -6
- package/lib/shell-sidebar-layout.ts +83 -15
- package/lib/shell-sidebar.ts +15 -1
- package/lib/shell-todo.ts +84 -31
- package/lib/shell-usage-view.ts +9 -4
- package/lib/shell-usage.ts +70 -13
- package/lib/stats-collector.ts +381 -0
- package/lib/stats-view.ts +431 -0
- package/lib/status-timing-diagnostics.ts +201 -0
- package/lib/telemetry-trigger.ts +3 -1
- package/lib/theme-customization.ts +52 -0
- package/lib/vim-editor-adapter.ts +383 -0
- package/lib/vim-normal-engine.ts +154 -0
- package/lib/vim-operator-engine.ts +416 -0
- package/lib/vim-policy.ts +49 -0
- package/lib/vim-visual-engine.ts +107 -0
- package/lib/visual-customization-policy.ts +119 -0
- package/lib/visual-customize-view.ts +481 -0
- package/lib/visual-profiles.ts +228 -0
- package/lib/writer-surfaces.ts +162 -0
- package/lib/yolo-session-policy.ts +317 -0
- package/package.json +20 -8
- package/runtime/child-package-injection.mjs +63 -0
- package/runtime/gentle-ai-binary.mjs +109 -4
- package/runtime/gentle-shell-bundled.mjs +518 -0
- package/runtime/gentle-shell-launcher.mjs +87 -25
- package/runtime/gentle-shell-resume-hint.mjs +177 -0
- package/runtime/gentle-shell-startup-flow.mjs +55 -0
- package/runtime/gentle-shell-startup-update.mjs +93 -0
- package/runtime/gentle-shell-uninstall.mjs +841 -0
- package/runtime/gentle-shell-update-panel.mjs +232 -0
- package/runtime/native-review-cli.mjs +239 -177
- package/runtime/review-integration-v2.mjs +100 -22
- package/runtime/review-risk-assessment.mjs +225 -54
- package/runtime/status-timing-diagnostics.mjs +202 -0
- package/runtime/telemetry-trigger.mjs +3 -1
- package/scripts/bootstrap.cmd +255 -0
- package/scripts/bootstrap.sh +168 -0
- package/scripts/build-distribution.mjs +157 -0
- package/scripts/build-installer-bundles.mjs +136 -0
- package/scripts/build-runtime-modules.mjs +8 -0
- package/scripts/bundled-install.mjs +1308 -0
- package/scripts/bundled-wizard.mjs +399 -0
- package/scripts/check-types.mjs +4 -6
- package/scripts/gentle-ai-installer.mjs +83 -35
- package/scripts/install-gentle-ai.mjs +48 -1
- package/scripts/install-tui-mode-setting.mjs +10 -1
- package/scripts/install-wizard-preview.mjs +154 -0
- package/scripts/installer-downloads.mjs +648 -0
- package/scripts/installer-preflight.mjs +387 -0
- package/scripts/installer-probes.mjs +564 -0
- package/scripts/installer-runner.mjs +1102 -0
- package/scripts/installer-server.mjs +881 -0
- package/scripts/installer-windows-artifacts.json +15 -0
- package/scripts/installer-windows.mjs +752 -0
- package/scripts/main-channel.mjs +452 -0
- package/scripts/mirror-odd-routing.mjs +2 -2
- package/scripts/npm-pack-result.mjs +31 -0
- package/scripts/render-cache-performance-worker.mjs +205 -0
- package/scripts/render-cache-performance.mjs +115 -0
- package/scripts/run-test-suite.mjs +90 -0
- package/scripts/test-installer-acceptance.sh +161 -0
- package/scripts/test-packed-runner.mjs +54 -19
- package/scripts/types-baseline.json +2 -92
- package/scripts/verify-package-files.mjs +91 -38
- package/skills/branch-pr/SKILL.md +24 -52
- package/skills/chained-pr/SKILL.md +31 -15
- package/skills/chained-pr/references/chaining-details.md +31 -20
- package/skills/gentle-ai/SKILL.md +16 -21
- package/skills/issue-creation/SKILL.md +8 -2
- package/skills/issue-creation/references/delegated-workflow-actions.md +19 -0
- package/skills/work-unit-commits/SKILL.md +4 -3
- package/tests/agent-home.test.ts +56 -3
- package/tests/agent-model-resolution.test.ts +113 -0
- package/tests/agent-profiles.test.ts +30 -0
- package/tests/agent-risk-escalation.test.ts +123 -0
- package/tests/agents-completion-delivery.test.ts +17 -0
- package/tests/agents-fake-child.ts +2 -2
- package/tests/agents-message-delivery.test.ts +106 -0
- package/tests/agents-protocol.test.ts +97 -0
- package/tests/agents-queries.test.ts +9 -9
- package/tests/agents-runner-process.test.ts +31 -0
- package/tests/agents-runner.test.ts +467 -93
- package/tests/agents-view-thread-identity.test.ts +169 -0
- package/tests/agents-view.test.ts +8 -2
- package/tests/agents-widget.test.ts +154 -15
- package/tests/append-system-prompt-route.test.ts +160 -0
- package/tests/append-system-prompt.test.ts +46 -0
- package/tests/artifact-language.test.ts +19 -213
- package/tests/ask-user-question.test.ts +44 -1
- package/tests/asset-installation-runtime.test.ts +5 -16
- package/tests/auto-mode-invariants.test.ts +258 -0
- package/tests/auto-mode-sdk.test.ts +57 -0
- package/tests/auto-mode.test.ts +49 -0
- package/tests/auto-permission-classifier.test.ts +283 -0
- package/tests/auto-permission-runtime.test.ts +196 -0
- package/tests/autonomous-guard.test.ts +69 -1
- package/tests/background-cache-warming.test.ts +2 -1
- package/tests/background-jobs.test.ts +227 -0
- package/tests/background-monitor.test.ts +195 -0
- package/tests/background-subagents-default-mode.test.ts +28 -5
- package/tests/bounded-stop-contract.test.ts +85 -0
- package/tests/bounded-writer-admission.test.ts +146 -0
- package/tests/branch-pr-skill.test.ts +43 -0
- package/tests/bridge-wake-lifecycle.test.ts +129 -0
- package/tests/bridge-wake-rendering.test.ts +56 -0
- package/tests/build-distribution.test.ts +269 -0
- package/tests/builtin-codemode-optout.test.ts +242 -0
- package/tests/bundled-install.test.ts +1319 -0
- package/tests/bundled-wizard.test.ts +505 -0
- package/tests/card-content-policy.test.ts +68 -0
- package/tests/card-style-policy.test.ts +87 -0
- package/tests/chained-pr-skill.test.ts +124 -0
- package/tests/child-codegraph-bootstrap.test.ts +74 -0
- package/tests/child-context-files.test.ts +255 -0
- package/tests/child-package-entrypoints.test.ts +65 -0
- package/tests/child-package-injection.test.ts +74 -0
- package/tests/child-safety.test.ts +94 -0
- package/tests/child-web-package.test.ts +224 -0
- package/tests/codegraph-tools.test.ts +35 -0
- package/tests/codemode-rendering.test.ts +596 -0
- package/tests/command-palette.test.ts +43 -7
- package/tests/context-threshold.test.ts +31 -0
- package/tests/delegate-for-reason-contract.test.ts +327 -0
- package/tests/delegated-key-learnings-contract.test.ts +3 -78
- package/tests/destructive-command-guard.test.ts +84 -0
- package/tests/devbinary/native-review-parity.devtest.ts +171 -3
- package/tests/devbinary/non-git-subagent-bootstrap.devtest.ts +193 -0
- package/tests/devbinary/pi-host-relay.devtest.ts +173 -3
- package/tests/engram-safety.test.ts +288 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +7 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +3 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T08-00-00-000Z_ddd.jsonl +2 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T09-00-00-000Z_eee.jsonl +3 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/run-1/session.jsonl +2 -0
- package/tests/fixtures/stats/sessions/--work-beta--/2026-09-01T12-00-00-000Z_ccc.jsonl +2 -0
- package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +2 -0
- package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +4 -0
- package/tests/fixtures/stats/user-pi/sessions/--work-gamma--/2026-09-20T09-00-00-000Z_fff.jsonl +3 -0
- package/tests/generic-agent-tools.test.ts +69 -0
- package/tests/gentle-agents.test.ts +2804 -346
- package/tests/gentle-ai-binary-isolation.test.ts +135 -0
- package/tests/gentle-ai-binary.test.ts +21 -4
- package/tests/gentle-ai-child-guards.test.ts +202 -0
- package/tests/gentle-ai-dev-binary-surfacing.test.ts +125 -15
- package/tests/gentle-ai-elapsed-store.test.ts +68 -0
- package/tests/gentle-ai-installer.test.ts +566 -62
- package/tests/gentle-ai-prompt-model-routing.test.ts +123 -0
- package/tests/gentle-ai-renderer.test.ts +493 -14
- package/tests/gentle-ai.test.ts +1427 -236
- package/tests/gentle-card-text.ts +2 -1
- package/tests/gentle-herdr-activity.test.ts +100 -0
- package/tests/gentle-notifications.test.ts +427 -0
- package/tests/gentle-shell-bin.test.ts +896 -115
- package/tests/gentle-shell-bundled.test.ts +1002 -0
- package/tests/gentle-shell-launcher.test.ts +333 -65
- package/tests/gentle-shell-resume-hint.test.ts +270 -0
- package/tests/gentle-shell-startup-flow.test.ts +75 -0
- package/tests/gentle-shell-startup-update.test.ts +85 -0
- package/tests/gentle-shell-uninstall.test.ts +978 -0
- package/tests/gentle-shell-update-panel.test.ts +158 -0
- package/tests/gentle-shell.test.ts +4216 -119
- package/tests/gentle-stats.test.ts +152 -0
- package/tests/gentle-theme.test.ts +4 -1
- package/tests/gentle-todo.test.ts +333 -10
- package/tests/herdr-activity.test.ts +243 -0
- package/tests/history-agent-home.test.ts +81 -0
- package/tests/history-atomic-write.test.ts +57 -0
- package/tests/history-capture-policy.test.ts +102 -0
- package/tests/history-child-guard.test.ts +70 -0
- package/tests/history-command-registration.test.ts +164 -0
- package/tests/history-dedupe-entries.test.ts +123 -0
- package/tests/history-delete-backfill.test.ts +190 -0
- package/tests/history-delete-confirm.test.ts +460 -0
- package/tests/history-dispatch.test.ts +180 -0
- package/tests/history-drain-hidden.test.ts +110 -0
- package/tests/history-drain-order.test.ts +98 -0
- package/tests/history-expanded-globals.test.ts +62 -0
- package/tests/history-gc.test.ts +832 -0
- package/tests/history-header-layout.test.ts +265 -0
- package/tests/history-hide-prompts.test.ts +275 -0
- package/tests/history-lazy-windowing.test.ts +508 -0
- package/tests/history-legacy-migrate-v2.test.ts +297 -0
- package/tests/history-load-shared-history.test.ts +53 -0
- package/tests/history-max-results-cap.test.ts +76 -0
- package/tests/history-multi-reader.test.ts +203 -0
- package/tests/history-off-path.test.ts +170 -0
- package/tests/history-openflow-integration.test.ts +173 -0
- package/tests/history-overlay-margin.test.ts +326 -0
- package/tests/history-preview-layout.test.ts +93 -0
- package/tests/history-registry.test.ts +143 -0
- package/tests/history-scope-delete.test.ts +411 -0
- package/tests/history-search-caret-keys.test.ts +142 -0
- package/tests/history-seed-bootstrap.test.ts +170 -0
- package/tests/history-seed-regen.test.ts +129 -0
- package/tests/history-selector-windowing.test.ts +94 -0
- package/tests/history-session-scan-directory.test.ts +87 -0
- package/tests/history-session-scan-extract.test.ts +583 -0
- package/tests/history-session-writer.test.ts +351 -0
- package/tests/history-store-paths.test.ts +79 -0
- package/tests/history-tombstone-exact.test.ts +139 -0
- package/tests/history-wheel-mouse.test.ts +242 -0
- package/tests/inprocess-reviewer.test.ts +49 -19
- package/tests/install-gentle-ai-failure.test.ts +69 -0
- package/tests/install-tui-mode-setting.test.ts +15 -0
- package/tests/install-wizard.test.ts +1223 -0
- package/tests/installer-bundles.test.ts +122 -0
- package/tests/installer-posix-bootstrap.test.ts +1201 -0
- package/tests/installer-preflight.test.ts +750 -0
- package/tests/installer-probes.test.ts +1106 -0
- package/tests/installer-runner.test.ts +2158 -0
- package/tests/installer-server.test.ts +1567 -0
- package/tests/installer-windows-bootstrap.test.ts +2410 -0
- package/tests/issue-creation-skill.test.ts +61 -0
- package/tests/jobs-view.test.ts +160 -0
- package/tests/main-channel-upgrade.test.ts +389 -0
- package/tests/main-channel.test.ts +344 -0
- package/tests/maintainer/provider-relay.maintest.ts +13 -12
- package/tests/model-price-ratio.test.ts +116 -0
- package/tests/model-profiles-preservation.test.ts +153 -0
- package/tests/model-routing-authority.test.ts +16 -0
- package/tests/model-routing-package.test.ts +10 -0
- package/tests/nan-provider-runtime.test.ts +156 -0
- package/tests/nan-provider.test.ts +481 -0
- package/tests/native-binary-gate.test.ts +2 -0
- package/tests/native-review-capability-contract.test.ts +13 -1
- package/tests/native-review-cli.test.ts +239 -118
- package/tests/native-review-consent.test.ts +2 -2
- package/tests/native-review-parity-runtime.test.ts +101 -4
- package/tests/native-review-parity.test.ts +12 -48
- package/tests/native-risk-evidence-parity.test.ts +21 -0
- package/tests/no-self-pause-contract.test.ts +87 -0
- package/tests/notification-audio-native.test.ts +281 -0
- package/tests/notification-audio-windows.test.ts +581 -0
- package/tests/notification-audio.test.ts +548 -0
- package/tests/notification-customize.test.ts +655 -0
- package/tests/notification-events.test.ts +115 -0
- package/tests/notification-policy.test.ts +201 -0
- package/tests/notification-pulse-client.test.ts +356 -0
- package/tests/notification-pulse-protocol.test.ts +213 -0
- package/tests/notification-pulse-stream.test.ts +320 -0
- package/tests/notification-scheduler.test.ts +258 -0
- package/tests/notification-ui.test.ts +157 -0
- package/tests/npm-pack-result.test.ts +41 -0
- package/tests/odd-integration.test.ts +76 -0
- package/tests/odd-phase-inference.test.ts +213 -0
- package/tests/odd-phase-loader.test.ts +253 -0
- package/tests/odd-phase.test.ts +307 -0
- package/tests/odd-routing-canonical-ratchet.test.ts +60 -14
- package/tests/odd-routing-contract.test.ts +158 -50
- package/tests/orchestrator-budget.test.ts +43 -45
- package/tests/orchestrator-catalog.test.ts +146 -0
- package/tests/orchestrator-consultation-sdk.test.ts +614 -0
- package/tests/orchestrator-consultation.test.ts +111 -0
- package/tests/orchestrator-discovery.test.ts +241 -0
- package/tests/orchestrator-helper-consent.test.ts +171 -0
- package/tests/orchestrator-helper.test.ts +297 -0
- package/tests/orchestrator-message-card.test.ts +190 -0
- package/tests/orchestrator-presence.test.ts +23 -0
- package/tests/orchestrator-rdd-ownership.test.ts +8 -7
- package/tests/orchestrator-scope.test.ts +107 -0
- package/tests/orchestrator-state.test.ts +114 -0
- package/tests/orchestrator-status-publication-contract.test.ts +34 -0
- package/tests/orchestrator-tool-card.test.ts +398 -0
- package/tests/orchestrator-work-search.test.ts +318 -0
- package/tests/orchestrator-work.test.ts +147 -0
- package/tests/overlay-repaint.test.ts +74 -0
- package/tests/package-manifest.test.ts +344 -123
- package/tests/packed-runner-owned-path.test.ts +80 -0
- package/tests/parallel-reason-contract.test.ts +21 -0
- package/tests/parallel-writer-admission.test.ts +210 -0
- package/tests/permission-mode-policy.test.ts +194 -0
- package/tests/persona-single-channel.test.ts +10 -18
- package/tests/pi-pretty.test.ts +56 -0
- package/tests/provider-defect-handoff.test.ts +7 -14
- package/tests/quiet-bash-runtime.test.ts +76 -0
- package/tests/quiet-tool-rendering.test.ts +569 -187
- package/tests/rdd-aware-verification-contract.test.ts +103 -40
- package/tests/rdd-status-line.test.ts +9 -4
- package/tests/render-cache-performance.test.ts +48 -0
- package/tests/resume-hint-extension.test.ts +122 -0
- package/tests/review-agent-end-preflight.test.ts +237 -21
- package/tests/review-candidate-owner-retry.test.ts +22 -1
- package/tests/review-candidate-view.test.ts +399 -12
- package/tests/review-contract-prompt.test.ts +138 -44
- package/tests/review-controller-lock-status.test.ts +0 -1
- package/tests/review-controller-native-recovery.test.ts +327 -17
- package/tests/review-controller-native-routing.test.ts +866 -6
- package/tests/review-controller-workspace-root.test.ts +206 -6
- package/tests/review-controller.test.ts +2 -2
- package/tests/review-correction-lifecycle.test.ts +2 -1
- package/tests/review-gate.test.ts +7 -4
- package/tests/review-graph-schema.test.ts +2 -2
- package/tests/review-host-relay-routing.test.ts +342 -4
- package/tests/review-host-relay.test.ts +49 -2
- package/tests/review-integration-v2-forward.test.ts +221 -1
- package/tests/review-json-arguments.test.ts +167 -0
- package/tests/review-last-event-closure.test.ts +157 -8
- package/tests/review-ledger-contract.test.ts +14 -37
- package/tests/review-policy-judgment-day.test.ts +1 -1
- package/tests/review-policy-ordinary.test.ts +2 -2
- package/tests/review-relay-transport-agent.test.ts +66 -1
- package/tests/review-reminder-receipt.test.ts +47 -1
- package/tests/review-risk-assessment.test.ts +594 -26
- package/tests/review-session-standing-permission-controller.test.ts +16 -17
- package/tests/review-session-standing-permission-ipc.test.ts +6 -5
- package/tests/review-sidebar-state.test.ts +402 -0
- package/tests/review-test-fixtures.ts +7 -1
- package/tests/review-transaction.test.ts +18 -1
- package/tests/run-test-suite.test.ts +202 -0
- package/tests/runtime-harness.mjs +186 -800
- package/tests/runtime-metrics-children.test.ts +16 -23
- package/tests/runtime-metrics-extension.test.ts +1 -1
- package/tests/runtime-metrics-native.test.ts +31 -1
- package/tests/selection-engine.test.ts +451 -0
- package/tests/session-change-capture.test.ts +10 -10
- package/tests/session-changes-shell.test.ts +242 -48
- package/tests/session-changes.test.ts +17 -24
- package/tests/session-messaging-grants.test.ts +255 -0
- package/tests/session-profile-binding.test.ts +163 -0
- package/tests/session-profile-freeze.test.ts +381 -0
- package/tests/session-subject-routing-contract.test.ts +34 -0
- package/tests/session-worktree-registry.test.ts +134 -2
- package/tests/shell-bar.test.ts +422 -1
- package/tests/shell-card.test.ts +353 -1
- package/tests/shell-changes-view.test.ts +135 -32
- package/tests/shell-changes.test.ts +61 -62
- package/tests/shell-prompt.test.ts +94 -2
- package/tests/shell-render-cache-runtime.test.ts +69 -0
- package/tests/shell-render-cache.test.ts +129 -0
- package/tests/shell-sidebar-layout.test.ts +351 -22
- package/tests/shell-sidebar-scroll-benchmark.test.ts +255 -0
- package/tests/shell-todo.test.ts +207 -3
- package/tests/shell-usage-view.test.ts +28 -1
- package/tests/shell-usage.test.ts +108 -0
- package/tests/short-check-routing-contract.test.ts +49 -0
- package/tests/skill-registry-watcher.test.ts +119 -0
- package/tests/skill-registry.test.ts +357 -2
- package/tests/startup-banner.test.ts +246 -4
- package/tests/startup-home-repo.test.ts +170 -0
- package/tests/stats-collector.test.ts +195 -0
- package/tests/stats-view.test.ts +202 -0
- package/tests/status-timing-diagnostics.test.ts +283 -0
- package/tests/support/orchestrator-modules.ts +19 -0
- package/tests/task-size-routing-contract.test.ts +241 -0
- package/tests/telemetry-trigger.test.ts +107 -20
- package/tests/test-strength-contract.test.ts +124 -0
- package/tests/theme-customization.test.ts +72 -0
- package/tests/verify-agent-contract.test.ts +90 -0
- package/tests/verify-first-launch-contract.test.ts +139 -0
- package/tests/verify-package-files.test.ts +56 -0
- package/tests/vim-editor-adapter-host-resolution.test.ts +37 -0
- package/tests/vim-editor-adapter.test.ts +804 -0
- package/tests/vim-normal-engine.test.ts +101 -0
- package/tests/vim-operator-engine.test.ts +215 -0
- package/tests/vim-policy.test.ts +19 -0
- package/tests/vim-visual-engine.test.ts +52 -0
- package/tests/visual-customization-policy.test.ts +139 -0
- package/tests/visual-customize-view.test.ts +696 -0
- package/tests/visual-profiles.test.ts +87 -0
- package/tests/writer-edit-surface-scope.test.ts +59 -2
- package/tests/writer-surfaces.test.ts +199 -0
- package/tests/yolo-customize.test.ts +271 -0
- package/tests/yolo-mode-runtime.test.ts +188 -0
- package/tests/yolo-mode.test.ts +289 -0
- package/tests/yolo-session-policy.test.ts +59 -0
- package/themes/Gentle.json +2 -1
- package/themes/Gentleman-Cute.json +2 -1
- package/themes/Gentleman-Sexy.json +2 -1
- package/assets/agents/sdd-apply.md +0 -159
- package/assets/agents/sdd-archive.md +0 -228
- package/assets/agents/sdd-design.md +0 -49
- package/assets/agents/sdd-explore.md +0 -48
- package/assets/agents/sdd-init.md +0 -56
- package/assets/agents/sdd-onboard.md +0 -52
- package/assets/agents/sdd-proposal.md +0 -64
- package/assets/agents/sdd-remediate.md +0 -37
- package/assets/agents/sdd-research.md +0 -49
- package/assets/agents/sdd-spec.md +0 -192
- package/assets/agents/sdd-status.md +0 -54
- package/assets/agents/sdd-tasks.md +0 -108
- package/assets/agents/sdd-verify.md +0 -124
- package/assets/chains/sdd-full.chain.md +0 -83
- package/assets/chains/sdd-plan.chain.md +0 -56
- package/assets/chains/sdd-verify.chain.md +0 -43
- package/assets/sdd-orchestrator-workflow.md +0 -319
- package/assets/support/sdd-status-contract.md +0 -77
- package/docs/assets/diagrams/sdd-cycle.svg +0 -14
- package/extensions/sdd-init.ts +0 -816
- package/lib/openspec-deltas.ts +0 -156
- package/lib/sdd-preflight.ts +0 -1066
- package/lib/sdd-research-capabilities.ts +0 -94
- package/lib/sdd-status.ts +0 -26
- package/tests/fixtures/legacy/sdd-research-v2.5.0.md +0 -54
- package/tests/fixtures/native-review-cli/v2.1.3/bind-sdd.json +0 -25
- package/tests/fixtures/v0.10.7/assets/agents/sdd-apply.md +0 -132
- package/tests/openspec-deltas.test.ts +0 -209
- package/tests/sdd-agent-tools.test.ts +0 -156
- package/tests/sdd-archive-replay.test.ts +0 -82
- package/tests/sdd-classical-continuation.test.ts +0 -74
- package/tests/sdd-execution-routing-contract.test.ts +0 -44
- package/tests/sdd-managed-runtime-settlement.test.ts +0 -155
- package/tests/sdd-native-managed-uptake.test.ts +0 -243
- package/tests/sdd-no-attempts-contract.test.ts +0 -15
- package/tests/sdd-odd-integration.test.ts +0 -33
- package/tests/sdd-optional-research.test.ts +0 -124
- package/tests/sdd-planning-routing-contract.test.ts +0 -45
- package/tests/sdd-preflight-rpc-input.test.ts +0 -125
- package/tests/sdd-preflight.test.ts +0 -541
- package/tests/sdd-research-capabilities.test.ts +0 -114
- package/tests/sdd-research-live.test.ts +0 -241
- package/tests/sdd-selection-transport.test.ts +0 -653
- package/tests/sdd-status.test.ts +0 -9
- package/tests/sdd-task-truth.test.ts +0 -43
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/README.md +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/lens.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/refuter.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/targeted-validator.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/lens.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/refuter.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/targeted-validator.json +0 -0
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, isAbsolute, join, relative, sep } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { resolveGentlePiAgentHome } from "./agent-home.ts";
|
|
6
|
+
|
|
7
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
8
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const PACKAGE_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
12
|
+
const ASSETS_DIR = join(PACKAGE_ROOT, "assets");
|
|
13
|
+
const MANAGED_ASSETS_MANIFEST = "managed-assets.json";
|
|
14
|
+
const MANAGED_ASSETS_LOCK = "managed-assets.lock";
|
|
15
|
+
const MANAGED_ASSETS_SCHEMA_VERSION = 1;
|
|
16
|
+
const MANAGED_ASSETS_LOCK_TIMEOUT_MS = 5_000;
|
|
17
|
+
const MANAGED_ASSETS_LOCK_RETRY_MS = 25;
|
|
18
|
+
const LEGACY_MANAGED_ASSET_MANIFESTS = Object.freeze([
|
|
19
|
+
{ path: join(ASSETS_DIR, "migrations", "managed-assets-v0.10.7.json"), version: "0.10.7" },
|
|
20
|
+
{ path: join(ASSETS_DIR, "migrations", "managed-assets-v0.13.json"), version: "0.13.0" },
|
|
21
|
+
{ path: join(ASSETS_DIR, "migrations", "managed-assets-v0.14.json"), version: "0.14.0" },
|
|
22
|
+
{ path: join(ASSETS_DIR, "migrations", "managed-assets-v2.5.0.json"), version: "2.5.0" },
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
const ASSET_OWNER_BY_KEY = Object.freeze({
|
|
26
|
+
"agents/gentle-ai-explore.md": "delegation",
|
|
27
|
+
"agents/gentle-ai-verify.md": "delegation",
|
|
28
|
+
"agents/gentle-ai-worker.md": "delegation",
|
|
29
|
+
"agents/jd-fix-agent.md": "review",
|
|
30
|
+
"agents/jd-judge-a.md": "review",
|
|
31
|
+
"agents/jd-judge-b.md": "review",
|
|
32
|
+
"agents/review-readability.md": "review",
|
|
33
|
+
"agents/review-reliability.md": "review",
|
|
34
|
+
"agents/review-resilience.md": "review",
|
|
35
|
+
"agents/review-risk.md": "review",
|
|
36
|
+
"agents/review-refuter.md": "review",
|
|
37
|
+
"agents/review-validator.md": "review",
|
|
38
|
+
"chains/4r-review.chain.md": "review",
|
|
39
|
+
"gentle-ai/support/strict-tdd.md": "delegation",
|
|
40
|
+
"gentle-ai/support/strict-tdd-verify.md": "delegation",
|
|
41
|
+
} as const);
|
|
42
|
+
|
|
43
|
+
export type PackageAssetOwner = (typeof ASSET_OWNER_BY_KEY)[keyof typeof ASSET_OWNER_BY_KEY];
|
|
44
|
+
|
|
45
|
+
export function getPackageAssetOwner(ownershipKey: string): PackageAssetOwner | undefined {
|
|
46
|
+
return Object.hasOwn(ASSET_OWNER_BY_KEY, ownershipKey)
|
|
47
|
+
? ASSET_OWNER_BY_KEY[ownershipKey as keyof typeof ASSET_OWNER_BY_KEY]
|
|
48
|
+
: undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function gentlePiAgentHome(): string {
|
|
52
|
+
return resolveGentlePiAgentHome();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
interface ManagedAssetsManifest {
|
|
57
|
+
schemaVersion: number;
|
|
58
|
+
assets: Record<string, string>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface LegacyManagedAssetsManifest extends ManagedAssetsManifest {
|
|
62
|
+
packageVersion: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface ManagedAssetsLockOwner {
|
|
66
|
+
schemaVersion: 1;
|
|
67
|
+
token: string;
|
|
68
|
+
pid: number;
|
|
69
|
+
createdAtMs: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** @internal The hold option exists only to make process-lock regression tests deterministic. */
|
|
73
|
+
interface PackageAssetInstallLockOptions {
|
|
74
|
+
timeoutMs?: number;
|
|
75
|
+
retryMs?: number;
|
|
76
|
+
holdLockMs?: number;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
function emptyManagedAssetsManifest(): ManagedAssetsManifest {
|
|
81
|
+
return {
|
|
82
|
+
schemaVersion: MANAGED_ASSETS_SCHEMA_VERSION,
|
|
83
|
+
assets: {},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function readManagedAssetsManifest(path: string): ManagedAssetsManifest {
|
|
88
|
+
if (!existsSync(path)) return emptyManagedAssetsManifest();
|
|
89
|
+
try {
|
|
90
|
+
const parsed: unknown = JSON.parse(readFileSync(path, "utf8"));
|
|
91
|
+
if (
|
|
92
|
+
!isRecord(parsed) ||
|
|
93
|
+
parsed.schemaVersion !== MANAGED_ASSETS_SCHEMA_VERSION ||
|
|
94
|
+
!isRecord(parsed.assets)
|
|
95
|
+
) {
|
|
96
|
+
return emptyManagedAssetsManifest();
|
|
97
|
+
}
|
|
98
|
+
const assets = Object.fromEntries(
|
|
99
|
+
Object.entries(parsed.assets).filter(
|
|
100
|
+
(entry): entry is [string, string] => typeof entry[1] === "string",
|
|
101
|
+
),
|
|
102
|
+
);
|
|
103
|
+
return { schemaVersion: MANAGED_ASSETS_SCHEMA_VERSION, assets };
|
|
104
|
+
} catch {
|
|
105
|
+
return emptyManagedAssetsManifest();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function managedAssetHash(content: string): string {
|
|
110
|
+
return createHash("sha256").update(content).digest("hex");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function readManagedAssetsLockOwner(lockPath: string): ManagedAssetsLockOwner | undefined {
|
|
114
|
+
try {
|
|
115
|
+
if (!lstatSync(lockPath).isFile()) return undefined;
|
|
116
|
+
const parsed: unknown = JSON.parse(readFileSync(lockPath, "utf8"));
|
|
117
|
+
if (!isRecord(parsed) || parsed.schemaVersion !== 1 || typeof parsed.token !== "string" || parsed.token.length === 0 || (typeof parsed.pid !== "number" || !Number.isInteger(parsed.pid) || parsed.pid <= 0) || typeof parsed.createdAtMs !== "number" || !Number.isFinite(parsed.createdAtMs)) return undefined;
|
|
118
|
+
return parsed as unknown as ManagedAssetsLockOwner;
|
|
119
|
+
} catch {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function waitForManagedAssetsLock(milliseconds: number): void {
|
|
125
|
+
if (milliseconds <= 0) return;
|
|
126
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function normalizedLockDuration(value: number | undefined, fallback: number): number {
|
|
130
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0
|
|
131
|
+
? Math.floor(value)
|
|
132
|
+
: fallback;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function acquireManagedAssetsLock(
|
|
136
|
+
agentHome: string,
|
|
137
|
+
options: PackageAssetInstallLockOptions = {},
|
|
138
|
+
): { path: string; owner: ManagedAssetsLockOwner } {
|
|
139
|
+
const lockParent = join(agentHome, "gentle-ai");
|
|
140
|
+
const lockPath = join(lockParent, MANAGED_ASSETS_LOCK);
|
|
141
|
+
const timeoutMs = normalizedLockDuration(options.timeoutMs, MANAGED_ASSETS_LOCK_TIMEOUT_MS);
|
|
142
|
+
const retryMs = Math.max(1, normalizedLockDuration(options.retryMs, MANAGED_ASSETS_LOCK_RETRY_MS));
|
|
143
|
+
const deadline = Date.now() + timeoutMs;
|
|
144
|
+
mkdirSync(lockParent, { recursive: true });
|
|
145
|
+
for (;;) {
|
|
146
|
+
const owner: ManagedAssetsLockOwner = {
|
|
147
|
+
schemaVersion: 1,
|
|
148
|
+
token: randomUUID(),
|
|
149
|
+
pid: process.pid,
|
|
150
|
+
createdAtMs: Date.now(),
|
|
151
|
+
};
|
|
152
|
+
try {
|
|
153
|
+
writeFileSync(lockPath, JSON.stringify(owner), { encoding: "utf8", flag: "wx" });
|
|
154
|
+
return { path: lockPath, owner };
|
|
155
|
+
} catch (error) {
|
|
156
|
+
if (!isRecord(error) || error.code !== "EEXIST") throw error;
|
|
157
|
+
try {
|
|
158
|
+
if (!lstatSync(lockPath).isFile()) {
|
|
159
|
+
throw new Error(`Managed-assets lock path is unsafe and must be a regular file: ${lockPath}`);
|
|
160
|
+
}
|
|
161
|
+
} catch (inspectionError) {
|
|
162
|
+
if (isRecord(inspectionError) && inspectionError.code === "ENOENT") continue;
|
|
163
|
+
throw inspectionError;
|
|
164
|
+
}
|
|
165
|
+
if (Date.now() >= deadline) {
|
|
166
|
+
throw new Error(`Timed out acquiring managed-assets lock file ${lockPath}. Verify no installer is active before removing this exact lock file.`);
|
|
167
|
+
}
|
|
168
|
+
waitForManagedAssetsLock(retryMs);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function releaseManagedAssetsLock(lock: { path: string; owner: ManagedAssetsLockOwner }): void {
|
|
174
|
+
if (readManagedAssetsLockOwner(lock.path)?.token !== lock.owner.token) return;
|
|
175
|
+
try {
|
|
176
|
+
unlinkSync(lock.path);
|
|
177
|
+
} catch {
|
|
178
|
+
// An unreadable or replaced lock remains for an operator to inspect.
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function withManagedAssetsLock<T>(
|
|
183
|
+
agentHome: string,
|
|
184
|
+
action: () => T,
|
|
185
|
+
options: PackageAssetInstallLockOptions | undefined,
|
|
186
|
+
): T {
|
|
187
|
+
const lock = acquireManagedAssetsLock(agentHome, options);
|
|
188
|
+
try {
|
|
189
|
+
waitForManagedAssetsLock(normalizedLockDuration(options?.holdLockMs, 0));
|
|
190
|
+
return action();
|
|
191
|
+
} finally {
|
|
192
|
+
releaseManagedAssetsLock(lock);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function readLegacyManagedAssets(
|
|
197
|
+
path: string,
|
|
198
|
+
version: string,
|
|
199
|
+
): LegacyManagedAssetsManifest | undefined {
|
|
200
|
+
try {
|
|
201
|
+
const parsed: unknown = JSON.parse(readFileSync(path, "utf8"));
|
|
202
|
+
if (
|
|
203
|
+
!isRecord(parsed) ||
|
|
204
|
+
parsed.schemaVersion !== MANAGED_ASSETS_SCHEMA_VERSION ||
|
|
205
|
+
parsed.packageVersion !== version ||
|
|
206
|
+
!isRecord(parsed.assets)
|
|
207
|
+
) {
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
const assets = Object.fromEntries(
|
|
211
|
+
Object.entries(parsed.assets).filter(
|
|
212
|
+
(entry): entry is [string, string] => typeof entry[1] === "string",
|
|
213
|
+
),
|
|
214
|
+
);
|
|
215
|
+
return {
|
|
216
|
+
schemaVersion: MANAGED_ASSETS_SCHEMA_VERSION,
|
|
217
|
+
packageVersion: version,
|
|
218
|
+
assets,
|
|
219
|
+
};
|
|
220
|
+
} catch {
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function readLegacyManagedAssetHashes(): Record<string, readonly string[]> {
|
|
226
|
+
const hashes: Record<string, string[]> = {};
|
|
227
|
+
for (const manifest of LEGACY_MANAGED_ASSET_MANIFESTS) {
|
|
228
|
+
const assets = readLegacyManagedAssets(manifest.path, manifest.version)?.assets;
|
|
229
|
+
if (!assets) continue;
|
|
230
|
+
for (const [ownershipKey, hash] of Object.entries(assets)) {
|
|
231
|
+
const known = hashes[ownershipKey] ?? [];
|
|
232
|
+
if (!known.includes(hash)) known.push(hash);
|
|
233
|
+
hashes[ownershipKey] = known;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return hashes;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function updateAgentFrontmatterRouting(
|
|
240
|
+
content: string,
|
|
241
|
+
routingLines: readonly string[],
|
|
242
|
+
): string {
|
|
243
|
+
if (!content.startsWith("---\n")) return content;
|
|
244
|
+
const endIndex = content.indexOf("\n---", 4);
|
|
245
|
+
if (endIndex === -1) return content;
|
|
246
|
+
const frontmatter = content.slice(4, endIndex);
|
|
247
|
+
const body = content.slice(endIndex);
|
|
248
|
+
const lines = frontmatter
|
|
249
|
+
.split("\n")
|
|
250
|
+
.filter((line) => !/^(?:model|thinking):/.test(line));
|
|
251
|
+
if (routingLines.length > 0) {
|
|
252
|
+
const descriptionIndex = lines.findIndex((line) =>
|
|
253
|
+
line.startsWith("description:"),
|
|
254
|
+
);
|
|
255
|
+
const insertIndex =
|
|
256
|
+
descriptionIndex >= 0 ? descriptionIndex + 1 : Math.min(1, lines.length);
|
|
257
|
+
lines.splice(insertIndex, 0, ...routingLines);
|
|
258
|
+
}
|
|
259
|
+
return `---\n${lines.join("\n")}${body}`;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function legacyComparableAssetContent(
|
|
263
|
+
ownershipKey: string,
|
|
264
|
+
content: string,
|
|
265
|
+
): string {
|
|
266
|
+
return ownershipKey.startsWith("agents/")
|
|
267
|
+
? updateAgentFrontmatterRouting(content, [])
|
|
268
|
+
: content;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function migrateLegacyAssetContent(
|
|
272
|
+
ownershipKey: string,
|
|
273
|
+
installedContent: string,
|
|
274
|
+
packagedContent: string,
|
|
275
|
+
): string {
|
|
276
|
+
if (!ownershipKey.startsWith("agents/")) return packagedContent;
|
|
277
|
+
if (!installedContent.startsWith("---\n")) return packagedContent;
|
|
278
|
+
const endIndex = installedContent.indexOf("\n---", 4);
|
|
279
|
+
if (endIndex === -1) return packagedContent;
|
|
280
|
+
const routingLines = installedContent
|
|
281
|
+
.slice(4, endIndex)
|
|
282
|
+
.split("\n")
|
|
283
|
+
.filter((line) => /^(?:model|thinking):/.test(line));
|
|
284
|
+
return updateAgentFrontmatterRouting(packagedContent, routingLines);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function replaceManagedAssetFileAtomically(path: string, content: string): void {
|
|
288
|
+
const temporaryPath = join(dirname(path), `.${randomUUID()}.tmp`);
|
|
289
|
+
let mode: number | undefined;
|
|
290
|
+
try {
|
|
291
|
+
const stat = lstatSync(path);
|
|
292
|
+
if (stat.isFile()) mode = stat.mode & 0o777;
|
|
293
|
+
} catch (error) {
|
|
294
|
+
if (!isRecord(error) || error.code !== "ENOENT") throw error;
|
|
295
|
+
}
|
|
296
|
+
try {
|
|
297
|
+
writeFileSync(temporaryPath, content, {
|
|
298
|
+
encoding: "utf8",
|
|
299
|
+
flag: "wx",
|
|
300
|
+
...(mode === undefined ? {} : { mode }),
|
|
301
|
+
});
|
|
302
|
+
renameSync(temporaryPath, path);
|
|
303
|
+
} finally {
|
|
304
|
+
try {
|
|
305
|
+
unlinkSync(temporaryPath);
|
|
306
|
+
} catch {
|
|
307
|
+
// A renamed or otherwise inaccessible temporary file needs no further action.
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export function updatePackageManagedSddAgentOwnership(
|
|
313
|
+
installedPath: string,
|
|
314
|
+
previousContent: string,
|
|
315
|
+
nextContent: string,
|
|
316
|
+
lockOptions?: PackageAssetInstallLockOptions,
|
|
317
|
+
): boolean {
|
|
318
|
+
const agentHome = gentlePiAgentHome();
|
|
319
|
+
const relativePath = relative(join(agentHome, "agents"), installedPath);
|
|
320
|
+
if (
|
|
321
|
+
relativePath.length === 0 ||
|
|
322
|
+
relativePath === ".." ||
|
|
323
|
+
relativePath.startsWith(`..${sep}`) ||
|
|
324
|
+
isAbsolute(relativePath)
|
|
325
|
+
) {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
const ownershipKey = `agents/${relativePath.split(sep).join("/")}`;
|
|
329
|
+
return withManagedAssetsLock(agentHome, () => {
|
|
330
|
+
const manifestPath = join(agentHome, "gentle-ai", MANAGED_ASSETS_MANIFEST);
|
|
331
|
+
const manifest = readManagedAssetsManifest(manifestPath);
|
|
332
|
+
if (manifest.assets[ownershipKey] !== managedAssetHash(previousContent)) {
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
const installedContent = readFileSync(installedPath, "utf8");
|
|
336
|
+
// The next-content branch preserves the prior internal caller contract:
|
|
337
|
+
// callers that wrote the file before this function still receive a managed
|
|
338
|
+
// manifest update. New callers take the previous-content branch below so
|
|
339
|
+
// the file and manifest update share this lock.
|
|
340
|
+
if (installedContent !== nextContent && installedContent !== previousContent) {
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
try {
|
|
344
|
+
if (installedContent === previousContent) {
|
|
345
|
+
replaceManagedAssetFileAtomically(installedPath, nextContent);
|
|
346
|
+
}
|
|
347
|
+
manifest.assets[ownershipKey] = managedAssetHash(nextContent);
|
|
348
|
+
replaceManagedAssetFileAtomically(
|
|
349
|
+
manifestPath,
|
|
350
|
+
JSON.stringify(manifest, null, 2),
|
|
351
|
+
);
|
|
352
|
+
} catch (error) {
|
|
353
|
+
try {
|
|
354
|
+
replaceManagedAssetFileAtomically(installedPath, previousContent);
|
|
355
|
+
} catch (rollbackError) {
|
|
356
|
+
throw new Error(
|
|
357
|
+
`Managed routing update failed and could not restore ${installedPath}: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`,
|
|
358
|
+
{ cause: error },
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
throw error;
|
|
362
|
+
}
|
|
363
|
+
return true;
|
|
364
|
+
}, lockOptions);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function hasPackageAssetOwnerInstallation(owner: PackageAssetOwner): boolean {
|
|
368
|
+
const agentHome = gentlePiAgentHome();
|
|
369
|
+
const manifest = readManagedAssetsManifest(join(agentHome, "gentle-ai", MANAGED_ASSETS_MANIFEST));
|
|
370
|
+
return Object.keys(manifest.assets).some((key) => getPackageAssetOwner(key) === owner) ||
|
|
371
|
+
Object.entries(ASSET_OWNER_BY_KEY).some(([key, candidate]) =>
|
|
372
|
+
candidate === owner && existsSync(join(agentHome, key)),
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export function isPackageManagedSddAsset(
|
|
377
|
+
installedPath: string,
|
|
378
|
+
ownershipKey: string,
|
|
379
|
+
): boolean {
|
|
380
|
+
const manifest = readManagedAssetsManifest(
|
|
381
|
+
join(gentlePiAgentHome(), "gentle-ai", MANAGED_ASSETS_MANIFEST),
|
|
382
|
+
);
|
|
383
|
+
const expectedHash = manifest.assets[ownershipKey];
|
|
384
|
+
if (expectedHash === undefined || !existsSync(installedPath)) return false;
|
|
385
|
+
try {
|
|
386
|
+
return (
|
|
387
|
+
managedAssetHash(readFileSync(installedPath, "utf8")) ===
|
|
388
|
+
expectedHash
|
|
389
|
+
);
|
|
390
|
+
} catch {
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
function copyDirectoryFiles(
|
|
397
|
+
sourceDir: string,
|
|
398
|
+
targetDir: string,
|
|
399
|
+
ownershipPrefix: string,
|
|
400
|
+
force: boolean,
|
|
401
|
+
manifest: ManagedAssetsManifest,
|
|
402
|
+
legacyAssetHashes: (() => Readonly<Record<string, readonly string[]>>) | undefined,
|
|
403
|
+
selected?: ReadonlySet<string>,
|
|
404
|
+
): { copied: number; skipped: number } {
|
|
405
|
+
if (!existsSync(sourceDir)) return { copied: 0, skipped: 0 };
|
|
406
|
+
if (selected && ![...selected].some(key => key.startsWith(`${ownershipPrefix}/`))) {
|
|
407
|
+
return { copied: 0, skipped: 0 };
|
|
408
|
+
}
|
|
409
|
+
mkdirSync(targetDir, { recursive: true });
|
|
410
|
+
let copied = 0;
|
|
411
|
+
let skipped = 0;
|
|
412
|
+
for (const entry of readdirSync(sourceDir, { withFileTypes: true })) {
|
|
413
|
+
const sourcePath = join(sourceDir, entry.name);
|
|
414
|
+
const targetPath = join(targetDir, entry.name);
|
|
415
|
+
const ownershipKey = `${ownershipPrefix}/${entry.name}`;
|
|
416
|
+
if (entry.isDirectory()) {
|
|
417
|
+
const child = copyDirectoryFiles(
|
|
418
|
+
sourcePath,
|
|
419
|
+
targetPath,
|
|
420
|
+
ownershipKey,
|
|
421
|
+
force,
|
|
422
|
+
manifest,
|
|
423
|
+
legacyAssetHashes,
|
|
424
|
+
selected,
|
|
425
|
+
);
|
|
426
|
+
copied += child.copied;
|
|
427
|
+
skipped += child.skipped;
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
if (!entry.isFile() || (selected && !selected.has(ownershipKey))) continue;
|
|
431
|
+
const source = readFileSync(sourcePath, "utf8");
|
|
432
|
+
let nextSource = source;
|
|
433
|
+
if (existsSync(targetPath)) {
|
|
434
|
+
if (!force) {
|
|
435
|
+
skipped += 1;
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
const managedHash = manifest.assets[ownershipKey];
|
|
439
|
+
let installedContent: string | undefined;
|
|
440
|
+
try {
|
|
441
|
+
installedContent = readFileSync(targetPath, "utf8");
|
|
442
|
+
} catch {
|
|
443
|
+
installedContent = undefined;
|
|
444
|
+
}
|
|
445
|
+
const installedHash = installedContent === undefined
|
|
446
|
+
? undefined
|
|
447
|
+
: managedAssetHash(installedContent);
|
|
448
|
+
if (managedHash === undefined) {
|
|
449
|
+
const legacyHashes = legacyAssetHashes?.()[ownershipKey];
|
|
450
|
+
const comparableLegacyHash = installedContent === undefined
|
|
451
|
+
? undefined
|
|
452
|
+
: managedAssetHash(
|
|
453
|
+
legacyComparableAssetContent(ownershipKey, installedContent),
|
|
454
|
+
);
|
|
455
|
+
if (
|
|
456
|
+
legacyHashes === undefined ||
|
|
457
|
+
comparableLegacyHash === undefined ||
|
|
458
|
+
!legacyHashes.includes(comparableLegacyHash)
|
|
459
|
+
) {
|
|
460
|
+
delete manifest.assets[ownershipKey];
|
|
461
|
+
skipped += 1;
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
nextSource = migrateLegacyAssetContent(
|
|
465
|
+
ownershipKey,
|
|
466
|
+
installedContent,
|
|
467
|
+
source,
|
|
468
|
+
);
|
|
469
|
+
} else if (installedHash !== managedHash) {
|
|
470
|
+
delete manifest.assets[ownershipKey];
|
|
471
|
+
skipped += 1;
|
|
472
|
+
continue;
|
|
473
|
+
} else if (ownershipKey === "agents/sdd-research.md" && installedContent !== undefined) {
|
|
474
|
+
// Keep routing adopted by the legacy migration on subsequent refreshes.
|
|
475
|
+
nextSource = migrateLegacyAssetContent(ownershipKey, installedContent, source);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
writeFileSync(targetPath, nextSource);
|
|
479
|
+
manifest.assets[ownershipKey] = managedAssetHash(nextSource);
|
|
480
|
+
copied += 1;
|
|
481
|
+
}
|
|
482
|
+
return { copied, skipped };
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Assets retired by gentle-pi#311 P5: the Pi-owned adversarial review actors.
|
|
486
|
+
// The refuter and validator verdicts now execute through Go-owned pi
|
|
487
|
+
// processes via provider-rendered self-contained vectors, so these agent
|
|
488
|
+
// definitions have no runtime consumer. The migration manifests under
|
|
489
|
+
// assets/migrations are append-only legacy-hash HISTORY (adoption evidence
|
|
490
|
+
// for force-installs) with no removal semantics, so history stays untouched
|
|
491
|
+
// and retirement happens here: an installed copy is deleted only when its
|
|
492
|
+
// content hash proves package ownership (current manifest or legacy
|
|
493
|
+
// history); user-modified copies are left in place and only lose managed
|
|
494
|
+
// ownership.
|
|
495
|
+
const RETIRED_MANAGED_ASSETS = Object.freeze([
|
|
496
|
+
// Canonical spec composition now belongs to archive (gentle-pi#1051).
|
|
497
|
+
"agents/sdd-sync.md",
|
|
498
|
+
...[
|
|
499
|
+
"apply", "archive", "design", "explore", "init", "onboard", "proposal",
|
|
500
|
+
"remediate", "research", "spec", "status", "tasks", "verify",
|
|
501
|
+
].map(name => `agents/sdd-${name}.md`),
|
|
502
|
+
"chains/sdd-full.chain.md",
|
|
503
|
+
"chains/sdd-plan.chain.md",
|
|
504
|
+
"chains/sdd-verify.chain.md",
|
|
505
|
+
"gentle-ai/support/sdd-status-contract.md",
|
|
506
|
+
"agents/review-refuter.md",
|
|
507
|
+
"agents/review-validator.md",
|
|
508
|
+
]);
|
|
509
|
+
|
|
510
|
+
function removeRetiredManagedAssets(
|
|
511
|
+
agentHome: string,
|
|
512
|
+
manifest: ManagedAssetsManifest,
|
|
513
|
+
selected?: ReadonlySet<string>,
|
|
514
|
+
): void {
|
|
515
|
+
let legacyHashes: Record<string, readonly string[]> | undefined;
|
|
516
|
+
for (const ownershipKey of RETIRED_MANAGED_ASSETS) {
|
|
517
|
+
if (selected && !selected.has(ownershipKey) && !(ownershipKey.includes("sdd-") && selected.has("gentle-ai/support/strict-tdd.md"))) continue;
|
|
518
|
+
const installedPath = join(agentHome, ...ownershipKey.split("/"));
|
|
519
|
+
if (!existsSync(installedPath)) {
|
|
520
|
+
delete manifest.assets[ownershipKey];
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
let installedContent: string | undefined;
|
|
524
|
+
try {
|
|
525
|
+
installedContent = readFileSync(installedPath, "utf8");
|
|
526
|
+
} catch {
|
|
527
|
+
installedContent = undefined;
|
|
528
|
+
}
|
|
529
|
+
if (installedContent === undefined) continue;
|
|
530
|
+
const installedHash = managedAssetHash(installedContent);
|
|
531
|
+
const managed = manifest.assets[ownershipKey] === installedHash;
|
|
532
|
+
const legacy = (legacyHashes ??= readLegacyManagedAssetHashes())[ownershipKey]?.includes(
|
|
533
|
+
managedAssetHash(legacyComparableAssetContent(ownershipKey, installedContent)),
|
|
534
|
+
) === true;
|
|
535
|
+
if (managed || legacy) {
|
|
536
|
+
try {
|
|
537
|
+
rmSync(installedPath);
|
|
538
|
+
} catch {
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
// Managed copies are gone; user-modified copies stay but stop being
|
|
543
|
+
// package-managed either way.
|
|
544
|
+
delete manifest.assets[ownershipKey];
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
export function installPackageAssets(
|
|
549
|
+
_cwd: string,
|
|
550
|
+
force: boolean,
|
|
551
|
+
owners?: readonly PackageAssetOwner[],
|
|
552
|
+
lockOptions?: PackageAssetInstallLockOptions,
|
|
553
|
+
): { agents: number; chains: number; support: number; skipped: number } {
|
|
554
|
+
const agentHome = gentlePiAgentHome();
|
|
555
|
+
return withManagedAssetsLock(agentHome, () => {
|
|
556
|
+
const selected = owners === undefined ? undefined : new Set(
|
|
557
|
+
Object.entries(ASSET_OWNER_BY_KEY).filter(([, owner]) => owners.includes(owner)).map(([key]) => key),
|
|
558
|
+
);
|
|
559
|
+
const manifestPath = join(agentHome, "gentle-ai", MANAGED_ASSETS_MANIFEST);
|
|
560
|
+
let legacyAssetHashes: (() => Readonly<Record<string, readonly string[]>>) | undefined;
|
|
561
|
+
if (force) {
|
|
562
|
+
let cachedLegacyAssetHashes: Record<string, readonly string[]> | undefined;
|
|
563
|
+
legacyAssetHashes = () =>
|
|
564
|
+
(cachedLegacyAssetHashes ??= readLegacyManagedAssetHashes());
|
|
565
|
+
}
|
|
566
|
+
const manifest = readManagedAssetsManifest(manifestPath);
|
|
567
|
+
removeRetiredManagedAssets(agentHome, manifest, selected);
|
|
568
|
+
const agents = copyDirectoryFiles(
|
|
569
|
+
join(ASSETS_DIR, "agents"),
|
|
570
|
+
join(agentHome, "agents"),
|
|
571
|
+
"agents",
|
|
572
|
+
force,
|
|
573
|
+
manifest,
|
|
574
|
+
legacyAssetHashes,
|
|
575
|
+
selected,
|
|
576
|
+
);
|
|
577
|
+
const chains = copyDirectoryFiles(
|
|
578
|
+
join(ASSETS_DIR, "chains"),
|
|
579
|
+
join(agentHome, "chains"),
|
|
580
|
+
"chains",
|
|
581
|
+
force,
|
|
582
|
+
manifest,
|
|
583
|
+
legacyAssetHashes,
|
|
584
|
+
selected,
|
|
585
|
+
);
|
|
586
|
+
const support = copyDirectoryFiles(
|
|
587
|
+
join(ASSETS_DIR, "support"),
|
|
588
|
+
join(agentHome, "gentle-ai", "support"),
|
|
589
|
+
"gentle-ai/support",
|
|
590
|
+
force,
|
|
591
|
+
manifest,
|
|
592
|
+
legacyAssetHashes,
|
|
593
|
+
selected,
|
|
594
|
+
);
|
|
595
|
+
mkdirSync(dirname(manifestPath), { recursive: true });
|
|
596
|
+
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
597
|
+
return {
|
|
598
|
+
agents: agents.copied,
|
|
599
|
+
chains: chains.copied,
|
|
600
|
+
support: support.copied,
|
|
601
|
+
skipped: agents.skipped + chains.skipped + support.skipped,
|
|
602
|
+
};
|
|
603
|
+
}, lockOptions);
|
|
604
|
+
}
|
package/lib/agent-home.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { bundledGentlePiConfigHome, runningBundledPrefix } from "./gentle-shell-bundled.ts";
|
|
3
4
|
|
|
4
5
|
// Pi Subagents resolves its global directory as `PI_CODING_AGENT_DIR || ~/.pi/agent`,
|
|
5
6
|
// so an empty value must fall through here too or the two homes diverge again.
|
|
@@ -11,6 +12,14 @@ export function resolveGentlePiAgentHome(env: NodeJS.ProcessEnv = process.env):
|
|
|
11
12
|
// commands own lives here. It is defined once because the launch-time profile pin
|
|
12
13
|
// resolver reads the same profiles store the `/gentle:profiles` panel writes, and
|
|
13
14
|
// two spellings of the override would silently read two different stores.
|
|
15
|
+
// A bundled Gentle Shell keeps it inside its prefix (S9, bundledGentlePiConfigHome).
|
|
14
16
|
export function gentlePiConfigHome(env: NodeJS.ProcessEnv = process.env): string {
|
|
15
|
-
return env.GENTLE_PI_CONFIG_HOME || join(homedir(), ".pi", "gentle-ai");
|
|
17
|
+
return bundledGentlePiConfigHome(env) ?? (env.GENTLE_PI_CONFIG_HOME || join(homedir(), ".pi", "gentle-ai"));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// The agent home Pi runs with, only in a bundled Gentle Shell (S9): there the
|
|
21
|
+
// extensions that otherwise write under ~/.pi/agent keep their files in it.
|
|
22
|
+
// Undefined elsewhere, where they keep their existing locations.
|
|
23
|
+
export function bundledGentlePiAgentHome(env: NodeJS.ProcessEnv = process.env): string | undefined {
|
|
24
|
+
return runningBundledPrefix(env) === undefined ? undefined : resolveGentlePiAgentHome(env);
|
|
16
25
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { join, resolve } from "node:path";
|
|
2
|
+
import { resolveGentlePiAgentHome } from "./agent-home.ts";
|
|
3
|
+
import { resolveProfilePin } from "./agent-profile-pin.ts";
|
|
4
|
+
import {
|
|
5
|
+
discoverAgents,
|
|
6
|
+
loadAgentsConfig,
|
|
7
|
+
resolveAgentProfile,
|
|
8
|
+
withPinnedModelProfiles,
|
|
9
|
+
type AgentDefinition,
|
|
10
|
+
type DiscoveryRoots,
|
|
11
|
+
type ModelRef,
|
|
12
|
+
type ResolvedProfile,
|
|
13
|
+
} from "./agents-config.ts";
|
|
14
|
+
import type { WorktreeResolver } from "./session-worktree-registry.ts";
|
|
15
|
+
|
|
16
|
+
// gentle-shell#1731: subagent launches and the harness model-routing fact share
|
|
17
|
+
// one resolution of the effective subagent model, so the fact the orchestrator
|
|
18
|
+
// reads can never disagree with the model a launch would actually use.
|
|
19
|
+
|
|
20
|
+
export interface AgentHomeSelection {
|
|
21
|
+
env: NodeJS.ProcessEnv;
|
|
22
|
+
home: string;
|
|
23
|
+
/** An explicit agent home, kept literal. */
|
|
24
|
+
agentHome?: string;
|
|
25
|
+
/** An explicit home selects `<home>/.pi/agent` and ignores the environment. */
|
|
26
|
+
homeOverridden?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** The agent home Gentle Agents discovers definitions and `subagents.json` from. */
|
|
30
|
+
export function resolveAgentHomeDirectory(selection: AgentHomeSelection): string {
|
|
31
|
+
const selectedHome = selection.agentHome ?? (selection.homeOverridden ? join(selection.home, ".pi", "agent") : resolveGentlePiAgentHome(selection.env));
|
|
32
|
+
// Expand environment tildes like Pi, but leave explicit path APIs literal.
|
|
33
|
+
const environmentHome = selection.agentHome === undefined && !selection.homeOverridden;
|
|
34
|
+
const expandedHome = environmentHome && selectedHome === "~" ? selection.home
|
|
35
|
+
: environmentHome && (selectedHome.startsWith("~/") || (process.platform === "win32" && selectedHome.startsWith("~\\"))) ? join(selection.home, selectedHome.slice(2)) : selectedHome;
|
|
36
|
+
return resolve(expandedHome);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface AgentProfileScope {
|
|
40
|
+
roots: DiscoveryRoots;
|
|
41
|
+
/** The directory whose repository profile pin applies to the launch. */
|
|
42
|
+
pinCwd: string;
|
|
43
|
+
configHome: string;
|
|
44
|
+
resolveWorktree?: WorktreeResolver;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The launch profile for an agent: global and project `subagents.json`, with a
|
|
49
|
+
* repository profile pin replacing `modelProfiles` wholesale when one applies.
|
|
50
|
+
*/
|
|
51
|
+
export function resolvePinnedAgentProfile(agent: AgentDefinition, scope: AgentProfileScope): ResolvedProfile {
|
|
52
|
+
const config = withPinnedModelProfiles(
|
|
53
|
+
loadAgentsConfig(scope.roots),
|
|
54
|
+
resolveProfilePin({ cwd: scope.pinCwd, configHome: scope.configHome, resolveWorktree: scope.resolveWorktree })?.modelProfiles,
|
|
55
|
+
);
|
|
56
|
+
return resolveAgentProfile(agent, config);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface EffectiveAgentModel {
|
|
60
|
+
agent: string;
|
|
61
|
+
model: ModelRef | undefined;
|
|
62
|
+
/** No profile, definition, or default routes the agent: it inherits the session model. */
|
|
63
|
+
inherited: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The model a launch of `agentName` would use, falling back to the session model
|
|
68
|
+
* when nothing routes it; `undefined` when the agent is not discovered.
|
|
69
|
+
*/
|
|
70
|
+
export function resolveEffectiveAgentModel(
|
|
71
|
+
agentName: string,
|
|
72
|
+
scope: AgentProfileScope & { fallback?: ModelRef },
|
|
73
|
+
): EffectiveAgentModel | undefined {
|
|
74
|
+
const agent = discoverAgents(scope.roots).agents.find((candidate) => candidate.name === agentName);
|
|
75
|
+
if (!agent) return undefined;
|
|
76
|
+
const profile = resolvePinnedAgentProfile(agent, scope);
|
|
77
|
+
return profile.model === undefined
|
|
78
|
+
? { agent: agentName, model: scope.fallback, inherited: true }
|
|
79
|
+
: { agent: agentName, model: profile.model, inherited: false };
|
|
80
|
+
}
|