gentle-pi 3.7.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +155 -10
- package/assets/agents/gentle-ai-explore.md +13 -6
- package/assets/agents/gentle-ai-verify.md +27 -6
- package/assets/agents/gentle-ai-worker.md +13 -15
- package/assets/install-wizard/index.html +47 -0
- package/assets/install-wizard/wizard.css +1279 -0
- package/assets/install-wizard/wizard.js +951 -0
- package/assets/orchestrator-delegation.md +75 -169
- package/assets/orchestrator-memory.md +12 -26
- package/assets/orchestrator-prompts.md +48 -0
- package/assets/orchestrator-skills.md +1 -1
- package/assets/orchestrator-tracking.md +53 -0
- package/assets/orchestrator-verification.md +28 -0
- package/assets/orchestrator-writer.md +38 -0
- package/assets/orchestrator.md +30 -38
- package/assets/sounds/LICENSE.md +52 -0
- package/assets/sounds/attention.wav +0 -0
- package/assets/sounds/error.wav +0 -0
- package/assets/sounds/success.wav +0 -0
- package/assets/support/strict-tdd-verify.md +4 -266
- package/assets/support/strict-tdd.md +8 -360
- package/bin/engram-guard +9 -0
- package/bin/gentle-shell-install.mjs +302 -0
- package/bin/gentle-shell.mjs +956 -50
- package/contracts/review-provider-contract-mirror/provider-contract.lock.json +7 -7
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/manifest.json +2 -2
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/orchestration/pi.md +16 -4
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-capabilities.baseline.json +2 -2
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-roles.baseline.json +1 -1
- package/docs/bundled-install.md +397 -0
- package/docs/delegated-verification.md +27 -4
- package/docs/gentle-agents-activity.md +537 -0
- package/docs/gentle-shell-uninstall.md +136 -0
- package/docs/gentle-shell.md +165 -51
- package/docs/install-wizard.md +2040 -0
- package/docs/native-authority-architecture.md +3 -3
- package/docs/permission-modes.md +82 -0
- package/docs/prompt-history.md +280 -0
- package/docs/readme-reference.md +364 -231
- package/docs/render-cache-performance.md +28 -0
- package/docs/sound-notifications-proposal.md +191 -0
- package/docs/sound-notifications.md +114 -0
- package/docs/telemetry.md +1 -1
- package/docs/yolo-mode.md +86 -0
- package/extensions/child-context.ts +35 -0
- package/extensions/child-safety.ts +25 -0
- package/extensions/codegraph-tools.ts +18 -5
- package/extensions/gentle-agents.ts +1093 -326
- package/extensions/gentle-ai.ts +1636 -890
- package/extensions/gentle-herdr-activity.ts +81 -0
- package/extensions/gentle-notifications.ts +104 -0
- package/extensions/gentle-shell.ts +1619 -136
- package/extensions/gentle-stats.ts +101 -0
- package/extensions/gentle-todo.ts +126 -20
- package/extensions/history/atomic-write.ts +38 -0
- package/extensions/history/hide-prompts.ts +183 -0
- package/extensions/history/index.ts +1424 -0
- package/extensions/history/load-shared-history.ts +39 -0
- package/extensions/history/selector-helpers.ts +538 -0
- package/extensions/history/session-scan.ts +233 -0
- package/extensions/history/store.ts +1119 -0
- package/extensions/nan-provider.ts +6 -0
- package/extensions/pi-pretty.ts +39 -3
- package/extensions/quiet-tools.ts +254 -92
- package/extensions/resume-hint.ts +60 -0
- package/extensions/skill-registry.ts +191 -58
- package/extensions/startup-banner.ts +71 -33
- package/lib/agent-assets.ts +604 -0
- package/lib/agent-home.ts +10 -1
- package/lib/agent-model-resolution.ts +80 -0
- package/lib/agent-profile-pin.ts +12 -0
- package/lib/agent-profiles.ts +13 -3
- package/lib/agents-completion-delivery.ts +11 -3
- package/lib/agents-message-delivery.ts +181 -0
- package/lib/agents-messaging.ts +1 -1
- package/lib/agents-protocol.ts +99 -0
- package/lib/agents-runner.ts +180 -107
- package/lib/agents-view.ts +26 -3
- package/lib/agents-widget.ts +16 -9
- package/lib/append-system-prompt.ts +21 -0
- package/lib/auto-permission-classifier.ts +280 -0
- package/lib/auto-permission-runtime.ts +268 -0
- package/lib/background-jobs-tools.ts +202 -0
- package/lib/background-jobs.ts +253 -0
- package/lib/background-monitor.ts +154 -0
- package/lib/bounded-writer-admission.ts +220 -0
- package/lib/builtin-codemode-optout.ts +166 -0
- package/lib/card-content-policy.ts +85 -0
- package/lib/card-style-policy.ts +60 -0
- package/lib/child-context-files.ts +166 -0
- package/lib/child-package-injection.ts +62 -0
- package/lib/codemode-renderer.ts +195 -0
- package/lib/command-palette-catalog.ts +5 -10
- package/lib/command-palette.ts +25 -14
- package/lib/context-threshold.ts +26 -0
- package/lib/destructive-command-guard.ts +144 -0
- package/lib/gentle-ai-binary.ts +109 -4
- package/lib/gentle-ai-elapsed-store.ts +87 -0
- package/lib/gentle-ai-renderer.ts +196 -39
- package/lib/gentle-shell-bundled.ts +517 -0
- package/lib/gentle-shell-launcher.ts +89 -27
- package/lib/gentle-shell-resume-hint.ts +176 -0
- package/lib/gentle-shell-startup-flow.ts +54 -0
- package/lib/gentle-shell-startup-update.ts +92 -0
- package/lib/gentle-shell-uninstall.ts +840 -0
- package/lib/gentle-shell-update-panel.ts +231 -0
- package/lib/herdr-activity.ts +183 -0
- package/lib/history-capture-policy.ts +95 -0
- package/lib/inprocess-reviewer.ts +3 -0
- package/lib/jobs-sidebar-state.ts +20 -0
- package/lib/jobs-view.ts +161 -0
- package/lib/model-price-ratio.ts +83 -0
- package/lib/model-routing-authority.ts +5 -1
- package/lib/nan-provider.ts +249 -0
- package/lib/native-review-cli.ts +242 -180
- package/lib/notification-audio-native.ts +162 -0
- package/lib/notification-audio-windows.ts +399 -0
- package/lib/notification-audio.ts +332 -0
- package/lib/notification-customize.ts +241 -0
- package/lib/notification-events.ts +113 -0
- package/lib/notification-policy.ts +148 -0
- package/lib/notification-pulse-client.ts +367 -0
- package/lib/notification-pulse-protocol.ts +305 -0
- package/lib/notification-pulse-stream.ts +213 -0
- package/lib/notification-pulse-worker.ts +66 -0
- package/lib/notification-scheduler.ts +173 -0
- package/lib/notification-service.ts +202 -0
- package/lib/notification-ui.ts +87 -0
- package/lib/odd-phase-inference.ts +231 -0
- package/lib/odd-phase.ts +141 -0
- package/lib/orchestrator-alias.ts +20 -0
- package/lib/orchestrator-catalog.ts +79 -0
- package/lib/orchestrator-consultation.ts +61 -0
- package/lib/orchestrator-discovery.ts +49 -0
- package/lib/orchestrator-helper-consent.ts +122 -0
- package/lib/orchestrator-helper.ts +167 -0
- package/lib/orchestrator-message-card.ts +105 -0
- package/lib/orchestrator-presence.ts +115 -14
- package/lib/orchestrator-scope.ts +80 -0
- package/lib/orchestrator-state.ts +107 -0
- package/lib/orchestrator-tool-card.ts +280 -0
- package/lib/orchestrator-work-search.ts +218 -0
- package/lib/orchestrator-work.ts +79 -0
- package/lib/overlay-repaint.ts +26 -0
- package/lib/permission-mode-policy.ts +59 -0
- package/lib/pi-tui-keys.ts +53 -0
- package/lib/review-candidate-view-owner.ts +73 -21
- package/lib/review-candidate-view.ts +115 -27
- package/lib/review-compact-contract.ts +2 -2
- package/lib/review-host-relay.ts +7 -2
- package/lib/review-integration-v2.ts +100 -22
- package/lib/review-publication-gate.ts +1 -1
- package/lib/review-reminder-receipt.ts +48 -8
- package/lib/review-risk-assessment.ts +227 -56
- package/lib/review-sidebar-state.ts +224 -0
- package/lib/review-transaction.ts +72 -3
- package/lib/runtime-metrics-children.ts +1 -1
- package/lib/runtime-metrics-native.ts +3 -2
- package/lib/selection-engine.ts +527 -0
- package/lib/session-change-capture.ts +5 -4
- package/lib/session-changes.ts +27 -22
- package/lib/session-messaging-grants.ts +135 -0
- package/lib/session-profile-binding.ts +83 -0
- package/lib/session-profile-freeze.ts +266 -0
- package/lib/session-worktree-registry.ts +87 -9
- package/lib/shell-bar.ts +215 -30
- package/lib/shell-card.ts +287 -18
- package/lib/shell-changes-view.ts +42 -14
- package/lib/shell-changes.ts +86 -71
- package/lib/shell-prompt.ts +102 -6
- package/lib/shell-sidebar-layout.ts +83 -15
- package/lib/shell-sidebar.ts +15 -1
- package/lib/shell-todo.ts +84 -31
- package/lib/shell-usage-view.ts +9 -4
- package/lib/shell-usage.ts +70 -13
- package/lib/stats-collector.ts +381 -0
- package/lib/stats-view.ts +431 -0
- package/lib/status-timing-diagnostics.ts +201 -0
- package/lib/telemetry-trigger.ts +3 -1
- package/lib/theme-customization.ts +52 -0
- package/lib/vim-editor-adapter.ts +383 -0
- package/lib/vim-normal-engine.ts +154 -0
- package/lib/vim-operator-engine.ts +416 -0
- package/lib/vim-policy.ts +49 -0
- package/lib/vim-visual-engine.ts +107 -0
- package/lib/visual-customization-policy.ts +119 -0
- package/lib/visual-customize-view.ts +481 -0
- package/lib/visual-profiles.ts +228 -0
- package/lib/writer-surfaces.ts +162 -0
- package/lib/yolo-session-policy.ts +317 -0
- package/package.json +20 -8
- package/runtime/child-package-injection.mjs +63 -0
- package/runtime/gentle-ai-binary.mjs +109 -4
- package/runtime/gentle-shell-bundled.mjs +518 -0
- package/runtime/gentle-shell-launcher.mjs +87 -25
- package/runtime/gentle-shell-resume-hint.mjs +177 -0
- package/runtime/gentle-shell-startup-flow.mjs +55 -0
- package/runtime/gentle-shell-startup-update.mjs +93 -0
- package/runtime/gentle-shell-uninstall.mjs +841 -0
- package/runtime/gentle-shell-update-panel.mjs +232 -0
- package/runtime/native-review-cli.mjs +239 -177
- package/runtime/review-integration-v2.mjs +100 -22
- package/runtime/review-risk-assessment.mjs +225 -54
- package/runtime/status-timing-diagnostics.mjs +202 -0
- package/runtime/telemetry-trigger.mjs +3 -1
- package/scripts/bootstrap.cmd +255 -0
- package/scripts/bootstrap.sh +168 -0
- package/scripts/build-distribution.mjs +157 -0
- package/scripts/build-installer-bundles.mjs +136 -0
- package/scripts/build-runtime-modules.mjs +8 -0
- package/scripts/bundled-install.mjs +1308 -0
- package/scripts/bundled-wizard.mjs +399 -0
- package/scripts/check-types.mjs +4 -6
- package/scripts/gentle-ai-installer.mjs +83 -35
- package/scripts/install-gentle-ai.mjs +48 -1
- package/scripts/install-tui-mode-setting.mjs +10 -1
- package/scripts/install-wizard-preview.mjs +154 -0
- package/scripts/installer-downloads.mjs +648 -0
- package/scripts/installer-preflight.mjs +387 -0
- package/scripts/installer-probes.mjs +564 -0
- package/scripts/installer-runner.mjs +1102 -0
- package/scripts/installer-server.mjs +881 -0
- package/scripts/installer-windows-artifacts.json +15 -0
- package/scripts/installer-windows.mjs +752 -0
- package/scripts/main-channel.mjs +452 -0
- package/scripts/mirror-odd-routing.mjs +2 -2
- package/scripts/npm-pack-result.mjs +31 -0
- package/scripts/render-cache-performance-worker.mjs +205 -0
- package/scripts/render-cache-performance.mjs +115 -0
- package/scripts/run-test-suite.mjs +90 -0
- package/scripts/test-installer-acceptance.sh +161 -0
- package/scripts/test-packed-runner.mjs +54 -19
- package/scripts/types-baseline.json +2 -92
- package/scripts/verify-package-files.mjs +91 -38
- package/skills/branch-pr/SKILL.md +24 -52
- package/skills/chained-pr/SKILL.md +31 -15
- package/skills/chained-pr/references/chaining-details.md +31 -20
- package/skills/gentle-ai/SKILL.md +16 -21
- package/skills/issue-creation/SKILL.md +8 -2
- package/skills/issue-creation/references/delegated-workflow-actions.md +19 -0
- package/skills/work-unit-commits/SKILL.md +4 -3
- package/tests/agent-home.test.ts +56 -3
- package/tests/agent-model-resolution.test.ts +113 -0
- package/tests/agent-profiles.test.ts +30 -0
- package/tests/agent-risk-escalation.test.ts +123 -0
- package/tests/agents-completion-delivery.test.ts +17 -0
- package/tests/agents-fake-child.ts +2 -2
- package/tests/agents-message-delivery.test.ts +106 -0
- package/tests/agents-protocol.test.ts +97 -0
- package/tests/agents-queries.test.ts +9 -9
- package/tests/agents-runner-process.test.ts +31 -0
- package/tests/agents-runner.test.ts +467 -93
- package/tests/agents-view-thread-identity.test.ts +169 -0
- package/tests/agents-view.test.ts +8 -2
- package/tests/agents-widget.test.ts +154 -15
- package/tests/append-system-prompt-route.test.ts +160 -0
- package/tests/append-system-prompt.test.ts +46 -0
- package/tests/artifact-language.test.ts +19 -213
- package/tests/ask-user-question.test.ts +44 -1
- package/tests/asset-installation-runtime.test.ts +5 -16
- package/tests/auto-mode-invariants.test.ts +258 -0
- package/tests/auto-mode-sdk.test.ts +57 -0
- package/tests/auto-mode.test.ts +49 -0
- package/tests/auto-permission-classifier.test.ts +283 -0
- package/tests/auto-permission-runtime.test.ts +196 -0
- package/tests/autonomous-guard.test.ts +69 -1
- package/tests/background-cache-warming.test.ts +2 -1
- package/tests/background-jobs.test.ts +227 -0
- package/tests/background-monitor.test.ts +195 -0
- package/tests/background-subagents-default-mode.test.ts +28 -5
- package/tests/bounded-stop-contract.test.ts +85 -0
- package/tests/bounded-writer-admission.test.ts +146 -0
- package/tests/branch-pr-skill.test.ts +43 -0
- package/tests/bridge-wake-lifecycle.test.ts +129 -0
- package/tests/bridge-wake-rendering.test.ts +56 -0
- package/tests/build-distribution.test.ts +269 -0
- package/tests/builtin-codemode-optout.test.ts +242 -0
- package/tests/bundled-install.test.ts +1319 -0
- package/tests/bundled-wizard.test.ts +505 -0
- package/tests/card-content-policy.test.ts +68 -0
- package/tests/card-style-policy.test.ts +87 -0
- package/tests/chained-pr-skill.test.ts +124 -0
- package/tests/child-codegraph-bootstrap.test.ts +74 -0
- package/tests/child-context-files.test.ts +255 -0
- package/tests/child-package-entrypoints.test.ts +65 -0
- package/tests/child-package-injection.test.ts +74 -0
- package/tests/child-safety.test.ts +94 -0
- package/tests/child-web-package.test.ts +224 -0
- package/tests/codegraph-tools.test.ts +35 -0
- package/tests/codemode-rendering.test.ts +596 -0
- package/tests/command-palette.test.ts +43 -7
- package/tests/context-threshold.test.ts +31 -0
- package/tests/delegate-for-reason-contract.test.ts +327 -0
- package/tests/delegated-key-learnings-contract.test.ts +3 -78
- package/tests/destructive-command-guard.test.ts +84 -0
- package/tests/devbinary/native-review-parity.devtest.ts +171 -3
- package/tests/devbinary/non-git-subagent-bootstrap.devtest.ts +193 -0
- package/tests/devbinary/pi-host-relay.devtest.ts +173 -3
- package/tests/engram-safety.test.ts +288 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +7 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +3 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T08-00-00-000Z_ddd.jsonl +2 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T09-00-00-000Z_eee.jsonl +3 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/run-1/session.jsonl +2 -0
- package/tests/fixtures/stats/sessions/--work-beta--/2026-09-01T12-00-00-000Z_ccc.jsonl +2 -0
- package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +2 -0
- package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +4 -0
- package/tests/fixtures/stats/user-pi/sessions/--work-gamma--/2026-09-20T09-00-00-000Z_fff.jsonl +3 -0
- package/tests/generic-agent-tools.test.ts +69 -0
- package/tests/gentle-agents.test.ts +2804 -346
- package/tests/gentle-ai-binary-isolation.test.ts +135 -0
- package/tests/gentle-ai-binary.test.ts +21 -4
- package/tests/gentle-ai-child-guards.test.ts +202 -0
- package/tests/gentle-ai-dev-binary-surfacing.test.ts +125 -15
- package/tests/gentle-ai-elapsed-store.test.ts +68 -0
- package/tests/gentle-ai-installer.test.ts +566 -62
- package/tests/gentle-ai-prompt-model-routing.test.ts +123 -0
- package/tests/gentle-ai-renderer.test.ts +493 -14
- package/tests/gentle-ai.test.ts +1427 -236
- package/tests/gentle-card-text.ts +2 -1
- package/tests/gentle-herdr-activity.test.ts +100 -0
- package/tests/gentle-notifications.test.ts +427 -0
- package/tests/gentle-shell-bin.test.ts +896 -115
- package/tests/gentle-shell-bundled.test.ts +1002 -0
- package/tests/gentle-shell-launcher.test.ts +333 -65
- package/tests/gentle-shell-resume-hint.test.ts +270 -0
- package/tests/gentle-shell-startup-flow.test.ts +75 -0
- package/tests/gentle-shell-startup-update.test.ts +85 -0
- package/tests/gentle-shell-uninstall.test.ts +978 -0
- package/tests/gentle-shell-update-panel.test.ts +158 -0
- package/tests/gentle-shell.test.ts +4216 -119
- package/tests/gentle-stats.test.ts +152 -0
- package/tests/gentle-theme.test.ts +4 -1
- package/tests/gentle-todo.test.ts +333 -10
- package/tests/herdr-activity.test.ts +243 -0
- package/tests/history-agent-home.test.ts +81 -0
- package/tests/history-atomic-write.test.ts +57 -0
- package/tests/history-capture-policy.test.ts +102 -0
- package/tests/history-child-guard.test.ts +70 -0
- package/tests/history-command-registration.test.ts +164 -0
- package/tests/history-dedupe-entries.test.ts +123 -0
- package/tests/history-delete-backfill.test.ts +190 -0
- package/tests/history-delete-confirm.test.ts +460 -0
- package/tests/history-dispatch.test.ts +180 -0
- package/tests/history-drain-hidden.test.ts +110 -0
- package/tests/history-drain-order.test.ts +98 -0
- package/tests/history-expanded-globals.test.ts +62 -0
- package/tests/history-gc.test.ts +832 -0
- package/tests/history-header-layout.test.ts +265 -0
- package/tests/history-hide-prompts.test.ts +275 -0
- package/tests/history-lazy-windowing.test.ts +508 -0
- package/tests/history-legacy-migrate-v2.test.ts +297 -0
- package/tests/history-load-shared-history.test.ts +53 -0
- package/tests/history-max-results-cap.test.ts +76 -0
- package/tests/history-multi-reader.test.ts +203 -0
- package/tests/history-off-path.test.ts +170 -0
- package/tests/history-openflow-integration.test.ts +173 -0
- package/tests/history-overlay-margin.test.ts +326 -0
- package/tests/history-preview-layout.test.ts +93 -0
- package/tests/history-registry.test.ts +143 -0
- package/tests/history-scope-delete.test.ts +411 -0
- package/tests/history-search-caret-keys.test.ts +142 -0
- package/tests/history-seed-bootstrap.test.ts +170 -0
- package/tests/history-seed-regen.test.ts +129 -0
- package/tests/history-selector-windowing.test.ts +94 -0
- package/tests/history-session-scan-directory.test.ts +87 -0
- package/tests/history-session-scan-extract.test.ts +583 -0
- package/tests/history-session-writer.test.ts +351 -0
- package/tests/history-store-paths.test.ts +79 -0
- package/tests/history-tombstone-exact.test.ts +139 -0
- package/tests/history-wheel-mouse.test.ts +242 -0
- package/tests/inprocess-reviewer.test.ts +49 -19
- package/tests/install-gentle-ai-failure.test.ts +69 -0
- package/tests/install-tui-mode-setting.test.ts +15 -0
- package/tests/install-wizard.test.ts +1223 -0
- package/tests/installer-bundles.test.ts +122 -0
- package/tests/installer-posix-bootstrap.test.ts +1201 -0
- package/tests/installer-preflight.test.ts +750 -0
- package/tests/installer-probes.test.ts +1106 -0
- package/tests/installer-runner.test.ts +2158 -0
- package/tests/installer-server.test.ts +1567 -0
- package/tests/installer-windows-bootstrap.test.ts +2410 -0
- package/tests/issue-creation-skill.test.ts +61 -0
- package/tests/jobs-view.test.ts +160 -0
- package/tests/main-channel-upgrade.test.ts +389 -0
- package/tests/main-channel.test.ts +344 -0
- package/tests/maintainer/provider-relay.maintest.ts +13 -12
- package/tests/model-price-ratio.test.ts +116 -0
- package/tests/model-profiles-preservation.test.ts +153 -0
- package/tests/model-routing-authority.test.ts +16 -0
- package/tests/model-routing-package.test.ts +10 -0
- package/tests/nan-provider-runtime.test.ts +156 -0
- package/tests/nan-provider.test.ts +481 -0
- package/tests/native-binary-gate.test.ts +2 -0
- package/tests/native-review-capability-contract.test.ts +13 -1
- package/tests/native-review-cli.test.ts +239 -118
- package/tests/native-review-consent.test.ts +2 -2
- package/tests/native-review-parity-runtime.test.ts +101 -4
- package/tests/native-review-parity.test.ts +12 -48
- package/tests/native-risk-evidence-parity.test.ts +21 -0
- package/tests/no-self-pause-contract.test.ts +87 -0
- package/tests/notification-audio-native.test.ts +281 -0
- package/tests/notification-audio-windows.test.ts +581 -0
- package/tests/notification-audio.test.ts +548 -0
- package/tests/notification-customize.test.ts +655 -0
- package/tests/notification-events.test.ts +115 -0
- package/tests/notification-policy.test.ts +201 -0
- package/tests/notification-pulse-client.test.ts +356 -0
- package/tests/notification-pulse-protocol.test.ts +213 -0
- package/tests/notification-pulse-stream.test.ts +320 -0
- package/tests/notification-scheduler.test.ts +258 -0
- package/tests/notification-ui.test.ts +157 -0
- package/tests/npm-pack-result.test.ts +41 -0
- package/tests/odd-integration.test.ts +76 -0
- package/tests/odd-phase-inference.test.ts +213 -0
- package/tests/odd-phase-loader.test.ts +253 -0
- package/tests/odd-phase.test.ts +307 -0
- package/tests/odd-routing-canonical-ratchet.test.ts +60 -14
- package/tests/odd-routing-contract.test.ts +158 -50
- package/tests/orchestrator-budget.test.ts +43 -45
- package/tests/orchestrator-catalog.test.ts +146 -0
- package/tests/orchestrator-consultation-sdk.test.ts +614 -0
- package/tests/orchestrator-consultation.test.ts +111 -0
- package/tests/orchestrator-discovery.test.ts +241 -0
- package/tests/orchestrator-helper-consent.test.ts +171 -0
- package/tests/orchestrator-helper.test.ts +297 -0
- package/tests/orchestrator-message-card.test.ts +190 -0
- package/tests/orchestrator-presence.test.ts +23 -0
- package/tests/orchestrator-rdd-ownership.test.ts +8 -7
- package/tests/orchestrator-scope.test.ts +107 -0
- package/tests/orchestrator-state.test.ts +114 -0
- package/tests/orchestrator-status-publication-contract.test.ts +34 -0
- package/tests/orchestrator-tool-card.test.ts +398 -0
- package/tests/orchestrator-work-search.test.ts +318 -0
- package/tests/orchestrator-work.test.ts +147 -0
- package/tests/overlay-repaint.test.ts +74 -0
- package/tests/package-manifest.test.ts +344 -123
- package/tests/packed-runner-owned-path.test.ts +80 -0
- package/tests/parallel-reason-contract.test.ts +21 -0
- package/tests/parallel-writer-admission.test.ts +210 -0
- package/tests/permission-mode-policy.test.ts +194 -0
- package/tests/persona-single-channel.test.ts +10 -18
- package/tests/pi-pretty.test.ts +56 -0
- package/tests/provider-defect-handoff.test.ts +7 -14
- package/tests/quiet-bash-runtime.test.ts +76 -0
- package/tests/quiet-tool-rendering.test.ts +569 -187
- package/tests/rdd-aware-verification-contract.test.ts +103 -40
- package/tests/rdd-status-line.test.ts +9 -4
- package/tests/render-cache-performance.test.ts +48 -0
- package/tests/resume-hint-extension.test.ts +122 -0
- package/tests/review-agent-end-preflight.test.ts +237 -21
- package/tests/review-candidate-owner-retry.test.ts +22 -1
- package/tests/review-candidate-view.test.ts +399 -12
- package/tests/review-contract-prompt.test.ts +138 -44
- package/tests/review-controller-lock-status.test.ts +0 -1
- package/tests/review-controller-native-recovery.test.ts +327 -17
- package/tests/review-controller-native-routing.test.ts +866 -6
- package/tests/review-controller-workspace-root.test.ts +206 -6
- package/tests/review-controller.test.ts +2 -2
- package/tests/review-correction-lifecycle.test.ts +2 -1
- package/tests/review-gate.test.ts +7 -4
- package/tests/review-graph-schema.test.ts +2 -2
- package/tests/review-host-relay-routing.test.ts +342 -4
- package/tests/review-host-relay.test.ts +49 -2
- package/tests/review-integration-v2-forward.test.ts +221 -1
- package/tests/review-json-arguments.test.ts +167 -0
- package/tests/review-last-event-closure.test.ts +157 -8
- package/tests/review-ledger-contract.test.ts +14 -37
- package/tests/review-policy-judgment-day.test.ts +1 -1
- package/tests/review-policy-ordinary.test.ts +2 -2
- package/tests/review-relay-transport-agent.test.ts +66 -1
- package/tests/review-reminder-receipt.test.ts +47 -1
- package/tests/review-risk-assessment.test.ts +594 -26
- package/tests/review-session-standing-permission-controller.test.ts +16 -17
- package/tests/review-session-standing-permission-ipc.test.ts +6 -5
- package/tests/review-sidebar-state.test.ts +402 -0
- package/tests/review-test-fixtures.ts +7 -1
- package/tests/review-transaction.test.ts +18 -1
- package/tests/run-test-suite.test.ts +202 -0
- package/tests/runtime-harness.mjs +186 -800
- package/tests/runtime-metrics-children.test.ts +16 -23
- package/tests/runtime-metrics-extension.test.ts +1 -1
- package/tests/runtime-metrics-native.test.ts +31 -1
- package/tests/selection-engine.test.ts +451 -0
- package/tests/session-change-capture.test.ts +10 -10
- package/tests/session-changes-shell.test.ts +242 -48
- package/tests/session-changes.test.ts +17 -24
- package/tests/session-messaging-grants.test.ts +255 -0
- package/tests/session-profile-binding.test.ts +163 -0
- package/tests/session-profile-freeze.test.ts +381 -0
- package/tests/session-subject-routing-contract.test.ts +34 -0
- package/tests/session-worktree-registry.test.ts +134 -2
- package/tests/shell-bar.test.ts +422 -1
- package/tests/shell-card.test.ts +353 -1
- package/tests/shell-changes-view.test.ts +135 -32
- package/tests/shell-changes.test.ts +61 -62
- package/tests/shell-prompt.test.ts +94 -2
- package/tests/shell-render-cache-runtime.test.ts +69 -0
- package/tests/shell-render-cache.test.ts +129 -0
- package/tests/shell-sidebar-layout.test.ts +351 -22
- package/tests/shell-sidebar-scroll-benchmark.test.ts +255 -0
- package/tests/shell-todo.test.ts +207 -3
- package/tests/shell-usage-view.test.ts +28 -1
- package/tests/shell-usage.test.ts +108 -0
- package/tests/short-check-routing-contract.test.ts +49 -0
- package/tests/skill-registry-watcher.test.ts +119 -0
- package/tests/skill-registry.test.ts +357 -2
- package/tests/startup-banner.test.ts +246 -4
- package/tests/startup-home-repo.test.ts +170 -0
- package/tests/stats-collector.test.ts +195 -0
- package/tests/stats-view.test.ts +202 -0
- package/tests/status-timing-diagnostics.test.ts +283 -0
- package/tests/support/orchestrator-modules.ts +19 -0
- package/tests/task-size-routing-contract.test.ts +241 -0
- package/tests/telemetry-trigger.test.ts +107 -20
- package/tests/test-strength-contract.test.ts +124 -0
- package/tests/theme-customization.test.ts +72 -0
- package/tests/verify-agent-contract.test.ts +90 -0
- package/tests/verify-first-launch-contract.test.ts +139 -0
- package/tests/verify-package-files.test.ts +56 -0
- package/tests/vim-editor-adapter-host-resolution.test.ts +37 -0
- package/tests/vim-editor-adapter.test.ts +804 -0
- package/tests/vim-normal-engine.test.ts +101 -0
- package/tests/vim-operator-engine.test.ts +215 -0
- package/tests/vim-policy.test.ts +19 -0
- package/tests/vim-visual-engine.test.ts +52 -0
- package/tests/visual-customization-policy.test.ts +139 -0
- package/tests/visual-customize-view.test.ts +696 -0
- package/tests/visual-profiles.test.ts +87 -0
- package/tests/writer-edit-surface-scope.test.ts +59 -2
- package/tests/writer-surfaces.test.ts +199 -0
- package/tests/yolo-customize.test.ts +271 -0
- package/tests/yolo-mode-runtime.test.ts +188 -0
- package/tests/yolo-mode.test.ts +289 -0
- package/tests/yolo-session-policy.test.ts +59 -0
- package/themes/Gentle.json +2 -1
- package/themes/Gentleman-Cute.json +2 -1
- package/themes/Gentleman-Sexy.json +2 -1
- package/assets/agents/sdd-apply.md +0 -159
- package/assets/agents/sdd-archive.md +0 -228
- package/assets/agents/sdd-design.md +0 -49
- package/assets/agents/sdd-explore.md +0 -48
- package/assets/agents/sdd-init.md +0 -56
- package/assets/agents/sdd-onboard.md +0 -52
- package/assets/agents/sdd-proposal.md +0 -64
- package/assets/agents/sdd-remediate.md +0 -37
- package/assets/agents/sdd-research.md +0 -49
- package/assets/agents/sdd-spec.md +0 -192
- package/assets/agents/sdd-status.md +0 -54
- package/assets/agents/sdd-tasks.md +0 -108
- package/assets/agents/sdd-verify.md +0 -124
- package/assets/chains/sdd-full.chain.md +0 -83
- package/assets/chains/sdd-plan.chain.md +0 -56
- package/assets/chains/sdd-verify.chain.md +0 -43
- package/assets/sdd-orchestrator-workflow.md +0 -319
- package/assets/support/sdd-status-contract.md +0 -77
- package/docs/assets/diagrams/sdd-cycle.svg +0 -14
- package/extensions/sdd-init.ts +0 -816
- package/lib/openspec-deltas.ts +0 -156
- package/lib/sdd-preflight.ts +0 -1066
- package/lib/sdd-research-capabilities.ts +0 -94
- package/lib/sdd-status.ts +0 -26
- package/tests/fixtures/legacy/sdd-research-v2.5.0.md +0 -54
- package/tests/fixtures/native-review-cli/v2.1.3/bind-sdd.json +0 -25
- package/tests/fixtures/v0.10.7/assets/agents/sdd-apply.md +0 -132
- package/tests/openspec-deltas.test.ts +0 -209
- package/tests/sdd-agent-tools.test.ts +0 -156
- package/tests/sdd-archive-replay.test.ts +0 -82
- package/tests/sdd-classical-continuation.test.ts +0 -74
- package/tests/sdd-execution-routing-contract.test.ts +0 -44
- package/tests/sdd-managed-runtime-settlement.test.ts +0 -155
- package/tests/sdd-native-managed-uptake.test.ts +0 -243
- package/tests/sdd-no-attempts-contract.test.ts +0 -15
- package/tests/sdd-odd-integration.test.ts +0 -33
- package/tests/sdd-optional-research.test.ts +0 -124
- package/tests/sdd-planning-routing-contract.test.ts +0 -45
- package/tests/sdd-preflight-rpc-input.test.ts +0 -125
- package/tests/sdd-preflight.test.ts +0 -541
- package/tests/sdd-research-capabilities.test.ts +0 -114
- package/tests/sdd-research-live.test.ts +0 -241
- package/tests/sdd-selection-transport.test.ts +0 -653
- package/tests/sdd-status.test.ts +0 -9
- package/tests/sdd-task-truth.test.ts +0 -43
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/README.md +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/lens.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/refuter.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/targeted-validator.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/lens.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/refuter.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/targeted-validator.json +0 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// Generated by scripts/build-runtime-modules.mjs. Do not edit.
|
|
2
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const MAX_STATUSES = 8;
|
|
7
|
+
const MAX_ACTIVE_OBSERVATIONS = 8;
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
const hostScope = new AsyncLocalStorage ();
|
|
38
|
+
const nativeScope = new AsyncLocalStorage ();
|
|
39
|
+
|
|
40
|
+
// Observer failures must never replace the operation's result or exception.
|
|
41
|
+
function safely (run ) {
|
|
42
|
+
try { return run(); } catch { return undefined; }
|
|
43
|
+
}
|
|
44
|
+
function now(host ) {
|
|
45
|
+
const value = safely(host.clock);
|
|
46
|
+
if (value !== undefined && Number.isFinite(value) && value >= 0) return value;
|
|
47
|
+
host.summary.clock_unavailable = true;
|
|
48
|
+
return 0;
|
|
49
|
+
}
|
|
50
|
+
function elapsed(start , end ) { return Math.max(0, end - start); }
|
|
51
|
+
|
|
52
|
+
// Failure/v2 codes and next_action are extensible strings, not safe diagnostic
|
|
53
|
+
// text. Only recognized values enter this summary; no messages or raw envelopes.
|
|
54
|
+
const CLI_CODES = new Set(["unavailable", "timeout", "non-zero", "signal", "unexpected-stderr", "output-limit", "empty-output", "malformed-json", "schema-incompatible", "identity-mismatch", "version-incompatible", "cancelled", "package-local-binary-missing", "unsupported-transition-operation"]);
|
|
55
|
+
const NATIVE_CODES = new Set(["operation_timeout", "invalid_request", "binding_revision_conflict", "internal_error", "cancelled"]);
|
|
56
|
+
function outcome(error ) {
|
|
57
|
+
return safely(() => {
|
|
58
|
+
if (typeof error !== "object" || error === null) return { kind: "unknown" };
|
|
59
|
+
const value = error ;
|
|
60
|
+
if (value.name === "NativeReviewCliError") return { kind: "cli_error", code: typeof value.code === "string" && CLI_CODES.has(value.code) ? value.code : "other" };
|
|
61
|
+
if (value.name !== "NativeReviewIntegrationError" || !value.failureEnvelope) return { kind: "unknown" };
|
|
62
|
+
const failure = value.failureEnvelope;
|
|
63
|
+
const result = { kind: "native_failure", code: typeof failure.code === "string" && NATIVE_CODES.has(failure.code) ? failure.code : "other" };
|
|
64
|
+
if (["preflight", "pre_native", "native_running", "native_committed", "reconciliation"].includes(String(failure.phase))) result.phase = String(failure.phase);
|
|
65
|
+
if (["not_started", "committed", "unknown"].includes(String(failure.mutationOutcome))) result.mutation_outcome = String(failure.mutationOutcome);
|
|
66
|
+
if (typeof failure.retrySafe === "boolean") result.retry_safe = failure.retrySafe;
|
|
67
|
+
if (["stop", "review.status"].includes(String(failure.nextAction))) result.next_action = String(failure.nextAction);
|
|
68
|
+
return result;
|
|
69
|
+
}) ?? { kind: "unknown" };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The next already-authorized STATUS-bearing tool call consumes the arm. */
|
|
73
|
+
export class StatusTimingDiagnostics {
|
|
74
|
+
armed = false;
|
|
75
|
+
epoch = 0;
|
|
76
|
+
manager ;
|
|
77
|
+
session ;
|
|
78
|
+
last ;
|
|
79
|
+
active = new Set ();
|
|
80
|
+
clock ;
|
|
81
|
+
constructor(clock = () => performance.now()) { this.clock = clock; }
|
|
82
|
+
|
|
83
|
+
reset(ctx ) {
|
|
84
|
+
this.epoch++;
|
|
85
|
+
this.armed = false;
|
|
86
|
+
this.last = undefined;
|
|
87
|
+
for (const host of this.active) host.summary.statuses.length = 0;
|
|
88
|
+
this.active.clear();
|
|
89
|
+
this.manager = ctx?.sessionManager;
|
|
90
|
+
this.session = safely(() => ctx?.sessionManager.getSessionId());
|
|
91
|
+
}
|
|
92
|
+
matches(ctx ) {
|
|
93
|
+
return safely(() => this.manager === ctx.sessionManager && !!this.session && this.session === ctx.sessionManager.getSessionId()) === true;
|
|
94
|
+
}
|
|
95
|
+
command(args , ctx ) {
|
|
96
|
+
if (!this.matches(ctx)) this.reset(ctx);
|
|
97
|
+
const action = args.trim() || "show";
|
|
98
|
+
if (action === "enable") {
|
|
99
|
+
this.reset(ctx);
|
|
100
|
+
this.armed = !!this.session;
|
|
101
|
+
ctx.ui.notify(this.armed
|
|
102
|
+
? "STATUS timing armed for the next separately authorized STATUS-bearing tool call. No STATUS is invoked or retried. Use /gentle:status-timing show to consult the completed summary."
|
|
103
|
+
: "STATUS timing requires a live session.", "info");
|
|
104
|
+
} else if (action === "disable") {
|
|
105
|
+
this.reset(ctx);
|
|
106
|
+
ctx.ui.notify("STATUS timing disabled; in-memory observations cleared.", "info");
|
|
107
|
+
} else if (action === "show") {
|
|
108
|
+
ctx.ui.notify(this.last ? JSON.stringify(this.last, null, 2) : `No completed STATUS timing summary. Diagnostic is ${this.armed ? "armed" : "off"}.`, "info");
|
|
109
|
+
} else {
|
|
110
|
+
ctx.ui.notify("Unknown /gentle:status-timing sub-action. Use enable, disable, or show.", "warning");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
tool (definition ) {
|
|
114
|
+
return {
|
|
115
|
+
...definition,
|
|
116
|
+
execute: async (toolCallId, params, signal, onUpdate, ctx) => {
|
|
117
|
+
const run = () => definition.execute(toolCallId, params, signal, onUpdate, ctx);
|
|
118
|
+
if (!this.matches(ctx)) this.reset(ctx);
|
|
119
|
+
if (!this.armed || this.active.size >= MAX_ACTIVE_OBSERVATIONS) return run();
|
|
120
|
+
const epoch = this.epoch;
|
|
121
|
+
const host = {
|
|
122
|
+
clock: this.clock,
|
|
123
|
+
current: () => this.epoch === epoch && this.matches(ctx),
|
|
124
|
+
claim: () => {
|
|
125
|
+
if (!host.current() || !this.armed) return false;
|
|
126
|
+
this.armed = false;
|
|
127
|
+
return true;
|
|
128
|
+
},
|
|
129
|
+
claimed: false, started: 0, lastNativeEnd: 0,
|
|
130
|
+
summary: { schema: "gentle-pi.status-timing/v1", host_total_ms: 0, dispatch_ms: 0, sidebar_ms: 0, completion_ms: 0, truncated: false, statuses: [] },
|
|
131
|
+
};
|
|
132
|
+
this.active.add(host);
|
|
133
|
+
host.started = now(host);
|
|
134
|
+
return hostScope.run(host, async () => {
|
|
135
|
+
try { return await run(); }
|
|
136
|
+
finally {
|
|
137
|
+
this.active.delete(host);
|
|
138
|
+
safely(() => {
|
|
139
|
+
if (!host.claimed || !host.current()) return;
|
|
140
|
+
const ended = now(host);
|
|
141
|
+
host.summary.host_total_ms = elapsed(host.started, ended);
|
|
142
|
+
host.summary.completion_ms = elapsed(host.lastNativeEnd, ended);
|
|
143
|
+
this.last = host.summary;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Only called by the existing STATUS entry points; never invokes one itself. */
|
|
153
|
+
export async function observeNativeStatus (timeoutMs , run ) {
|
|
154
|
+
const host = hostScope.getStore();
|
|
155
|
+
if (!host || !safely(host.current)) return run();
|
|
156
|
+
if (!host.claimed) {
|
|
157
|
+
if (!safely(host.claim)) return run();
|
|
158
|
+
host.claimed = true;
|
|
159
|
+
host.summary.dispatch_ms = elapsed(host.started, now(host));
|
|
160
|
+
}
|
|
161
|
+
const sample = { timeout_ms: timeoutMs, total_ms: 0, resolution_ms: 0, adapter_ms: 0, decode_ms: 0, outcome: { kind: "success" } };
|
|
162
|
+
if (host.summary.statuses.length < MAX_STATUSES) host.summary.statuses.push(sample);
|
|
163
|
+
else host.summary.truncated = true;
|
|
164
|
+
const started = now(host);
|
|
165
|
+
return nativeScope.run({ host, sample }, async () => {
|
|
166
|
+
try { return await run(); }
|
|
167
|
+
catch (error) { safely(() => { sample.outcome = outcome(error); }); throw error; }
|
|
168
|
+
finally {
|
|
169
|
+
safely(() => {
|
|
170
|
+
const ended = now(host);
|
|
171
|
+
sample.total_ms = elapsed(started, ended);
|
|
172
|
+
host.lastNativeEnd = ended;
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function activeNative() {
|
|
179
|
+
const observation = nativeScope.getStore();
|
|
180
|
+
return observation && safely(observation.host.current) ? observation : undefined;
|
|
181
|
+
}
|
|
182
|
+
export function measureStatusSync (stage , run ) {
|
|
183
|
+
const observation = activeNative();
|
|
184
|
+
if (!observation) return run();
|
|
185
|
+
const started = now(observation.host);
|
|
186
|
+
try { return run(); }
|
|
187
|
+
finally { safely(() => { observation.sample[`${stage}_ms`] += elapsed(started, now(observation.host)); }); }
|
|
188
|
+
}
|
|
189
|
+
export async function measureStatusAsync (stage , run ) {
|
|
190
|
+
const observation = activeNative();
|
|
191
|
+
if (!observation) return run();
|
|
192
|
+
const started = now(observation.host);
|
|
193
|
+
try { return await run(); }
|
|
194
|
+
finally { safely(() => { observation.sample[`${stage}_ms`] += elapsed(started, now(observation.host)); }); }
|
|
195
|
+
}
|
|
196
|
+
export function measureStatusSidebar (run ) {
|
|
197
|
+
const host = hostScope.getStore();
|
|
198
|
+
if (!host || !safely(host.current)) return run();
|
|
199
|
+
const started = now(host);
|
|
200
|
+
try { return run(); }
|
|
201
|
+
finally { safely(() => { host.summary.sidebar_ms += elapsed(started, now(host)); }); }
|
|
202
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Generated by scripts/build-runtime-modules.mjs. Do not edit.
|
|
2
2
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
3
|
+
import { gentleAiIsolationEnv } from "./gentle-ai-binary.mjs";
|
|
3
4
|
|
|
4
5
|
// gentle-ai#4309 owns anonymous usage telemetry end to end: install and
|
|
5
6
|
// heartbeat events, the notice, every kill switch, and the wire contract.
|
|
@@ -129,7 +130,8 @@ export function spawnTelemetryTrigger(options )
|
|
|
129
130
|
try {
|
|
130
131
|
const child = spawnImpl(options.executable, ["telemetry", "trigger", "--json"], {
|
|
131
132
|
cwd: options.cwd,
|
|
132
|
-
|
|
133
|
+
// S9: a bundled install keeps Gentle AI's telemetry state inside its prefix.
|
|
134
|
+
env: gentleAiIsolationEnv(options.env),
|
|
133
135
|
detached: true,
|
|
134
136
|
windowsHide: true,
|
|
135
137
|
stdio: "ignore",
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
setlocal DisableDelayedExpansion
|
|
3
|
+
rem Trusted extracted/checkout bundle only. Never pipe a remote script here.
|
|
4
|
+
rem Fixed stock PowerShell commands; paths are environment DATA, not PS source.
|
|
5
|
+
rem Each invocation is below CMD's logical command length limit.
|
|
6
|
+
set "GENTLE_BOOTSTRAP_BUNDLE=%~dp0.."
|
|
7
|
+
set "GENTLE_BOOTSTRAP_TOOLS=%LOCALAPPDATA%\.gentle-shell-bootstrap-tools.%RANDOM%.%RANDOM%.%RANDOM%"
|
|
8
|
+
set "GENTLE_BOOTSTRAP_FALLBACK_TOOLS=%USERPROFILE%\.gentle-shell-bootstrap-tools.%RANDOM%.%RANDOM%.%RANDOM%"
|
|
9
|
+
set "GENTLE_BOOTSTRAP_OWNED="
|
|
10
|
+
set "GENTLE_BOOTSTRAP_PS=%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe"
|
|
11
|
+
if not exist "%GENTLE_BOOTSTRAP_PS%" goto unavailable
|
|
12
|
+
|
|
13
|
+
rem Bundle/dependency checks precede ALL acquisition and per-user writes.
|
|
14
|
+
"%GENTLE_BOOTSTRAP_PS%" -NoLogo -NoProfile -NonInteractive -Command ^
|
|
15
|
+
"& { try { $ErrorActionPreference = 'Stop';" ^
|
|
16
|
+
"if ($ExecutionContext.SessionState.LanguageMode -ne 'FullLanguage') { throw 'Managed policy constraint' };" ^
|
|
17
|
+
"$bundle = [IO.Path]::GetFullPath($env:GENTLE_BOOTSTRAP_BUNDLE);" ^
|
|
18
|
+
"foreach ($file in @('bin/gentle-shell-install.mjs','package.json','scripts/installer-downloads.mjs','scripts/installer-windows.mjs','scripts/installer-windows-artifacts.json')) {" ^
|
|
19
|
+
" $item = Get-Item -LiteralPath (Join-Path $bundle $file) -Force;" ^
|
|
20
|
+
" if ($item.PSIsContainer -or ($item.Attributes -band [IO.FileAttributes]::ReparsePoint)) { throw 'Unsafe bundle file' };" ^
|
|
21
|
+
"};" ^
|
|
22
|
+
"$metadata = Get-Content -LiteralPath (Join-Path $bundle 'package.json') -Raw | ConvertFrom-Json;" ^
|
|
23
|
+
"if ($metadata.engines.node -notmatch '^>=[0-9]+\.[0-9]+\.[0-9]+$' -or $metadata.packageManager -ne 'pnpm@11.1.1') { throw 'Repository prerequisite unknown' };" ^
|
|
24
|
+
"} catch { [Console]::Error.WriteLine('Bootstrap: future wizard/dependent bundle file missing, unsupported metadata, or managed policy denial. No acquisition attempted.'); exit 1 } }"
|
|
25
|
+
if errorlevel 1 goto failed
|
|
26
|
+
|
|
27
|
+
rem Claim an absent unique directory beneath a verified local per-user parent:
|
|
28
|
+
rem %LOCALAPPDATA% first, then one %USERPROFILE% fallback only when the first fails
|
|
29
|
+
rem acl-mask or home-owner. Each candidate gets the exact-parent rule and the whole
|
|
30
|
+
rem walk; exit 2 means the fallback was claimed. Its owner may be the user, SYSTEM
|
|
31
|
+
rem or Administrators; the new directory's owner is set to the user explicitly.
|
|
32
|
+
rem No security changes outside this new prerequisite directory.
|
|
33
|
+
rem Strict target/parent rights: ReadAndExecute + Synchronize (0x1200a9).
|
|
34
|
+
rem Distant existing ancestors additionally permit sibling CreateDirectories (4).
|
|
35
|
+
rem WriteData/reparse-affecting writes, deletion and ACL/owner changes still fail.
|
|
36
|
+
rem Failures append one fixed reason code (the failed check), then one line per
|
|
37
|
+
rem candidate naming its folder and, for acl-mask, the principal and its rights;
|
|
38
|
+
rem other exceptions report unexpected-<step>. ACLs use .NET, not Security-module
|
|
39
|
+
rem cmdlets: PowerShell 7's inherited PSModulePath breaks their 5.1 autoload.
|
|
40
|
+
"%GENTLE_BOOTSTRAP_PS%" -NoLogo -NoProfile -NonInteractive -Command ^
|
|
41
|
+
"& { $claimed = $false; $step = 'policy'; $tools = $null; $tried = @(); try { $ErrorActionPreference = 'Stop';" ^
|
|
42
|
+
"if ($ExecutionContext.SessionState.LanguageMode -ne 'FullLanguage') { throw 'policy' };" ^
|
|
43
|
+
"$me = [Security.Principal.WindowsIdentity]::GetCurrent().User;" ^
|
|
44
|
+
"$trusted = @($me.Value,'S-1-5-18','S-1-5-32-544','S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464');" ^
|
|
45
|
+
"$owners = @($me.Value,'S-1-5-18','S-1-5-32-544'); $account = { param($sid) try { ' (' + $sid.Translate([Security.Principal.NTAccount]).Value + ')' } catch { '' } };" ^
|
|
46
|
+
"$candidates = @(,@($env:GENTLE_BOOTSTRAP_TOOLS,$env:LOCALAPPDATA)); if ($env:GENTLE_BOOTSTRAP_FALLBACK_TOOLS) { $candidates += ,@($env:GENTLE_BOOTSTRAP_FALLBACK_TOOLS,$env:USERPROFILE) };" ^
|
|
47
|
+
"for ($index = 0; $index -lt $candidates.Count; $index++) { $base = $candidates[$index][1]; $detail = ''; try {" ^
|
|
48
|
+
" $step = 'path-mismatch'; $tools = [IO.Path]::GetFullPath($candidates[$index][0]); $path = [IO.Directory]::GetParent($tools).FullName;" ^
|
|
49
|
+
" if (-not [IO.Path]::IsPathRooted($base) -or $tools.StartsWith('\\') -or $path -ne [IO.Path]::GetFullPath($base)) { throw 'path-mismatch' };" ^
|
|
50
|
+
" $depth = 1;" ^
|
|
51
|
+
" $step = 'home-owner'; $owner = ([IO.Directory]::GetAccessControl($path)).GetOwner([Security.Principal.SecurityIdentifier]); if ($owners -notcontains $owner.Value) { $detail = ' owned by ' + $owner.Value + (& $account $owner); throw 'home-owner' };" ^
|
|
52
|
+
" $step = 'ancestor-walk'; while ($path) {" ^
|
|
53
|
+
" $item = Get-Item -LiteralPath $path -Force; if (-not $item.PSIsContainer -or ($item.Attributes -band [IO.FileAttributes]::ReparsePoint)) { throw 'ancestor-reparse' };" ^
|
|
54
|
+
" $acl = [IO.Directory]::GetAccessControl($path); if ($trusted -notcontains $acl.GetOwner([Security.Principal.SecurityIdentifier]).Value) { throw 'ancestor-owner' };" ^
|
|
55
|
+
" $allowedRights = 0x1200a9; if ($depth -ge 2) { $allowedRights = 0x1200ad };" ^
|
|
56
|
+
" foreach ($rule in $acl.GetAccessRules($true,$true,[Security.Principal.SecurityIdentifier])) {" ^
|
|
57
|
+
" if ($rule.AccessControlType -eq 'Allow' -and -not ($rule.PropagationFlags -band [Security.AccessControl.PropagationFlags]::InheritOnly) -and ([long]$rule.FileSystemRights -band (-bnot [long]$allowedRights)) -and $trusted -notcontains $rule.IdentityReference.Value) {" ^
|
|
58
|
+
" $detail = ' at ' + $path + ': ' + $rule.IdentityReference.Value + (& $account $rule.IdentityReference) + ' allowed 0x' + ([int]$rule.FileSystemRights).ToString('X8'); throw 'acl-mask' };" ^
|
|
59
|
+
" }; $parent = [IO.Directory]::GetParent($path); if ($null -eq $parent) { break }; $path = $parent.FullName; $depth++;" ^
|
|
60
|
+
" }; break;" ^
|
|
61
|
+
"} catch { $code = 'unexpected-' + $step; if ($_.Exception.Message -cmatch '^(path-mismatch|home-owner|ancestor-reparse|ancestor-owner|acl-mask)$') { $code = $_.Exception.Message };" ^
|
|
62
|
+
" $tried += ($base + ': ' + $code + $detail); if ($index -eq 0 -and $candidates.Count -gt 1 -and $code -cmatch '^(acl-mask|home-owner)$') { continue }; throw } };" ^
|
|
63
|
+
"$step = 'create'; if (Test-Path -LiteralPath $tools) { throw 'collision' }; $null = New-Item -ItemType Directory -Path $tools; $claimed = $true;" ^
|
|
64
|
+
"$step = 'private-acl'; $acl = New-Object Security.AccessControl.DirectorySecurity; $acl.SetOwner($me); $acl.SetAccessRuleProtection($true,$false);" ^
|
|
65
|
+
"foreach ($sid in @($me.Value,'S-1-5-18','S-1-5-32-544')) {" ^
|
|
66
|
+
" $identity = New-Object Security.Principal.SecurityIdentifier($sid);" ^
|
|
67
|
+
" $rule = New-Object Security.AccessControl.FileSystemAccessRule($identity,'FullControl','ContainerInherit,ObjectInherit','None','Allow'); $acl.AddAccessRule($rule);" ^
|
|
68
|
+
"}; [IO.Directory]::SetAccessControl($tools,$acl);" ^
|
|
69
|
+
"$verified = [IO.Directory]::GetAccessControl($tools); if (-not $verified.AreAccessRulesProtected) { throw 'protected-dacl' };" ^
|
|
70
|
+
"if ($verified.GetOwner([Security.Principal.SecurityIdentifier]).Value -ne $me.Value) { throw 'private-owner' };" ^
|
|
71
|
+
"foreach ($rule in $verified.GetAccessRules($true,$true,[Security.Principal.SecurityIdentifier])) { if ($rule.AccessControlType -ne 'Allow' -or @($me.Value,'S-1-5-18','S-1-5-32-544') -notcontains $rule.IdentityReference.Value) { throw 'private-ace' } };" ^
|
|
72
|
+
"$step = 'marker'; $null = New-Item -ItemType File -Path (Join-Path $tools '.bootstrap-owned') -Value 'gentle-pi prerequisite tooling only';" ^
|
|
73
|
+
"if ($index -gt 0) { exit 2 };" ^
|
|
74
|
+
"} catch { $reason = 'unexpected-' + $step; if ($_.Exception.Message -cmatch '^(policy|path-mismatch|home-owner|ancestor-reparse|ancestor-owner|acl-mask|collision|protected-dacl|private-owner|private-ace)$') { $reason = $_.Exception.Message };" ^
|
|
75
|
+
"if ($step -cmatch '^(create|private-acl|marker)$') { $tried += ($tools + ': ' + $reason) };" ^
|
|
76
|
+
"if ($claimed) { Remove-Item -LiteralPath $tools -Recurse -Force -ErrorAction SilentlyContinue }; [Console]::Error.WriteLine('Bootstrap: private storage ACL/reparse/ownership claim failed or policy denied it. Reason: ' + $reason); foreach ($line in $tried) { [Console]::Error.WriteLine('Bootstrap: storage candidate ' + $line) }; exit 1 } }"
|
|
77
|
+
if errorlevel 3 goto failed
|
|
78
|
+
if errorlevel 2 goto fallbackclaimed
|
|
79
|
+
if errorlevel 1 goto failed
|
|
80
|
+
goto claimed
|
|
81
|
+
:fallbackclaimed
|
|
82
|
+
set "GENTLE_BOOTSTRAP_TOOLS=%GENTLE_BOOTSTRAP_FALLBACK_TOOLS%"
|
|
83
|
+
:claimed
|
|
84
|
+
set "GENTLE_BOOTSTRAP_FALLBACK_TOOLS="
|
|
85
|
+
set "GENTLE_BOOTSTRAP_OWNED=1"
|
|
86
|
+
|
|
87
|
+
rem Select an existing native Node without invoking any command interpreter shim.
|
|
88
|
+
rem Absence authorizes pinned acquisition, and so does an older stable Node or one
|
|
89
|
+
rem whose storage the probe below cannot trust: it is never replaced or run again.
|
|
90
|
+
"%GENTLE_BOOTSTRAP_PS%" -NoLogo -NoProfile -NonInteractive -Command ^
|
|
91
|
+
"& { try { $ErrorActionPreference = 'Stop';" ^
|
|
92
|
+
"if ($ExecutionContext.SessionState.LanguageMode -ne 'FullLanguage') { throw 'Managed policy constraint' };" ^
|
|
93
|
+
"$command = $null; try { $command = Get-Command node.exe -CommandType Application -ErrorAction Stop | Select-Object -First 1 } catch [Management.Automation.CommandNotFoundException] { $command = $null };" ^
|
|
94
|
+
"if ($null -ne $command) { $bytes = (New-Object Text.UTF8Encoding($false)).GetBytes($command.Source); $record = [IO.File]::Open((Join-Path $env:GENTLE_BOOTSTRAP_TOOLS '.node-target'),[IO.FileMode]::CreateNew,[IO.FileAccess]::Write,[IO.FileShare]::None); try { $record.Write($bytes,0,$bytes.Length) } finally { $record.Dispose() } };" ^
|
|
95
|
+
"} catch { [Console]::Error.WriteLine('Bootstrap: existing Node resolution failed or policy denied it.'); exit 1 } }"
|
|
96
|
+
if errorlevel 1 goto failed
|
|
97
|
+
|
|
98
|
+
:acquirenode
|
|
99
|
+
rem Fixed official ZIP transport: no redirects, bounded bytes/time, no TLS changes.
|
|
100
|
+
rem Integrity is verified before ZIP processing or executable publication.
|
|
101
|
+
"%GENTLE_BOOTSTRAP_PS%" -NoLogo -NoProfile -NonInteractive -Command ^
|
|
102
|
+
"& { try { $ErrorActionPreference = 'Stop';" ^
|
|
103
|
+
"if ($ExecutionContext.SessionState.LanguageMode -ne 'FullLanguage') { throw 'Managed policy constraint' };" ^
|
|
104
|
+
"$tools = $env:GENTLE_BOOTSTRAP_TOOLS; if (Test-Path -LiteralPath (Join-Path $tools '.node-target')) { exit 0 };" ^
|
|
105
|
+
"$architecture = $env:PROCESSOR_ARCHITECTURE; if ($env:PROCESSOR_ARCHITEW6432) { $architecture = $env:PROCESSOR_ARCHITEW6432 };" ^
|
|
106
|
+
"switch ($architecture) { 'AMD64' { $arch = 'x64' } 'ARM64' { $arch = 'arm64' } default { throw 'Unsupported architecture' } };" ^
|
|
107
|
+
"$pins = Get-Content -LiteralPath (Join-Path $env:GENTLE_BOOTSTRAP_BUNDLE 'scripts/installer-windows-artifacts.json') -Raw | ConvertFrom-Json; $pin = $pins.node.$arch;" ^
|
|
108
|
+
"$stem = 'node-v24.21.0-win-' + $arch;" ^
|
|
109
|
+
"if ($pins.node.version -ne '24.21.0' -or $pin.url -ne ('https://nodejs.org/dist/v24.21.0/' + $stem + '.zip') -or $pin.sha256 -notmatch '^[a-f0-9]{64}$' -or $pins.node.maxBytes -ne 104857600) { throw 'Artifact pin rejected' };" ^
|
|
110
|
+
"$request = [Net.HttpWebRequest]::Create($pin.url); $request.AllowAutoRedirect = $false; $request.Timeout = 60000; $request.ReadWriteTimeout = 10000;" ^
|
|
111
|
+
"$watch = [Diagnostics.Stopwatch]::StartNew(); $response = $null; $output = $null; $input = $null;" ^
|
|
112
|
+
"try {" ^
|
|
113
|
+
" $response = $request.GetResponse(); if ([int]$response.StatusCode -ne 200 -or $response.ContentLength -gt 104857600) { throw 'Download rejected' };" ^
|
|
114
|
+
" $input = $response.GetResponseStream(); $output = [IO.File]::Open((Join-Path $tools 'node.zip'),[IO.FileMode]::CreateNew,[IO.FileAccess]::Write,[IO.FileShare]::None);" ^
|
|
115
|
+
" $buffer = New-Object byte[] 65536; $total = 0;" ^
|
|
116
|
+
" while (($count = $input.Read($buffer,0,$buffer.Length)) -gt 0) { $total += $count; if ($total -gt 104857600 -or $watch.ElapsedMilliseconds -gt 60000) { throw 'Download bound exceeded' }; $output.Write($buffer,0,$count) };" ^
|
|
117
|
+
" if ($total -eq 0 -or ($response.ContentLength -ge 0 -and $response.ContentLength -ne $total) -or $watch.ElapsedMilliseconds -gt 60000) { throw 'Download truncated or timed out' };" ^
|
|
118
|
+
"} finally { if ($output) { $output.Dispose() }; if ($input) { $input.Dispose() }; if ($response) { $response.Dispose() } };" ^
|
|
119
|
+
"$actual = (Get-FileHash -LiteralPath (Join-Path $tools 'node.zip') -Algorithm SHA256).Hash; if ($actual -ne $pin.sha256) { throw 'Integrity mismatch' };" ^
|
|
120
|
+
"$null = New-Item -ItemType File -Path (Join-Path $tools '.node-stem') -Value $stem;" ^
|
|
121
|
+
"} catch { [Console]::Error.WriteLine('Bootstrap: Node download, bounds, integrity or policy check failed.'); exit 1 } }"
|
|
122
|
+
if errorlevel 1 goto failed
|
|
123
|
+
|
|
124
|
+
rem Inspect the entire ZIP namespace before extracting ONLY its regular node.exe.
|
|
125
|
+
rem Reject links, Windows device/ADS names, traversal and case aliases.
|
|
126
|
+
"%GENTLE_BOOTSTRAP_PS%" -NoLogo -NoProfile -NonInteractive -Command ^
|
|
127
|
+
"& { try { $ErrorActionPreference = 'Stop';" ^
|
|
128
|
+
"if ($ExecutionContext.SessionState.LanguageMode -ne 'FullLanguage') { throw 'Managed policy constraint' };" ^
|
|
129
|
+
"$tools = $env:GENTLE_BOOTSTRAP_TOOLS; if (Test-Path -LiteralPath (Join-Path $tools '.node-target')) { exit 0 };" ^
|
|
130
|
+
"Add-Type -AssemblyName System.IO.Compression.FileSystem;" ^
|
|
131
|
+
"$stem = (Get-Content -LiteralPath (Join-Path $tools '.node-stem') -Raw).Trim();" ^
|
|
132
|
+
"$zip = [IO.Compression.ZipFile]::OpenRead((Join-Path $tools 'node.zip'));" ^
|
|
133
|
+
"try {" ^
|
|
134
|
+
" if ($zip.Entries.Count -eq 0 -or $zip.Entries.Count -gt 50000) { throw 'ZIP count rejected' };" ^
|
|
135
|
+
" $explicit = @{}; $names = @{}; $types = @{}; $selected = $null; $expanded = 0;" ^
|
|
136
|
+
" foreach ($entry in $zip.Entries) {" ^
|
|
137
|
+
" $name = $entry.FullName; $directory = $name.EndsWith('/'); if ($directory) { $name = $name.Substring(0,$name.Length - 1) };" ^
|
|
138
|
+
" $unixType = ($entry.ExternalAttributes -shr 16) -band 61440;" ^
|
|
139
|
+
" if (($entry.ExternalAttributes -band 1024) -or ($unixType -ne 0 -and $unixType -ne 32768 -and $unixType -ne 16384) -or ($unixType -eq 16384 -and -not $directory) -or ($directory -and $entry.Length -ne 0)) { throw 'ZIP links/types rejected' };" ^
|
|
140
|
+
" if ($entry.FullName.Contains('\') -or $explicit.ContainsKey($name)) { throw 'ZIP duplicate/path rejected' }; $explicit[$name] = $true;" ^
|
|
141
|
+
" $parts = $name.Split('/'); $path = '';" ^
|
|
142
|
+
" for ($index = 0; $index -lt $parts.Length; $index++) {" ^
|
|
143
|
+
" $part = $parts[$index];" ^
|
|
144
|
+
" if (-not $part -or $part -eq '.' -or $part -eq '..' -or $part -match '[<>:\x22|?*\x00-\x1f\x7f]' -or $part -match '[. ]$' -or $part -match '^(CON|PRN|AUX|NUL|COM[1-9\u00b9\u00b2\u00b3]|LPT[1-9\u00b9\u00b2\u00b3])(\.|$)') { throw 'ZIP Windows name rejected' };" ^
|
|
145
|
+
" if ($path) { $path += '/' }; $path += $part; $isDirectory = $directory -or $index -lt ($parts.Length - 1);" ^
|
|
146
|
+
" if ($names.ContainsKey($path) -and ($names[$path] -cne $path -or $types[$path] -ne $isDirectory)) { throw 'ZIP case/type alias rejected' }; $names[$path] = $path; $types[$path] = $isDirectory;" ^
|
|
147
|
+
" };" ^
|
|
148
|
+
" $expanded += $entry.Length; if ($expanded -gt 536870912 -or ($name -ne $stem -and -not $name.StartsWith($stem + '/',[StringComparison]::Ordinal))) { throw 'ZIP root/size rejected' };" ^
|
|
149
|
+
" if ($name -ceq ($stem + '/node.exe')) { if ($directory -or $entry.Length -eq 0 -or $entry.Length -gt 157286400) { throw 'Node member rejected' }; $selected = $entry };" ^
|
|
150
|
+
" }; if ($null -eq $selected) { throw 'Node member missing' };" ^
|
|
151
|
+
" $destination = Join-Path $tools 'node'; $null = New-Item -ItemType Directory -Path $destination;" ^
|
|
152
|
+
" $target = Join-Path $destination 'node.exe'; $output = [IO.File]::Open($target,[IO.FileMode]::CreateNew,[IO.FileAccess]::Write,[IO.FileShare]::None); $input = $selected.Open();" ^
|
|
153
|
+
" try { $input.CopyTo($output) } finally { $input.Dispose(); $output.Dispose() };" ^
|
|
154
|
+
" $bytes = (New-Object Text.UTF8Encoding($false)).GetBytes($target); $record = [IO.File]::Open((Join-Path $tools '.node-target'),[IO.FileMode]::CreateNew,[IO.FileAccess]::Write,[IO.FileShare]::None); try { $record.Write($bytes,0,$bytes.Length) } finally { $record.Dispose() };" ^
|
|
155
|
+
"} finally { $zip.Dispose() };" ^
|
|
156
|
+
"} catch { [Console]::Error.WriteLine('Bootstrap: Node ZIP namespace, extraction or policy check failed.'); exit 1 } }"
|
|
157
|
+
if errorlevel 1 goto failed
|
|
158
|
+
|
|
159
|
+
rem Production direct non-forking Node probe: 10s deadline and combined 1-MiB cap.
|
|
160
|
+
rem Drain both pipes asynchronously; valid output followed by a hang still fails.
|
|
161
|
+
rem Failures append one fixed reason code; walk codes name the component role only.
|
|
162
|
+
rem .node-target holds a possibly non-ASCII path: written and read as explicit UTF-8.
|
|
163
|
+
rem The user's Node (no .node-stem) is left as it is when it is an older stable
|
|
164
|
+
rem version, or when its storage fails the reparse/owner/ACL checks before it ever
|
|
165
|
+
rem runs: its record is removed and the pinned Node is acquired as when absent.
|
|
166
|
+
rem The acquired Node still has to pass every check; unknown versions still refuse.
|
|
167
|
+
"%GENTLE_BOOTSTRAP_PS%" -NoLogo -NoProfile -NonInteractive -Command ^
|
|
168
|
+
"& { $child = $null; $started = $false; $acquired = $true; $step = 'policy'; try { $ErrorActionPreference = 'Stop';" ^
|
|
169
|
+
"if ($ExecutionContext.SessionState.LanguageMode -ne 'FullLanguage') { throw 'policy' };" ^
|
|
170
|
+
"$step = 'target'; $tools = $env:GENTLE_BOOTSTRAP_TOOLS; $record = Join-Path $tools '.node-target'; if (-not [IO.File]::Exists($record)) { throw 'missing-target' }; $acquired = Test-Path -LiteralPath (Join-Path $tools '.node-stem');" ^
|
|
171
|
+
"$node = [IO.File]::ReadAllText($record,[Text.Encoding]::UTF8).TrimEnd([char]13,[char]10); if (-not $node) { throw 'missing-target' };" ^
|
|
172
|
+
"$item = Get-Item -LiteralPath $node -Force; if ($item.PSIsContainer -or ($item.Attributes -band [IO.FileAttributes]::ReparsePoint)) { throw 'unsafe-target' };" ^
|
|
173
|
+
"if (-not [IO.Path]::IsPathRooted($node) -or $node.StartsWith('\\')) { throw 'unsafe-path' };" ^
|
|
174
|
+
"$me = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value; $trusted = @($me,'S-1-5-18','S-1-5-32-544','S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464'); $path = $node;" ^
|
|
175
|
+
"$depth = 0;" ^
|
|
176
|
+
"$step = 'acl-walk'; while ($path) {" ^
|
|
177
|
+
" $role = 'ancestor'; if ($depth -eq 0) { $role = 'target' } elseif ($depth -eq 1) { $role = 'parent' };" ^
|
|
178
|
+
" $item = Get-Item -LiteralPath $path -Force; if ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) { throw ($role + '-reparse') };" ^
|
|
179
|
+
" if ($item.PSIsContainer) { $acl = [IO.Directory]::GetAccessControl($path) } else { $acl = [IO.File]::GetAccessControl($path) };" ^
|
|
180
|
+
" if ($trusted -notcontains $acl.GetOwner([Security.Principal.SecurityIdentifier]).Value) { throw ($role + '-owner') };" ^
|
|
181
|
+
" $allowedRights = 0x1200a9; if ($depth -ge 2) { $allowedRights = 0x1200ad };" ^
|
|
182
|
+
" foreach ($rule in $acl.GetAccessRules($true,$true,[Security.Principal.SecurityIdentifier])) { if ($rule.AccessControlType -eq 'Allow' -and -not ($rule.PropagationFlags -band [Security.AccessControl.PropagationFlags]::InheritOnly) -and ([long]$rule.FileSystemRights -band (-bnot [long]$allowedRights)) -and $trusted -notcontains $rule.IdentityReference.Value) { throw ($role + '-acl-mask') } };" ^
|
|
183
|
+
" $parent = [IO.Directory]::GetParent($path); if ($null -eq $parent) { break }; $path = $parent.FullName; $depth++;" ^
|
|
184
|
+
"};" ^
|
|
185
|
+
"$start = New-Object Diagnostics.ProcessStartInfo; $start.FileName = $node; $start.Arguments = '--version'; $start.UseShellExecute = $false; $start.CreateNoWindow = $true; $start.RedirectStandardOutput = $true; $start.RedirectStandardError = $true;" ^
|
|
186
|
+
"$step = 'start'; $child = New-Object Diagnostics.Process; $child.StartInfo = $start; if (-not $child.Start()) { throw 'no-start' }; $started = $true;" ^
|
|
187
|
+
"$step = 'drain'; $streams = @($child.StandardOutput.BaseStream,$child.StandardError.BaseStream); $buffers = @((New-Object byte[] 4096),(New-Object byte[] 4096));" ^
|
|
188
|
+
"$tasks = @($streams[0].ReadAsync($buffers[0],0,4096),$streams[1].ReadAsync($buffers[1],0,4096)); $closed = @($false,$false); $text = ''; $total = 0; $watch = [Diagnostics.Stopwatch]::StartNew();" ^
|
|
189
|
+
"while (-not ($closed[0] -and $closed[1] -and $child.HasExited)) {" ^
|
|
190
|
+
" if ($watch.ElapsedMilliseconds -ge 10000) { throw 'deadline' };" ^
|
|
191
|
+
" for ($index = 0; $index -lt 2; $index++) { if (-not $closed[$index] -and $tasks[$index].IsCompleted) {" ^
|
|
192
|
+
" $count = $tasks[$index].GetAwaiter().GetResult(); if ($count -eq 0) { $closed[$index] = $true } else {" ^
|
|
193
|
+
" $total += $count; if ($total -gt 1048576) { throw 'output-limit' };" ^
|
|
194
|
+
" if ($index -eq 0) { $text += [Text.Encoding]::UTF8.GetString($buffers[$index],0,$count) };" ^
|
|
195
|
+
" $tasks[$index] = $streams[$index].ReadAsync($buffers[$index],0,4096);" ^
|
|
196
|
+
" }" ^
|
|
197
|
+
" } }; Start-Sleep -Milliseconds 10;" ^
|
|
198
|
+
"}; if ($child.ExitCode -ne 0) { throw 'exit-code' };" ^
|
|
199
|
+
"$step = 'version'; $version = $text.Trim(); if ($version -notmatch '^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$') { throw 'version-format' };" ^
|
|
200
|
+
"$actual = [Version]$version.Substring(1); $metadata = Get-Content -LiteralPath (Join-Path $env:GENTLE_BOOTSTRAP_BUNDLE 'package.json') -Raw | ConvertFrom-Json;" ^
|
|
201
|
+
"if ($acquired -and $version -ne 'v24.21.0') { throw 'acquired-version' };" ^
|
|
202
|
+
"if ($actual -lt [Version]'24.3.0' -or $actual -lt [Version]$metadata.engines.node.Substring(2)) { if ($acquired) { throw 'engine' }; [IO.File]::Delete((Join-Path $tools '.node-target')) };" ^
|
|
203
|
+
"} catch { $reason = 'unexpected-' + $step; if ($_.Exception.Message -cmatch '^(policy|missing-target|unsafe-target|unsafe-path|(target|parent|ancestor)-(reparse|owner|acl-mask)|no-start|deadline|output-limit|exit-code|version-format|engine|acquired-version)$') { $reason = $_.Exception.Message };" ^
|
|
204
|
+
"if (-not $acquired -and $reason -cmatch '^(unsafe-target|unsafe-path|(target|parent|ancestor)-(reparse|owner|acl-mask))$') { [IO.File]::Delete((Join-Path $env:GENTLE_BOOTSTRAP_TOOLS '.node-target')) }" ^
|
|
205
|
+
"else { [Console]::Error.WriteLine('Bootstrap: Node version, execution, deadline, output bound or policy check failed; refusing replacement. Reason: ' + $reason); exit 1 } }" ^
|
|
206
|
+
"finally { try { if ($child) { if ($started -and -not $child.HasExited) { $child.Kill(); if (-not $child.WaitForExit(1000)) { throw 'Child termination unconfirmed' } }; $child.Dispose() } } catch { [Console]::Error.WriteLine('Bootstrap: direct-child termination could not be confirmed.'); exit 1 } } }"
|
|
207
|
+
if errorlevel 1 goto failed
|
|
208
|
+
if not exist "%GENTLE_BOOTSTRAP_TOOLS%\.node-target" goto acquirenode
|
|
209
|
+
|
|
210
|
+
rem Launch the existing Node helper with data arguments; no shell evaluation.
|
|
211
|
+
rem Interactive wizard duration is intentionally unbounded. Only child PATH changes.
|
|
212
|
+
"%GENTLE_BOOTSTRAP_PS%" -NoLogo -NoProfile -NonInteractive -Command ^
|
|
213
|
+
"& { try { $ErrorActionPreference = 'Stop';" ^
|
|
214
|
+
"if ($ExecutionContext.SessionState.LanguageMode -ne 'FullLanguage') { throw 'Managed policy constraint' };" ^
|
|
215
|
+
"$tools = $env:GENTLE_BOOTSTRAP_TOOLS; $record = Join-Path $tools '.node-target'; $node = [IO.File]::ReadAllText($record,[Text.Encoding]::UTF8).TrimEnd([char]13,[char]10);" ^
|
|
216
|
+
"$env:PATH = [IO.Path]::GetDirectoryName($node) + ';' + $env:PATH;" ^
|
|
217
|
+
"$helper = Join-Path $env:GENTLE_BOOTSTRAP_BUNDLE 'scripts/installer-downloads.mjs';" ^
|
|
218
|
+
"if (Test-Path -LiteralPath (Join-Path $tools 'node.zip')) { Remove-Item -LiteralPath (Join-Path $tools 'node.zip') };" ^
|
|
219
|
+
"& $node $helper '--bootstrap-windows' $env:GENTLE_BOOTSTRAP_BUNDLE $tools; if ($LASTEXITCODE -ne 0) { throw 'Helper failed' };" ^
|
|
220
|
+
"} catch { [Console]::Error.WriteLine('Bootstrap: prerequisite helper/wizard failed or managed policy denied execution.'); exit 1 } }"
|
|
221
|
+
if errorlevel 1 goto failed
|
|
222
|
+
|
|
223
|
+
rem Success: node/npm/pnpm now persist under PNPM_HOME or were already the user's.
|
|
224
|
+
rem Remove only our exact claimed, marked root; Directory.Delete does not recurse
|
|
225
|
+
rem through reparse points. A removal problem never fails the installation.
|
|
226
|
+
"%GENTLE_BOOTSTRAP_PS%" -NoLogo -NoProfile -NonInteractive -Command ^
|
|
227
|
+
"& { try { $ErrorActionPreference = 'Stop';" ^
|
|
228
|
+
"if ($ExecutionContext.SessionState.LanguageMode -ne 'FullLanguage') { throw 'Managed policy constraint' };" ^
|
|
229
|
+
"$tools = [IO.Path]::GetFullPath($env:GENTLE_BOOTSTRAP_TOOLS); $parent = [IO.Directory]::GetParent($tools).FullName;" ^
|
|
230
|
+
"if ($tools.StartsWith('\\') -or ($parent -ne [IO.Path]::GetFullPath($env:LOCALAPPDATA) -and $parent -ne [IO.Path]::GetFullPath($env:USERPROFILE)) -or -not [IO.Path]::GetFileName($tools).StartsWith('.gentle-shell-bootstrap-tools.',[StringComparison]::Ordinal)) { throw 'Cleanup target rejected' };" ^
|
|
231
|
+
"$item = Get-Item -LiteralPath $tools -Force;" ^
|
|
232
|
+
"if (-not $item.PSIsContainer -or ($item.Attributes -band [IO.FileAttributes]::ReparsePoint)) { throw 'Cleanup target rejected' };" ^
|
|
233
|
+
"$marker = Get-Item -LiteralPath (Join-Path $tools '.bootstrap-owned') -Force;" ^
|
|
234
|
+
"if ($marker.PSIsContainer -or ($marker.Attributes -band [IO.FileAttributes]::ReparsePoint) -or (Get-Content -LiteralPath $marker.FullName -Raw) -ne 'gentle-pi prerequisite tooling only') { throw 'Cleanup marker rejected' };" ^
|
|
235
|
+
"[IO.Directory]::Delete($tools, $true);" ^
|
|
236
|
+
"} catch { $message = 'Bootstrap: installation finished, but temporary tools could not be removed: ' + $env:GENTLE_BOOTSTRAP_TOOLS;" ^
|
|
237
|
+
"if ([Console]::IsErrorRedirected) { $bytes = [Text.Encoding]::UTF8.GetBytes($message + [Environment]::NewLine); $stderr = [Console]::OpenStandardError(); $stderr.Write($bytes, 0, $bytes.Length); $stderr.Flush() } else { [Console]::Error.WriteLine($message) }; exit 1 } }"
|
|
238
|
+
endlocal & exit /b 0
|
|
239
|
+
|
|
240
|
+
:unavailable
|
|
241
|
+
>&2 echo Bootstrap: stock Windows PowerShell is unavailable. No acquisition attempted.
|
|
242
|
+
:failed
|
|
243
|
+
rem Never scan/reuse/clean other attempts. Only our successfully claimed root.
|
|
244
|
+
if not defined GENTLE_BOOTSTRAP_OWNED goto finishfailure
|
|
245
|
+
"%GENTLE_BOOTSTRAP_PS%" -NoLogo -NoProfile -NonInteractive -Command ^
|
|
246
|
+
"& { try { $ErrorActionPreference = 'Stop';" ^
|
|
247
|
+
"if ($ExecutionContext.SessionState.LanguageMode -ne 'FullLanguage') { throw 'Managed policy constraint' };" ^
|
|
248
|
+
"$tools = $env:GENTLE_BOOTSTRAP_TOOLS; $item = Get-Item -LiteralPath $tools -Force;" ^
|
|
249
|
+
"if (-not $item.PSIsContainer -or ($item.Attributes -band [IO.FileAttributes]::ReparsePoint)) { throw 'Cleanup target rejected' };" ^
|
|
250
|
+
"$marker = Get-Item -LiteralPath (Join-Path $tools '.bootstrap-owned') -Force;" ^
|
|
251
|
+
"if ($marker.PSIsContainer -or ($marker.Attributes -band [IO.FileAttributes]::ReparsePoint) -or (Get-Content -LiteralPath $marker.FullName -Raw) -ne 'gentle-pi prerequisite tooling only') { throw 'Cleanup marker rejected' };" ^
|
|
252
|
+
"Remove-Item -LiteralPath $tools -Recurse -Force;" ^
|
|
253
|
+
"} catch { [Console]::Error.WriteLine('Bootstrap: attempt-owned cleanup unavailable; no other storage touched.'); exit 1 } }"
|
|
254
|
+
:finishfailure
|
|
255
|
+
endlocal & exit /b 1
|