gentle-pi 4.0.0 → 4.2.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 +157 -18
- package/assets/agents/gentle-ai-explore.md +11 -4
- package/assets/agents/gentle-ai-verify.md +23 -4
- package/assets/agents/gentle-ai-worker.md +6 -6
- 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 +70 -163
- package/assets/orchestrator-memory.md +11 -4
- package/assets/orchestrator-prompts.md +48 -0
- 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 +29 -25
- 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/bin/engram-guard +9 -0
- package/bin/gentle-shell-install.mjs +302 -0
- package/bin/gentle-shell.mjs +708 -22
- 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/auto-permission-answers.md +25 -0
- package/docs/auto-permission-evidence.md +39 -0
- package/docs/bundled-install.md +399 -0
- package/docs/delegated-verification.md +3 -5
- package/docs/gentle-agents-activity.md +513 -0
- package/docs/gentle-shell-uninstall.md +136 -0
- package/docs/gentle-shell.md +90 -33
- package/docs/install-wizard.md +2040 -0
- package/docs/native-authority-architecture.md +1 -1
- package/docs/permission-modes.md +127 -0
- package/docs/prompt-history.md +1 -1
- package/docs/readme-reference.md +310 -47
- package/docs/render-cache-performance.md +28 -0
- package/docs/session-profile-format.md +39 -0
- package/docs/sound-notifications-proposal.md +191 -0
- package/docs/sound-notifications.md +114 -0
- package/docs/yolo-mode.md +11 -10
- package/extensions/ask-user-choice.ts +4 -0
- package/extensions/ask-user-question.ts +6 -1
- package/extensions/child-context.ts +11 -2
- package/extensions/child-safety.ts +4 -2
- package/extensions/codegraph-tools.ts +18 -5
- package/extensions/gentle-agents.ts +718 -87
- package/extensions/gentle-ai.ts +991 -263
- package/extensions/gentle-herdr-activity.ts +81 -0
- package/extensions/gentle-notifications.ts +104 -0
- package/extensions/gentle-shell.ts +271 -80
- package/extensions/gentle-todo.ts +123 -18
- package/extensions/history/index.ts +11 -6
- package/extensions/pi-pretty.ts +39 -3
- package/extensions/quiet-tools.ts +91 -21
- package/extensions/skill-registry.ts +175 -46
- package/extensions/startup-banner.ts +11 -2
- package/lib/agent-home.ts +10 -1
- package/lib/agent-model-resolution.ts +80 -0
- package/lib/agent-profiles.ts +13 -3
- package/lib/agents-completion-delivery.ts +11 -3
- package/lib/agents-messaging.ts +1 -1
- package/lib/agents-protocol.ts +40 -1
- package/lib/agents-runner.ts +90 -15
- package/lib/auto-permission-answers.ts +133 -0
- package/lib/auto-permission-classifier.ts +459 -0
- package/lib/auto-permission-evidence.ts +88 -0
- package/lib/auto-permission-memory.ts +69 -0
- package/lib/auto-permission-runtime.ts +484 -0
- package/lib/auto-permission-tool-context.ts +61 -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 +83 -10
- package/lib/builtin-codemode-optout.ts +166 -0
- package/lib/card-content-policy.ts +85 -0
- package/lib/child-package-injection.ts +62 -0
- package/lib/codemode-renderer.ts +14 -4
- package/lib/command-palette-catalog.ts +3 -2
- package/lib/context-threshold.ts +26 -0
- package/lib/destructive-command-guard.ts +104 -28
- package/lib/gentle-ai-binary.ts +109 -4
- package/lib/gentle-shell-bundled.ts +535 -0
- package/lib/gentle-shell-launcher.ts +75 -24
- 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/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 +6 -1
- package/lib/nan-provider.ts +33 -11
- package/lib/native-review-cli.ts +194 -73
- 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.ts +1 -1
- 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/permission-mode-policy.ts +59 -0
- package/lib/review-candidate-view-owner.ts +8 -6
- package/lib/review-candidate-view.ts +3 -2
- 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-risk-assessment.ts +77 -51
- package/lib/review-sidebar-state.ts +2 -1
- 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 +22 -7
- package/lib/session-change-capture.ts +5 -4
- package/lib/session-changes.ts +27 -22
- package/lib/session-profile-binding.ts +83 -0
- package/lib/session-profile-freeze.ts +261 -0
- package/lib/session-profile-persistence.ts +187 -0
- package/lib/session-worktree-registry.ts +74 -8
- package/lib/shell-bar.ts +41 -11
- package/lib/shell-changes-view.ts +40 -13
- package/lib/shell-changes.ts +88 -71
- package/lib/shell-prompt.ts +6 -4
- package/lib/shell-sidebar-layout.ts +5 -1
- package/lib/shell-todo.ts +81 -26
- package/lib/shell-usage.ts +4 -3
- package/lib/status-timing-diagnostics.ts +201 -0
- package/lib/telemetry-trigger.ts +3 -1
- package/lib/vim-editor-adapter.ts +27 -5
- package/lib/visual-customization-policy.ts +20 -9
- package/lib/visual-customize-view.ts +171 -20
- package/lib/writer-surfaces.ts +162 -0
- package/lib/yolo-session-policy.ts +198 -121
- package/package.json +5 -5
- package/runtime/child-package-injection.mjs +63 -0
- package/runtime/gentle-ai-binary.mjs +109 -4
- package/runtime/gentle-shell-bundled.mjs +536 -0
- package/runtime/gentle-shell-launcher.mjs +74 -23
- 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 +191 -70
- package/runtime/review-integration-v2.mjs +100 -22
- package/runtime/review-risk-assessment.mjs +77 -51
- 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 +7 -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 +79 -32
- 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/npm-pack-result.mjs +31 -0
- package/scripts/pi-update.mjs +223 -0
- package/scripts/render-cache-performance-worker.mjs +205 -0
- package/scripts/render-cache-performance.mjs +115 -0
- package/scripts/run-test-suite.mjs +22 -8
- package/scripts/test-installer-acceptance.sh +161 -0
- package/scripts/test-packed-runner.mjs +23 -5
- package/scripts/types-baseline.json +2 -92
- package/scripts/verify-package-files.mjs +82 -18
- package/skills/gentle-ai/SKILL.md +12 -11
- package/tests/agent-home.test.ts +56 -3
- package/tests/agent-model-resolution.test.ts +113 -0
- package/tests/agent-profiles.test.ts +12 -0
- package/tests/agent-risk-escalation.test.ts +123 -0
- package/tests/agents-completion-delivery.test.ts +17 -0
- package/tests/agents-protocol.test.ts +57 -0
- package/tests/agents-queries.test.ts +9 -9
- package/tests/agents-runner-process.test.ts +31 -0
- package/tests/agents-runner.test.ts +89 -4
- package/tests/append-system-prompt-route.test.ts +18 -1
- package/tests/auto-mode-human-answers.test.ts +68 -0
- package/tests/auto-mode-invariants.test.ts +276 -0
- package/tests/auto-mode-sdk.test.ts +84 -0
- package/tests/auto-mode.test.ts +49 -0
- package/tests/auto-permission-answers.test.ts +170 -0
- package/tests/auto-permission-classifier.test.ts +634 -0
- package/tests/auto-permission-compound.test.ts +24 -0
- package/tests/auto-permission-evidence.test.ts +88 -0
- package/tests/auto-permission-memory.test.ts +46 -0
- package/tests/auto-permission-public-regressions.test.ts +111 -0
- package/tests/auto-permission-runtime.test.ts +454 -0
- package/tests/autonomous-guard.test.ts +86 -16
- 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 +52 -1
- 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 +34 -2
- package/tests/child-codegraph-bootstrap.test.ts +74 -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 +12 -0
- package/tests/child-web-package.test.ts +224 -0
- package/tests/codegraph-tools.test.ts +35 -0
- package/tests/codemode-rendering.test.ts +107 -2
- package/tests/command-palette.test.ts +5 -5
- 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 -2
- package/tests/devbinary/native-review-parity.devtest.ts +1 -1
- package/tests/devbinary/pi-host-relay.devtest.ts +173 -3
- package/tests/engram-safety.test.ts +316 -0
- package/tests/generic-agent-tools.test.ts +16 -1
- package/tests/gentle-agents.test.ts +1895 -76
- package/tests/gentle-ai-binary-isolation.test.ts +135 -0
- package/tests/gentle-ai-binary.test.ts +19 -2
- 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-installer.test.ts +548 -58
- package/tests/gentle-ai-prompt-model-routing.test.ts +123 -0
- package/tests/gentle-ai-renderer.test.ts +6 -6
- package/tests/gentle-ai.test.ts +999 -31
- package/tests/gentle-herdr-activity.test.ts +100 -0
- package/tests/gentle-notifications.test.ts +427 -0
- package/tests/gentle-shell-bin.test.ts +263 -19
- package/tests/gentle-shell-bundled.test.ts +1036 -0
- package/tests/gentle-shell-launcher.test.ts +251 -25
- 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 +611 -100
- package/tests/gentle-todo.test.ts +317 -14
- package/tests/herdr-activity.test.ts +243 -0
- package/tests/history-agent-home.test.ts +81 -0
- package/tests/history-child-guard.test.ts +70 -0
- package/tests/inprocess-reviewer.test.ts +20 -0
- 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 +2159 -0
- package/tests/installer-server.test.ts +1567 -0
- package/tests/installer-windows-bootstrap.test.ts +2410 -0
- package/tests/jobs-view.test.ts +160 -0
- package/tests/main-channel-upgrade.test.ts +395 -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-package.test.ts +10 -0
- package/tests/nan-provider-runtime.test.ts +156 -0
- package/tests/nan-provider.test.ts +28 -18
- package/tests/native-binary-gate.test.ts +2 -0
- package/tests/native-review-capability-contract.test.ts +7 -1
- package/tests/native-review-cli.test.ts +236 -1
- package/tests/native-review-consent.test.ts +2 -2
- package/tests/native-review-parity-runtime.test.ts +1 -1
- 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 +14 -5
- package/tests/odd-routing-canonical-ratchet.test.ts +53 -12
- package/tests/odd-routing-contract.test.ts +80 -23
- package/tests/odd-runtime-delegation-gate.test.ts +4 -1
- package/tests/orchestrator-budget.test.ts +29 -6
- 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 +6 -5
- 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/package-manifest.test.ts +105 -21
- package/tests/packed-runner-owned-path.test.ts +35 -1
- 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 +4 -12
- package/tests/pi-pretty.test.ts +56 -0
- package/tests/pi-update-launcher.test.ts +87 -0
- package/tests/pi-update.test.ts +186 -0
- package/tests/provider-defect-handoff.test.ts +4 -3
- package/tests/quiet-tool-rendering.test.ts +160 -3
- package/tests/rdd-aware-verification-contract.test.ts +29 -41
- package/tests/rdd-status-line.test.ts +52 -0
- package/tests/render-cache-performance.test.ts +48 -0
- package/tests/review-agent-end-preflight.test.ts +62 -10
- package/tests/review-candidate-view.test.ts +101 -12
- package/tests/review-contract-prompt.test.ts +33 -4
- package/tests/review-controller-native-recovery.test.ts +327 -17
- package/tests/review-controller-native-routing.test.ts +255 -1
- package/tests/review-controller-workspace-root.test.ts +43 -2
- 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 +5 -3
- package/tests/review-host-relay.test.ts +49 -2
- package/tests/review-integration-v2-forward.test.ts +21 -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 +5 -4
- 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-risk-assessment.test.ts +109 -33
- 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-test-fixtures.ts +7 -1
- package/tests/review-transaction.test.ts +18 -1
- package/tests/run-test-suite.test.ts +83 -5
- package/tests/runtime-harness.mjs +42 -15
- package/tests/runtime-metrics-extension.test.ts +1 -1
- package/tests/runtime-metrics-native.test.ts +31 -1
- package/tests/selection-engine.test.ts +65 -5
- 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-profile-binding.test.ts +163 -0
- package/tests/session-profile-freeze.test.ts +394 -0
- package/tests/session-profile-persistence.test.ts +334 -0
- package/tests/session-subject-routing-contract.test.ts +34 -0
- package/tests/session-worktree-registry.test.ts +57 -2
- package/tests/shell-bar.test.ts +40 -0
- package/tests/shell-changes-view.test.ts +85 -34
- package/tests/shell-changes.test.ts +79 -62
- 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 +27 -2
- package/tests/shell-todo.test.ts +132 -11
- package/tests/shell-usage-view.test.ts +1 -1
- package/tests/shell-usage.test.ts +35 -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 +311 -5
- package/tests/startup-banner.test.ts +117 -3
- package/tests/startup-home-repo.test.ts +170 -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 +27 -1
- package/tests/test-strength-contract.test.ts +124 -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.test.ts +47 -21
- package/tests/visual-customization-policy.test.ts +32 -3
- package/tests/visual-customize-view.test.ts +288 -10
- package/tests/writer-edit-surface-scope.test.ts +59 -2
- package/tests/writer-surfaces.test.ts +199 -0
- package/tests/yolo-customize.test.ts +18 -3
- package/tests/yolo-mode-runtime.test.ts +37 -10
- package/tests/yolo-mode.test.ts +38 -5
- package/tests/yolo-session-policy.test.ts +1 -1
- /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
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import test from "node:test";
|
|
6
|
+
import { CHILD_PACKAGE_INJECTION_ENV, encodeChildPackageInjection, parseChildPackageInjection } from "../lib/child-package-injection.ts";
|
|
6
7
|
import {
|
|
7
8
|
MIN_PI_VERSION,
|
|
8
9
|
MIN_SETUP_GENTLE_AI_VERSION,
|
|
@@ -154,6 +155,50 @@ test("parseLauncherArgs recognises the home subcommand as argv[0] and captures t
|
|
|
154
155
|
assert.deepEqual(parsed.passthrough, []);
|
|
155
156
|
});
|
|
156
157
|
|
|
158
|
+
test("parseLauncherArgs recognises the upgrade subcommand as argv[0] and captures its arguments", () => {
|
|
159
|
+
const parsed = parseLauncherArgs(["upgrade", "--channel", "main"]);
|
|
160
|
+
assert.equal(parsed.command, "upgrade");
|
|
161
|
+
assert.deepEqual(parsed.commandArgs, ["--channel", "main"]);
|
|
162
|
+
assert.deepEqual(parsed.passthrough, []);
|
|
163
|
+
assert.equal(parsed.piSubcommand, undefined);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("parseLauncherArgs recognises the self-uninstall subcommand as argv[0] and captures its arguments", () => {
|
|
167
|
+
const parsed = parseLauncherArgs(["self-uninstall", "--dry-run", "--include-shared"]);
|
|
168
|
+
assert.equal(parsed.command, "self-uninstall");
|
|
169
|
+
assert.deepEqual(parsed.commandArgs, ["--dry-run", "--include-shared"]);
|
|
170
|
+
assert.deepEqual(parsed.passthrough, []);
|
|
171
|
+
assert.equal(parsed.piSubcommand, undefined);
|
|
172
|
+
assert.equal(parsed.error, undefined);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("parseLauncherArgs keeps uninstall as pi's own alias for remove", () => {
|
|
176
|
+
const parsed = parseLauncherArgs(["uninstall", "npm:x"]);
|
|
177
|
+
assert.equal(parsed.command, undefined);
|
|
178
|
+
assert.equal(parsed.piSubcommand, "uninstall");
|
|
179
|
+
assert.deepEqual(parsed.passthrough, ["uninstall", "npm:x"]);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("help text documents gentle-shell self-uninstall and its flags", () => {
|
|
183
|
+
const text = helpText();
|
|
184
|
+
assert.ok(text.includes(" gentle-shell self-uninstall [--dry-run] [--yes] [--include-shared]"));
|
|
185
|
+
assert.ok(text.includes(" self-uninstall Remove Gentle Shell: its own data, the isolated home and the gentle-pi"));
|
|
186
|
+
assert.match(text, /'gentle-shell uninstall <source>' is pi's own alias for 'remove'/);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("help text documents gentle-shell upgrade and its channels", () => {
|
|
190
|
+
const text = helpText();
|
|
191
|
+
assert.ok(text.includes(" gentle-shell upgrade [--channel release|main]"));
|
|
192
|
+
assert.ok(text.includes(" upgrade Update Gentle Shell along its channel: the latest release, or the latest"));
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("help text documents the bundled upgrade, its rollback and the pinned Pi update", () => {
|
|
196
|
+
const text = helpText();
|
|
197
|
+
assert.ok(text.includes(" gentle-shell upgrade --rollback"));
|
|
198
|
+
assert.match(text, /bundled install.*release channel only.*--rollback switches back to the previous kept version/s);
|
|
199
|
+
assert.match(text, /In a bundled install Pi ships pinned: 'update' without a package\n\s+runs 'gentle-shell upgrade'/);
|
|
200
|
+
});
|
|
201
|
+
|
|
157
202
|
test("parseLauncherArgs treats home as a plain passthrough token when it is not argv[0]", () => {
|
|
158
203
|
const parsed = parseLauncherArgs(["--isolated", "home"]);
|
|
159
204
|
assert.equal(parsed.command, undefined);
|
|
@@ -310,6 +355,22 @@ test("resolveHome falls back to <homedir>/.pi/agent for --link with no override"
|
|
|
310
355
|
assert.deepEqual(resolved, { mode: "link", dir: join("/home/alan", ".pi", "agent"), source: "flag" });
|
|
311
356
|
});
|
|
312
357
|
|
|
358
|
+
test("--link inside an isolated session resolves the preserved original Pi home (#2015)", () => {
|
|
359
|
+
const nestedEnv = { PI_CODING_AGENT_DIR: "/home/alan/.gentle-shell/agent", GENTLE_SHELL_USER_PI_HOME: "/custom/pi" };
|
|
360
|
+
// Nested: the inherited isolated home must not be re-selected.
|
|
361
|
+
assert.deepEqual(
|
|
362
|
+
resolveHome({ args: args({ link: true }), env: nestedEnv, homedir: "/home/alan", config: undefined }),
|
|
363
|
+
{ mode: "link", dir: "/custom/pi", source: "flag" },
|
|
364
|
+
);
|
|
365
|
+
assert.deepEqual(
|
|
366
|
+
resolveHome({ args: args(), env: nestedEnv, homedir: "/home/alan", config: { mode: "link" } }),
|
|
367
|
+
{ mode: "link", dir: "/custom/pi", source: "config" },
|
|
368
|
+
);
|
|
369
|
+
// Clean terminal: a custom override and the conventional home are unchanged.
|
|
370
|
+
assert.equal(resolveHome({ args: args({ link: true }), env: { PI_CODING_AGENT_DIR: "/custom/pi" }, homedir: "/home/alan", config: undefined }).dir, "/custom/pi");
|
|
371
|
+
assert.equal(resolveHome({ args: args({ link: true }), env: {}, homedir: "/home/alan", config: undefined }).dir, join("/home/alan", ".pi", "agent"));
|
|
372
|
+
});
|
|
373
|
+
|
|
313
374
|
test("resolveHome honours --isolated and reads GENTLE_SHELL_HOME", () => {
|
|
314
375
|
const resolved = resolveHome({
|
|
315
376
|
args: args({ isolated: true }),
|
|
@@ -622,9 +683,10 @@ test("checkPeerVersionPin passes for a matching pin", () => {
|
|
|
622
683
|
assert.deepEqual(result, { ok: true, pinned: ">=0.85.1" });
|
|
623
684
|
});
|
|
624
685
|
|
|
625
|
-
test("Pi
|
|
686
|
+
test("Pi 1.2.0 baseline and host peers follow the open development range policy", () => {
|
|
626
687
|
const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
627
|
-
assert.equal(MIN_PI_VERSION, "
|
|
688
|
+
assert.equal(MIN_PI_VERSION, "1.2.0");
|
|
689
|
+
assert.equal(pkg.peerDependencies["@earendil-works/pi-coding-agent"], ">=1.2.0");
|
|
628
690
|
assert.equal(pkg.engines.node, ">=22.19.0");
|
|
629
691
|
for (const name of ["@earendil-works/pi-ai", "@earendil-works/pi-tui"]) {
|
|
630
692
|
assert.equal(pkg.peerDependencies[name], "*");
|
|
@@ -634,34 +696,38 @@ test("Pi 0.99.1 baseline and host peers follow the open development range policy
|
|
|
634
696
|
// Development ranges are policy specifiers; their floor never drops below
|
|
635
697
|
// the runtime minimum the launcher enforces.
|
|
636
698
|
for (const name of ["@earendil-works/pi-coding-agent", "@earendil-works/pi-ai", "@earendil-works/pi-tui"]) {
|
|
637
|
-
assert.equal(pkg.devDependencies[name], ">=1.
|
|
699
|
+
assert.equal(pkg.devDependencies[name], ">=1.2.0", name);
|
|
638
700
|
assert.equal(checkPiVersion(pkg.devDependencies[name].slice(2)).ok, true, name);
|
|
639
701
|
}
|
|
640
702
|
});
|
|
641
703
|
|
|
642
704
|
test("checkPiVersion accepts a version equal to the minimum", () => {
|
|
643
|
-
assert.deepEqual(checkPiVersion("
|
|
705
|
+
assert.deepEqual(checkPiVersion("1.2.0"), { ok: true, version: "1.2.0" });
|
|
644
706
|
});
|
|
645
707
|
|
|
646
708
|
test("checkPiVersion accepts a version above the minimum", () => {
|
|
647
|
-
|
|
709
|
+
for (const version of ["1.2.1", "1.3.0", "2.0.0"]) {
|
|
710
|
+
assert.deepEqual(checkPiVersion(version), { ok: true, version });
|
|
711
|
+
}
|
|
648
712
|
});
|
|
649
713
|
|
|
650
714
|
test("checkPiVersion accepts a v-prefixed version", () => {
|
|
651
|
-
assert.deepEqual(checkPiVersion("
|
|
715
|
+
assert.deepEqual(checkPiVersion("v1.2.0"), { ok: true, version: "1.2.0" });
|
|
652
716
|
});
|
|
653
717
|
|
|
654
718
|
test("checkPiVersion accepts a prerelease suffix at the minimum", () => {
|
|
655
|
-
assert.deepEqual(checkPiVersion("pi version
|
|
719
|
+
assert.deepEqual(checkPiVersion("pi version 1.2.0-rc.2"), { ok: true, version: "1.2.0" });
|
|
656
720
|
});
|
|
657
721
|
|
|
658
722
|
test("checkPiVersion rejects a version below the minimum and names both versions", () => {
|
|
659
|
-
const result = checkPiVersion("
|
|
723
|
+
const result = checkPiVersion("1.1.0");
|
|
660
724
|
assert.equal(result.ok, false);
|
|
661
725
|
if (result.ok) throw new Error("expected a failing result");
|
|
662
|
-
assert.equal(result.version, "
|
|
663
|
-
assert.match(result.message, /
|
|
664
|
-
assert.match(result.message, /
|
|
726
|
+
assert.equal(result.version, "1.1.0");
|
|
727
|
+
assert.match(result.message, /1\.1\.0/);
|
|
728
|
+
assert.match(result.message, /1\.2\.0/);
|
|
729
|
+
assert.equal(checkPiVersion("1.0.0").ok, false);
|
|
730
|
+
assert.equal(checkPiVersion("0.99.1").ok, false);
|
|
665
731
|
});
|
|
666
732
|
|
|
667
733
|
test("checkPiVersion rejects a prerelease below the minimum", () => {
|
|
@@ -675,7 +741,7 @@ test("checkPiVersion reports unparsable output with the raw text and the minimum
|
|
|
675
741
|
if (result.ok) throw new Error("expected a failing result");
|
|
676
742
|
assert.equal(result.version, undefined);
|
|
677
743
|
assert.match(result.message, /not a version/);
|
|
678
|
-
assert.match(result.message, /
|
|
744
|
+
assert.match(result.message, /1\.2\.0/);
|
|
679
745
|
});
|
|
680
746
|
|
|
681
747
|
test("checkPiVersion accepts a custom minimum", () => {
|
|
@@ -852,7 +918,7 @@ test("findGentlePiDeclaration detects a bare npm:gentle-pi object source entry",
|
|
|
852
918
|
});
|
|
853
919
|
|
|
854
920
|
test("findGentlePiDeclaration recognises a relative path entry whose package.json name is gentle-pi", () => {
|
|
855
|
-
const resolvedDir =
|
|
921
|
+
const resolvedDir = resolve("/agent", "..", "..", "work", "gentle-pi");
|
|
856
922
|
const result = findGentlePiDeclaration('{"packages":["../../work/gentle-pi"]}', {
|
|
857
923
|
agentDir: "/agent",
|
|
858
924
|
readPackageName: readPackageNameStub({ [resolvedDir]: "gentle-pi" }),
|
|
@@ -863,17 +929,17 @@ test("findGentlePiDeclaration recognises a relative path entry whose package.jso
|
|
|
863
929
|
test("findGentlePiDeclaration recognises an absolute path entry whose package.json name is gentle-pi", () => {
|
|
864
930
|
const result = findGentlePiDeclaration('{"packages":["/checkouts/gentle-pi"]}', {
|
|
865
931
|
agentDir: "/agent",
|
|
866
|
-
readPackageName: readPackageNameStub({ "/checkouts/gentle-pi": "gentle-pi" }),
|
|
932
|
+
readPackageName: readPackageNameStub({ [resolve("/checkouts/gentle-pi")]: "gentle-pi" }),
|
|
867
933
|
});
|
|
868
|
-
assert.deepEqual(result, { kind: "path", dir: "/checkouts/gentle-pi" });
|
|
934
|
+
assert.deepEqual(result, { kind: "path", dir: resolve("/checkouts/gentle-pi") });
|
|
869
935
|
});
|
|
870
936
|
|
|
871
937
|
test("findGentlePiDeclaration recognises a path object source entry whose package.json name is gentle-pi", () => {
|
|
872
938
|
const result = findGentlePiDeclaration('{"packages":[{"source":"../gentle-pi","extensions":[]}]}', {
|
|
873
939
|
agentDir: "/agent",
|
|
874
|
-
readPackageName: readPackageNameStub({ [
|
|
940
|
+
readPackageName: readPackageNameStub({ [resolve("/agent", "..", "gentle-pi")]: "gentle-pi" }),
|
|
875
941
|
});
|
|
876
|
-
assert.deepEqual(result, { kind: "path", dir:
|
|
942
|
+
assert.deepEqual(result, { kind: "path", dir: resolve("/agent", "..", "gentle-pi") });
|
|
877
943
|
});
|
|
878
944
|
|
|
879
945
|
test("findGentlePiDeclaration is undefined for a path entry whose package.json name is not gentle-pi", () => {
|
|
@@ -906,9 +972,9 @@ test("findGentlePiDeclaration is undefined when packages lists unrelated entries
|
|
|
906
972
|
test("findGentlePiDeclaration returns the first matching declaration, path or npm, in list order", () => {
|
|
907
973
|
const result = findGentlePiDeclaration('{"packages":["../gentle-pi","npm:gentle-pi"]}', {
|
|
908
974
|
agentDir: "/agent",
|
|
909
|
-
readPackageName: readPackageNameStub({ [
|
|
975
|
+
readPackageName: readPackageNameStub({ [resolve("/agent", "..", "gentle-pi")]: "gentle-pi" }),
|
|
910
976
|
});
|
|
911
|
-
assert.deepEqual(result, { kind: "path", dir:
|
|
977
|
+
assert.deepEqual(result, { kind: "path", dir: resolve("/agent", "..", "gentle-pi") });
|
|
912
978
|
});
|
|
913
979
|
|
|
914
980
|
// --- decideTakeOver -----------------------------------------------------------
|
|
@@ -992,7 +1058,7 @@ test("otherPackageInjections resolves a path entry relative to agentDir", () =>
|
|
|
992
1058
|
agentDir: "/agent",
|
|
993
1059
|
skip: { kind: "npm" },
|
|
994
1060
|
});
|
|
995
|
-
assert.deepEqual(result.paths, [
|
|
1061
|
+
assert.deepEqual(result.paths, [resolve("/agent", "..", "..", "work", "pi-qwen-ambassador")]);
|
|
996
1062
|
});
|
|
997
1063
|
|
|
998
1064
|
test("otherPackageInjections skips a git entry and warns", () => {
|
|
@@ -1018,7 +1084,7 @@ test("otherPackageInjections warns for an object entry with extensions or autolo
|
|
|
1018
1084
|
});
|
|
1019
1085
|
|
|
1020
1086
|
test("otherPackageInjections skips the entry matching a path declaration being taken over", () => {
|
|
1021
|
-
const declaredDir =
|
|
1087
|
+
const declaredDir = resolve("/agent", "..", "..", "work", "gentle-pi");
|
|
1022
1088
|
const result = otherPackageInjections({
|
|
1023
1089
|
settingsText: '{"packages":["npm:some-other","../../work/gentle-pi"]}',
|
|
1024
1090
|
agentDir: "/agent",
|
|
@@ -1033,7 +1099,7 @@ test("otherPackageInjections tolerates undefined or malformed settings text", ()
|
|
|
1033
1099
|
});
|
|
1034
1100
|
|
|
1035
1101
|
test("otherPackageInjections skips every gentle-pi entry, not only the one matching the declaration kind", () => {
|
|
1036
|
-
const declaredDir =
|
|
1102
|
+
const declaredDir = resolve("/agent", "..", "..", "work", "gentle-pi");
|
|
1037
1103
|
const result = otherPackageInjections({
|
|
1038
1104
|
// The declaration being taken over is the path entry, but settings
|
|
1039
1105
|
// also carries a second, unrelated npm:gentle-pi entry: both must be
|
|
@@ -1088,7 +1154,7 @@ test("otherPackageInjections keeps a declared package directory that isDirectory
|
|
|
1088
1154
|
});
|
|
1089
1155
|
|
|
1090
1156
|
test("otherPackageInjections excludes a settings path entry that resolves via realpath to the same physical directory as a realpath'd skip (R4-forced-root-symlink-double-injection)", () => {
|
|
1091
|
-
const linkOtherDir =
|
|
1157
|
+
const linkOtherDir = resolve("/agent", "link-other");
|
|
1092
1158
|
const result = otherPackageInjections({
|
|
1093
1159
|
settingsText: '{"packages":["link-other","npm:some-other"]}',
|
|
1094
1160
|
agentDir: "/agent",
|
|
@@ -1107,7 +1173,7 @@ test("otherPackageInjections keeps comparing raw strings when no realpath resolv
|
|
|
1107
1173
|
agentDir: "/agent",
|
|
1108
1174
|
skip: { kind: "path", dir: "/real/other" },
|
|
1109
1175
|
});
|
|
1110
|
-
assert.deepEqual(result.paths, [
|
|
1176
|
+
assert.deepEqual(result.paths, [resolve("/agent", "link-other"), join("/agent", "npm", "node_modules", "some-other")]);
|
|
1111
1177
|
});
|
|
1112
1178
|
|
|
1113
1179
|
test("otherPackageInjections defaults to including every declared package when isDirectory is not provided (existing callers keep pure string resolution)", () => {
|
|
@@ -1134,6 +1200,7 @@ test("package injection relies on Pi's -e resource discovery in isolated and tak
|
|
|
1134
1200
|
passthrough: [],
|
|
1135
1201
|
baseEnv: {},
|
|
1136
1202
|
homedir: "/home/u",
|
|
1203
|
+
cwd: "/work",
|
|
1137
1204
|
});
|
|
1138
1205
|
assert.deepEqual(built.args, takeOver ? ["--no-extensions", "-e", "/pkg"] : ["-e", "/pkg"]);
|
|
1139
1206
|
}
|
|
@@ -1153,6 +1220,7 @@ test("buildPiInvocation injects the launcher env into baseEnv", () => {
|
|
|
1153
1220
|
passthrough: [],
|
|
1154
1221
|
baseEnv: { PATH: "/usr/bin" },
|
|
1155
1222
|
homedir: "/home/u",
|
|
1223
|
+
cwd: "/work",
|
|
1156
1224
|
});
|
|
1157
1225
|
assert.deepEqual(built.env, { PATH: "/usr/bin", PI_CODING_AGENT_DIR: "/pi/agent", GENTLE_PI_AGENT_HOME: "/pi/agent", GENTLE_SHELL_USER_PI_HOME: join("/home/u", ".pi", "agent") });
|
|
1158
1226
|
});
|
|
@@ -1168,6 +1236,7 @@ test("buildPiInvocation skips injection when there is a declaration and no takeo
|
|
|
1168
1236
|
passthrough: ["--mode", "rpc"],
|
|
1169
1237
|
baseEnv: {},
|
|
1170
1238
|
homedir: "/home/u",
|
|
1239
|
+
cwd: "/work",
|
|
1171
1240
|
});
|
|
1172
1241
|
assert.deepEqual(built.command, "/usr/bin/pi");
|
|
1173
1242
|
assert.deepEqual(built.args, ["--mode", "rpc"]);
|
|
@@ -1184,6 +1253,7 @@ test("buildPiInvocation adds the gentle-pi injection flags when there is no decl
|
|
|
1184
1253
|
passthrough: ["--mode", "rpc"],
|
|
1185
1254
|
baseEnv: {},
|
|
1186
1255
|
homedir: "/home/u",
|
|
1256
|
+
cwd: "/work",
|
|
1187
1257
|
});
|
|
1188
1258
|
assert.deepEqual(built.args, [
|
|
1189
1259
|
"-e",
|
|
@@ -1205,6 +1275,7 @@ test("buildPiInvocation emits only runtime args and passthrough when a pi subcom
|
|
|
1205
1275
|
piSubcommand: "install",
|
|
1206
1276
|
baseEnv: {},
|
|
1207
1277
|
homedir: "/home/u",
|
|
1278
|
+
cwd: "/work",
|
|
1208
1279
|
});
|
|
1209
1280
|
assert.deepEqual(built.args, ["install", "npm:x"]);
|
|
1210
1281
|
assert.equal(built.command, "/usr/bin/pi");
|
|
@@ -1222,6 +1293,7 @@ test("buildPiInvocation still injects the launcher env for a pi subcommand", ()
|
|
|
1222
1293
|
piSubcommand: "list",
|
|
1223
1294
|
baseEnv: { PATH: "/usr/bin" },
|
|
1224
1295
|
homedir: "/home/u",
|
|
1296
|
+
cwd: "/work",
|
|
1225
1297
|
});
|
|
1226
1298
|
assert.deepEqual(built.env, {
|
|
1227
1299
|
PATH: "/usr/bin",
|
|
@@ -1243,6 +1315,7 @@ test("buildPiInvocation in link mode with a pi subcommand is exactly pi <subcomm
|
|
|
1243
1315
|
piSubcommand: "auth",
|
|
1244
1316
|
baseEnv: { PI_CODING_AGENT_DIR: "/pi/agent" },
|
|
1245
1317
|
homedir: "/home/u",
|
|
1318
|
+
cwd: "/work",
|
|
1246
1319
|
});
|
|
1247
1320
|
assert.equal(built.command, "/usr/bin/pi");
|
|
1248
1321
|
assert.deepEqual(built.args, ["auth", "status"]);
|
|
@@ -1263,6 +1336,7 @@ test("buildPiInvocation carries the user's original Pi home without weakening is
|
|
|
1263
1336
|
passthrough: [],
|
|
1264
1337
|
baseEnv,
|
|
1265
1338
|
homedir: "/home/u",
|
|
1339
|
+
cwd: "/work",
|
|
1266
1340
|
}).env;
|
|
1267
1341
|
const conventional = build({});
|
|
1268
1342
|
assert.equal(conventional.PI_CODING_AGENT_DIR, "/gentle-shell/agent");
|
|
@@ -1297,6 +1371,7 @@ test("buildPiInvocation keeps the runtime's own args ahead of the injection and
|
|
|
1297
1371
|
passthrough: [],
|
|
1298
1372
|
baseEnv: {},
|
|
1299
1373
|
homedir: "/home/u",
|
|
1374
|
+
cwd: "/work",
|
|
1300
1375
|
});
|
|
1301
1376
|
assert.equal(built.args[0], "/bundled/cli.js");
|
|
1302
1377
|
assert.equal(built.command, "/usr/bin/node");
|
|
@@ -1313,6 +1388,7 @@ test("buildPiInvocation takes over a conflicting path declaration: --no-extensio
|
|
|
1313
1388
|
passthrough: ["--mode", "rpc"],
|
|
1314
1389
|
baseEnv: {},
|
|
1315
1390
|
homedir: "/home/u",
|
|
1391
|
+
cwd: "/work",
|
|
1316
1392
|
});
|
|
1317
1393
|
assert.deepEqual(built.args, [
|
|
1318
1394
|
"--no-extensions",
|
|
@@ -1336,6 +1412,7 @@ test("buildPiInvocation takes over with --package-root even for a matching npm d
|
|
|
1336
1412
|
passthrough: [],
|
|
1337
1413
|
baseEnv: {},
|
|
1338
1414
|
homedir: "/home/u",
|
|
1415
|
+
cwd: "/work",
|
|
1339
1416
|
});
|
|
1340
1417
|
assert.deepEqual(built.args, [
|
|
1341
1418
|
"--no-extensions",
|
|
@@ -1355,6 +1432,7 @@ test("buildPiInvocation takes over with --package-root even when there is no dec
|
|
|
1355
1432
|
passthrough: [],
|
|
1356
1433
|
baseEnv: {},
|
|
1357
1434
|
homedir: "/home/u",
|
|
1435
|
+
cwd: "/work",
|
|
1358
1436
|
});
|
|
1359
1437
|
assert.deepEqual(built.args, [
|
|
1360
1438
|
"--no-extensions",
|
|
@@ -1377,6 +1455,7 @@ test("buildPiInvocation injects loose extension entries during a takeover, after
|
|
|
1377
1455
|
passthrough: ["--mode", "rpc"],
|
|
1378
1456
|
baseEnv: {},
|
|
1379
1457
|
homedir: "/home/u",
|
|
1458
|
+
cwd: "/work",
|
|
1380
1459
|
});
|
|
1381
1460
|
assert.deepEqual(built.args, [
|
|
1382
1461
|
"--no-extensions",
|
|
@@ -1404,6 +1483,7 @@ test("buildPiInvocation omits loose extension entry flags when the list is empty
|
|
|
1404
1483
|
passthrough: [],
|
|
1405
1484
|
baseEnv: {},
|
|
1406
1485
|
homedir: "/home/u",
|
|
1486
|
+
cwd: "/work",
|
|
1407
1487
|
});
|
|
1408
1488
|
assert.deepEqual(withoutField.args, ["--no-extensions", "-e", "/pkg"]);
|
|
1409
1489
|
|
|
@@ -1418,6 +1498,7 @@ test("buildPiInvocation omits loose extension entry flags when the list is empty
|
|
|
1418
1498
|
passthrough: [],
|
|
1419
1499
|
baseEnv: {},
|
|
1420
1500
|
homedir: "/home/u",
|
|
1501
|
+
cwd: "/work",
|
|
1421
1502
|
});
|
|
1422
1503
|
assert.deepEqual(withEmptyField.args, withoutField.args);
|
|
1423
1504
|
});
|
|
@@ -1439,6 +1520,7 @@ test("buildPiInvocation dedupes loose extension entries against other-package pa
|
|
|
1439
1520
|
passthrough: [],
|
|
1440
1521
|
baseEnv: {},
|
|
1441
1522
|
homedir: "/home/u",
|
|
1523
|
+
cwd: "/work",
|
|
1442
1524
|
});
|
|
1443
1525
|
assert.deepEqual(built.args, [
|
|
1444
1526
|
"--no-extensions",
|
|
@@ -1468,6 +1550,7 @@ test("buildPiInvocation dedupes the launcher's own package root against an other
|
|
|
1468
1550
|
passthrough: [],
|
|
1469
1551
|
baseEnv: {},
|
|
1470
1552
|
homedir: "/home/u",
|
|
1553
|
+
cwd: "/work",
|
|
1471
1554
|
});
|
|
1472
1555
|
const eFlags = built.args.filter((arg, index) => built.args[index - 1] === "-e");
|
|
1473
1556
|
assert.deepEqual(eFlags, [join("/agent", "npm", "node_modules", "some-other"), "/pkg"]);
|
|
@@ -1480,6 +1563,127 @@ test("buildPiInvocation dedupes the launcher's own package root against an other
|
|
|
1480
1563
|
]);
|
|
1481
1564
|
});
|
|
1482
1565
|
|
|
1566
|
+
// --- child package injection signal (#1690) ----------------------------------
|
|
1567
|
+
//
|
|
1568
|
+
// The launcher exports exactly the -e set it computed itself, so the subagent
|
|
1569
|
+
// runner can forward it to delegated children. Passthrough -e flags are not
|
|
1570
|
+
// part of the signal.
|
|
1571
|
+
|
|
1572
|
+
const injectionInput = {
|
|
1573
|
+
runtime: { kind: "path" as const, command: "/usr/bin/pi", args: [] },
|
|
1574
|
+
home: isolatedHomeResolved,
|
|
1575
|
+
packageRoot: "/pkg",
|
|
1576
|
+
otherPackagePaths: [],
|
|
1577
|
+
passthrough: ["-e", "/user/ext.ts", "--mode", "rpc"],
|
|
1578
|
+
homedir: "/home/u",
|
|
1579
|
+
cwd: "/work",
|
|
1580
|
+
};
|
|
1581
|
+
|
|
1582
|
+
test("buildPiInvocation signals the packageRoot injection when there is no declaration", () => {
|
|
1583
|
+
const built = buildPiInvocation({ ...injectionInput, declaration: undefined, takeOver: false, baseEnv: {} });
|
|
1584
|
+
assert.deepEqual(parseChildPackageInjection(built.env), { noExtensions: false, extensionPaths: ["/pkg"] });
|
|
1585
|
+
});
|
|
1586
|
+
|
|
1587
|
+
test("buildPiInvocation signals the exact deduped takeover -e set in order, without passthrough -e flags", () => {
|
|
1588
|
+
const built = buildPiInvocation({
|
|
1589
|
+
...injectionInput,
|
|
1590
|
+
home: linkHome,
|
|
1591
|
+
declaration: { kind: "path", dir: "/other/checkout" },
|
|
1592
|
+
takeOver: true,
|
|
1593
|
+
otherPackagePaths: ["/agent/npm/node_modules/some-other", "/shared/dup.ts", "/pkg"],
|
|
1594
|
+
looseExtensionEntries: ["/shared/dup.ts", "/agent/extensions/a.ts", "/agent/extensions/a.ts"],
|
|
1595
|
+
baseEnv: {},
|
|
1596
|
+
});
|
|
1597
|
+
const launcherPaths = built.args.slice(0, built.args.indexOf("--mode") - 2).filter((_, index, all) => all[index - 1] === "-e");
|
|
1598
|
+
const signal = parseChildPackageInjection(built.env);
|
|
1599
|
+
assert.deepEqual(signal, {
|
|
1600
|
+
noExtensions: true,
|
|
1601
|
+
// /pkg keeps its first (other-package) position, exactly as in argv.
|
|
1602
|
+
extensionPaths: ["/agent/npm/node_modules/some-other", "/shared/dup.ts", "/pkg", "/agent/extensions/a.ts"],
|
|
1603
|
+
});
|
|
1604
|
+
assert.deepEqual(signal?.extensionPaths, launcherPaths);
|
|
1605
|
+
});
|
|
1606
|
+
|
|
1607
|
+
test("buildPiInvocation signals absolute paths resolved against the given cwd when a takeover receives a relative loose entry", () => {
|
|
1608
|
+
const cwd = resolve("/elsewhere", "launch-dir");
|
|
1609
|
+
assert.notEqual(cwd, process.cwd());
|
|
1610
|
+
const built = buildPiInvocation({
|
|
1611
|
+
...injectionInput,
|
|
1612
|
+
cwd,
|
|
1613
|
+
home: linkHome,
|
|
1614
|
+
declaration: { kind: "npm" },
|
|
1615
|
+
takeOver: true,
|
|
1616
|
+
looseExtensionEntries: [join("relative-home", "extensions", "a.ts")],
|
|
1617
|
+
baseEnv: {},
|
|
1618
|
+
});
|
|
1619
|
+
assert.ok(built.args.includes(join("relative-home", "extensions", "a.ts")), "the -e argv itself is unchanged");
|
|
1620
|
+
assert.deepEqual(parseChildPackageInjection(built.env)?.extensionPaths, [resolve(cwd, "relative-home", "extensions", "a.ts"), "/pkg"]);
|
|
1621
|
+
});
|
|
1622
|
+
|
|
1623
|
+
test("buildPiInvocation signals one entry for a file spelled both relative and absolute, while argv keeps both spellings", () => {
|
|
1624
|
+
const cwd = resolve("/elsewhere", "launch-dir");
|
|
1625
|
+
const relative = join("relative-home", "extensions", "a.ts");
|
|
1626
|
+
const absolute = resolve(cwd, relative);
|
|
1627
|
+
const built = buildPiInvocation({
|
|
1628
|
+
...injectionInput,
|
|
1629
|
+
cwd,
|
|
1630
|
+
home: linkHome,
|
|
1631
|
+
declaration: { kind: "npm" },
|
|
1632
|
+
takeOver: true,
|
|
1633
|
+
otherPackagePaths: ["/agent/npm/node_modules/some-other", absolute],
|
|
1634
|
+
looseExtensionEntries: [relative, "/agent/extensions/b.ts"],
|
|
1635
|
+
baseEnv: {},
|
|
1636
|
+
});
|
|
1637
|
+
assert.deepEqual(built.args.slice(0, built.args.indexOf("--mode") - 2), [
|
|
1638
|
+
"--no-extensions",
|
|
1639
|
+
"-e",
|
|
1640
|
+
"/agent/npm/node_modules/some-other",
|
|
1641
|
+
"-e",
|
|
1642
|
+
absolute,
|
|
1643
|
+
"-e",
|
|
1644
|
+
relative,
|
|
1645
|
+
"-e",
|
|
1646
|
+
"/agent/extensions/b.ts",
|
|
1647
|
+
"-e",
|
|
1648
|
+
"/pkg",
|
|
1649
|
+
]);
|
|
1650
|
+
assert.deepEqual(parseChildPackageInjection(built.env)?.extensionPaths, [
|
|
1651
|
+
"/agent/npm/node_modules/some-other",
|
|
1652
|
+
absolute,
|
|
1653
|
+
"/agent/extensions/b.ts",
|
|
1654
|
+
"/pkg",
|
|
1655
|
+
]);
|
|
1656
|
+
});
|
|
1657
|
+
|
|
1658
|
+
test("buildPiInvocation does not signal when settings already declare gentle-pi, and drops an inherited signal", () => {
|
|
1659
|
+
const stale = encodeChildPackageInjection({ noExtensions: false, extensionPaths: ["/outer/pkg"] });
|
|
1660
|
+
for (const baseEnv of [{}, { [CHILD_PACKAGE_INJECTION_ENV]: stale }]) {
|
|
1661
|
+
const built = buildPiInvocation({ ...injectionInput, home: linkHome, declaration: { kind: "npm" }, takeOver: false, baseEnv });
|
|
1662
|
+
assert.equal(Object.hasOwn(built.env, CHILD_PACKAGE_INJECTION_ENV), false);
|
|
1663
|
+
}
|
|
1664
|
+
});
|
|
1665
|
+
|
|
1666
|
+
test("buildPiInvocation does not signal for a pi subcommand, and drops an inherited signal", () => {
|
|
1667
|
+
const stale = encodeChildPackageInjection({ noExtensions: false, extensionPaths: ["/outer/pkg"] });
|
|
1668
|
+
for (const takeOver of [false, true]) {
|
|
1669
|
+
const built = buildPiInvocation({
|
|
1670
|
+
...injectionInput,
|
|
1671
|
+
declaration: undefined,
|
|
1672
|
+
takeOver,
|
|
1673
|
+
passthrough: ["list"],
|
|
1674
|
+
piSubcommand: "list",
|
|
1675
|
+
baseEnv: { [CHILD_PACKAGE_INJECTION_ENV]: stale },
|
|
1676
|
+
});
|
|
1677
|
+
assert.equal(Object.hasOwn(built.env, CHILD_PACKAGE_INJECTION_ENV), false);
|
|
1678
|
+
}
|
|
1679
|
+
});
|
|
1680
|
+
|
|
1681
|
+
test("buildPiInvocation replaces an inherited signal with its own injection", () => {
|
|
1682
|
+
const stale = encodeChildPackageInjection({ noExtensions: true, extensionPaths: ["/outer/pkg"] });
|
|
1683
|
+
const built = buildPiInvocation({ ...injectionInput, declaration: undefined, takeOver: false, baseEnv: { [CHILD_PACKAGE_INJECTION_ENV]: stale } });
|
|
1684
|
+
assert.deepEqual(parseChildPackageInjection(built.env), { noExtensions: false, extensionPaths: ["/pkg"] });
|
|
1685
|
+
});
|
|
1686
|
+
|
|
1483
1687
|
// --- discoverLooseExtensionEntries -------------------------------------------
|
|
1484
1688
|
//
|
|
1485
1689
|
// Pure mirror of pi's own discoverExtensionsInDir (packages/coding-agent/src/
|
|
@@ -1806,3 +2010,25 @@ test("helpText documents pi's own package-management subcommands", () => {
|
|
|
1806
2010
|
assert.match(text, /\bconfig\b/);
|
|
1807
2011
|
assert.match(text, /\bauth\b/);
|
|
1808
2012
|
});
|
|
2013
|
+
|
|
2014
|
+
// T3 B3: the isolated-home bootstrap creates ~/.gentle-shell private (0700) even under
|
|
2015
|
+
// umask 002, so the bundled prefix claim (which refuses group/other-writable folders) accepts it.
|
|
2016
|
+
test("the isolated-home bootstrap creates ~/.gentle-shell as 0700 under umask 002", { skip: process.platform === "win32" ? "POSIX modes" : false }, async () => {
|
|
2017
|
+
const { chmodSync, mkdtempSync, rmSync, statSync, writeFileSync } = await import("node:fs");
|
|
2018
|
+
const { spawnSync } = await import("node:child_process");
|
|
2019
|
+
const { tmpdir } = await import("node:os");
|
|
2020
|
+
const root = mkdtempSync(join(tmpdir(), "gentle-shell-umask-"));
|
|
2021
|
+
try {
|
|
2022
|
+
const piScript = join(root, "fake-pi.mjs");
|
|
2023
|
+
writeFileSync(piScript, "#!/usr/bin/env node\nif (process.argv.includes('--version')) console.log('1.2.0');\n");
|
|
2024
|
+
chmodSync(piScript, 0o755);
|
|
2025
|
+
const binPath = fileURLToPath(new URL("../bin/gentle-shell.mjs", import.meta.url));
|
|
2026
|
+
const env = { HOME: root, USERPROFILE: root, PATH: process.env.PATH ?? "/usr/bin:/bin", GENTLE_SHELL_PI: piScript, GENTLE_SHELL_NO_AUTO_SETUP: "1" };
|
|
2027
|
+
const result = spawnSync("/bin/sh", ["-c", 'umask 002; exec "$0" "$@"', process.execPath, binPath, "hello"], { env, encoding: "utf8", input: "" });
|
|
2028
|
+
assert.equal(result.status, 0, result.stderr);
|
|
2029
|
+
assert.equal(statSync(join(root, ".gentle-shell")).mode & 0o777, 0o700);
|
|
2030
|
+
assert.equal(statSync(join(root, ".gentle-shell", "agent")).mode & 0o777, 0o700);
|
|
2031
|
+
} finally {
|
|
2032
|
+
rmSync(root, { recursive: true, force: true });
|
|
2033
|
+
}
|
|
2034
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, symlinkSync, realpathSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { applyStartupChoice, omitStartupVersion, resolveStartupRelaunch } from "../lib/gentle-shell-startup-flow.ts";
|
|
7
|
+
|
|
8
|
+
test("relaunch resolves the activated version's own Node and preserves arguments without shell quoting", async (t) => {
|
|
9
|
+
const root = realpathSync(mkdtempSync(join(tmpdir(), "startup-relaunch-")));
|
|
10
|
+
t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
11
|
+
const version = join(root, "versions", "4.2.0-1.2.0");
|
|
12
|
+
const pkg = join(version, "node_modules", "gentle-pi");
|
|
13
|
+
mkdirSync(join(pkg, "scripts"), { recursive: true });
|
|
14
|
+
mkdirSync(join(pkg, "bin"));
|
|
15
|
+
writeFileSync(join(version, ".gentle-shell-version"), "4.2.0-1.2.0\n");
|
|
16
|
+
writeFileSync(join(pkg, "bin", "gentle-shell.mjs"), "// verified launcher\n");
|
|
17
|
+
writeFileSync(join(pkg, "scripts", "bundled-install.mjs"), `export function prefixLayout({root}) { return {node: root + '/runtime/new node/bin/node'}; }`);
|
|
18
|
+
if (process.platform === "win32") writeFileSync(join(root, "current"), "4.2.0-1.2.0\n");
|
|
19
|
+
else symlinkSync("versions/4.2.0-1.2.0", join(root, "current"));
|
|
20
|
+
const argv = ["--home", "path with spaces", "--continue", "a;literal"];
|
|
21
|
+
const result = await resolveStartupRelaunch({ root, home: tmpdir(), platform: process.platform, env: {}, argv });
|
|
22
|
+
assert.equal(result.command, root + "/runtime/new node/bin/node");
|
|
23
|
+
assert.deepEqual(result.args, [join(pkg, "bin", "gentle-shell.mjs"), ...argv]);
|
|
24
|
+
assert.equal(result.env.GENTLE_SHELL_SKIP_STARTUP_UPDATE, "1");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
function effects(code = 0) {
|
|
28
|
+
const events: string[] = [];
|
|
29
|
+
return { events, omit: () => { events.push("omit"); }, upgrade: async () => { events.push("upgrade"); return code; },
|
|
30
|
+
relaunch: async () => { events.push("relaunch"); return 17; } };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
test("not now and cancellation never update or persist; cancellation exits 130", async () => {
|
|
34
|
+
const deps = effects();
|
|
35
|
+
assert.equal(await applyStartupChoice("skip", deps), undefined);
|
|
36
|
+
assert.equal(await applyStartupChoice("cancel", deps), 130);
|
|
37
|
+
assert.deepEqual(deps.events, []);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("omit persists only the selected version and launches normally", async () => {
|
|
41
|
+
const deps = effects();
|
|
42
|
+
assert.equal(await applyStartupChoice("omit", deps), undefined);
|
|
43
|
+
assert.deepEqual(deps.events, ["omit"]);
|
|
44
|
+
let stored = '{"home":"link","provisioned":{"a":1},"other":true}';
|
|
45
|
+
omitStartupVersion(() => stored, (text) => { stored = text; }, "4.2.0");
|
|
46
|
+
assert.deepEqual(JSON.parse(stored), { home: "link", provisioned: { a: 1 }, other: true, skippedStartupRelease: "4.2.0" });
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("malformed settings are not overwritten by omit; normal launch remains possible", async () => {
|
|
50
|
+
for (const text of ["{broken", "[]", "null", '"text"']) {
|
|
51
|
+
let writes = 0;
|
|
52
|
+
assert.throws(() => omitStartupVersion(() => text, () => { writes++; }, "4.2.0"));
|
|
53
|
+
assert.equal(writes, 0);
|
|
54
|
+
}
|
|
55
|
+
const deps = effects();
|
|
56
|
+
deps.omit = () => { throw new Error("read-only config"); };
|
|
57
|
+
assert.equal(await applyStartupChoice("omit", deps), undefined);
|
|
58
|
+
assert.deepEqual(deps.events, []);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("accepted update reopens active Shell only after upgrade success and preserves exit status", async () => {
|
|
62
|
+
const deps = effects();
|
|
63
|
+
assert.equal(await applyStartupChoice("update", deps), 17);
|
|
64
|
+
assert.deepEqual(deps.events, ["upgrade", "relaunch"]);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("failed upgrade keeps ordinary launch; failed relaunch never falls through to old Shell", async () => {
|
|
68
|
+
const deps = effects(1);
|
|
69
|
+
assert.equal(await applyStartupChoice("update", deps), undefined);
|
|
70
|
+
assert.deepEqual(deps.events, ["upgrade"]);
|
|
71
|
+
const successful = effects();
|
|
72
|
+
successful.relaunch = async () => { throw new Error("new runtime failed"); };
|
|
73
|
+
await assert.rejects(applyStartupChoice("update", successful), /new runtime failed/);
|
|
74
|
+
assert.deepEqual(successful.events, ["upgrade"]);
|
|
75
|
+
});
|