gentle-pi 3.7.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +155 -10
- package/assets/agents/gentle-ai-explore.md +13 -6
- package/assets/agents/gentle-ai-verify.md +27 -6
- package/assets/agents/gentle-ai-worker.md +13 -15
- package/assets/install-wizard/index.html +47 -0
- package/assets/install-wizard/wizard.css +1279 -0
- package/assets/install-wizard/wizard.js +951 -0
- package/assets/orchestrator-delegation.md +75 -169
- package/assets/orchestrator-memory.md +12 -26
- package/assets/orchestrator-prompts.md +48 -0
- package/assets/orchestrator-skills.md +1 -1
- package/assets/orchestrator-tracking.md +53 -0
- package/assets/orchestrator-verification.md +28 -0
- package/assets/orchestrator-writer.md +38 -0
- package/assets/orchestrator.md +30 -38
- package/assets/sounds/LICENSE.md +52 -0
- package/assets/sounds/attention.wav +0 -0
- package/assets/sounds/error.wav +0 -0
- package/assets/sounds/success.wav +0 -0
- package/assets/support/strict-tdd-verify.md +4 -266
- package/assets/support/strict-tdd.md +8 -360
- package/bin/engram-guard +9 -0
- package/bin/gentle-shell-install.mjs +302 -0
- package/bin/gentle-shell.mjs +956 -50
- package/contracts/review-provider-contract-mirror/provider-contract.lock.json +7 -7
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/manifest.json +2 -2
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/orchestration/pi.md +16 -4
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-capabilities.baseline.json +2 -2
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-roles.baseline.json +1 -1
- package/docs/bundled-install.md +397 -0
- package/docs/delegated-verification.md +27 -4
- package/docs/gentle-agents-activity.md +537 -0
- package/docs/gentle-shell-uninstall.md +136 -0
- package/docs/gentle-shell.md +165 -51
- package/docs/install-wizard.md +2040 -0
- package/docs/native-authority-architecture.md +3 -3
- package/docs/permission-modes.md +82 -0
- package/docs/prompt-history.md +280 -0
- package/docs/readme-reference.md +364 -231
- package/docs/render-cache-performance.md +28 -0
- package/docs/sound-notifications-proposal.md +191 -0
- package/docs/sound-notifications.md +114 -0
- package/docs/telemetry.md +1 -1
- package/docs/yolo-mode.md +86 -0
- package/extensions/child-context.ts +35 -0
- package/extensions/child-safety.ts +25 -0
- package/extensions/codegraph-tools.ts +18 -5
- package/extensions/gentle-agents.ts +1093 -326
- package/extensions/gentle-ai.ts +1636 -890
- package/extensions/gentle-herdr-activity.ts +81 -0
- package/extensions/gentle-notifications.ts +104 -0
- package/extensions/gentle-shell.ts +1619 -136
- package/extensions/gentle-stats.ts +101 -0
- package/extensions/gentle-todo.ts +126 -20
- package/extensions/history/atomic-write.ts +38 -0
- package/extensions/history/hide-prompts.ts +183 -0
- package/extensions/history/index.ts +1424 -0
- package/extensions/history/load-shared-history.ts +39 -0
- package/extensions/history/selector-helpers.ts +538 -0
- package/extensions/history/session-scan.ts +233 -0
- package/extensions/history/store.ts +1119 -0
- package/extensions/nan-provider.ts +6 -0
- package/extensions/pi-pretty.ts +39 -3
- package/extensions/quiet-tools.ts +254 -92
- package/extensions/resume-hint.ts +60 -0
- package/extensions/skill-registry.ts +191 -58
- package/extensions/startup-banner.ts +71 -33
- package/lib/agent-assets.ts +604 -0
- package/lib/agent-home.ts +10 -1
- package/lib/agent-model-resolution.ts +80 -0
- package/lib/agent-profile-pin.ts +12 -0
- package/lib/agent-profiles.ts +13 -3
- package/lib/agents-completion-delivery.ts +11 -3
- package/lib/agents-message-delivery.ts +181 -0
- package/lib/agents-messaging.ts +1 -1
- package/lib/agents-protocol.ts +99 -0
- package/lib/agents-runner.ts +180 -107
- package/lib/agents-view.ts +26 -3
- package/lib/agents-widget.ts +16 -9
- package/lib/append-system-prompt.ts +21 -0
- package/lib/auto-permission-classifier.ts +280 -0
- package/lib/auto-permission-runtime.ts +268 -0
- package/lib/background-jobs-tools.ts +202 -0
- package/lib/background-jobs.ts +253 -0
- package/lib/background-monitor.ts +154 -0
- package/lib/bounded-writer-admission.ts +220 -0
- package/lib/builtin-codemode-optout.ts +166 -0
- package/lib/card-content-policy.ts +85 -0
- package/lib/card-style-policy.ts +60 -0
- package/lib/child-context-files.ts +166 -0
- package/lib/child-package-injection.ts +62 -0
- package/lib/codemode-renderer.ts +195 -0
- package/lib/command-palette-catalog.ts +5 -10
- package/lib/command-palette.ts +25 -14
- package/lib/context-threshold.ts +26 -0
- package/lib/destructive-command-guard.ts +144 -0
- package/lib/gentle-ai-binary.ts +109 -4
- package/lib/gentle-ai-elapsed-store.ts +87 -0
- package/lib/gentle-ai-renderer.ts +196 -39
- package/lib/gentle-shell-bundled.ts +517 -0
- package/lib/gentle-shell-launcher.ts +89 -27
- package/lib/gentle-shell-resume-hint.ts +176 -0
- package/lib/gentle-shell-startup-flow.ts +54 -0
- package/lib/gentle-shell-startup-update.ts +92 -0
- package/lib/gentle-shell-uninstall.ts +840 -0
- package/lib/gentle-shell-update-panel.ts +231 -0
- package/lib/herdr-activity.ts +183 -0
- package/lib/history-capture-policy.ts +95 -0
- package/lib/inprocess-reviewer.ts +3 -0
- package/lib/jobs-sidebar-state.ts +20 -0
- package/lib/jobs-view.ts +161 -0
- package/lib/model-price-ratio.ts +83 -0
- package/lib/model-routing-authority.ts +5 -1
- package/lib/nan-provider.ts +249 -0
- package/lib/native-review-cli.ts +242 -180
- package/lib/notification-audio-native.ts +162 -0
- package/lib/notification-audio-windows.ts +399 -0
- package/lib/notification-audio.ts +332 -0
- package/lib/notification-customize.ts +241 -0
- package/lib/notification-events.ts +113 -0
- package/lib/notification-policy.ts +148 -0
- package/lib/notification-pulse-client.ts +367 -0
- package/lib/notification-pulse-protocol.ts +305 -0
- package/lib/notification-pulse-stream.ts +213 -0
- package/lib/notification-pulse-worker.ts +66 -0
- package/lib/notification-scheduler.ts +173 -0
- package/lib/notification-service.ts +202 -0
- package/lib/notification-ui.ts +87 -0
- package/lib/odd-phase-inference.ts +231 -0
- package/lib/odd-phase.ts +141 -0
- package/lib/orchestrator-alias.ts +20 -0
- package/lib/orchestrator-catalog.ts +79 -0
- package/lib/orchestrator-consultation.ts +61 -0
- package/lib/orchestrator-discovery.ts +49 -0
- package/lib/orchestrator-helper-consent.ts +122 -0
- package/lib/orchestrator-helper.ts +167 -0
- package/lib/orchestrator-message-card.ts +105 -0
- package/lib/orchestrator-presence.ts +115 -14
- package/lib/orchestrator-scope.ts +80 -0
- package/lib/orchestrator-state.ts +107 -0
- package/lib/orchestrator-tool-card.ts +280 -0
- package/lib/orchestrator-work-search.ts +218 -0
- package/lib/orchestrator-work.ts +79 -0
- package/lib/overlay-repaint.ts +26 -0
- package/lib/permission-mode-policy.ts +59 -0
- package/lib/pi-tui-keys.ts +53 -0
- package/lib/review-candidate-view-owner.ts +73 -21
- package/lib/review-candidate-view.ts +115 -27
- package/lib/review-compact-contract.ts +2 -2
- package/lib/review-host-relay.ts +7 -2
- package/lib/review-integration-v2.ts +100 -22
- package/lib/review-publication-gate.ts +1 -1
- package/lib/review-reminder-receipt.ts +48 -8
- package/lib/review-risk-assessment.ts +227 -56
- package/lib/review-sidebar-state.ts +224 -0
- package/lib/review-transaction.ts +72 -3
- package/lib/runtime-metrics-children.ts +1 -1
- package/lib/runtime-metrics-native.ts +3 -2
- package/lib/selection-engine.ts +527 -0
- package/lib/session-change-capture.ts +5 -4
- package/lib/session-changes.ts +27 -22
- package/lib/session-messaging-grants.ts +135 -0
- package/lib/session-profile-binding.ts +83 -0
- package/lib/session-profile-freeze.ts +266 -0
- package/lib/session-worktree-registry.ts +87 -9
- package/lib/shell-bar.ts +215 -30
- package/lib/shell-card.ts +287 -18
- package/lib/shell-changes-view.ts +42 -14
- package/lib/shell-changes.ts +86 -71
- package/lib/shell-prompt.ts +102 -6
- package/lib/shell-sidebar-layout.ts +83 -15
- package/lib/shell-sidebar.ts +15 -1
- package/lib/shell-todo.ts +84 -31
- package/lib/shell-usage-view.ts +9 -4
- package/lib/shell-usage.ts +70 -13
- package/lib/stats-collector.ts +381 -0
- package/lib/stats-view.ts +431 -0
- package/lib/status-timing-diagnostics.ts +201 -0
- package/lib/telemetry-trigger.ts +3 -1
- package/lib/theme-customization.ts +52 -0
- package/lib/vim-editor-adapter.ts +383 -0
- package/lib/vim-normal-engine.ts +154 -0
- package/lib/vim-operator-engine.ts +416 -0
- package/lib/vim-policy.ts +49 -0
- package/lib/vim-visual-engine.ts +107 -0
- package/lib/visual-customization-policy.ts +119 -0
- package/lib/visual-customize-view.ts +481 -0
- package/lib/visual-profiles.ts +228 -0
- package/lib/writer-surfaces.ts +162 -0
- package/lib/yolo-session-policy.ts +317 -0
- package/package.json +20 -8
- package/runtime/child-package-injection.mjs +63 -0
- package/runtime/gentle-ai-binary.mjs +109 -4
- package/runtime/gentle-shell-bundled.mjs +518 -0
- package/runtime/gentle-shell-launcher.mjs +87 -25
- package/runtime/gentle-shell-resume-hint.mjs +177 -0
- package/runtime/gentle-shell-startup-flow.mjs +55 -0
- package/runtime/gentle-shell-startup-update.mjs +93 -0
- package/runtime/gentle-shell-uninstall.mjs +841 -0
- package/runtime/gentle-shell-update-panel.mjs +232 -0
- package/runtime/native-review-cli.mjs +239 -177
- package/runtime/review-integration-v2.mjs +100 -22
- package/runtime/review-risk-assessment.mjs +225 -54
- package/runtime/status-timing-diagnostics.mjs +202 -0
- package/runtime/telemetry-trigger.mjs +3 -1
- package/scripts/bootstrap.cmd +255 -0
- package/scripts/bootstrap.sh +168 -0
- package/scripts/build-distribution.mjs +157 -0
- package/scripts/build-installer-bundles.mjs +136 -0
- package/scripts/build-runtime-modules.mjs +8 -0
- package/scripts/bundled-install.mjs +1308 -0
- package/scripts/bundled-wizard.mjs +399 -0
- package/scripts/check-types.mjs +4 -6
- package/scripts/gentle-ai-installer.mjs +83 -35
- package/scripts/install-gentle-ai.mjs +48 -1
- package/scripts/install-tui-mode-setting.mjs +10 -1
- package/scripts/install-wizard-preview.mjs +154 -0
- package/scripts/installer-downloads.mjs +648 -0
- package/scripts/installer-preflight.mjs +387 -0
- package/scripts/installer-probes.mjs +564 -0
- package/scripts/installer-runner.mjs +1102 -0
- package/scripts/installer-server.mjs +881 -0
- package/scripts/installer-windows-artifacts.json +15 -0
- package/scripts/installer-windows.mjs +752 -0
- package/scripts/main-channel.mjs +452 -0
- package/scripts/mirror-odd-routing.mjs +2 -2
- package/scripts/npm-pack-result.mjs +31 -0
- package/scripts/render-cache-performance-worker.mjs +205 -0
- package/scripts/render-cache-performance.mjs +115 -0
- package/scripts/run-test-suite.mjs +90 -0
- package/scripts/test-installer-acceptance.sh +161 -0
- package/scripts/test-packed-runner.mjs +54 -19
- package/scripts/types-baseline.json +2 -92
- package/scripts/verify-package-files.mjs +91 -38
- package/skills/branch-pr/SKILL.md +24 -52
- package/skills/chained-pr/SKILL.md +31 -15
- package/skills/chained-pr/references/chaining-details.md +31 -20
- package/skills/gentle-ai/SKILL.md +16 -21
- package/skills/issue-creation/SKILL.md +8 -2
- package/skills/issue-creation/references/delegated-workflow-actions.md +19 -0
- package/skills/work-unit-commits/SKILL.md +4 -3
- package/tests/agent-home.test.ts +56 -3
- package/tests/agent-model-resolution.test.ts +113 -0
- package/tests/agent-profiles.test.ts +30 -0
- package/tests/agent-risk-escalation.test.ts +123 -0
- package/tests/agents-completion-delivery.test.ts +17 -0
- package/tests/agents-fake-child.ts +2 -2
- package/tests/agents-message-delivery.test.ts +106 -0
- package/tests/agents-protocol.test.ts +97 -0
- package/tests/agents-queries.test.ts +9 -9
- package/tests/agents-runner-process.test.ts +31 -0
- package/tests/agents-runner.test.ts +467 -93
- package/tests/agents-view-thread-identity.test.ts +169 -0
- package/tests/agents-view.test.ts +8 -2
- package/tests/agents-widget.test.ts +154 -15
- package/tests/append-system-prompt-route.test.ts +160 -0
- package/tests/append-system-prompt.test.ts +46 -0
- package/tests/artifact-language.test.ts +19 -213
- package/tests/ask-user-question.test.ts +44 -1
- package/tests/asset-installation-runtime.test.ts +5 -16
- package/tests/auto-mode-invariants.test.ts +258 -0
- package/tests/auto-mode-sdk.test.ts +57 -0
- package/tests/auto-mode.test.ts +49 -0
- package/tests/auto-permission-classifier.test.ts +283 -0
- package/tests/auto-permission-runtime.test.ts +196 -0
- package/tests/autonomous-guard.test.ts +69 -1
- package/tests/background-cache-warming.test.ts +2 -1
- package/tests/background-jobs.test.ts +227 -0
- package/tests/background-monitor.test.ts +195 -0
- package/tests/background-subagents-default-mode.test.ts +28 -5
- package/tests/bounded-stop-contract.test.ts +85 -0
- package/tests/bounded-writer-admission.test.ts +146 -0
- package/tests/branch-pr-skill.test.ts +43 -0
- package/tests/bridge-wake-lifecycle.test.ts +129 -0
- package/tests/bridge-wake-rendering.test.ts +56 -0
- package/tests/build-distribution.test.ts +269 -0
- package/tests/builtin-codemode-optout.test.ts +242 -0
- package/tests/bundled-install.test.ts +1319 -0
- package/tests/bundled-wizard.test.ts +505 -0
- package/tests/card-content-policy.test.ts +68 -0
- package/tests/card-style-policy.test.ts +87 -0
- package/tests/chained-pr-skill.test.ts +124 -0
- package/tests/child-codegraph-bootstrap.test.ts +74 -0
- package/tests/child-context-files.test.ts +255 -0
- package/tests/child-package-entrypoints.test.ts +65 -0
- package/tests/child-package-injection.test.ts +74 -0
- package/tests/child-safety.test.ts +94 -0
- package/tests/child-web-package.test.ts +224 -0
- package/tests/codegraph-tools.test.ts +35 -0
- package/tests/codemode-rendering.test.ts +596 -0
- package/tests/command-palette.test.ts +43 -7
- package/tests/context-threshold.test.ts +31 -0
- package/tests/delegate-for-reason-contract.test.ts +327 -0
- package/tests/delegated-key-learnings-contract.test.ts +3 -78
- package/tests/destructive-command-guard.test.ts +84 -0
- package/tests/devbinary/native-review-parity.devtest.ts +171 -3
- package/tests/devbinary/non-git-subagent-bootstrap.devtest.ts +193 -0
- package/tests/devbinary/pi-host-relay.devtest.ts +173 -3
- package/tests/engram-safety.test.ts +288 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +7 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +3 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T08-00-00-000Z_ddd.jsonl +2 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T09-00-00-000Z_eee.jsonl +3 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/run-1/session.jsonl +2 -0
- package/tests/fixtures/stats/sessions/--work-beta--/2026-09-01T12-00-00-000Z_ccc.jsonl +2 -0
- package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +2 -0
- package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +4 -0
- package/tests/fixtures/stats/user-pi/sessions/--work-gamma--/2026-09-20T09-00-00-000Z_fff.jsonl +3 -0
- package/tests/generic-agent-tools.test.ts +69 -0
- package/tests/gentle-agents.test.ts +2804 -346
- package/tests/gentle-ai-binary-isolation.test.ts +135 -0
- package/tests/gentle-ai-binary.test.ts +21 -4
- package/tests/gentle-ai-child-guards.test.ts +202 -0
- package/tests/gentle-ai-dev-binary-surfacing.test.ts +125 -15
- package/tests/gentle-ai-elapsed-store.test.ts +68 -0
- package/tests/gentle-ai-installer.test.ts +566 -62
- package/tests/gentle-ai-prompt-model-routing.test.ts +123 -0
- package/tests/gentle-ai-renderer.test.ts +493 -14
- package/tests/gentle-ai.test.ts +1427 -236
- package/tests/gentle-card-text.ts +2 -1
- package/tests/gentle-herdr-activity.test.ts +100 -0
- package/tests/gentle-notifications.test.ts +427 -0
- package/tests/gentle-shell-bin.test.ts +896 -115
- package/tests/gentle-shell-bundled.test.ts +1002 -0
- package/tests/gentle-shell-launcher.test.ts +333 -65
- package/tests/gentle-shell-resume-hint.test.ts +270 -0
- package/tests/gentle-shell-startup-flow.test.ts +75 -0
- package/tests/gentle-shell-startup-update.test.ts +85 -0
- package/tests/gentle-shell-uninstall.test.ts +978 -0
- package/tests/gentle-shell-update-panel.test.ts +158 -0
- package/tests/gentle-shell.test.ts +4216 -119
- package/tests/gentle-stats.test.ts +152 -0
- package/tests/gentle-theme.test.ts +4 -1
- package/tests/gentle-todo.test.ts +333 -10
- package/tests/herdr-activity.test.ts +243 -0
- package/tests/history-agent-home.test.ts +81 -0
- package/tests/history-atomic-write.test.ts +57 -0
- package/tests/history-capture-policy.test.ts +102 -0
- package/tests/history-child-guard.test.ts +70 -0
- package/tests/history-command-registration.test.ts +164 -0
- package/tests/history-dedupe-entries.test.ts +123 -0
- package/tests/history-delete-backfill.test.ts +190 -0
- package/tests/history-delete-confirm.test.ts +460 -0
- package/tests/history-dispatch.test.ts +180 -0
- package/tests/history-drain-hidden.test.ts +110 -0
- package/tests/history-drain-order.test.ts +98 -0
- package/tests/history-expanded-globals.test.ts +62 -0
- package/tests/history-gc.test.ts +832 -0
- package/tests/history-header-layout.test.ts +265 -0
- package/tests/history-hide-prompts.test.ts +275 -0
- package/tests/history-lazy-windowing.test.ts +508 -0
- package/tests/history-legacy-migrate-v2.test.ts +297 -0
- package/tests/history-load-shared-history.test.ts +53 -0
- package/tests/history-max-results-cap.test.ts +76 -0
- package/tests/history-multi-reader.test.ts +203 -0
- package/tests/history-off-path.test.ts +170 -0
- package/tests/history-openflow-integration.test.ts +173 -0
- package/tests/history-overlay-margin.test.ts +326 -0
- package/tests/history-preview-layout.test.ts +93 -0
- package/tests/history-registry.test.ts +143 -0
- package/tests/history-scope-delete.test.ts +411 -0
- package/tests/history-search-caret-keys.test.ts +142 -0
- package/tests/history-seed-bootstrap.test.ts +170 -0
- package/tests/history-seed-regen.test.ts +129 -0
- package/tests/history-selector-windowing.test.ts +94 -0
- package/tests/history-session-scan-directory.test.ts +87 -0
- package/tests/history-session-scan-extract.test.ts +583 -0
- package/tests/history-session-writer.test.ts +351 -0
- package/tests/history-store-paths.test.ts +79 -0
- package/tests/history-tombstone-exact.test.ts +139 -0
- package/tests/history-wheel-mouse.test.ts +242 -0
- package/tests/inprocess-reviewer.test.ts +49 -19
- package/tests/install-gentle-ai-failure.test.ts +69 -0
- package/tests/install-tui-mode-setting.test.ts +15 -0
- package/tests/install-wizard.test.ts +1223 -0
- package/tests/installer-bundles.test.ts +122 -0
- package/tests/installer-posix-bootstrap.test.ts +1201 -0
- package/tests/installer-preflight.test.ts +750 -0
- package/tests/installer-probes.test.ts +1106 -0
- package/tests/installer-runner.test.ts +2158 -0
- package/tests/installer-server.test.ts +1567 -0
- package/tests/installer-windows-bootstrap.test.ts +2410 -0
- package/tests/issue-creation-skill.test.ts +61 -0
- package/tests/jobs-view.test.ts +160 -0
- package/tests/main-channel-upgrade.test.ts +389 -0
- package/tests/main-channel.test.ts +344 -0
- package/tests/maintainer/provider-relay.maintest.ts +13 -12
- package/tests/model-price-ratio.test.ts +116 -0
- package/tests/model-profiles-preservation.test.ts +153 -0
- package/tests/model-routing-authority.test.ts +16 -0
- package/tests/model-routing-package.test.ts +10 -0
- package/tests/nan-provider-runtime.test.ts +156 -0
- package/tests/nan-provider.test.ts +481 -0
- package/tests/native-binary-gate.test.ts +2 -0
- package/tests/native-review-capability-contract.test.ts +13 -1
- package/tests/native-review-cli.test.ts +239 -118
- package/tests/native-review-consent.test.ts +2 -2
- package/tests/native-review-parity-runtime.test.ts +101 -4
- package/tests/native-review-parity.test.ts +12 -48
- package/tests/native-risk-evidence-parity.test.ts +21 -0
- package/tests/no-self-pause-contract.test.ts +87 -0
- package/tests/notification-audio-native.test.ts +281 -0
- package/tests/notification-audio-windows.test.ts +581 -0
- package/tests/notification-audio.test.ts +548 -0
- package/tests/notification-customize.test.ts +655 -0
- package/tests/notification-events.test.ts +115 -0
- package/tests/notification-policy.test.ts +201 -0
- package/tests/notification-pulse-client.test.ts +356 -0
- package/tests/notification-pulse-protocol.test.ts +213 -0
- package/tests/notification-pulse-stream.test.ts +320 -0
- package/tests/notification-scheduler.test.ts +258 -0
- package/tests/notification-ui.test.ts +157 -0
- package/tests/npm-pack-result.test.ts +41 -0
- package/tests/odd-integration.test.ts +76 -0
- package/tests/odd-phase-inference.test.ts +213 -0
- package/tests/odd-phase-loader.test.ts +253 -0
- package/tests/odd-phase.test.ts +307 -0
- package/tests/odd-routing-canonical-ratchet.test.ts +60 -14
- package/tests/odd-routing-contract.test.ts +158 -50
- package/tests/orchestrator-budget.test.ts +43 -45
- package/tests/orchestrator-catalog.test.ts +146 -0
- package/tests/orchestrator-consultation-sdk.test.ts +614 -0
- package/tests/orchestrator-consultation.test.ts +111 -0
- package/tests/orchestrator-discovery.test.ts +241 -0
- package/tests/orchestrator-helper-consent.test.ts +171 -0
- package/tests/orchestrator-helper.test.ts +297 -0
- package/tests/orchestrator-message-card.test.ts +190 -0
- package/tests/orchestrator-presence.test.ts +23 -0
- package/tests/orchestrator-rdd-ownership.test.ts +8 -7
- package/tests/orchestrator-scope.test.ts +107 -0
- package/tests/orchestrator-state.test.ts +114 -0
- package/tests/orchestrator-status-publication-contract.test.ts +34 -0
- package/tests/orchestrator-tool-card.test.ts +398 -0
- package/tests/orchestrator-work-search.test.ts +318 -0
- package/tests/orchestrator-work.test.ts +147 -0
- package/tests/overlay-repaint.test.ts +74 -0
- package/tests/package-manifest.test.ts +344 -123
- package/tests/packed-runner-owned-path.test.ts +80 -0
- package/tests/parallel-reason-contract.test.ts +21 -0
- package/tests/parallel-writer-admission.test.ts +210 -0
- package/tests/permission-mode-policy.test.ts +194 -0
- package/tests/persona-single-channel.test.ts +10 -18
- package/tests/pi-pretty.test.ts +56 -0
- package/tests/provider-defect-handoff.test.ts +7 -14
- package/tests/quiet-bash-runtime.test.ts +76 -0
- package/tests/quiet-tool-rendering.test.ts +569 -187
- package/tests/rdd-aware-verification-contract.test.ts +103 -40
- package/tests/rdd-status-line.test.ts +9 -4
- package/tests/render-cache-performance.test.ts +48 -0
- package/tests/resume-hint-extension.test.ts +122 -0
- package/tests/review-agent-end-preflight.test.ts +237 -21
- package/tests/review-candidate-owner-retry.test.ts +22 -1
- package/tests/review-candidate-view.test.ts +399 -12
- package/tests/review-contract-prompt.test.ts +138 -44
- package/tests/review-controller-lock-status.test.ts +0 -1
- package/tests/review-controller-native-recovery.test.ts +327 -17
- package/tests/review-controller-native-routing.test.ts +866 -6
- package/tests/review-controller-workspace-root.test.ts +206 -6
- package/tests/review-controller.test.ts +2 -2
- package/tests/review-correction-lifecycle.test.ts +2 -1
- package/tests/review-gate.test.ts +7 -4
- package/tests/review-graph-schema.test.ts +2 -2
- package/tests/review-host-relay-routing.test.ts +342 -4
- package/tests/review-host-relay.test.ts +49 -2
- package/tests/review-integration-v2-forward.test.ts +221 -1
- package/tests/review-json-arguments.test.ts +167 -0
- package/tests/review-last-event-closure.test.ts +157 -8
- package/tests/review-ledger-contract.test.ts +14 -37
- package/tests/review-policy-judgment-day.test.ts +1 -1
- package/tests/review-policy-ordinary.test.ts +2 -2
- package/tests/review-relay-transport-agent.test.ts +66 -1
- package/tests/review-reminder-receipt.test.ts +47 -1
- package/tests/review-risk-assessment.test.ts +594 -26
- package/tests/review-session-standing-permission-controller.test.ts +16 -17
- package/tests/review-session-standing-permission-ipc.test.ts +6 -5
- package/tests/review-sidebar-state.test.ts +402 -0
- package/tests/review-test-fixtures.ts +7 -1
- package/tests/review-transaction.test.ts +18 -1
- package/tests/run-test-suite.test.ts +202 -0
- package/tests/runtime-harness.mjs +186 -800
- package/tests/runtime-metrics-children.test.ts +16 -23
- package/tests/runtime-metrics-extension.test.ts +1 -1
- package/tests/runtime-metrics-native.test.ts +31 -1
- package/tests/selection-engine.test.ts +451 -0
- package/tests/session-change-capture.test.ts +10 -10
- package/tests/session-changes-shell.test.ts +242 -48
- package/tests/session-changes.test.ts +17 -24
- package/tests/session-messaging-grants.test.ts +255 -0
- package/tests/session-profile-binding.test.ts +163 -0
- package/tests/session-profile-freeze.test.ts +381 -0
- package/tests/session-subject-routing-contract.test.ts +34 -0
- package/tests/session-worktree-registry.test.ts +134 -2
- package/tests/shell-bar.test.ts +422 -1
- package/tests/shell-card.test.ts +353 -1
- package/tests/shell-changes-view.test.ts +135 -32
- package/tests/shell-changes.test.ts +61 -62
- package/tests/shell-prompt.test.ts +94 -2
- package/tests/shell-render-cache-runtime.test.ts +69 -0
- package/tests/shell-render-cache.test.ts +129 -0
- package/tests/shell-sidebar-layout.test.ts +351 -22
- package/tests/shell-sidebar-scroll-benchmark.test.ts +255 -0
- package/tests/shell-todo.test.ts +207 -3
- package/tests/shell-usage-view.test.ts +28 -1
- package/tests/shell-usage.test.ts +108 -0
- package/tests/short-check-routing-contract.test.ts +49 -0
- package/tests/skill-registry-watcher.test.ts +119 -0
- package/tests/skill-registry.test.ts +357 -2
- package/tests/startup-banner.test.ts +246 -4
- package/tests/startup-home-repo.test.ts +170 -0
- package/tests/stats-collector.test.ts +195 -0
- package/tests/stats-view.test.ts +202 -0
- package/tests/status-timing-diagnostics.test.ts +283 -0
- package/tests/support/orchestrator-modules.ts +19 -0
- package/tests/task-size-routing-contract.test.ts +241 -0
- package/tests/telemetry-trigger.test.ts +107 -20
- package/tests/test-strength-contract.test.ts +124 -0
- package/tests/theme-customization.test.ts +72 -0
- package/tests/verify-agent-contract.test.ts +90 -0
- package/tests/verify-first-launch-contract.test.ts +139 -0
- package/tests/verify-package-files.test.ts +56 -0
- package/tests/vim-editor-adapter-host-resolution.test.ts +37 -0
- package/tests/vim-editor-adapter.test.ts +804 -0
- package/tests/vim-normal-engine.test.ts +101 -0
- package/tests/vim-operator-engine.test.ts +215 -0
- package/tests/vim-policy.test.ts +19 -0
- package/tests/vim-visual-engine.test.ts +52 -0
- package/tests/visual-customization-policy.test.ts +139 -0
- package/tests/visual-customize-view.test.ts +696 -0
- package/tests/visual-profiles.test.ts +87 -0
- package/tests/writer-edit-surface-scope.test.ts +59 -2
- package/tests/writer-surfaces.test.ts +199 -0
- package/tests/yolo-customize.test.ts +271 -0
- package/tests/yolo-mode-runtime.test.ts +188 -0
- package/tests/yolo-mode.test.ts +289 -0
- package/tests/yolo-session-policy.test.ts +59 -0
- package/themes/Gentle.json +2 -1
- package/themes/Gentleman-Cute.json +2 -1
- package/themes/Gentleman-Sexy.json +2 -1
- package/assets/agents/sdd-apply.md +0 -159
- package/assets/agents/sdd-archive.md +0 -228
- package/assets/agents/sdd-design.md +0 -49
- package/assets/agents/sdd-explore.md +0 -48
- package/assets/agents/sdd-init.md +0 -56
- package/assets/agents/sdd-onboard.md +0 -52
- package/assets/agents/sdd-proposal.md +0 -64
- package/assets/agents/sdd-remediate.md +0 -37
- package/assets/agents/sdd-research.md +0 -49
- package/assets/agents/sdd-spec.md +0 -192
- package/assets/agents/sdd-status.md +0 -54
- package/assets/agents/sdd-tasks.md +0 -108
- package/assets/agents/sdd-verify.md +0 -124
- package/assets/chains/sdd-full.chain.md +0 -83
- package/assets/chains/sdd-plan.chain.md +0 -56
- package/assets/chains/sdd-verify.chain.md +0 -43
- package/assets/sdd-orchestrator-workflow.md +0 -319
- package/assets/support/sdd-status-contract.md +0 -77
- package/docs/assets/diagrams/sdd-cycle.svg +0 -14
- package/extensions/sdd-init.ts +0 -816
- package/lib/openspec-deltas.ts +0 -156
- package/lib/sdd-preflight.ts +0 -1066
- package/lib/sdd-research-capabilities.ts +0 -94
- package/lib/sdd-status.ts +0 -26
- package/tests/fixtures/legacy/sdd-research-v2.5.0.md +0 -54
- package/tests/fixtures/native-review-cli/v2.1.3/bind-sdd.json +0 -25
- package/tests/fixtures/v0.10.7/assets/agents/sdd-apply.md +0 -132
- package/tests/openspec-deltas.test.ts +0 -209
- package/tests/sdd-agent-tools.test.ts +0 -156
- package/tests/sdd-archive-replay.test.ts +0 -82
- package/tests/sdd-classical-continuation.test.ts +0 -74
- package/tests/sdd-execution-routing-contract.test.ts +0 -44
- package/tests/sdd-managed-runtime-settlement.test.ts +0 -155
- package/tests/sdd-native-managed-uptake.test.ts +0 -243
- package/tests/sdd-no-attempts-contract.test.ts +0 -15
- package/tests/sdd-odd-integration.test.ts +0 -33
- package/tests/sdd-optional-research.test.ts +0 -124
- package/tests/sdd-planning-routing-contract.test.ts +0 -45
- package/tests/sdd-preflight-rpc-input.test.ts +0 -125
- package/tests/sdd-preflight.test.ts +0 -541
- package/tests/sdd-research-capabilities.test.ts +0 -114
- package/tests/sdd-research-live.test.ts +0 -241
- package/tests/sdd-selection-transport.test.ts +0 -653
- package/tests/sdd-status.test.ts +0 -9
- package/tests/sdd-task-truth.test.ts +0 -43
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/README.md +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/lens.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/refuter.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/targeted-validator.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/lens.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/refuter.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/targeted-validator.json +0 -0
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
writeFile,
|
|
14
14
|
} from "node:fs/promises";
|
|
15
15
|
import https from "node:https";
|
|
16
|
+
import { tmpdir } from "node:os";
|
|
16
17
|
import { dirname, isAbsolute, join, relative } from "node:path";
|
|
17
18
|
import { fileURLToPath } from "node:url";
|
|
18
19
|
import { execFile } from "node:child_process";
|
|
@@ -36,19 +37,20 @@ const WINDOWS_SYSTEM_ROOT = "C:\\Windows";
|
|
|
36
37
|
// version check below) derives from this constant instead of repeating the
|
|
37
38
|
// literal, so a pin bump cannot leave a stale copy behind. See
|
|
38
39
|
// scripts/install-gentle-ai.mjs for the incident that motivated this.
|
|
39
|
-
export const INSTALLER_VERSION = "
|
|
40
|
+
export const INSTALLER_VERSION = "4.1.1";
|
|
40
41
|
export const RELEASE_BASE_URL = `https://github.com/Gentleman-Programming/gentle-ai/releases/download/v${INSTALLER_VERSION}/`;
|
|
41
42
|
export const GENTLE_AI_INSTALL_METHOD = Object.freeze({
|
|
42
43
|
SIGNED_RELEASE_ASSET: "signed-release-asset",
|
|
43
44
|
GO_SUMDB_SOURCE_BUILD: "go-sumdb-source-build",
|
|
44
45
|
});
|
|
45
|
-
export const GENTLE_AI_WINDOWS_SOURCE_PACKAGE_PATH = "github.com/gentleman-programming/gentle-ai/
|
|
46
|
-
export const GENTLE_AI_WINDOWS_SOURCE_MODULE = "github.com/gentleman-programming/gentle-ai/
|
|
46
|
+
export const GENTLE_AI_WINDOWS_SOURCE_PACKAGE_PATH = "github.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai";
|
|
47
|
+
export const GENTLE_AI_WINDOWS_SOURCE_MODULE = "github.com/gentleman-programming/gentle-ai/v4";
|
|
47
48
|
export const GENTLE_AI_WINDOWS_SOURCE_TAG = `v${INSTALLER_VERSION}`;
|
|
48
|
-
// `go mod download -json github.com/gentleman-programming/gentle-ai/
|
|
49
|
+
// `go mod download -json github.com/gentleman-programming/gentle-ai/v4@v4.1.1`
|
|
49
50
|
// with GOSUMDB=sum.golang.org reports this exact module SumDB checksum, and the
|
|
50
|
-
// tag resolves to commit
|
|
51
|
-
|
|
51
|
+
// tag resolves to commit 898d3976e8049a7238eaa7de60bff325c30d2b3d, the published v4.1.1 release head.
|
|
52
|
+
// v4.0.0 moved the Go module path to the /v4 major-version suffix.
|
|
53
|
+
export const GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:+WmZ2IvIOiJ8qtHa7wsjnYKNasem+PByeOZ6HIjoAL8=";
|
|
52
54
|
export const GENTLE_AI_WINDOWS_SOURCE_PACKAGE = `${GENTLE_AI_WINDOWS_SOURCE_PACKAGE_PATH}@${GENTLE_AI_WINDOWS_SOURCE_TAG}`;
|
|
53
55
|
export const GENTLE_AI_WINDOWS_MINIMUM_GO_VERSION = "1.25.10";
|
|
54
56
|
export const GENTLE_AI_GO_TOOLCHAIN_UNAVAILABLE_CODE = "GENTLE_AI_GO_TOOLCHAIN_UNAVAILABLE";
|
|
@@ -67,7 +69,7 @@ export class GentleAiInstallerError extends Error {
|
|
|
67
69
|
// Sentinel used while a re-pinned gentle-ai release is not yet published. A
|
|
68
70
|
// sentinel digest can never match a real SHA-256, so installation fails closed,
|
|
69
71
|
// and verify-package-files.mjs refuses to pack/publish while any digest below
|
|
70
|
-
// still holds it. The
|
|
72
|
+
// still holds it. The v4.1.1 digests are pinned from the published release:
|
|
71
73
|
// archive sha256 values verified against the minisign-signed checksums.txt and
|
|
72
74
|
// freshly computed hashes; binary sha256 values computed from the extracted
|
|
73
75
|
// executables.
|
|
@@ -109,15 +111,15 @@ async function downloadPinnedGentleAiAsset(asset, destination, options) {
|
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
// Windows is absent from signed release archives on purpose. gentle-ai stopped
|
|
112
|
-
// distributing unsigned Windows builds in c4b764d0, so
|
|
114
|
+
// distributing unsigned Windows builds in c4b764d0, so v4.1.1 publishes signed
|
|
113
115
|
// Darwin/Linux archives only. Windows x64/arm64 uses the separately verified
|
|
114
116
|
// exact-tag Go SumDB source-build path below; restore archive rows only when
|
|
115
117
|
// upstream ships signed Windows assets.
|
|
116
118
|
export const GENTLE_AI_RELEASE_ASSETS = Object.freeze({
|
|
117
|
-
"darwin/amd64": asset("gentle-
|
|
118
|
-
"darwin/arm64": asset("gentle-
|
|
119
|
-
"linux/amd64": asset("gentle-
|
|
120
|
-
"linux/arm64": asset("gentle-
|
|
119
|
+
"darwin/amd64": asset("gentle-ai_4.1.1_darwin_amd64.tar.gz", "f2e9aebfba3056e88a8d7c7193e4e8c04282dc126d5eaa2216bc807deadecffd", "82351dd86bf7323c6a5b9e9e68b3cc2893a65a4df04fbdf1ef1801cabf5d2338", "gentle-ai"),
|
|
120
|
+
"darwin/arm64": asset("gentle-ai_4.1.1_darwin_arm64.tar.gz", "8093dced37130cb26534d81663013f81d9039342e33f00b8c85aa5319aa7eea9", "6362e3641b87ae2b41a8dd7695dad4cf47457ee86c9f63792e474476bfdb6b6a", "gentle-ai"),
|
|
121
|
+
"linux/amd64": asset("gentle-ai_4.1.1_linux_amd64.tar.gz", "57869ebcdba5ba9942493ab413bbc15d146e2451c4dca3eaafcf163fcbd646b4", "7c638c8467efc7036e0ec6b8a1d9080a5e9a64f7ae6cbc6074fce577f789b7fe", "gentle-ai"),
|
|
122
|
+
"linux/arm64": asset("gentle-ai_4.1.1_linux_arm64.tar.gz", "5f8fa38573eea0f2110902f0996aa0b008aac58dc0f9a6abbdf301dcbc93f4eb", "76a36a73d6905a16221496ffe9a193bd79e17951463d711cd03abf370349ef55", "gentle-ai"),
|
|
121
123
|
});
|
|
122
124
|
|
|
123
125
|
// A pinned asset is either a signed archive or, for a prerelease pin only,
|
|
@@ -193,16 +195,32 @@ export async function downloadGentleAiAsset(url, destination, maxBytes = MAX_DOW
|
|
|
193
195
|
const downloadOnce = async () => {
|
|
194
196
|
const response = await responseFor(url, redirects), contentLength = Number(response.headers["content-length"] ?? "0");
|
|
195
197
|
if (!Number.isSafeInteger(contentLength) || contentLength < 0 || contentLength > maxBytes) { response.resume(); throw new Error("Gentle AI download exceeds the maximum allowed size"); }
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
198
|
+
let created = false;
|
|
199
|
+
try {
|
|
200
|
+
await new Promise((resolve, reject) => {
|
|
201
|
+
const output = createWriteStream(destination, { flags: "wx", mode: 0o600 }); let received = 0, settled = false;
|
|
202
|
+
output.on("open", () => { created = true; });
|
|
203
|
+
let timer = setTimeout(() => response.destroy(downloadTimeoutError("body")), bodyTimeoutMs);
|
|
204
|
+
// The exclusive open is asynchronous, so a failed attempt may settle the
|
|
205
|
+
// download before its destination stream finished with the filesystem. Wait
|
|
206
|
+
// for that stream to close first: otherwise a late creation outlives the
|
|
207
|
+
// retry's removal of the destination and the next exclusive open fails with
|
|
208
|
+
// EEXIST instead of reporting the timeout that caused the retry.
|
|
209
|
+
const closed = () => new Promise((done) => { if (output.closed) done(); else output.on("close", done); });
|
|
210
|
+
const finish = (callback, value) => { if (settled) return; settled = true; clearTimeout(timer); void closed().then(() => callback(value)); };
|
|
211
|
+
const fail = (error) => { response.destroy(); output.destroy(); finish(reject, error); };
|
|
212
|
+
const reset = () => { clearTimeout(timer); timer = setTimeout(() => response.destroy(downloadTimeoutError("body")), bodyTimeoutMs); };
|
|
213
|
+
response.on("data", (chunk) => { reset(); received += chunk.length; if (received > maxBytes) response.destroy(new Error("Gentle AI download exceeds the maximum allowed size")); });
|
|
214
|
+
response.on("error", fail); response.setTimeout?.(bodyTimeoutMs, () => response.destroy(downloadTimeoutError("body")));
|
|
215
|
+
output.on("error", fail); output.on("finish", () => finish(resolve)); response.pipe(output);
|
|
216
|
+
});
|
|
217
|
+
} catch (error) {
|
|
218
|
+
// A failed attempt removes only the file it created, once its stream closed, so
|
|
219
|
+
// the retry starts from a free destination path. A path this download never
|
|
220
|
+
// owned is left alone instead of being deleted with it.
|
|
221
|
+
if (created) await rm(destination, { force: true });
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
206
224
|
};
|
|
207
225
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) try { if (attempt > 1) await rm(destination, { force: true }); await downloadOnce(); return; } catch (error) {
|
|
208
226
|
if (attempt === maxAttempts || !isRetryableDownloadError(error)) throw error;
|
|
@@ -402,7 +420,7 @@ async function safeRemoveDirectory(path) {
|
|
|
402
420
|
try {
|
|
403
421
|
const details = await lstat(path);
|
|
404
422
|
if (!details.isDirectory() || details.isSymbolicLink()) throw new Error("not a real directory");
|
|
405
|
-
await rm(path, { recursive: true, force: true });
|
|
423
|
+
await rm(path, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
406
424
|
} catch (error) { if (!(error && typeof error === "object" && error.code === "ENOENT")) throw error; }
|
|
407
425
|
}
|
|
408
426
|
|
|
@@ -567,7 +585,7 @@ async function recoverInterruptedPublication(runtimeRoot, bundleIsValid, options
|
|
|
567
585
|
const live = versionBundlePath(runtimeRoot);
|
|
568
586
|
const liveExists = await realBundleDirectory(live, runtimeRoot, "live bundle");
|
|
569
587
|
if (!liveExists) {
|
|
570
|
-
try { await (
|
|
588
|
+
try { await renameWithWindowsPublicationRetry(backup, live, options); }
|
|
571
589
|
catch (error) { throw bundleRecoveryError(runtimeRoot, `could not restore valid backup ${backup}: ${error instanceof Error ? error.message : String(error)}`); }
|
|
572
590
|
return;
|
|
573
591
|
}
|
|
@@ -584,21 +602,43 @@ async function cleanupStaleStagingBundles(runtimeRoot) {
|
|
|
584
602
|
}
|
|
585
603
|
}
|
|
586
604
|
|
|
605
|
+
// Windows publication can lose a race with the just-executed gentle-ai.exe or a
|
|
606
|
+
// real-time scanner still holding the staging directory. Retry only those lock
|
|
607
|
+
// codes, and only on the effective win32 platform, so a permanent failure still
|
|
608
|
+
// surfaces as the original error instead of a successful install.
|
|
609
|
+
const WINDOWS_PUBLICATION_RETRY_DELAYS_MS = [200, 400, 800, 1600];
|
|
610
|
+
|
|
611
|
+
function isRetryableWindowsPublicationLock(error) {
|
|
612
|
+
return Boolean(error && typeof error === "object" && ["EPERM", "EBUSY", "EACCES"].includes(error.code));
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
async function renameWithWindowsPublicationRetry(from, to, options) {
|
|
616
|
+
const renameFile = options.rename ?? rename;
|
|
617
|
+
if ((options.platform ?? process.platform) !== "win32") return renameFile(from, to);
|
|
618
|
+
for (let attempt = 0; attempt <= WINDOWS_PUBLICATION_RETRY_DELAYS_MS.length; attempt += 1) {
|
|
619
|
+
try { return await renameFile(from, to); }
|
|
620
|
+
catch (error) {
|
|
621
|
+
if (attempt === WINDOWS_PUBLICATION_RETRY_DELAYS_MS.length || !isRetryableWindowsPublicationLock(error)) throw error;
|
|
622
|
+
await new Promise((resolve) => setTimeout(resolve, WINDOWS_PUBLICATION_RETRY_DELAYS_MS[attempt]));
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
587
627
|
async function publishBundle(runtimeRoot, stagingDirectory, options) {
|
|
588
|
-
const versionDirectory = join(runtimeRoot, `v${INSTALLER_VERSION}`)
|
|
628
|
+
const versionDirectory = join(runtimeRoot, `v${INSTALLER_VERSION}`);
|
|
589
629
|
const backupDirectory = join(runtimeRoot, `.v${INSTALLER_VERSION}.backup-${process.pid}-${Date.now()}`);
|
|
590
630
|
let movedPrior = false;
|
|
591
631
|
try {
|
|
592
632
|
try {
|
|
593
633
|
const current = await lstat(versionDirectory);
|
|
594
634
|
if (!current.isDirectory() || current.isSymbolicLink()) throw new Error("Gentle AI package-local version directory must be a real directory");
|
|
595
|
-
await
|
|
635
|
+
await renameWithWindowsPublicationRetry(versionDirectory, backupDirectory, options);
|
|
596
636
|
movedPrior = true;
|
|
597
637
|
} catch (error) { if (!(error && typeof error === "object" && error.code === "ENOENT")) throw error; }
|
|
598
|
-
await
|
|
638
|
+
await renameWithWindowsPublicationRetry(stagingDirectory, versionDirectory, options);
|
|
599
639
|
} catch (error) {
|
|
600
640
|
if (movedPrior) {
|
|
601
|
-
try { await
|
|
641
|
+
try { await renameWithWindowsPublicationRetry(backupDirectory, versionDirectory, options); }
|
|
602
642
|
catch (rollbackError) { throw new Error("Gentle AI bundle publication failed and rollback could not restore the prior bundle", { cause: rollbackError }); }
|
|
603
643
|
}
|
|
604
644
|
throw error;
|
|
@@ -622,31 +662,39 @@ async function installWindowsGentleAiFromGoSumdb(options, packageRoot, architect
|
|
|
622
662
|
return withInstallLock(packageRoot, options, async (runtimeRoot) => {
|
|
623
663
|
await cleanupStaleStagingBundles(runtimeRoot);
|
|
624
664
|
const stagingDirectory = await mkdtemp(join(runtimeRoot, `.v${INSTALLER_VERSION}.staging-`));
|
|
665
|
+
// Go builds in a short private directory under the system temp directory, never inside
|
|
666
|
+
// the package: from a deep pnpm store path (179 characters, run 38063142924) the
|
|
667
|
+
// working directories Go gives asm.exe inside GOMODCACHE exceed MAX_PATH and
|
|
668
|
+
// CreateProcess fails with "The directory name is invalid."
|
|
669
|
+
let buildDirectory = null;
|
|
625
670
|
try {
|
|
626
671
|
await chmod(stagingDirectory, 0o700);
|
|
627
|
-
|
|
628
|
-
await
|
|
672
|
+
buildDirectory = await mkdtemp(join(options.temporaryDirectory ?? tmpdir(), "gai-"));
|
|
673
|
+
await chmod(buildDirectory, 0o700);
|
|
629
674
|
const goPath = await resolveWindowsGoExecutable(options);
|
|
630
675
|
const environment = sealedGoEnvironment(goPath, buildDirectory, architecture);
|
|
631
676
|
for (const directory of [environment.GOBIN, environment.GOPATH, environment.GOMODCACHE, environment.GOCACHE, environment.TEMP]) await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
632
677
|
await recoverInterruptedPublication(runtimeRoot, (directory) => existingWindowsSourceBundleMatches(directory, execute, goPath, environment, architecture), options);
|
|
633
678
|
const existing = versionBundlePath(runtimeRoot);
|
|
634
679
|
if (await existingWindowsSourceBundleMatches(existing, execute, goPath, environment, architecture)) return { installed: false, binaryPath: join(existing, "gentle-ai.exe"), method: GENTLE_AI_INSTALL_METHOD.GO_SUMDB_SOURCE_BUILD };
|
|
635
|
-
await assertGoToolchain(execute, goPath, environment,
|
|
636
|
-
try { await runCommand(execute, goPath, ["install", GENTLE_AI_WINDOWS_SOURCE_PACKAGE], commandOptions(environment,
|
|
680
|
+
await assertGoToolchain(execute, goPath, environment, buildDirectory);
|
|
681
|
+
try { await runCommand(execute, goPath, ["install", GENTLE_AI_WINDOWS_SOURCE_PACKAGE], commandOptions(environment, buildDirectory, GO_INSTALL_TIMEOUT_MS)); }
|
|
637
682
|
catch (error) { throw new GentleAiInstallerError(GENTLE_AI_GO_INSTALL_FAILED_CODE, `Gentle AI Go SumDB source installation failed for ${GENTLE_AI_WINDOWS_SOURCE_PACKAGE}.`, error); }
|
|
638
683
|
const builtBinary = join(environment.GOBIN, "gentle-ai.exe"), binaryPath = join(stagingDirectory, "gentle-ai.exe");
|
|
639
684
|
const details = await lstat(builtBinary);
|
|
640
685
|
if (!details.isFile() || details.isSymbolicLink()) throw new GentleAiInstallerError(GENTLE_AI_GO_INSTALL_FAILED_CODE, "Gentle AI Go installation produced a non-regular gentle-ai.exe.");
|
|
641
686
|
await copyFile(builtBinary, binaryPath);
|
|
642
|
-
const metadata = await verifyGoBuildMetadata(execute, goPath, binaryPath, environment,
|
|
643
|
-
await assertExactGentleAiVersion(execute, binaryPath, environment,
|
|
687
|
+
const metadata = await verifyGoBuildMetadata(execute, goPath, binaryPath, environment, buildDirectory, architecture);
|
|
688
|
+
await assertExactGentleAiVersion(execute, binaryPath, environment, buildDirectory);
|
|
644
689
|
const binarySha256 = await sha256File(binaryPath);
|
|
645
690
|
await writeFile(join(stagingDirectory, "integrity.json"), canonicalManifest(windowsSourceManifest(metadata, binarySha256, architecture)), { mode: 0o600 });
|
|
646
691
|
await safeRemoveDirectory(buildDirectory);
|
|
647
692
|
const published = await publishBundle(runtimeRoot, stagingDirectory, options);
|
|
648
693
|
return { installed: true, binaryPath: join(published, "gentle-ai.exe"), method: GENTLE_AI_INSTALL_METHOD.GO_SUMDB_SOURCE_BUILD };
|
|
649
|
-
} finally {
|
|
694
|
+
} finally {
|
|
695
|
+
try { if (buildDirectory !== null) await safeRemoveDirectory(buildDirectory); }
|
|
696
|
+
finally { await safeRemoveDirectory(stagingDirectory); }
|
|
697
|
+
}
|
|
650
698
|
});
|
|
651
699
|
}
|
|
652
700
|
|
|
@@ -8,6 +8,52 @@ import { fileURLToPath } from "node:url";
|
|
|
8
8
|
import { INSTALLER_VERSION, installGentleAi } from "./gentle-ai-installer.mjs";
|
|
9
9
|
import { installTuiModeSetting, isPiManagedInstall } from "./install-tui-mode-setting.mjs";
|
|
10
10
|
|
|
11
|
+
const FAILURE_CAUSE_DEPTH = 6;
|
|
12
|
+
const FAILURE_OUTPUT_TAIL = 4000;
|
|
13
|
+
|
|
14
|
+
function failureOutputs(error) {
|
|
15
|
+
return ["stderr", "stdout"].map((stream) => [stream, typeof error?.[stream] === "string" ? error[stream].trimEnd() : ""]).filter(([, text]) => text.trim().length > 0);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function failureHeadline(error, outputs) {
|
|
19
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20
|
+
// A child-process error repeats its stderr after the first message line; it is printed below, bounded.
|
|
21
|
+
const text = outputs.length > 0 ? message.split(/\r?\n/)[0] : message;
|
|
22
|
+
const status = [
|
|
23
|
+
...(typeof error?.code === "string" || typeof error?.code === "number" ? [`code ${error.code}`] : []),
|
|
24
|
+
...(typeof error?.signal === "string" ? [`signal ${error.signal}`] : []),
|
|
25
|
+
];
|
|
26
|
+
return `${text}${status.length > 0 ? ` (${status.join(", ")})` : ""}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A failed installation for the postinstall's stderr: the error's message, then its
|
|
31
|
+
* `cause` chain (at most 6 deep) with each code and the last 4000 characters of any
|
|
32
|
+
* stdout/stderr a failed command left on it. When a cause carried output, the last
|
|
33
|
+
* line repeats the deepest one's last output line in `Error:` form, the line the
|
|
34
|
+
* installer wizard's one-line failure detail selects after pnpm's own summary.
|
|
35
|
+
*/
|
|
36
|
+
function describeGentleAiInstallFailure(error) {
|
|
37
|
+
const lines = [];
|
|
38
|
+
let rootCause = null;
|
|
39
|
+
let cause = error;
|
|
40
|
+
for (let depth = 0; cause !== undefined && cause !== null && depth <= FAILURE_CAUSE_DEPTH; depth += 1) {
|
|
41
|
+
const outputs = failureOutputs(cause);
|
|
42
|
+
const name = cause instanceof Error && typeof cause.name === "string" && cause.name !== "" ? cause.name : "Error";
|
|
43
|
+
lines.push(depth === 0 ? failureHeadline(cause, outputs) : ` caused by ${name}: ${failureHeadline(cause, outputs)}`);
|
|
44
|
+
for (const [stream, text] of outputs) {
|
|
45
|
+
const tail = text.length > FAILURE_OUTPUT_TAIL ? text.slice(-FAILURE_OUTPUT_TAIL) : text;
|
|
46
|
+
lines.push(` ${stream}${tail === text ? "" : ` (last ${FAILURE_OUTPUT_TAIL} characters)`}:`, ...tail.split(/\r?\n/).map((line) => ` | ${line}`));
|
|
47
|
+
}
|
|
48
|
+
// stderr first: a deeper cause's output replaces a shallower one's.
|
|
49
|
+
if (outputs.length > 0) rootCause = outputs[0][1].split(/\r?\n/).findLast((line) => line.trim().length > 0).trim();
|
|
50
|
+
cause = cause instanceof Error ? cause.cause : undefined;
|
|
51
|
+
}
|
|
52
|
+
if (cause !== undefined && cause !== null) lines.push(" ... further causes omitted");
|
|
53
|
+
if (rootCause !== null) lines.push(` root cause Error: ${rootCause}`);
|
|
54
|
+
return lines.join("\n");
|
|
55
|
+
}
|
|
56
|
+
|
|
11
57
|
if (process.env.GENTLE_PI_SKIP_GENTLE_AI_INSTALL === "1") {
|
|
12
58
|
console.warn("GENTLE_PI_SKIP_GENTLE_AI_INSTALL=1: skipped package-local Gentle AI installation; native review operations will fail with package-local-binary-missing until gentle-pi is reinstalled.");
|
|
13
59
|
} else {
|
|
@@ -15,7 +61,8 @@ if (process.env.GENTLE_PI_SKIP_GENTLE_AI_INSTALL === "1") {
|
|
|
15
61
|
const result = await installGentleAi();
|
|
16
62
|
console.log(`Gentle AI v${INSTALLER_VERSION} ${result.installed ? "installed" : "integrity-verified"} at ${result.binaryPath}`);
|
|
17
63
|
} catch (error) {
|
|
18
|
-
|
|
64
|
+
// The whole cause chain: the failing command's own output is the diagnosis.
|
|
65
|
+
console.error(`gentle-pi could not install its package-local Gentle AI v${INSTALLER_VERSION} binary: ${describeGentleAiInstallFailure(error)}`);
|
|
19
66
|
process.exitCode = 1;
|
|
20
67
|
}
|
|
21
68
|
}
|
|
@@ -65,6 +65,15 @@ function sameFile(left, right) {
|
|
|
65
65
|
return left && right && left.dev === right.dev && left.ino === right.ino;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
// A path's lstat against the fstat of the handle just opened on it. On recent
|
|
69
|
+
// Windows, lstat by path reports dev 0 (no volume serial) while fstat reports
|
|
70
|
+
// the volume's (#1258, #2109): there the file index alone identifies it, and a
|
|
71
|
+
// known dev must still match. POSIX keeps the full device and inode check.
|
|
72
|
+
export function sameOpenedFile(pathStat, handleStat, platform = process.platform) {
|
|
73
|
+
if (platform !== "win32" || !pathStat || !handleStat || (pathStat.dev !== 0 && handleStat.dev !== 0)) return Boolean(sameFile(pathStat, handleStat));
|
|
74
|
+
return pathStat.ino === handleStat.ino;
|
|
75
|
+
}
|
|
76
|
+
|
|
68
77
|
function assertDirectories(paths) {
|
|
69
78
|
for (const path of paths) {
|
|
70
79
|
const stat = inspect(path);
|
|
@@ -80,7 +89,7 @@ function readSettings(path) {
|
|
|
80
89
|
if (!before.isFile() || before.isSymbolicLink()) throw new Error("settings.json must be a regular, non-symlink file");
|
|
81
90
|
const fd = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
82
91
|
try {
|
|
83
|
-
if (!
|
|
92
|
+
if (!sameOpenedFile(before, fstatSync(fd))) throw new Error("settings.json changed while opening");
|
|
84
93
|
const text = readFileSync(fd, "utf8");
|
|
85
94
|
const value = JSON.parse(text.replace(/^\uFEFF/, ""));
|
|
86
95
|
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("settings.json must contain a JSON object");
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Development preview for the browser installation wizard.
|
|
3
|
+
//
|
|
4
|
+
// Starts the REAL local host (createInstallerServer) with the real assets, but
|
|
5
|
+
// with fake inventories and a fake runner: it never runs host probes, package
|
|
6
|
+
// managers, downloads or installs. Use it to develop and visually check the UI:
|
|
7
|
+
//
|
|
8
|
+
// node scripts/install-wizard-preview.mjs --scenario=terminal --step-ms=600
|
|
9
|
+
//
|
|
10
|
+
// Then open the printed one-time session URL. Not part of the installer entry.
|
|
11
|
+
|
|
12
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
13
|
+
import { planPreflight, requirements } from "./installer-preflight.mjs";
|
|
14
|
+
import { createInstallerServer } from "./installer-server.mjs";
|
|
15
|
+
|
|
16
|
+
const BIN = "/home/you/.local/share/pnpm/bin";
|
|
17
|
+
const baseInventory = Object.freeze({
|
|
18
|
+
platform: "linux",
|
|
19
|
+
arch: "x64",
|
|
20
|
+
node: { available: true, version: "24.21.0", usable: true, persistent: true, npm: true },
|
|
21
|
+
pnpm: { available: true, version: "11.1.1", usable: true, compatible: true, persistent: true },
|
|
22
|
+
pi: { available: false },
|
|
23
|
+
shell: { available: false },
|
|
24
|
+
gentleAi: { available: false },
|
|
25
|
+
go: { available: false },
|
|
26
|
+
globalBin: { available: true, path: BIN, writable: true, onPath: true },
|
|
27
|
+
setup: false,
|
|
28
|
+
});
|
|
29
|
+
// A machine that only has the bootstrap's temporary Node and pnpm, with no pnpm bin on PATH.
|
|
30
|
+
const freshInventory = Object.freeze({
|
|
31
|
+
...baseInventory,
|
|
32
|
+
node: { available: true, version: "24.21.0", usable: true, persistent: false, npm: false },
|
|
33
|
+
pnpm: { available: true, version: "11.1.1", usable: true, compatible: true, persistent: false },
|
|
34
|
+
globalBin: { ...baseInventory.globalBin, onPath: false },
|
|
35
|
+
});
|
|
36
|
+
// An earlier run installed the pinned stack, but its setup and `pnpm setup` did not finish.
|
|
37
|
+
const recoveryInventory = Object.freeze({
|
|
38
|
+
...baseInventory,
|
|
39
|
+
pi: { available: true, version: "1.0.0", usable: true },
|
|
40
|
+
shell: { available: true, version: requirements.shell, usable: true, global: true },
|
|
41
|
+
gentleAi: { available: true, version: requirements.gentleAi, usable: true, compatible: true },
|
|
42
|
+
globalBin: { ...baseInventory.globalBin, onPath: false },
|
|
43
|
+
setup: { available: true, recoverable: true },
|
|
44
|
+
});
|
|
45
|
+
const checks = ["check-npm", "check-global-bin", "check-existing-stack"];
|
|
46
|
+
const install = ["install-global", "verify-global-list", "verify-shell-bin", "verify-gentle-ai", "shell-setup"];
|
|
47
|
+
const persistence = ["persist-node", "persist-package-managers", "verify-persistent-runtime", "check-npm", "configure-npm-prefix"];
|
|
48
|
+
|
|
49
|
+
// Each scenario mirrors the runner's fixed step order for its plan.
|
|
50
|
+
const scenarios = Object.freeze({
|
|
51
|
+
ready: { inventories: [baseInventory], steps: [...checks, ...install], result: { outcome: "ready" } },
|
|
52
|
+
terminal: {
|
|
53
|
+
inventories: [freshInventory],
|
|
54
|
+
steps: ["check-global-bin", "check-existing-stack", ...persistence, ...install, "persist-path"],
|
|
55
|
+
result: { outcome: "terminal-action-required", action: "open-new-terminal", npmPrefix: "configured" },
|
|
56
|
+
},
|
|
57
|
+
blocked: {
|
|
58
|
+
inventories: [baseInventory],
|
|
59
|
+
steps: ["check-npm", "check-global-bin"],
|
|
60
|
+
result: { outcome: "blocked", reason: "existing-stack" },
|
|
61
|
+
},
|
|
62
|
+
failed: {
|
|
63
|
+
inventories: [freshInventory],
|
|
64
|
+
steps: ["check-global-bin", "check-existing-stack", ...persistence],
|
|
65
|
+
result: { outcome: "failed", failedStep: "install-global" },
|
|
66
|
+
},
|
|
67
|
+
// The first plan goes stale before install (the PATH was fixed meanwhile): the host
|
|
68
|
+
// answers 409 plan-changed and the wizard must show the new plan and ask again.
|
|
69
|
+
"plan-changed": { inventories: [freshInventory, baseInventory], steps: [...checks, ...install], result: { outcome: "ready" } },
|
|
70
|
+
recovery: {
|
|
71
|
+
inventories: [recoveryInventory],
|
|
72
|
+
steps: ["check-npm", "check-global-bin", "check-recoverable-stack", ...install.slice(1), "persist-path"],
|
|
73
|
+
result: { outcome: "terminal-action-required", action: "open-new-terminal" },
|
|
74
|
+
},
|
|
75
|
+
preflight: {
|
|
76
|
+
inventories: [{ ...baseInventory, node: { available: true, version: "18.20.0", usable: true, persistent: true, npm: true },
|
|
77
|
+
gentleAi: { available: true, version: "1.0.0", usable: true, compatible: false } }],
|
|
78
|
+
steps: [],
|
|
79
|
+
result: { outcome: "blocked", reason: "preflight-blocked" },
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
export const scenarioNames = Object.freeze(Object.keys(scenarios));
|
|
83
|
+
|
|
84
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
85
|
+
|
|
86
|
+
/** Fake collectPlan/runInstall for a scenario; nothing touches the machine. */
|
|
87
|
+
export function previewScenario(name, { stepMs = 600 } = {}) {
|
|
88
|
+
const scenario = scenarios[name];
|
|
89
|
+
if (!scenario) throw new Error(`Unknown scenario "${name}". Use one of: ${scenarioNames.join(", ")}`);
|
|
90
|
+
let collected = 0;
|
|
91
|
+
return {
|
|
92
|
+
collectPlan: async () => {
|
|
93
|
+
const inventory = structuredClone(scenario.inventories[Math.min(collected, scenario.inventories.length - 1)]);
|
|
94
|
+
collected += 1;
|
|
95
|
+
await sleep(Math.min(stepMs, 400));
|
|
96
|
+
return { inventory, plan: planPreflight(inventory) };
|
|
97
|
+
},
|
|
98
|
+
runInstall: async (_request, log) => {
|
|
99
|
+
const completed = [];
|
|
100
|
+
for (const step of scenario.steps) {
|
|
101
|
+
await sleep(stepMs);
|
|
102
|
+
completed.push(step);
|
|
103
|
+
log({ step, status: "done" });
|
|
104
|
+
}
|
|
105
|
+
await sleep(stepMs);
|
|
106
|
+
const { result } = scenario;
|
|
107
|
+
if (result.outcome === "blocked") log({ step: "gate", status: "blocked", reason: result.reason });
|
|
108
|
+
if (result.outcome === "failed") log({ step: result.failedStep, status: "failed" });
|
|
109
|
+
return { ...result, completed };
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Starts the real host on 127.0.0.1 with a fake scenario. Returns { host, url }. */
|
|
115
|
+
export async function startPreview({ scenario = "ready", stepMs = 600, log = console.log } = {}) {
|
|
116
|
+
const fake = previewScenario(scenario, { stepMs });
|
|
117
|
+
const host = createInstallerServer({
|
|
118
|
+
assetsDir: fileURLToPath(new URL("../assets/install-wizard/", import.meta.url)),
|
|
119
|
+
collectPlan: fake.collectPlan,
|
|
120
|
+
runInstall: fake.runInstall,
|
|
121
|
+
});
|
|
122
|
+
const { url } = await host.listen();
|
|
123
|
+
log(`Gentle Shell wizard preview (scenario: ${scenario}). Fake plan and fake installer: nothing is installed.`);
|
|
124
|
+
log(`Open this one-time link (expires in 2 minutes):\n ${url}`);
|
|
125
|
+
return { host, url };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function option(args, name, fallback) {
|
|
129
|
+
const found = args.find((arg) => arg.startsWith(`--${name}=`));
|
|
130
|
+
return found === undefined ? fallback : found.slice(name.length + 3);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function main(args) {
|
|
134
|
+
if (args.includes("--help")) {
|
|
135
|
+
console.log(`Usage: node scripts/install-wizard-preview.mjs [--scenario=${scenarioNames.join("|")}] [--step-ms=600]`);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const scenario = option(args, "scenario", "ready");
|
|
139
|
+
const stepMs = Number(option(args, "step-ms", "600"));
|
|
140
|
+
if (!scenarioNames.includes(scenario) || !Number.isFinite(stepMs) || stepMs < 0) {
|
|
141
|
+
console.error(`Use --scenario=${scenarioNames.join("|")} and a non-negative --step-ms.`);
|
|
142
|
+
process.exitCode = 2;
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const { host } = await startPreview({ scenario, stepMs });
|
|
146
|
+
process.once("SIGINT", () => host.close("signal"));
|
|
147
|
+
process.once("SIGTERM", () => host.close("signal"));
|
|
148
|
+
const { reason, outcome } = await host.closed;
|
|
149
|
+
console.log(`Preview closed (${reason}); last outcome: ${outcome?.outcome ?? "none"}.`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
153
|
+
await main(process.argv.slice(2));
|
|
154
|
+
}
|