gentle-pi 4.0.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 +122 -8
- 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 +703 -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/bundled-install.md +397 -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 +81 -31
- package/docs/install-wizard.md +2040 -0
- package/docs/native-authority-architecture.md +1 -1
- package/docs/permission-modes.md +82 -0
- package/docs/prompt-history.md +1 -1
- package/docs/readme-reference.md +267 -40
- 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/yolo-mode.md +9 -9
- 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 +688 -88
- package/extensions/gentle-ai.ts +886 -238
- package/extensions/gentle-herdr-activity.ts +81 -0
- package/extensions/gentle-notifications.ts +104 -0
- package/extensions/gentle-shell.ts +265 -69
- package/extensions/gentle-todo.ts +108 -13
- 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-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 +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/gentle-ai-binary.ts +109 -4
- package/lib/gentle-shell-bundled.ts +517 -0
- package/lib/gentle-shell-launcher.ts +74 -23
- 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/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 +18 -6
- 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 +266 -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 +86 -71
- package/lib/shell-sidebar-layout.ts +5 -1
- package/lib/shell-todo.ts +62 -19
- 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 +7 -3
- 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 +1 -1
- 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 +73 -22
- 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/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/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/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 +288 -0
- package/tests/generic-agent-tools.test.ts +16 -1
- package/tests/gentle-agents.test.ts +1722 -59
- 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 +249 -5
- package/tests/gentle-shell-bundled.test.ts +1002 -0
- package/tests/gentle-shell-launcher.test.ts +234 -13
- 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 +541 -96
- package/tests/gentle-todo.test.ts +253 -2
- 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 +2158 -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 +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-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/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 +101 -17
- 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/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/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 +30 -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-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 +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 +61 -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 +120 -2
- 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 +7 -7
- 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 +32 -4
- 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 }),
|
|
@@ -852,7 +913,7 @@ test("findGentlePiDeclaration detects a bare npm:gentle-pi object source entry",
|
|
|
852
913
|
});
|
|
853
914
|
|
|
854
915
|
test("findGentlePiDeclaration recognises a relative path entry whose package.json name is gentle-pi", () => {
|
|
855
|
-
const resolvedDir =
|
|
916
|
+
const resolvedDir = resolve("/agent", "..", "..", "work", "gentle-pi");
|
|
856
917
|
const result = findGentlePiDeclaration('{"packages":["../../work/gentle-pi"]}', {
|
|
857
918
|
agentDir: "/agent",
|
|
858
919
|
readPackageName: readPackageNameStub({ [resolvedDir]: "gentle-pi" }),
|
|
@@ -863,17 +924,17 @@ test("findGentlePiDeclaration recognises a relative path entry whose package.jso
|
|
|
863
924
|
test("findGentlePiDeclaration recognises an absolute path entry whose package.json name is gentle-pi", () => {
|
|
864
925
|
const result = findGentlePiDeclaration('{"packages":["/checkouts/gentle-pi"]}', {
|
|
865
926
|
agentDir: "/agent",
|
|
866
|
-
readPackageName: readPackageNameStub({ "/checkouts/gentle-pi": "gentle-pi" }),
|
|
927
|
+
readPackageName: readPackageNameStub({ [resolve("/checkouts/gentle-pi")]: "gentle-pi" }),
|
|
867
928
|
});
|
|
868
|
-
assert.deepEqual(result, { kind: "path", dir: "/checkouts/gentle-pi" });
|
|
929
|
+
assert.deepEqual(result, { kind: "path", dir: resolve("/checkouts/gentle-pi") });
|
|
869
930
|
});
|
|
870
931
|
|
|
871
932
|
test("findGentlePiDeclaration recognises a path object source entry whose package.json name is gentle-pi", () => {
|
|
872
933
|
const result = findGentlePiDeclaration('{"packages":[{"source":"../gentle-pi","extensions":[]}]}', {
|
|
873
934
|
agentDir: "/agent",
|
|
874
|
-
readPackageName: readPackageNameStub({ [
|
|
935
|
+
readPackageName: readPackageNameStub({ [resolve("/agent", "..", "gentle-pi")]: "gentle-pi" }),
|
|
875
936
|
});
|
|
876
|
-
assert.deepEqual(result, { kind: "path", dir:
|
|
937
|
+
assert.deepEqual(result, { kind: "path", dir: resolve("/agent", "..", "gentle-pi") });
|
|
877
938
|
});
|
|
878
939
|
|
|
879
940
|
test("findGentlePiDeclaration is undefined for a path entry whose package.json name is not gentle-pi", () => {
|
|
@@ -906,9 +967,9 @@ test("findGentlePiDeclaration is undefined when packages lists unrelated entries
|
|
|
906
967
|
test("findGentlePiDeclaration returns the first matching declaration, path or npm, in list order", () => {
|
|
907
968
|
const result = findGentlePiDeclaration('{"packages":["../gentle-pi","npm:gentle-pi"]}', {
|
|
908
969
|
agentDir: "/agent",
|
|
909
|
-
readPackageName: readPackageNameStub({ [
|
|
970
|
+
readPackageName: readPackageNameStub({ [resolve("/agent", "..", "gentle-pi")]: "gentle-pi" }),
|
|
910
971
|
});
|
|
911
|
-
assert.deepEqual(result, { kind: "path", dir:
|
|
972
|
+
assert.deepEqual(result, { kind: "path", dir: resolve("/agent", "..", "gentle-pi") });
|
|
912
973
|
});
|
|
913
974
|
|
|
914
975
|
// --- decideTakeOver -----------------------------------------------------------
|
|
@@ -992,7 +1053,7 @@ test("otherPackageInjections resolves a path entry relative to agentDir", () =>
|
|
|
992
1053
|
agentDir: "/agent",
|
|
993
1054
|
skip: { kind: "npm" },
|
|
994
1055
|
});
|
|
995
|
-
assert.deepEqual(result.paths, [
|
|
1056
|
+
assert.deepEqual(result.paths, [resolve("/agent", "..", "..", "work", "pi-qwen-ambassador")]);
|
|
996
1057
|
});
|
|
997
1058
|
|
|
998
1059
|
test("otherPackageInjections skips a git entry and warns", () => {
|
|
@@ -1018,7 +1079,7 @@ test("otherPackageInjections warns for an object entry with extensions or autolo
|
|
|
1018
1079
|
});
|
|
1019
1080
|
|
|
1020
1081
|
test("otherPackageInjections skips the entry matching a path declaration being taken over", () => {
|
|
1021
|
-
const declaredDir =
|
|
1082
|
+
const declaredDir = resolve("/agent", "..", "..", "work", "gentle-pi");
|
|
1022
1083
|
const result = otherPackageInjections({
|
|
1023
1084
|
settingsText: '{"packages":["npm:some-other","../../work/gentle-pi"]}',
|
|
1024
1085
|
agentDir: "/agent",
|
|
@@ -1033,7 +1094,7 @@ test("otherPackageInjections tolerates undefined or malformed settings text", ()
|
|
|
1033
1094
|
});
|
|
1034
1095
|
|
|
1035
1096
|
test("otherPackageInjections skips every gentle-pi entry, not only the one matching the declaration kind", () => {
|
|
1036
|
-
const declaredDir =
|
|
1097
|
+
const declaredDir = resolve("/agent", "..", "..", "work", "gentle-pi");
|
|
1037
1098
|
const result = otherPackageInjections({
|
|
1038
1099
|
// The declaration being taken over is the path entry, but settings
|
|
1039
1100
|
// also carries a second, unrelated npm:gentle-pi entry: both must be
|
|
@@ -1088,7 +1149,7 @@ test("otherPackageInjections keeps a declared package directory that isDirectory
|
|
|
1088
1149
|
});
|
|
1089
1150
|
|
|
1090
1151
|
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 =
|
|
1152
|
+
const linkOtherDir = resolve("/agent", "link-other");
|
|
1092
1153
|
const result = otherPackageInjections({
|
|
1093
1154
|
settingsText: '{"packages":["link-other","npm:some-other"]}',
|
|
1094
1155
|
agentDir: "/agent",
|
|
@@ -1107,7 +1168,7 @@ test("otherPackageInjections keeps comparing raw strings when no realpath resolv
|
|
|
1107
1168
|
agentDir: "/agent",
|
|
1108
1169
|
skip: { kind: "path", dir: "/real/other" },
|
|
1109
1170
|
});
|
|
1110
|
-
assert.deepEqual(result.paths, [
|
|
1171
|
+
assert.deepEqual(result.paths, [resolve("/agent", "link-other"), join("/agent", "npm", "node_modules", "some-other")]);
|
|
1111
1172
|
});
|
|
1112
1173
|
|
|
1113
1174
|
test("otherPackageInjections defaults to including every declared package when isDirectory is not provided (existing callers keep pure string resolution)", () => {
|
|
@@ -1134,6 +1195,7 @@ test("package injection relies on Pi's -e resource discovery in isolated and tak
|
|
|
1134
1195
|
passthrough: [],
|
|
1135
1196
|
baseEnv: {},
|
|
1136
1197
|
homedir: "/home/u",
|
|
1198
|
+
cwd: "/work",
|
|
1137
1199
|
});
|
|
1138
1200
|
assert.deepEqual(built.args, takeOver ? ["--no-extensions", "-e", "/pkg"] : ["-e", "/pkg"]);
|
|
1139
1201
|
}
|
|
@@ -1153,6 +1215,7 @@ test("buildPiInvocation injects the launcher env into baseEnv", () => {
|
|
|
1153
1215
|
passthrough: [],
|
|
1154
1216
|
baseEnv: { PATH: "/usr/bin" },
|
|
1155
1217
|
homedir: "/home/u",
|
|
1218
|
+
cwd: "/work",
|
|
1156
1219
|
});
|
|
1157
1220
|
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
1221
|
});
|
|
@@ -1168,6 +1231,7 @@ test("buildPiInvocation skips injection when there is a declaration and no takeo
|
|
|
1168
1231
|
passthrough: ["--mode", "rpc"],
|
|
1169
1232
|
baseEnv: {},
|
|
1170
1233
|
homedir: "/home/u",
|
|
1234
|
+
cwd: "/work",
|
|
1171
1235
|
});
|
|
1172
1236
|
assert.deepEqual(built.command, "/usr/bin/pi");
|
|
1173
1237
|
assert.deepEqual(built.args, ["--mode", "rpc"]);
|
|
@@ -1184,6 +1248,7 @@ test("buildPiInvocation adds the gentle-pi injection flags when there is no decl
|
|
|
1184
1248
|
passthrough: ["--mode", "rpc"],
|
|
1185
1249
|
baseEnv: {},
|
|
1186
1250
|
homedir: "/home/u",
|
|
1251
|
+
cwd: "/work",
|
|
1187
1252
|
});
|
|
1188
1253
|
assert.deepEqual(built.args, [
|
|
1189
1254
|
"-e",
|
|
@@ -1205,6 +1270,7 @@ test("buildPiInvocation emits only runtime args and passthrough when a pi subcom
|
|
|
1205
1270
|
piSubcommand: "install",
|
|
1206
1271
|
baseEnv: {},
|
|
1207
1272
|
homedir: "/home/u",
|
|
1273
|
+
cwd: "/work",
|
|
1208
1274
|
});
|
|
1209
1275
|
assert.deepEqual(built.args, ["install", "npm:x"]);
|
|
1210
1276
|
assert.equal(built.command, "/usr/bin/pi");
|
|
@@ -1222,6 +1288,7 @@ test("buildPiInvocation still injects the launcher env for a pi subcommand", ()
|
|
|
1222
1288
|
piSubcommand: "list",
|
|
1223
1289
|
baseEnv: { PATH: "/usr/bin" },
|
|
1224
1290
|
homedir: "/home/u",
|
|
1291
|
+
cwd: "/work",
|
|
1225
1292
|
});
|
|
1226
1293
|
assert.deepEqual(built.env, {
|
|
1227
1294
|
PATH: "/usr/bin",
|
|
@@ -1243,6 +1310,7 @@ test("buildPiInvocation in link mode with a pi subcommand is exactly pi <subcomm
|
|
|
1243
1310
|
piSubcommand: "auth",
|
|
1244
1311
|
baseEnv: { PI_CODING_AGENT_DIR: "/pi/agent" },
|
|
1245
1312
|
homedir: "/home/u",
|
|
1313
|
+
cwd: "/work",
|
|
1246
1314
|
});
|
|
1247
1315
|
assert.equal(built.command, "/usr/bin/pi");
|
|
1248
1316
|
assert.deepEqual(built.args, ["auth", "status"]);
|
|
@@ -1263,6 +1331,7 @@ test("buildPiInvocation carries the user's original Pi home without weakening is
|
|
|
1263
1331
|
passthrough: [],
|
|
1264
1332
|
baseEnv,
|
|
1265
1333
|
homedir: "/home/u",
|
|
1334
|
+
cwd: "/work",
|
|
1266
1335
|
}).env;
|
|
1267
1336
|
const conventional = build({});
|
|
1268
1337
|
assert.equal(conventional.PI_CODING_AGENT_DIR, "/gentle-shell/agent");
|
|
@@ -1297,6 +1366,7 @@ test("buildPiInvocation keeps the runtime's own args ahead of the injection and
|
|
|
1297
1366
|
passthrough: [],
|
|
1298
1367
|
baseEnv: {},
|
|
1299
1368
|
homedir: "/home/u",
|
|
1369
|
+
cwd: "/work",
|
|
1300
1370
|
});
|
|
1301
1371
|
assert.equal(built.args[0], "/bundled/cli.js");
|
|
1302
1372
|
assert.equal(built.command, "/usr/bin/node");
|
|
@@ -1313,6 +1383,7 @@ test("buildPiInvocation takes over a conflicting path declaration: --no-extensio
|
|
|
1313
1383
|
passthrough: ["--mode", "rpc"],
|
|
1314
1384
|
baseEnv: {},
|
|
1315
1385
|
homedir: "/home/u",
|
|
1386
|
+
cwd: "/work",
|
|
1316
1387
|
});
|
|
1317
1388
|
assert.deepEqual(built.args, [
|
|
1318
1389
|
"--no-extensions",
|
|
@@ -1336,6 +1407,7 @@ test("buildPiInvocation takes over with --package-root even for a matching npm d
|
|
|
1336
1407
|
passthrough: [],
|
|
1337
1408
|
baseEnv: {},
|
|
1338
1409
|
homedir: "/home/u",
|
|
1410
|
+
cwd: "/work",
|
|
1339
1411
|
});
|
|
1340
1412
|
assert.deepEqual(built.args, [
|
|
1341
1413
|
"--no-extensions",
|
|
@@ -1355,6 +1427,7 @@ test("buildPiInvocation takes over with --package-root even when there is no dec
|
|
|
1355
1427
|
passthrough: [],
|
|
1356
1428
|
baseEnv: {},
|
|
1357
1429
|
homedir: "/home/u",
|
|
1430
|
+
cwd: "/work",
|
|
1358
1431
|
});
|
|
1359
1432
|
assert.deepEqual(built.args, [
|
|
1360
1433
|
"--no-extensions",
|
|
@@ -1377,6 +1450,7 @@ test("buildPiInvocation injects loose extension entries during a takeover, after
|
|
|
1377
1450
|
passthrough: ["--mode", "rpc"],
|
|
1378
1451
|
baseEnv: {},
|
|
1379
1452
|
homedir: "/home/u",
|
|
1453
|
+
cwd: "/work",
|
|
1380
1454
|
});
|
|
1381
1455
|
assert.deepEqual(built.args, [
|
|
1382
1456
|
"--no-extensions",
|
|
@@ -1404,6 +1478,7 @@ test("buildPiInvocation omits loose extension entry flags when the list is empty
|
|
|
1404
1478
|
passthrough: [],
|
|
1405
1479
|
baseEnv: {},
|
|
1406
1480
|
homedir: "/home/u",
|
|
1481
|
+
cwd: "/work",
|
|
1407
1482
|
});
|
|
1408
1483
|
assert.deepEqual(withoutField.args, ["--no-extensions", "-e", "/pkg"]);
|
|
1409
1484
|
|
|
@@ -1418,6 +1493,7 @@ test("buildPiInvocation omits loose extension entry flags when the list is empty
|
|
|
1418
1493
|
passthrough: [],
|
|
1419
1494
|
baseEnv: {},
|
|
1420
1495
|
homedir: "/home/u",
|
|
1496
|
+
cwd: "/work",
|
|
1421
1497
|
});
|
|
1422
1498
|
assert.deepEqual(withEmptyField.args, withoutField.args);
|
|
1423
1499
|
});
|
|
@@ -1439,6 +1515,7 @@ test("buildPiInvocation dedupes loose extension entries against other-package pa
|
|
|
1439
1515
|
passthrough: [],
|
|
1440
1516
|
baseEnv: {},
|
|
1441
1517
|
homedir: "/home/u",
|
|
1518
|
+
cwd: "/work",
|
|
1442
1519
|
});
|
|
1443
1520
|
assert.deepEqual(built.args, [
|
|
1444
1521
|
"--no-extensions",
|
|
@@ -1468,6 +1545,7 @@ test("buildPiInvocation dedupes the launcher's own package root against an other
|
|
|
1468
1545
|
passthrough: [],
|
|
1469
1546
|
baseEnv: {},
|
|
1470
1547
|
homedir: "/home/u",
|
|
1548
|
+
cwd: "/work",
|
|
1471
1549
|
});
|
|
1472
1550
|
const eFlags = built.args.filter((arg, index) => built.args[index - 1] === "-e");
|
|
1473
1551
|
assert.deepEqual(eFlags, [join("/agent", "npm", "node_modules", "some-other"), "/pkg"]);
|
|
@@ -1480,6 +1558,127 @@ test("buildPiInvocation dedupes the launcher's own package root against an other
|
|
|
1480
1558
|
]);
|
|
1481
1559
|
});
|
|
1482
1560
|
|
|
1561
|
+
// --- child package injection signal (#1690) ----------------------------------
|
|
1562
|
+
//
|
|
1563
|
+
// The launcher exports exactly the -e set it computed itself, so the subagent
|
|
1564
|
+
// runner can forward it to delegated children. Passthrough -e flags are not
|
|
1565
|
+
// part of the signal.
|
|
1566
|
+
|
|
1567
|
+
const injectionInput = {
|
|
1568
|
+
runtime: { kind: "path" as const, command: "/usr/bin/pi", args: [] },
|
|
1569
|
+
home: isolatedHomeResolved,
|
|
1570
|
+
packageRoot: "/pkg",
|
|
1571
|
+
otherPackagePaths: [],
|
|
1572
|
+
passthrough: ["-e", "/user/ext.ts", "--mode", "rpc"],
|
|
1573
|
+
homedir: "/home/u",
|
|
1574
|
+
cwd: "/work",
|
|
1575
|
+
};
|
|
1576
|
+
|
|
1577
|
+
test("buildPiInvocation signals the packageRoot injection when there is no declaration", () => {
|
|
1578
|
+
const built = buildPiInvocation({ ...injectionInput, declaration: undefined, takeOver: false, baseEnv: {} });
|
|
1579
|
+
assert.deepEqual(parseChildPackageInjection(built.env), { noExtensions: false, extensionPaths: ["/pkg"] });
|
|
1580
|
+
});
|
|
1581
|
+
|
|
1582
|
+
test("buildPiInvocation signals the exact deduped takeover -e set in order, without passthrough -e flags", () => {
|
|
1583
|
+
const built = buildPiInvocation({
|
|
1584
|
+
...injectionInput,
|
|
1585
|
+
home: linkHome,
|
|
1586
|
+
declaration: { kind: "path", dir: "/other/checkout" },
|
|
1587
|
+
takeOver: true,
|
|
1588
|
+
otherPackagePaths: ["/agent/npm/node_modules/some-other", "/shared/dup.ts", "/pkg"],
|
|
1589
|
+
looseExtensionEntries: ["/shared/dup.ts", "/agent/extensions/a.ts", "/agent/extensions/a.ts"],
|
|
1590
|
+
baseEnv: {},
|
|
1591
|
+
});
|
|
1592
|
+
const launcherPaths = built.args.slice(0, built.args.indexOf("--mode") - 2).filter((_, index, all) => all[index - 1] === "-e");
|
|
1593
|
+
const signal = parseChildPackageInjection(built.env);
|
|
1594
|
+
assert.deepEqual(signal, {
|
|
1595
|
+
noExtensions: true,
|
|
1596
|
+
// /pkg keeps its first (other-package) position, exactly as in argv.
|
|
1597
|
+
extensionPaths: ["/agent/npm/node_modules/some-other", "/shared/dup.ts", "/pkg", "/agent/extensions/a.ts"],
|
|
1598
|
+
});
|
|
1599
|
+
assert.deepEqual(signal?.extensionPaths, launcherPaths);
|
|
1600
|
+
});
|
|
1601
|
+
|
|
1602
|
+
test("buildPiInvocation signals absolute paths resolved against the given cwd when a takeover receives a relative loose entry", () => {
|
|
1603
|
+
const cwd = resolve("/elsewhere", "launch-dir");
|
|
1604
|
+
assert.notEqual(cwd, process.cwd());
|
|
1605
|
+
const built = buildPiInvocation({
|
|
1606
|
+
...injectionInput,
|
|
1607
|
+
cwd,
|
|
1608
|
+
home: linkHome,
|
|
1609
|
+
declaration: { kind: "npm" },
|
|
1610
|
+
takeOver: true,
|
|
1611
|
+
looseExtensionEntries: [join("relative-home", "extensions", "a.ts")],
|
|
1612
|
+
baseEnv: {},
|
|
1613
|
+
});
|
|
1614
|
+
assert.ok(built.args.includes(join("relative-home", "extensions", "a.ts")), "the -e argv itself is unchanged");
|
|
1615
|
+
assert.deepEqual(parseChildPackageInjection(built.env)?.extensionPaths, [resolve(cwd, "relative-home", "extensions", "a.ts"), "/pkg"]);
|
|
1616
|
+
});
|
|
1617
|
+
|
|
1618
|
+
test("buildPiInvocation signals one entry for a file spelled both relative and absolute, while argv keeps both spellings", () => {
|
|
1619
|
+
const cwd = resolve("/elsewhere", "launch-dir");
|
|
1620
|
+
const relative = join("relative-home", "extensions", "a.ts");
|
|
1621
|
+
const absolute = resolve(cwd, relative);
|
|
1622
|
+
const built = buildPiInvocation({
|
|
1623
|
+
...injectionInput,
|
|
1624
|
+
cwd,
|
|
1625
|
+
home: linkHome,
|
|
1626
|
+
declaration: { kind: "npm" },
|
|
1627
|
+
takeOver: true,
|
|
1628
|
+
otherPackagePaths: ["/agent/npm/node_modules/some-other", absolute],
|
|
1629
|
+
looseExtensionEntries: [relative, "/agent/extensions/b.ts"],
|
|
1630
|
+
baseEnv: {},
|
|
1631
|
+
});
|
|
1632
|
+
assert.deepEqual(built.args.slice(0, built.args.indexOf("--mode") - 2), [
|
|
1633
|
+
"--no-extensions",
|
|
1634
|
+
"-e",
|
|
1635
|
+
"/agent/npm/node_modules/some-other",
|
|
1636
|
+
"-e",
|
|
1637
|
+
absolute,
|
|
1638
|
+
"-e",
|
|
1639
|
+
relative,
|
|
1640
|
+
"-e",
|
|
1641
|
+
"/agent/extensions/b.ts",
|
|
1642
|
+
"-e",
|
|
1643
|
+
"/pkg",
|
|
1644
|
+
]);
|
|
1645
|
+
assert.deepEqual(parseChildPackageInjection(built.env)?.extensionPaths, [
|
|
1646
|
+
"/agent/npm/node_modules/some-other",
|
|
1647
|
+
absolute,
|
|
1648
|
+
"/agent/extensions/b.ts",
|
|
1649
|
+
"/pkg",
|
|
1650
|
+
]);
|
|
1651
|
+
});
|
|
1652
|
+
|
|
1653
|
+
test("buildPiInvocation does not signal when settings already declare gentle-pi, and drops an inherited signal", () => {
|
|
1654
|
+
const stale = encodeChildPackageInjection({ noExtensions: false, extensionPaths: ["/outer/pkg"] });
|
|
1655
|
+
for (const baseEnv of [{}, { [CHILD_PACKAGE_INJECTION_ENV]: stale }]) {
|
|
1656
|
+
const built = buildPiInvocation({ ...injectionInput, home: linkHome, declaration: { kind: "npm" }, takeOver: false, baseEnv });
|
|
1657
|
+
assert.equal(Object.hasOwn(built.env, CHILD_PACKAGE_INJECTION_ENV), false);
|
|
1658
|
+
}
|
|
1659
|
+
});
|
|
1660
|
+
|
|
1661
|
+
test("buildPiInvocation does not signal for a pi subcommand, and drops an inherited signal", () => {
|
|
1662
|
+
const stale = encodeChildPackageInjection({ noExtensions: false, extensionPaths: ["/outer/pkg"] });
|
|
1663
|
+
for (const takeOver of [false, true]) {
|
|
1664
|
+
const built = buildPiInvocation({
|
|
1665
|
+
...injectionInput,
|
|
1666
|
+
declaration: undefined,
|
|
1667
|
+
takeOver,
|
|
1668
|
+
passthrough: ["list"],
|
|
1669
|
+
piSubcommand: "list",
|
|
1670
|
+
baseEnv: { [CHILD_PACKAGE_INJECTION_ENV]: stale },
|
|
1671
|
+
});
|
|
1672
|
+
assert.equal(Object.hasOwn(built.env, CHILD_PACKAGE_INJECTION_ENV), false);
|
|
1673
|
+
}
|
|
1674
|
+
});
|
|
1675
|
+
|
|
1676
|
+
test("buildPiInvocation replaces an inherited signal with its own injection", () => {
|
|
1677
|
+
const stale = encodeChildPackageInjection({ noExtensions: true, extensionPaths: ["/outer/pkg"] });
|
|
1678
|
+
const built = buildPiInvocation({ ...injectionInput, declaration: undefined, takeOver: false, baseEnv: { [CHILD_PACKAGE_INJECTION_ENV]: stale } });
|
|
1679
|
+
assert.deepEqual(parseChildPackageInjection(built.env), { noExtensions: false, extensionPaths: ["/pkg"] });
|
|
1680
|
+
});
|
|
1681
|
+
|
|
1483
1682
|
// --- discoverLooseExtensionEntries -------------------------------------------
|
|
1484
1683
|
//
|
|
1485
1684
|
// Pure mirror of pi's own discoverExtensionsInDir (packages/coding-agent/src/
|
|
@@ -1806,3 +2005,25 @@ test("helpText documents pi's own package-management subcommands", () => {
|
|
|
1806
2005
|
assert.match(text, /\bconfig\b/);
|
|
1807
2006
|
assert.match(text, /\bauth\b/);
|
|
1808
2007
|
});
|
|
2008
|
+
|
|
2009
|
+
// T3 B3: the isolated-home bootstrap creates ~/.gentle-shell private (0700) even under
|
|
2010
|
+
// umask 002, so the bundled prefix claim (which refuses group/other-writable folders) accepts it.
|
|
2011
|
+
test("the isolated-home bootstrap creates ~/.gentle-shell as 0700 under umask 002", { skip: process.platform === "win32" ? "POSIX modes" : false }, async () => {
|
|
2012
|
+
const { chmodSync, mkdtempSync, rmSync, statSync, writeFileSync } = await import("node:fs");
|
|
2013
|
+
const { spawnSync } = await import("node:child_process");
|
|
2014
|
+
const { tmpdir } = await import("node:os");
|
|
2015
|
+
const root = mkdtempSync(join(tmpdir(), "gentle-shell-umask-"));
|
|
2016
|
+
try {
|
|
2017
|
+
const piScript = join(root, "fake-pi.mjs");
|
|
2018
|
+
writeFileSync(piScript, "#!/usr/bin/env node\nif (process.argv.includes('--version')) console.log('0.99.1');\n");
|
|
2019
|
+
chmodSync(piScript, 0o755);
|
|
2020
|
+
const binPath = fileURLToPath(new URL("../bin/gentle-shell.mjs", import.meta.url));
|
|
2021
|
+
const env = { HOME: root, USERPROFILE: root, PATH: process.env.PATH ?? "/usr/bin:/bin", GENTLE_SHELL_PI: piScript, GENTLE_SHELL_NO_AUTO_SETUP: "1" };
|
|
2022
|
+
const result = spawnSync("/bin/sh", ["-c", 'umask 002; exec "$0" "$@"', process.execPath, binPath, "hello"], { env, encoding: "utf8", input: "" });
|
|
2023
|
+
assert.equal(result.status, 0, result.stderr);
|
|
2024
|
+
assert.equal(statSync(join(root, ".gentle-shell")).mode & 0o777, 0o700);
|
|
2025
|
+
assert.equal(statSync(join(root, ".gentle-shell", "agent")).mode & 0o777, 0o700);
|
|
2026
|
+
} finally {
|
|
2027
|
+
rmSync(root, { recursive: true, force: true });
|
|
2028
|
+
}
|
|
2029
|
+
});
|
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { test } from "node:test";
|
|
3
|
+
import { parseLauncherArgs } from "../lib/gentle-shell-launcher.ts";
|
|
4
|
+
import { findStartupUpdate, shouldCheckStartupUpdate } from "../lib/gentle-shell-startup-update.ts";
|
|
5
|
+
|
|
6
|
+
const eligible = {
|
|
7
|
+
args: parseLauncherArgs([]), env: {}, bundled: true, inputTTY: true, outputTTY: true,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
test("ordinary bundled interactive launch and resume are eligible", () => {
|
|
11
|
+
for (const argv of [[], ["--resume"], ["-r"], ["--continue"], ["-c"]]) {
|
|
12
|
+
assert.equal(shouldCheckStartupUpdate({ ...eligible, args: parseLauncherArgs(argv) }), true);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("commands, overrides, non-TTY runs and child/offline opt-outs are excluded", () => {
|
|
17
|
+
for (const argv of [["upgrade"], ["home"], ["setup"], ["install", "npm:x"], ["--help"], ["--version"], ["--package-root", "/custom"]]) {
|
|
18
|
+
assert.equal(shouldCheckStartupUpdate({ ...eligible, args: parseLauncherArgs(argv) }), false, argv.join(" "));
|
|
19
|
+
}
|
|
20
|
+
for (const overrides of [{ bundled: false }, { inputTTY: false }, { outputTTY: false }, { args: { ...eligible.args, error: "invalid arguments" } }]) {
|
|
21
|
+
assert.equal(shouldCheckStartupUpdate({ ...eligible, ...overrides }), false);
|
|
22
|
+
}
|
|
23
|
+
for (const name of ["GENTLE_PI_AGENTS_CHILD", "PI_OFFLINE", "GENTLE_SHELL_SKIP_STARTUP_UPDATE"]) {
|
|
24
|
+
assert.equal(shouldCheckStartupUpdate({ ...eligible, env: { [name]: "1" } }), false);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("forwarded command modes and unknown flags never trigger a startup offer", () => {
|
|
29
|
+
for (const argv of [["--print", "hello"], ["-p", "hello"], ["--mode", "rpc"], ["--mode=json"], ["--export", "session.json"], ["--list-models"], ["auth"], ["mcp"], ["--unknown"], ["--resume", "--print"], ["--resume", "session", "--mode", "json"]]) {
|
|
30
|
+
assert.equal(shouldCheckStartupUpdate({ ...eligible, args: parseLauncherArgs(argv) }), false, argv.join(" "));
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("only a newer strict stable version produces official release notes, without numeric precision loss", async () => {
|
|
35
|
+
for (const [current, latest] of [["1.2.3", "1.2.4"], ["1.99.99", "2.0.0"], ["1.9007199254740992.0", "1.9007199254740993.0"]]) {
|
|
36
|
+
assert.deepEqual(await findStartupUpdate({ current, lookup: async () => latest }), {
|
|
37
|
+
current, latest, notesUrl: `https://github.com/Gentleman-Programming/gentle-shell/releases/tag/v${latest}`,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("same, older, prerelease and malformed versions produce no offer", async () => {
|
|
43
|
+
for (const latest of ["1.2.3", "1.2.2", "0.99.99", "2.0.0-beta.1", "2.0.0+build", "v2.0.0", "02.0.0", "2.0", "2.0.0\n", "garbage", undefined]) {
|
|
44
|
+
assert.equal(await findStartupUpdate({ current: "1.2.3", lookup: async () => latest }), undefined);
|
|
45
|
+
}
|
|
46
|
+
for (const current of ["v1.2.3", "1.2.3-beta", "01.2.3", "1.2", ""]) {
|
|
47
|
+
assert.equal(await findStartupUpdate({ current, lookup: async () => "2.0.0" }), undefined);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("skipping suppresses exactly the skipped release, not later releases", async () => {
|
|
52
|
+
assert.equal(await findStartupUpdate({ current: "1.0.0", skipped: "2.0.0", lookup: async () => "2.0.0" }), undefined);
|
|
53
|
+
assert.equal((await findStartupUpdate({ current: "1.0.0", skipped: "2.0.0", lookup: async () => "2.0.1" }))?.latest, "2.0.1");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("lookup rejection and synchronous failure silently return no offer", async () => {
|
|
57
|
+
assert.equal(await findStartupUpdate({ current: "1.0.0", lookup: async () => { throw new Error("offline"); } }), undefined);
|
|
58
|
+
assert.equal(await findStartupUpdate({ current: "1.0.0", lookup: () => { throw new Error("offline"); } }), undefined);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("timeout aborts and returns even when lookup ignores its signal", async (t) => {
|
|
62
|
+
t.mock.timers.enable({ apis: ["setTimeout"] });
|
|
63
|
+
let signal: AbortSignal | undefined;
|
|
64
|
+
const pending = findStartupUpdate({ current: "1.0.0", lookup: (value) => { signal = value; return new Promise(() => {}); } });
|
|
65
|
+
await Promise.resolve();
|
|
66
|
+
t.mock.timers.tick(1499);
|
|
67
|
+
assert.equal(signal?.aborted, false);
|
|
68
|
+
t.mock.timers.tick(1);
|
|
69
|
+
assert.equal(await pending, undefined);
|
|
70
|
+
assert.equal(signal?.aborted, true);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("custom deadline is bounded and settled lookups clear the timeout", async (t) => {
|
|
74
|
+
t.mock.timers.enable({ apis: ["setTimeout"] });
|
|
75
|
+
let timedSignal: AbortSignal | undefined;
|
|
76
|
+
const pending = findStartupUpdate({ current: "1.0.0", timeoutMs: 20, lookup: (signal) => { timedSignal = signal; return new Promise(() => {}); } });
|
|
77
|
+
await Promise.resolve();
|
|
78
|
+
t.mock.timers.tick(20);
|
|
79
|
+
assert.equal(await pending, undefined);
|
|
80
|
+
assert.equal(timedSignal?.aborted, true);
|
|
81
|
+
let settledSignal: AbortSignal | undefined;
|
|
82
|
+
assert.ok(await findStartupUpdate({ current: "1.0.0", timeoutMs: 20, lookup: async (signal) => { settledSignal = signal; return "2.0.0"; } }));
|
|
83
|
+
t.mock.timers.tick(20);
|
|
84
|
+
assert.equal(settledSignal?.aborted, false);
|
|
85
|
+
});
|