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.
Files changed (590) hide show
  1. package/README.md +155 -10
  2. package/assets/agents/gentle-ai-explore.md +13 -6
  3. package/assets/agents/gentle-ai-verify.md +27 -6
  4. package/assets/agents/gentle-ai-worker.md +13 -15
  5. package/assets/install-wizard/index.html +47 -0
  6. package/assets/install-wizard/wizard.css +1279 -0
  7. package/assets/install-wizard/wizard.js +951 -0
  8. package/assets/orchestrator-delegation.md +75 -169
  9. package/assets/orchestrator-memory.md +12 -26
  10. package/assets/orchestrator-prompts.md +48 -0
  11. package/assets/orchestrator-skills.md +1 -1
  12. package/assets/orchestrator-tracking.md +53 -0
  13. package/assets/orchestrator-verification.md +28 -0
  14. package/assets/orchestrator-writer.md +38 -0
  15. package/assets/orchestrator.md +30 -38
  16. package/assets/sounds/LICENSE.md +52 -0
  17. package/assets/sounds/attention.wav +0 -0
  18. package/assets/sounds/error.wav +0 -0
  19. package/assets/sounds/success.wav +0 -0
  20. package/assets/support/strict-tdd-verify.md +4 -266
  21. package/assets/support/strict-tdd.md +8 -360
  22. package/bin/engram-guard +9 -0
  23. package/bin/gentle-shell-install.mjs +302 -0
  24. package/bin/gentle-shell.mjs +956 -50
  25. package/contracts/review-provider-contract-mirror/provider-contract.lock.json +7 -7
  26. package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/manifest.json +2 -2
  27. package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/orchestration/pi.md +16 -4
  28. package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-capabilities.baseline.json +2 -2
  29. package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-roles.baseline.json +1 -1
  30. package/docs/bundled-install.md +397 -0
  31. package/docs/delegated-verification.md +27 -4
  32. package/docs/gentle-agents-activity.md +537 -0
  33. package/docs/gentle-shell-uninstall.md +136 -0
  34. package/docs/gentle-shell.md +165 -51
  35. package/docs/install-wizard.md +2040 -0
  36. package/docs/native-authority-architecture.md +3 -3
  37. package/docs/permission-modes.md +82 -0
  38. package/docs/prompt-history.md +280 -0
  39. package/docs/readme-reference.md +364 -231
  40. package/docs/render-cache-performance.md +28 -0
  41. package/docs/sound-notifications-proposal.md +191 -0
  42. package/docs/sound-notifications.md +114 -0
  43. package/docs/telemetry.md +1 -1
  44. package/docs/yolo-mode.md +86 -0
  45. package/extensions/child-context.ts +35 -0
  46. package/extensions/child-safety.ts +25 -0
  47. package/extensions/codegraph-tools.ts +18 -5
  48. package/extensions/gentle-agents.ts +1093 -326
  49. package/extensions/gentle-ai.ts +1636 -890
  50. package/extensions/gentle-herdr-activity.ts +81 -0
  51. package/extensions/gentle-notifications.ts +104 -0
  52. package/extensions/gentle-shell.ts +1619 -136
  53. package/extensions/gentle-stats.ts +101 -0
  54. package/extensions/gentle-todo.ts +126 -20
  55. package/extensions/history/atomic-write.ts +38 -0
  56. package/extensions/history/hide-prompts.ts +183 -0
  57. package/extensions/history/index.ts +1424 -0
  58. package/extensions/history/load-shared-history.ts +39 -0
  59. package/extensions/history/selector-helpers.ts +538 -0
  60. package/extensions/history/session-scan.ts +233 -0
  61. package/extensions/history/store.ts +1119 -0
  62. package/extensions/nan-provider.ts +6 -0
  63. package/extensions/pi-pretty.ts +39 -3
  64. package/extensions/quiet-tools.ts +254 -92
  65. package/extensions/resume-hint.ts +60 -0
  66. package/extensions/skill-registry.ts +191 -58
  67. package/extensions/startup-banner.ts +71 -33
  68. package/lib/agent-assets.ts +604 -0
  69. package/lib/agent-home.ts +10 -1
  70. package/lib/agent-model-resolution.ts +80 -0
  71. package/lib/agent-profile-pin.ts +12 -0
  72. package/lib/agent-profiles.ts +13 -3
  73. package/lib/agents-completion-delivery.ts +11 -3
  74. package/lib/agents-message-delivery.ts +181 -0
  75. package/lib/agents-messaging.ts +1 -1
  76. package/lib/agents-protocol.ts +99 -0
  77. package/lib/agents-runner.ts +180 -107
  78. package/lib/agents-view.ts +26 -3
  79. package/lib/agents-widget.ts +16 -9
  80. package/lib/append-system-prompt.ts +21 -0
  81. package/lib/auto-permission-classifier.ts +280 -0
  82. package/lib/auto-permission-runtime.ts +268 -0
  83. package/lib/background-jobs-tools.ts +202 -0
  84. package/lib/background-jobs.ts +253 -0
  85. package/lib/background-monitor.ts +154 -0
  86. package/lib/bounded-writer-admission.ts +220 -0
  87. package/lib/builtin-codemode-optout.ts +166 -0
  88. package/lib/card-content-policy.ts +85 -0
  89. package/lib/card-style-policy.ts +60 -0
  90. package/lib/child-context-files.ts +166 -0
  91. package/lib/child-package-injection.ts +62 -0
  92. package/lib/codemode-renderer.ts +195 -0
  93. package/lib/command-palette-catalog.ts +5 -10
  94. package/lib/command-palette.ts +25 -14
  95. package/lib/context-threshold.ts +26 -0
  96. package/lib/destructive-command-guard.ts +144 -0
  97. package/lib/gentle-ai-binary.ts +109 -4
  98. package/lib/gentle-ai-elapsed-store.ts +87 -0
  99. package/lib/gentle-ai-renderer.ts +196 -39
  100. package/lib/gentle-shell-bundled.ts +517 -0
  101. package/lib/gentle-shell-launcher.ts +89 -27
  102. package/lib/gentle-shell-resume-hint.ts +176 -0
  103. package/lib/gentle-shell-startup-flow.ts +54 -0
  104. package/lib/gentle-shell-startup-update.ts +92 -0
  105. package/lib/gentle-shell-uninstall.ts +840 -0
  106. package/lib/gentle-shell-update-panel.ts +231 -0
  107. package/lib/herdr-activity.ts +183 -0
  108. package/lib/history-capture-policy.ts +95 -0
  109. package/lib/inprocess-reviewer.ts +3 -0
  110. package/lib/jobs-sidebar-state.ts +20 -0
  111. package/lib/jobs-view.ts +161 -0
  112. package/lib/model-price-ratio.ts +83 -0
  113. package/lib/model-routing-authority.ts +5 -1
  114. package/lib/nan-provider.ts +249 -0
  115. package/lib/native-review-cli.ts +242 -180
  116. package/lib/notification-audio-native.ts +162 -0
  117. package/lib/notification-audio-windows.ts +399 -0
  118. package/lib/notification-audio.ts +332 -0
  119. package/lib/notification-customize.ts +241 -0
  120. package/lib/notification-events.ts +113 -0
  121. package/lib/notification-policy.ts +148 -0
  122. package/lib/notification-pulse-client.ts +367 -0
  123. package/lib/notification-pulse-protocol.ts +305 -0
  124. package/lib/notification-pulse-stream.ts +213 -0
  125. package/lib/notification-pulse-worker.ts +66 -0
  126. package/lib/notification-scheduler.ts +173 -0
  127. package/lib/notification-service.ts +202 -0
  128. package/lib/notification-ui.ts +87 -0
  129. package/lib/odd-phase-inference.ts +231 -0
  130. package/lib/odd-phase.ts +141 -0
  131. package/lib/orchestrator-alias.ts +20 -0
  132. package/lib/orchestrator-catalog.ts +79 -0
  133. package/lib/orchestrator-consultation.ts +61 -0
  134. package/lib/orchestrator-discovery.ts +49 -0
  135. package/lib/orchestrator-helper-consent.ts +122 -0
  136. package/lib/orchestrator-helper.ts +167 -0
  137. package/lib/orchestrator-message-card.ts +105 -0
  138. package/lib/orchestrator-presence.ts +115 -14
  139. package/lib/orchestrator-scope.ts +80 -0
  140. package/lib/orchestrator-state.ts +107 -0
  141. package/lib/orchestrator-tool-card.ts +280 -0
  142. package/lib/orchestrator-work-search.ts +218 -0
  143. package/lib/orchestrator-work.ts +79 -0
  144. package/lib/overlay-repaint.ts +26 -0
  145. package/lib/permission-mode-policy.ts +59 -0
  146. package/lib/pi-tui-keys.ts +53 -0
  147. package/lib/review-candidate-view-owner.ts +73 -21
  148. package/lib/review-candidate-view.ts +115 -27
  149. package/lib/review-compact-contract.ts +2 -2
  150. package/lib/review-host-relay.ts +7 -2
  151. package/lib/review-integration-v2.ts +100 -22
  152. package/lib/review-publication-gate.ts +1 -1
  153. package/lib/review-reminder-receipt.ts +48 -8
  154. package/lib/review-risk-assessment.ts +227 -56
  155. package/lib/review-sidebar-state.ts +224 -0
  156. package/lib/review-transaction.ts +72 -3
  157. package/lib/runtime-metrics-children.ts +1 -1
  158. package/lib/runtime-metrics-native.ts +3 -2
  159. package/lib/selection-engine.ts +527 -0
  160. package/lib/session-change-capture.ts +5 -4
  161. package/lib/session-changes.ts +27 -22
  162. package/lib/session-messaging-grants.ts +135 -0
  163. package/lib/session-profile-binding.ts +83 -0
  164. package/lib/session-profile-freeze.ts +266 -0
  165. package/lib/session-worktree-registry.ts +87 -9
  166. package/lib/shell-bar.ts +215 -30
  167. package/lib/shell-card.ts +287 -18
  168. package/lib/shell-changes-view.ts +42 -14
  169. package/lib/shell-changes.ts +86 -71
  170. package/lib/shell-prompt.ts +102 -6
  171. package/lib/shell-sidebar-layout.ts +83 -15
  172. package/lib/shell-sidebar.ts +15 -1
  173. package/lib/shell-todo.ts +84 -31
  174. package/lib/shell-usage-view.ts +9 -4
  175. package/lib/shell-usage.ts +70 -13
  176. package/lib/stats-collector.ts +381 -0
  177. package/lib/stats-view.ts +431 -0
  178. package/lib/status-timing-diagnostics.ts +201 -0
  179. package/lib/telemetry-trigger.ts +3 -1
  180. package/lib/theme-customization.ts +52 -0
  181. package/lib/vim-editor-adapter.ts +383 -0
  182. package/lib/vim-normal-engine.ts +154 -0
  183. package/lib/vim-operator-engine.ts +416 -0
  184. package/lib/vim-policy.ts +49 -0
  185. package/lib/vim-visual-engine.ts +107 -0
  186. package/lib/visual-customization-policy.ts +119 -0
  187. package/lib/visual-customize-view.ts +481 -0
  188. package/lib/visual-profiles.ts +228 -0
  189. package/lib/writer-surfaces.ts +162 -0
  190. package/lib/yolo-session-policy.ts +317 -0
  191. package/package.json +20 -8
  192. package/runtime/child-package-injection.mjs +63 -0
  193. package/runtime/gentle-ai-binary.mjs +109 -4
  194. package/runtime/gentle-shell-bundled.mjs +518 -0
  195. package/runtime/gentle-shell-launcher.mjs +87 -25
  196. package/runtime/gentle-shell-resume-hint.mjs +177 -0
  197. package/runtime/gentle-shell-startup-flow.mjs +55 -0
  198. package/runtime/gentle-shell-startup-update.mjs +93 -0
  199. package/runtime/gentle-shell-uninstall.mjs +841 -0
  200. package/runtime/gentle-shell-update-panel.mjs +232 -0
  201. package/runtime/native-review-cli.mjs +239 -177
  202. package/runtime/review-integration-v2.mjs +100 -22
  203. package/runtime/review-risk-assessment.mjs +225 -54
  204. package/runtime/status-timing-diagnostics.mjs +202 -0
  205. package/runtime/telemetry-trigger.mjs +3 -1
  206. package/scripts/bootstrap.cmd +255 -0
  207. package/scripts/bootstrap.sh +168 -0
  208. package/scripts/build-distribution.mjs +157 -0
  209. package/scripts/build-installer-bundles.mjs +136 -0
  210. package/scripts/build-runtime-modules.mjs +8 -0
  211. package/scripts/bundled-install.mjs +1308 -0
  212. package/scripts/bundled-wizard.mjs +399 -0
  213. package/scripts/check-types.mjs +4 -6
  214. package/scripts/gentle-ai-installer.mjs +83 -35
  215. package/scripts/install-gentle-ai.mjs +48 -1
  216. package/scripts/install-tui-mode-setting.mjs +10 -1
  217. package/scripts/install-wizard-preview.mjs +154 -0
  218. package/scripts/installer-downloads.mjs +648 -0
  219. package/scripts/installer-preflight.mjs +387 -0
  220. package/scripts/installer-probes.mjs +564 -0
  221. package/scripts/installer-runner.mjs +1102 -0
  222. package/scripts/installer-server.mjs +881 -0
  223. package/scripts/installer-windows-artifacts.json +15 -0
  224. package/scripts/installer-windows.mjs +752 -0
  225. package/scripts/main-channel.mjs +452 -0
  226. package/scripts/mirror-odd-routing.mjs +2 -2
  227. package/scripts/npm-pack-result.mjs +31 -0
  228. package/scripts/render-cache-performance-worker.mjs +205 -0
  229. package/scripts/render-cache-performance.mjs +115 -0
  230. package/scripts/run-test-suite.mjs +90 -0
  231. package/scripts/test-installer-acceptance.sh +161 -0
  232. package/scripts/test-packed-runner.mjs +54 -19
  233. package/scripts/types-baseline.json +2 -92
  234. package/scripts/verify-package-files.mjs +91 -38
  235. package/skills/branch-pr/SKILL.md +24 -52
  236. package/skills/chained-pr/SKILL.md +31 -15
  237. package/skills/chained-pr/references/chaining-details.md +31 -20
  238. package/skills/gentle-ai/SKILL.md +16 -21
  239. package/skills/issue-creation/SKILL.md +8 -2
  240. package/skills/issue-creation/references/delegated-workflow-actions.md +19 -0
  241. package/skills/work-unit-commits/SKILL.md +4 -3
  242. package/tests/agent-home.test.ts +56 -3
  243. package/tests/agent-model-resolution.test.ts +113 -0
  244. package/tests/agent-profiles.test.ts +30 -0
  245. package/tests/agent-risk-escalation.test.ts +123 -0
  246. package/tests/agents-completion-delivery.test.ts +17 -0
  247. package/tests/agents-fake-child.ts +2 -2
  248. package/tests/agents-message-delivery.test.ts +106 -0
  249. package/tests/agents-protocol.test.ts +97 -0
  250. package/tests/agents-queries.test.ts +9 -9
  251. package/tests/agents-runner-process.test.ts +31 -0
  252. package/tests/agents-runner.test.ts +467 -93
  253. package/tests/agents-view-thread-identity.test.ts +169 -0
  254. package/tests/agents-view.test.ts +8 -2
  255. package/tests/agents-widget.test.ts +154 -15
  256. package/tests/append-system-prompt-route.test.ts +160 -0
  257. package/tests/append-system-prompt.test.ts +46 -0
  258. package/tests/artifact-language.test.ts +19 -213
  259. package/tests/ask-user-question.test.ts +44 -1
  260. package/tests/asset-installation-runtime.test.ts +5 -16
  261. package/tests/auto-mode-invariants.test.ts +258 -0
  262. package/tests/auto-mode-sdk.test.ts +57 -0
  263. package/tests/auto-mode.test.ts +49 -0
  264. package/tests/auto-permission-classifier.test.ts +283 -0
  265. package/tests/auto-permission-runtime.test.ts +196 -0
  266. package/tests/autonomous-guard.test.ts +69 -1
  267. package/tests/background-cache-warming.test.ts +2 -1
  268. package/tests/background-jobs.test.ts +227 -0
  269. package/tests/background-monitor.test.ts +195 -0
  270. package/tests/background-subagents-default-mode.test.ts +28 -5
  271. package/tests/bounded-stop-contract.test.ts +85 -0
  272. package/tests/bounded-writer-admission.test.ts +146 -0
  273. package/tests/branch-pr-skill.test.ts +43 -0
  274. package/tests/bridge-wake-lifecycle.test.ts +129 -0
  275. package/tests/bridge-wake-rendering.test.ts +56 -0
  276. package/tests/build-distribution.test.ts +269 -0
  277. package/tests/builtin-codemode-optout.test.ts +242 -0
  278. package/tests/bundled-install.test.ts +1319 -0
  279. package/tests/bundled-wizard.test.ts +505 -0
  280. package/tests/card-content-policy.test.ts +68 -0
  281. package/tests/card-style-policy.test.ts +87 -0
  282. package/tests/chained-pr-skill.test.ts +124 -0
  283. package/tests/child-codegraph-bootstrap.test.ts +74 -0
  284. package/tests/child-context-files.test.ts +255 -0
  285. package/tests/child-package-entrypoints.test.ts +65 -0
  286. package/tests/child-package-injection.test.ts +74 -0
  287. package/tests/child-safety.test.ts +94 -0
  288. package/tests/child-web-package.test.ts +224 -0
  289. package/tests/codegraph-tools.test.ts +35 -0
  290. package/tests/codemode-rendering.test.ts +596 -0
  291. package/tests/command-palette.test.ts +43 -7
  292. package/tests/context-threshold.test.ts +31 -0
  293. package/tests/delegate-for-reason-contract.test.ts +327 -0
  294. package/tests/delegated-key-learnings-contract.test.ts +3 -78
  295. package/tests/destructive-command-guard.test.ts +84 -0
  296. package/tests/devbinary/native-review-parity.devtest.ts +171 -3
  297. package/tests/devbinary/non-git-subagent-bootstrap.devtest.ts +193 -0
  298. package/tests/devbinary/pi-host-relay.devtest.ts +173 -3
  299. package/tests/engram-safety.test.ts +288 -0
  300. package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +7 -0
  301. package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +3 -0
  302. package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T08-00-00-000Z_ddd.jsonl +2 -0
  303. package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T09-00-00-000Z_eee.jsonl +3 -0
  304. package/tests/fixtures/stats/sessions/--work-alpha--/run-1/session.jsonl +2 -0
  305. package/tests/fixtures/stats/sessions/--work-beta--/2026-09-01T12-00-00-000Z_ccc.jsonl +2 -0
  306. package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +2 -0
  307. package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +4 -0
  308. package/tests/fixtures/stats/user-pi/sessions/--work-gamma--/2026-09-20T09-00-00-000Z_fff.jsonl +3 -0
  309. package/tests/generic-agent-tools.test.ts +69 -0
  310. package/tests/gentle-agents.test.ts +2804 -346
  311. package/tests/gentle-ai-binary-isolation.test.ts +135 -0
  312. package/tests/gentle-ai-binary.test.ts +21 -4
  313. package/tests/gentle-ai-child-guards.test.ts +202 -0
  314. package/tests/gentle-ai-dev-binary-surfacing.test.ts +125 -15
  315. package/tests/gentle-ai-elapsed-store.test.ts +68 -0
  316. package/tests/gentle-ai-installer.test.ts +566 -62
  317. package/tests/gentle-ai-prompt-model-routing.test.ts +123 -0
  318. package/tests/gentle-ai-renderer.test.ts +493 -14
  319. package/tests/gentle-ai.test.ts +1427 -236
  320. package/tests/gentle-card-text.ts +2 -1
  321. package/tests/gentle-herdr-activity.test.ts +100 -0
  322. package/tests/gentle-notifications.test.ts +427 -0
  323. package/tests/gentle-shell-bin.test.ts +896 -115
  324. package/tests/gentle-shell-bundled.test.ts +1002 -0
  325. package/tests/gentle-shell-launcher.test.ts +333 -65
  326. package/tests/gentle-shell-resume-hint.test.ts +270 -0
  327. package/tests/gentle-shell-startup-flow.test.ts +75 -0
  328. package/tests/gentle-shell-startup-update.test.ts +85 -0
  329. package/tests/gentle-shell-uninstall.test.ts +978 -0
  330. package/tests/gentle-shell-update-panel.test.ts +158 -0
  331. package/tests/gentle-shell.test.ts +4216 -119
  332. package/tests/gentle-stats.test.ts +152 -0
  333. package/tests/gentle-theme.test.ts +4 -1
  334. package/tests/gentle-todo.test.ts +333 -10
  335. package/tests/herdr-activity.test.ts +243 -0
  336. package/tests/history-agent-home.test.ts +81 -0
  337. package/tests/history-atomic-write.test.ts +57 -0
  338. package/tests/history-capture-policy.test.ts +102 -0
  339. package/tests/history-child-guard.test.ts +70 -0
  340. package/tests/history-command-registration.test.ts +164 -0
  341. package/tests/history-dedupe-entries.test.ts +123 -0
  342. package/tests/history-delete-backfill.test.ts +190 -0
  343. package/tests/history-delete-confirm.test.ts +460 -0
  344. package/tests/history-dispatch.test.ts +180 -0
  345. package/tests/history-drain-hidden.test.ts +110 -0
  346. package/tests/history-drain-order.test.ts +98 -0
  347. package/tests/history-expanded-globals.test.ts +62 -0
  348. package/tests/history-gc.test.ts +832 -0
  349. package/tests/history-header-layout.test.ts +265 -0
  350. package/tests/history-hide-prompts.test.ts +275 -0
  351. package/tests/history-lazy-windowing.test.ts +508 -0
  352. package/tests/history-legacy-migrate-v2.test.ts +297 -0
  353. package/tests/history-load-shared-history.test.ts +53 -0
  354. package/tests/history-max-results-cap.test.ts +76 -0
  355. package/tests/history-multi-reader.test.ts +203 -0
  356. package/tests/history-off-path.test.ts +170 -0
  357. package/tests/history-openflow-integration.test.ts +173 -0
  358. package/tests/history-overlay-margin.test.ts +326 -0
  359. package/tests/history-preview-layout.test.ts +93 -0
  360. package/tests/history-registry.test.ts +143 -0
  361. package/tests/history-scope-delete.test.ts +411 -0
  362. package/tests/history-search-caret-keys.test.ts +142 -0
  363. package/tests/history-seed-bootstrap.test.ts +170 -0
  364. package/tests/history-seed-regen.test.ts +129 -0
  365. package/tests/history-selector-windowing.test.ts +94 -0
  366. package/tests/history-session-scan-directory.test.ts +87 -0
  367. package/tests/history-session-scan-extract.test.ts +583 -0
  368. package/tests/history-session-writer.test.ts +351 -0
  369. package/tests/history-store-paths.test.ts +79 -0
  370. package/tests/history-tombstone-exact.test.ts +139 -0
  371. package/tests/history-wheel-mouse.test.ts +242 -0
  372. package/tests/inprocess-reviewer.test.ts +49 -19
  373. package/tests/install-gentle-ai-failure.test.ts +69 -0
  374. package/tests/install-tui-mode-setting.test.ts +15 -0
  375. package/tests/install-wizard.test.ts +1223 -0
  376. package/tests/installer-bundles.test.ts +122 -0
  377. package/tests/installer-posix-bootstrap.test.ts +1201 -0
  378. package/tests/installer-preflight.test.ts +750 -0
  379. package/tests/installer-probes.test.ts +1106 -0
  380. package/tests/installer-runner.test.ts +2158 -0
  381. package/tests/installer-server.test.ts +1567 -0
  382. package/tests/installer-windows-bootstrap.test.ts +2410 -0
  383. package/tests/issue-creation-skill.test.ts +61 -0
  384. package/tests/jobs-view.test.ts +160 -0
  385. package/tests/main-channel-upgrade.test.ts +389 -0
  386. package/tests/main-channel.test.ts +344 -0
  387. package/tests/maintainer/provider-relay.maintest.ts +13 -12
  388. package/tests/model-price-ratio.test.ts +116 -0
  389. package/tests/model-profiles-preservation.test.ts +153 -0
  390. package/tests/model-routing-authority.test.ts +16 -0
  391. package/tests/model-routing-package.test.ts +10 -0
  392. package/tests/nan-provider-runtime.test.ts +156 -0
  393. package/tests/nan-provider.test.ts +481 -0
  394. package/tests/native-binary-gate.test.ts +2 -0
  395. package/tests/native-review-capability-contract.test.ts +13 -1
  396. package/tests/native-review-cli.test.ts +239 -118
  397. package/tests/native-review-consent.test.ts +2 -2
  398. package/tests/native-review-parity-runtime.test.ts +101 -4
  399. package/tests/native-review-parity.test.ts +12 -48
  400. package/tests/native-risk-evidence-parity.test.ts +21 -0
  401. package/tests/no-self-pause-contract.test.ts +87 -0
  402. package/tests/notification-audio-native.test.ts +281 -0
  403. package/tests/notification-audio-windows.test.ts +581 -0
  404. package/tests/notification-audio.test.ts +548 -0
  405. package/tests/notification-customize.test.ts +655 -0
  406. package/tests/notification-events.test.ts +115 -0
  407. package/tests/notification-policy.test.ts +201 -0
  408. package/tests/notification-pulse-client.test.ts +356 -0
  409. package/tests/notification-pulse-protocol.test.ts +213 -0
  410. package/tests/notification-pulse-stream.test.ts +320 -0
  411. package/tests/notification-scheduler.test.ts +258 -0
  412. package/tests/notification-ui.test.ts +157 -0
  413. package/tests/npm-pack-result.test.ts +41 -0
  414. package/tests/odd-integration.test.ts +76 -0
  415. package/tests/odd-phase-inference.test.ts +213 -0
  416. package/tests/odd-phase-loader.test.ts +253 -0
  417. package/tests/odd-phase.test.ts +307 -0
  418. package/tests/odd-routing-canonical-ratchet.test.ts +60 -14
  419. package/tests/odd-routing-contract.test.ts +158 -50
  420. package/tests/orchestrator-budget.test.ts +43 -45
  421. package/tests/orchestrator-catalog.test.ts +146 -0
  422. package/tests/orchestrator-consultation-sdk.test.ts +614 -0
  423. package/tests/orchestrator-consultation.test.ts +111 -0
  424. package/tests/orchestrator-discovery.test.ts +241 -0
  425. package/tests/orchestrator-helper-consent.test.ts +171 -0
  426. package/tests/orchestrator-helper.test.ts +297 -0
  427. package/tests/orchestrator-message-card.test.ts +190 -0
  428. package/tests/orchestrator-presence.test.ts +23 -0
  429. package/tests/orchestrator-rdd-ownership.test.ts +8 -7
  430. package/tests/orchestrator-scope.test.ts +107 -0
  431. package/tests/orchestrator-state.test.ts +114 -0
  432. package/tests/orchestrator-status-publication-contract.test.ts +34 -0
  433. package/tests/orchestrator-tool-card.test.ts +398 -0
  434. package/tests/orchestrator-work-search.test.ts +318 -0
  435. package/tests/orchestrator-work.test.ts +147 -0
  436. package/tests/overlay-repaint.test.ts +74 -0
  437. package/tests/package-manifest.test.ts +344 -123
  438. package/tests/packed-runner-owned-path.test.ts +80 -0
  439. package/tests/parallel-reason-contract.test.ts +21 -0
  440. package/tests/parallel-writer-admission.test.ts +210 -0
  441. package/tests/permission-mode-policy.test.ts +194 -0
  442. package/tests/persona-single-channel.test.ts +10 -18
  443. package/tests/pi-pretty.test.ts +56 -0
  444. package/tests/provider-defect-handoff.test.ts +7 -14
  445. package/tests/quiet-bash-runtime.test.ts +76 -0
  446. package/tests/quiet-tool-rendering.test.ts +569 -187
  447. package/tests/rdd-aware-verification-contract.test.ts +103 -40
  448. package/tests/rdd-status-line.test.ts +9 -4
  449. package/tests/render-cache-performance.test.ts +48 -0
  450. package/tests/resume-hint-extension.test.ts +122 -0
  451. package/tests/review-agent-end-preflight.test.ts +237 -21
  452. package/tests/review-candidate-owner-retry.test.ts +22 -1
  453. package/tests/review-candidate-view.test.ts +399 -12
  454. package/tests/review-contract-prompt.test.ts +138 -44
  455. package/tests/review-controller-lock-status.test.ts +0 -1
  456. package/tests/review-controller-native-recovery.test.ts +327 -17
  457. package/tests/review-controller-native-routing.test.ts +866 -6
  458. package/tests/review-controller-workspace-root.test.ts +206 -6
  459. package/tests/review-controller.test.ts +2 -2
  460. package/tests/review-correction-lifecycle.test.ts +2 -1
  461. package/tests/review-gate.test.ts +7 -4
  462. package/tests/review-graph-schema.test.ts +2 -2
  463. package/tests/review-host-relay-routing.test.ts +342 -4
  464. package/tests/review-host-relay.test.ts +49 -2
  465. package/tests/review-integration-v2-forward.test.ts +221 -1
  466. package/tests/review-json-arguments.test.ts +167 -0
  467. package/tests/review-last-event-closure.test.ts +157 -8
  468. package/tests/review-ledger-contract.test.ts +14 -37
  469. package/tests/review-policy-judgment-day.test.ts +1 -1
  470. package/tests/review-policy-ordinary.test.ts +2 -2
  471. package/tests/review-relay-transport-agent.test.ts +66 -1
  472. package/tests/review-reminder-receipt.test.ts +47 -1
  473. package/tests/review-risk-assessment.test.ts +594 -26
  474. package/tests/review-session-standing-permission-controller.test.ts +16 -17
  475. package/tests/review-session-standing-permission-ipc.test.ts +6 -5
  476. package/tests/review-sidebar-state.test.ts +402 -0
  477. package/tests/review-test-fixtures.ts +7 -1
  478. package/tests/review-transaction.test.ts +18 -1
  479. package/tests/run-test-suite.test.ts +202 -0
  480. package/tests/runtime-harness.mjs +186 -800
  481. package/tests/runtime-metrics-children.test.ts +16 -23
  482. package/tests/runtime-metrics-extension.test.ts +1 -1
  483. package/tests/runtime-metrics-native.test.ts +31 -1
  484. package/tests/selection-engine.test.ts +451 -0
  485. package/tests/session-change-capture.test.ts +10 -10
  486. package/tests/session-changes-shell.test.ts +242 -48
  487. package/tests/session-changes.test.ts +17 -24
  488. package/tests/session-messaging-grants.test.ts +255 -0
  489. package/tests/session-profile-binding.test.ts +163 -0
  490. package/tests/session-profile-freeze.test.ts +381 -0
  491. package/tests/session-subject-routing-contract.test.ts +34 -0
  492. package/tests/session-worktree-registry.test.ts +134 -2
  493. package/tests/shell-bar.test.ts +422 -1
  494. package/tests/shell-card.test.ts +353 -1
  495. package/tests/shell-changes-view.test.ts +135 -32
  496. package/tests/shell-changes.test.ts +61 -62
  497. package/tests/shell-prompt.test.ts +94 -2
  498. package/tests/shell-render-cache-runtime.test.ts +69 -0
  499. package/tests/shell-render-cache.test.ts +129 -0
  500. package/tests/shell-sidebar-layout.test.ts +351 -22
  501. package/tests/shell-sidebar-scroll-benchmark.test.ts +255 -0
  502. package/tests/shell-todo.test.ts +207 -3
  503. package/tests/shell-usage-view.test.ts +28 -1
  504. package/tests/shell-usage.test.ts +108 -0
  505. package/tests/short-check-routing-contract.test.ts +49 -0
  506. package/tests/skill-registry-watcher.test.ts +119 -0
  507. package/tests/skill-registry.test.ts +357 -2
  508. package/tests/startup-banner.test.ts +246 -4
  509. package/tests/startup-home-repo.test.ts +170 -0
  510. package/tests/stats-collector.test.ts +195 -0
  511. package/tests/stats-view.test.ts +202 -0
  512. package/tests/status-timing-diagnostics.test.ts +283 -0
  513. package/tests/support/orchestrator-modules.ts +19 -0
  514. package/tests/task-size-routing-contract.test.ts +241 -0
  515. package/tests/telemetry-trigger.test.ts +107 -20
  516. package/tests/test-strength-contract.test.ts +124 -0
  517. package/tests/theme-customization.test.ts +72 -0
  518. package/tests/verify-agent-contract.test.ts +90 -0
  519. package/tests/verify-first-launch-contract.test.ts +139 -0
  520. package/tests/verify-package-files.test.ts +56 -0
  521. package/tests/vim-editor-adapter-host-resolution.test.ts +37 -0
  522. package/tests/vim-editor-adapter.test.ts +804 -0
  523. package/tests/vim-normal-engine.test.ts +101 -0
  524. package/tests/vim-operator-engine.test.ts +215 -0
  525. package/tests/vim-policy.test.ts +19 -0
  526. package/tests/vim-visual-engine.test.ts +52 -0
  527. package/tests/visual-customization-policy.test.ts +139 -0
  528. package/tests/visual-customize-view.test.ts +696 -0
  529. package/tests/visual-profiles.test.ts +87 -0
  530. package/tests/writer-edit-surface-scope.test.ts +59 -2
  531. package/tests/writer-surfaces.test.ts +199 -0
  532. package/tests/yolo-customize.test.ts +271 -0
  533. package/tests/yolo-mode-runtime.test.ts +188 -0
  534. package/tests/yolo-mode.test.ts +289 -0
  535. package/tests/yolo-session-policy.test.ts +59 -0
  536. package/themes/Gentle.json +2 -1
  537. package/themes/Gentleman-Cute.json +2 -1
  538. package/themes/Gentleman-Sexy.json +2 -1
  539. package/assets/agents/sdd-apply.md +0 -159
  540. package/assets/agents/sdd-archive.md +0 -228
  541. package/assets/agents/sdd-design.md +0 -49
  542. package/assets/agents/sdd-explore.md +0 -48
  543. package/assets/agents/sdd-init.md +0 -56
  544. package/assets/agents/sdd-onboard.md +0 -52
  545. package/assets/agents/sdd-proposal.md +0 -64
  546. package/assets/agents/sdd-remediate.md +0 -37
  547. package/assets/agents/sdd-research.md +0 -49
  548. package/assets/agents/sdd-spec.md +0 -192
  549. package/assets/agents/sdd-status.md +0 -54
  550. package/assets/agents/sdd-tasks.md +0 -108
  551. package/assets/agents/sdd-verify.md +0 -124
  552. package/assets/chains/sdd-full.chain.md +0 -83
  553. package/assets/chains/sdd-plan.chain.md +0 -56
  554. package/assets/chains/sdd-verify.chain.md +0 -43
  555. package/assets/sdd-orchestrator-workflow.md +0 -319
  556. package/assets/support/sdd-status-contract.md +0 -77
  557. package/docs/assets/diagrams/sdd-cycle.svg +0 -14
  558. package/extensions/sdd-init.ts +0 -816
  559. package/lib/openspec-deltas.ts +0 -156
  560. package/lib/sdd-preflight.ts +0 -1066
  561. package/lib/sdd-research-capabilities.ts +0 -94
  562. package/lib/sdd-status.ts +0 -26
  563. package/tests/fixtures/legacy/sdd-research-v2.5.0.md +0 -54
  564. package/tests/fixtures/native-review-cli/v2.1.3/bind-sdd.json +0 -25
  565. package/tests/fixtures/v0.10.7/assets/agents/sdd-apply.md +0 -132
  566. package/tests/openspec-deltas.test.ts +0 -209
  567. package/tests/sdd-agent-tools.test.ts +0 -156
  568. package/tests/sdd-archive-replay.test.ts +0 -82
  569. package/tests/sdd-classical-continuation.test.ts +0 -74
  570. package/tests/sdd-execution-routing-contract.test.ts +0 -44
  571. package/tests/sdd-managed-runtime-settlement.test.ts +0 -155
  572. package/tests/sdd-native-managed-uptake.test.ts +0 -243
  573. package/tests/sdd-no-attempts-contract.test.ts +0 -15
  574. package/tests/sdd-odd-integration.test.ts +0 -33
  575. package/tests/sdd-optional-research.test.ts +0 -124
  576. package/tests/sdd-planning-routing-contract.test.ts +0 -45
  577. package/tests/sdd-preflight-rpc-input.test.ts +0 -125
  578. package/tests/sdd-preflight.test.ts +0 -541
  579. package/tests/sdd-research-capabilities.test.ts +0 -114
  580. package/tests/sdd-research-live.test.ts +0 -241
  581. package/tests/sdd-selection-transport.test.ts +0 -653
  582. package/tests/sdd-status.test.ts +0 -9
  583. package/tests/sdd-task-truth.test.ts +0 -43
  584. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/README.md +0 -0
  585. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/lens.schema.json +0 -0
  586. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/refuter.schema.json +0 -0
  587. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/targeted-validator.schema.json +0 -0
  588. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/lens.json +0 -0
  589. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/refuter.json +0 -0
  590. /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/targeted-validator.json +0 -0
@@ -1,10 +1,11 @@
1
- import { join, resolve as resolvePath } from "node:path";
1
+ import { isAbsolute, join, resolve as resolvePath } from "node:path";
2
+ import { CHILD_PACKAGE_INJECTION_ENV, encodeChildPackageInjection, type ChildPackageInjection } from "./child-package-injection.ts";
2
3
 
3
4
  // The gentle-shell launcher: pure, side-effect-free functions over injected
4
5
  // env/fs/exec. `bin/gentle-shell.mjs` (T2) wires these into the real process,
5
6
  // filesystem and child process so this module stays fully unit-testable.
6
7
 
7
- export type LauncherCommand = "home" | "setup";
8
+ export type LauncherCommand = "home" | "setup" | "upgrade" | "self-uninstall";
8
9
 
9
10
  // pi's own package-management subcommands (see pi's cli/args.ts printHelp
10
11
  // "Commands" list): each is dispatched by pi itself, before pi's own flag
@@ -35,11 +36,12 @@ export interface ParsedLauncherArgs {
35
36
  error?: string;
36
37
  }
37
38
 
38
- // Home-subcommand parsing is deliberately shallow: `home` only counts as the
39
- // subcommand when it is argv[0], and everything after it is handed over
39
+ // Home-, upgrade- and self-uninstall-subcommand parsing is deliberately shallow:
40
+ // each only counts as the subcommand when it is argv[0], and everything after it is handed over
40
41
  // untouched as commandArgs — T2 owns interpreting `home link|isolated|<path>`.
42
+ // `self-uninstall` is not `uninstall`, which stays pi's own alias for `remove`.
41
43
  export function parseLauncherArgs(argv: string[]): ParsedLauncherArgs {
42
- if (argv[0] === "home") {
44
+ if (argv[0] === "home" || argv[0] === "upgrade" || argv[0] === "self-uninstall") {
43
45
  return {
44
46
  link: false,
45
47
  isolated: false,
@@ -47,7 +49,7 @@ export function parseLauncherArgs(argv: string[]): ParsedLauncherArgs {
47
49
  packageRoot: undefined,
48
50
  help: false,
49
51
  version: false,
50
- command: "home",
52
+ command: argv[0],
51
53
  commandArgs: argv.slice(1),
52
54
  passthrough: [],
53
55
  piSubcommand: undefined,
@@ -188,25 +190,36 @@ export interface ResolveHomeInput {
188
190
  config: LauncherConfig | undefined;
189
191
  }
190
192
 
191
- // Pi Subagents resolves `PI_CODING_AGENT_DIR || ~/.pi/agent`; `--link` reuses
192
- // that exact home so gentle-shell never diverges from the user's own pi.
193
+ // Pi Subagents resolves `PI_CODING_AGENT_DIR || ~/.pi/agent`; this is the
194
+ // user's own pi home when no Gentle Shell session is involved.
193
195
  function linkDir(env: Record<string, string | undefined>, homedir: string): string {
194
196
  return env.PI_CODING_AGENT_DIR || join(homedir, ".pi", "agent");
195
197
  }
196
198
 
197
- function isolatedDir(env: Record<string, string | undefined>, homedir: string): string {
199
+ // The isolated home replaces PI_CODING_AGENT_DIR for the whole session, so the
200
+ // user's own Pi home travels in this variable for read-only features such as
201
+ // /gentle:stats and for `--link`. An inherited value wins: a gentle-shell
202
+ // launched from inside a Gentle Shell session sees the outer isolated home as
203
+ // PI_CODING_AGENT_DIR, so `--link` must not re-select it (#2015).
204
+ export const USER_PI_HOME_ENV = "GENTLE_SHELL_USER_PI_HOME";
205
+
206
+ export function userPiHome(env: Record<string, string | undefined>, homedir: string): string {
207
+ return env[USER_PI_HOME_ENV] || linkDir(env, homedir);
208
+ }
209
+
210
+ export function isolatedDir(env: Record<string, string | undefined>, homedir: string): string {
198
211
  return env.GENTLE_SHELL_HOME || join(homedir, ".gentle-shell", "agent");
199
212
  }
200
213
 
201
214
  export function resolveHome(input: ResolveHomeInput): ResolvedHome {
202
215
  const { args, env, homedir, config } = input;
203
216
 
204
- if (args.link) return { mode: "link", dir: linkDir(env, homedir), source: "flag" };
217
+ if (args.link) return { mode: "link", dir: userPiHome(env, homedir), source: "flag" };
205
218
  if (args.isolated) return { mode: "isolated", dir: isolatedDir(env, homedir), source: "flag" };
206
219
  if (args.home !== undefined) return { mode: "path", dir: args.home, source: "flag" };
207
220
 
208
221
  if (config !== undefined) {
209
- if (config.mode === "link") return { mode: "link", dir: linkDir(env, homedir), source: "config" };
222
+ if (config.mode === "link") return { mode: "link", dir: userPiHome(env, homedir), source: "config" };
210
223
  if (config.mode === "isolated") return { mode: "isolated", dir: isolatedDir(env, homedir), source: "config" };
211
224
  return { mode: "path", dir: config.dir, source: "config" };
212
225
  }
@@ -379,7 +392,7 @@ export function missingPiMessage(): string {
379
392
 
380
393
  // --- pi version gate ---------------------------------------------------------
381
394
 
382
- export const MIN_PI_VERSION = "0.85.1";
395
+ export const MIN_PI_VERSION = "0.99.1";
383
396
 
384
397
  export type PiVersionCheck = { ok: true; version: string } | { ok: false; message: string; version?: string };
385
398
 
@@ -866,6 +879,12 @@ export interface BuildPiInvocationInput {
866
879
  // gentle-pi extension injection below may precede it.
867
880
  piSubcommand?: PiSubcommand;
868
881
  baseEnv: Record<string, string | undefined>;
882
+ // The OS home behind userPiHome's conventional ~/.pi/agent fallback.
883
+ homedir: string;
884
+ // The directory pi is spawned in, which pi resolves a relative -e path
885
+ // against. bin/gentle-shell.mjs spawns pi without a cwd, so this is the
886
+ // launcher's own process.cwd().
887
+ cwd: string;
869
888
  }
870
889
 
871
890
  export interface PiInvocation {
@@ -874,18 +893,11 @@ export interface PiInvocation {
874
893
  env: Record<string, string | undefined>;
875
894
  }
876
895
 
877
- function packageRootAssetArgs(packageRoot: string): string[] {
878
- return ["--theme", join(packageRoot, "themes"), "--skill", join(packageRoot, "skills"), "--prompt-template", join(packageRoot, "prompts")];
879
- }
880
-
881
- function packageRootInjectionArgs(packageRoot: string): string[] {
882
- return ["-e", packageRoot, ...packageRootAssetArgs(packageRoot)];
883
- }
884
896
 
885
897
  // Four cases, checked in this order — `piSubcommand` first, then `takeOver`:
886
898
  // - piSubcommand: pi dispatches install/remove/uninstall/update/list/
887
899
  // config/auth on argv[0] before it even parses flags, so any injected
888
- // -e/--theme/--skill/--prompt-template flag ahead of it stops pi from
900
+ // -e flag ahead of it stops pi from
889
901
  // recognising its subcommand at all — this is exactly the observed
890
902
  // 2026-09-22 bug where `gentle-shell install npm:x` opened an
891
903
  // interactive pi session instead of running the package manager. No
@@ -908,13 +920,21 @@ function packageRootInjectionArgs(packageRoot: string): string[] {
908
920
  // (R3-001): a loose entry that duplicates an other-package path, or
909
921
  // repeats within looseExtensionEntries itself, is skipped rather than
910
922
  // loaded twice.
911
- // - Not takeOver, no declaration: inject this launcher's own packageRoot,
923
+ // - Not takeOver, no declaration: inject this launcher's own packageRoot
924
+ // once via -e; Pi discovers its extensions, skills, prompts and themes,
912
925
  // exactly as when nothing else in settings loads gentle-pi.
913
926
  // - Not takeOver, with a declaration: no injection at all — the target
914
927
  // settings already load a gentle-pi the launcher accepts as-is (the
915
928
  // `--link` case with a pi-managed install matching this launcher).
929
+ //
930
+ // The two injecting cases also export CHILD_PACKAGE_INJECTION_ENV (#1690) so
931
+ // the subagent runner can give delegated children the same package. It holds
932
+ // only the launcher's own computed -e set; passthrough -e flags (the managed
933
+ // herdr extension, or one the user typed) are not part of it. Every other case
934
+ // removes an inherited value, so a nested launch never leaks a stale signal.
916
935
  export function buildPiInvocation(input: BuildPiInvocationInput): PiInvocation {
917
936
  const args = [...input.runtime.args];
937
+ let childInjection: ChildPackageInjection | undefined;
918
938
 
919
939
  if (input.piSubcommand !== undefined) {
920
940
  // No injection at all: pi must see the bare subcommand as argv[0].
@@ -939,18 +959,36 @@ export function buildPiInvocation(input: BuildPiInvocationInput): PiInvocation {
939
959
  injected.add(input.packageRoot);
940
960
  args.push("-e", input.packageRoot);
941
961
  }
942
- args.push(...packageRootAssetArgs(input.packageRoot));
962
+ // The argv dedupe above compares raw strings; the signal dedupes again
963
+ // after absolutizing, so a relative and an absolute spelling of the same
964
+ // file appear once, in first-occurrence order.
965
+ const signalPaths = new Set([...injected].map((path) => absoluteExtensionPath(path, input.cwd)));
966
+ childInjection = { noExtensions: true, extensionPaths: [...signalPaths] };
943
967
  } else if (input.declaration === undefined) {
944
- args.push(...packageRootInjectionArgs(input.packageRoot));
968
+ args.push("-e", input.packageRoot);
969
+ childInjection = { noExtensions: false, extensionPaths: [absoluteExtensionPath(input.packageRoot, input.cwd)] };
945
970
  }
946
971
 
947
972
  args.push(...input.passthrough);
948
973
 
949
- return {
950
- command: input.runtime.command,
951
- args,
952
- env: { ...input.baseEnv, PI_CODING_AGENT_DIR: input.home.dir, GENTLE_PI_AGENT_HOME: input.home.dir },
974
+ const env: Record<string, string | undefined> = {
975
+ ...input.baseEnv,
976
+ PI_CODING_AGENT_DIR: input.home.dir,
977
+ GENTLE_PI_AGENT_HOME: input.home.dir,
978
+ [USER_PI_HOME_ENV]: userPiHome(input.baseEnv, input.homedir),
953
979
  };
980
+ if (childInjection === undefined) delete env[CHILD_PACKAGE_INJECTION_ENV];
981
+ else env[CHILD_PACKAGE_INJECTION_ENV] = encodeChildPackageInjection(childInjection);
982
+
983
+ return { command: input.runtime.command, args, env };
984
+ }
985
+
986
+ // pi resolves a relative -e path against its spawn cwd. Children may run
987
+ // elsewhere, so the signal carries the same file as an absolute path. Loose
988
+ // entries can be relative when the isolated or linked home comes from a
989
+ // relative env value.
990
+ function absoluteExtensionPath(path: string, cwd: string): string {
991
+ return isAbsolute(path) ? path : resolvePath(cwd, path);
954
992
  }
955
993
 
956
994
  // --- spawn planning ------------------------------------------------------------
@@ -1137,6 +1175,9 @@ export function helpText(): string {
1137
1175
  "Usage: gentle-shell [options] [-- pi-args...]",
1138
1176
  " gentle-shell home [link|isolated|<path>]",
1139
1177
  " gentle-shell [home selectors] setup [--dry-run]",
1178
+ " gentle-shell upgrade [--channel release|main]",
1179
+ " gentle-shell upgrade --rollback",
1180
+ " gentle-shell self-uninstall [--dry-run] [--yes] [--include-shared]",
1140
1181
  "",
1141
1182
  "Opens pi with the Gentle Shell package loaded, without touching your",
1142
1183
  "vanilla pi installation.",
@@ -1156,19 +1197,40 @@ export function helpText(): string {
1156
1197
  " (runs the package-local gentle-ai 'install --agent pi --scope global').",
1157
1198
  " Accepts --dry-run, forwarded to gentle-ai. Accepts a home selector",
1158
1199
  " (--link, --isolated, --home <dir>) before it.",
1200
+ " Warns if configured MCP servers remain disabled; never re-enables them.",
1201
+ " upgrade Update Gentle Shell along its channel: the latest release, or the latest",
1202
+ " main commits of Gentle Shell and Gentle AI (built locally; needs Go and",
1203
+ " pnpm). --channel switches the channel first. A bundled install (the web",
1204
+ " installer's own Node, pnpm and Pi) follows the release channel only:",
1205
+ " it installs the new version beside the current one, verifies it, then",
1206
+ " switches; --rollback switches back to the previous kept version.",
1207
+ " self-uninstall Remove Gentle Shell: its own data, the isolated home and the gentle-pi",
1208
+ " package. Shows the plan first and asks before removing (--yes skips",
1209
+ " the question). The shared ~/.pi/gentle-ai configuration is kept unless",
1210
+ " you accept or pass --include-shared; --dry-run only shows the plan.",
1211
+ " Never touches your pi home or custom --home homes.",
1159
1212
  "",
1160
1213
  "Managing packages:",
1161
1214
  " gentle-shell install npm:<pkg> Run pi's own 'install' against the resolved home.",
1162
1215
  " gentle-shell remove <source> Run pi's own 'remove' against the resolved home.",
1163
1216
  " gentle-shell list Run pi's own 'list' against the resolved home.",
1164
1217
  " gentle-shell update [target] Run pi's own 'update' against the resolved home.",
1218
+ " In a bundled install Pi ships pinned: 'update' without a package",
1219
+ " runs 'gentle-shell upgrade' instead of updating Pi.",
1165
1220
  " gentle-shell config Run pi's own 'config' against the resolved home.",
1166
1221
  " gentle-shell auth <command> Run pi's own 'auth' against the resolved home.",
1222
+ " 'gentle-shell uninstall <source>' is pi's own alias for 'remove'; to remove Gentle",
1223
+ " Shell itself, use 'gentle-shell self-uninstall'.",
1167
1224
  " These run pi's own commands, forwarded verbatim, against the --isolated home",
1168
1225
  " (or your own pi home with --link). Running 'gentle-shell install npm:gentle-pi'",
1169
1226
  " inside the isolated home is unnecessary: gentle-shell already loads the",
1170
1227
  " package itself.",
1171
1228
  "",
1229
+ "Bundled interactive startup:",
1230
+ " Before opening Pi, checks for a newer release (at most 1.5 seconds).",
1231
+ " Choose Update now to upgrade and reopen, Not now, or Skip this version.",
1232
+ " Offline, unavailable checks, commands and noninteractive runs open normally.",
1233
+ "",
1172
1234
  "Environment variables:",
1173
1235
  " GENTLE_SHELL_PI Path to the pi executable to run.",
1174
1236
  " GENTLE_SHELL_HOME Directory for the isolated home (default: ~/.gentle-shell/agent).",
@@ -0,0 +1,176 @@
1
+ // Pure logic behind the gentle-shell resume hint. On interactive quit pi
2
+ // prints "To resume this session: pi --session <id>" (interactive-mode
3
+ // formatResumeCommand). Under gentle-shell that command cannot find the
4
+ // session: pi resolves sessions from PI_CODING_AGENT_DIR, which the launcher
5
+ // points at the gentle-shell home, but the hint names the bare `pi` binary
6
+ // and never mentions that directory, so running it looks in ~/.pi/agent.
7
+ //
8
+ // The clean fix belongs in pi (earendil-works/pi#8048, #9750). Until then,
9
+ // gentle-shell appends its own line below pi's, leaving pi's output as is:
10
+ // extensions/resume-hint.ts writes a ResumeHandoff on session_shutdown, and
11
+ // bin/gentle-shell.mjs prints the planResumeHint line after pi exits.
12
+ import { basename, dirname, isAbsolute, join, resolve as resolvePath } from "node:path";
13
+ import { shellQuote } from "./gentle-shell-launcher.ts";
14
+
15
+ export const RESUME_HANDOFF_ENV = "GENTLE_SHELL_RESUME_HANDOFF";
16
+
17
+ const HINT_LABEL = "To resume in gentle-shell:";
18
+
19
+ // pi's assertValidSessionId charset. The handoff ends up on the terminal, so
20
+ // anything outside it (or any C0/C1 control in a session dir) is refused
21
+ // rather than escaped.
22
+ const SESSION_ID_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
23
+ const CONTROL_CHARS = /[\u0000-\u001f\u007f-\u009f]/;
24
+
25
+ // The launcher creates <tmpdir>/<RESUME_HANDOFF_DIR_PREFIX>XXXXXX/<RESUME_HANDOFF_FILE>.
26
+ export const RESUME_HANDOFF_DIR_PREFIX = "gentle-shell-resume-";
27
+ export const RESUME_HANDOFF_FILE = "handoff.json";
28
+
29
+ // The extension only writes to a path shaped like the launcher's private
30
+ // handoff, so an inherited or foreign env value cannot aim it at another file.
31
+ export function isResumeHandoffPath(path: string): boolean {
32
+ return (
33
+ isAbsolute(path) &&
34
+ basename(path) === RESUME_HANDOFF_FILE &&
35
+ basename(dirname(path)).startsWith(RESUME_HANDOFF_DIR_PREFIX) &&
36
+ !CONTROL_CHARS.test(path)
37
+ );
38
+ }
39
+
40
+ export interface ResumeHandoff {
41
+ sessionId: string;
42
+ // Present only when pi would add --session-dir, i.e. the session does not
43
+ // live in pi's default per-cwd directory under the agent dir.
44
+ sessionDir?: string;
45
+ // Present only when the session belongs to another project than the one
46
+ // gentle-shell was launched from (e.g. after /resume). A bare id would
47
+ // then make pi offer a fork into the launch directory, while an absolute
48
+ // session file path reopens the original session.
49
+ sessionFile?: string;
50
+ }
51
+
52
+ // Mirror of pi's getDefaultSessionDirPath (core/session-manager), which pi
53
+ // does not export. Kept byte-for-byte so usesDefaultSessionDir agrees.
54
+ export function piDefaultSessionDir(cwd: string, agentDir: string): string {
55
+ const resolvedCwd = resolvePath(cwd);
56
+ const safePath = `--${resolvedCwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
57
+ return join(resolvePath(agentDir), "sessions", safePath);
58
+ }
59
+
60
+ export interface SessionSnapshot {
61
+ sessionId: string;
62
+ sessionDir: string;
63
+ sessionFile: string | undefined;
64
+ cwd: string;
65
+ // The directory gentle-shell was launched from; pi never changes it.
66
+ launchCwd: string;
67
+ agentDir: string;
68
+ fileExists: (path: string) => boolean;
69
+ }
70
+
71
+ // Mirrors the guards in pi's formatResumeCommand: no hint for an
72
+ // unpersisted session or one whose file was never written.
73
+ export function resumeHandoffFromSession(snapshot: SessionSnapshot): ResumeHandoff | undefined {
74
+ const { sessionId, sessionDir, sessionFile, cwd, launchCwd, agentDir, fileExists } = snapshot;
75
+ if (!sessionFile || !fileExists(sessionFile)) return undefined;
76
+ if (resolvePath(cwd) !== resolvePath(launchCwd)) return { sessionId, sessionFile: resolvePath(sessionFile) };
77
+ if (sessionDir === piDefaultSessionDir(cwd, agentDir)) return { sessionId };
78
+ return { sessionId, sessionDir };
79
+ }
80
+
81
+ export function serializeResumeHandoff(handoff: ResumeHandoff): string {
82
+ return JSON.stringify(handoff);
83
+ }
84
+
85
+ // Tolerant on purpose: a missing, stale, or foreign handoff file just means
86
+ // "print nothing".
87
+ export function parseResumeHandoff(text: string): ResumeHandoff | undefined {
88
+ let parsed: unknown;
89
+ try {
90
+ parsed = JSON.parse(text);
91
+ } catch {
92
+ return undefined;
93
+ }
94
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return undefined;
95
+ const { sessionId, sessionDir, sessionFile } = parsed as Record<string, unknown>;
96
+ if (typeof sessionId !== "string" || !SESSION_ID_PATTERN.test(sessionId)) return undefined;
97
+ if (sessionFile !== undefined) {
98
+ if (sessionDir !== undefined || typeof sessionFile !== "string") return undefined;
99
+ if (!isAbsolute(sessionFile) || !sessionFile.endsWith(".jsonl") || CONTROL_CHARS.test(sessionFile)) return undefined;
100
+ return { sessionId, sessionFile };
101
+ }
102
+ if (sessionDir === undefined) return { sessionId };
103
+ if (typeof sessionDir !== "string" || sessionDir.length === 0 || CONTROL_CHARS.test(sessionDir)) return undefined;
104
+ return { sessionId, sessionDir };
105
+ }
106
+
107
+ // Values that need no quoting in any shell a user might paste the hint into.
108
+ const PLAIN_ARG = /^[A-Za-z0-9_\-.:/\\=]+$/;
109
+ // Characters that stay live inside double quotes: cmd.exe expands %VAR% (and
110
+ // !VAR! under delayed expansion), PowerShell expands $var and `escapes, and
111
+ // an inner " ends the quoted argument in both. A trailing backslash would
112
+ // escape the closing quote under the Windows argv rules. cmd.exe operators
113
+ // are refused too: gentle-shell is installed as a .cmd shim, and PowerShell
114
+ // drops the quotes of a space-free argument when it calls one, so cmd.exe
115
+ // would read & | < > ^ ( ) as operators.
116
+ const WINDOWS_UNQUOTABLE = /["%!$`&|<>^()]|\\$/;
117
+
118
+ // Quotes one argument for the shell the user is likely to paste into:
119
+ // POSIX single quotes elsewhere, double quotes on win32, where cmd.exe and
120
+ // PowerShell do not treat single quotes as quoting. Returns undefined when
121
+ // the value cannot be quoted safely there.
122
+ function quoteArg(value: string, platform: NodeJS.Platform): string | undefined {
123
+ if (platform !== "win32") return shellQuote(value);
124
+ if (PLAIN_ARG.test(value)) return value;
125
+ if (WINDOWS_UNQUOTABLE.test(value) || CONTROL_CHARS.test(value)) return undefined;
126
+ return `"${value}"`;
127
+ }
128
+
129
+ // Returns the gentle-shell command that resumes the handed-off session, or
130
+ // undefined when some argument cannot be quoted safely for the platform's
131
+ // shell (the caller then prints no hint and leaves pi's own line alone).
132
+ export function gentleShellResumeCommand(
133
+ handoff: ResumeHandoff,
134
+ homeFlags: string[],
135
+ platform: NodeJS.Platform,
136
+ ): string | undefined {
137
+ const values = [...homeFlags];
138
+ // pi treats a --session value containing a path separator as a file path
139
+ // and opens it directly, whatever the launch directory.
140
+ if (handoff.sessionFile !== undefined) {
141
+ values.push("--session", handoff.sessionFile);
142
+ } else {
143
+ if (handoff.sessionDir !== undefined) values.push("--session-dir", handoff.sessionDir);
144
+ values.push("--session", handoff.sessionId);
145
+ }
146
+ const args = ["gentle-shell"];
147
+ for (const value of values) {
148
+ const quoted = quoteArg(value, platform);
149
+ if (quoted === undefined) return undefined;
150
+ args.push(quoted);
151
+ }
152
+ return args.join(" ");
153
+ }
154
+
155
+ export interface ResumeHintInput {
156
+ handoff: ResumeHandoff | undefined;
157
+ homeFlags: string[];
158
+ stdoutIsTTY: boolean;
159
+ // True once the launcher got SIGHUP: the terminal is gone.
160
+ terminalHungUp: boolean;
161
+ platform: NodeJS.Platform;
162
+ // Whether stdout takes ANSI colors (NO_COLOR, FORCE_COLOR=0 and dumb
163
+ // terminals turn them off, as they do for pi's own dimmed label).
164
+ color: boolean;
165
+ }
166
+
167
+ // Returns the line to print after pi exits, or undefined to print nothing.
168
+ // Like pi, it only prints to a TTY, and never after the terminal hung up.
169
+ export function planResumeHint(input: ResumeHintInput): string | undefined {
170
+ const { handoff, homeFlags, stdoutIsTTY, terminalHungUp, platform, color } = input;
171
+ if (!handoff || !stdoutIsTTY || terminalHungUp) return undefined;
172
+ const command = gentleShellResumeCommand(handoff, homeFlags, platform);
173
+ if (command === undefined) return undefined;
174
+ const label = color ? `\u001b[2m${HINT_LABEL}\u001b[22m` : HINT_LABEL;
175
+ return `${label} ${command}\n`;
176
+ }
@@ -0,0 +1,54 @@
1
+ import { realpathSync, readFileSync } from "node:fs";
2
+ import { join, relative, isAbsolute } from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+
5
+ // Resolve through the switched version, never the old launcher's captured pins.
6
+ export async function resolveStartupRelaunch(input: {
7
+ root: string; home: string; platform: NodeJS.Platform; env: NodeJS.ProcessEnv; argv: string[];
8
+ }): Promise<{ command: string; args: string[]; env: NodeJS.ProcessEnv }> {
9
+ const { activeVersion, prefixLayout } = await import("../scripts/bundled-install.mjs");
10
+ const layout = prefixLayout(input);
11
+ const id = activeVersion(layout);
12
+ if (!id) throw new Error("No active Gentle Shell after upgrade");
13
+ const versions = realpathSync(layout.versions);
14
+ const version = realpathSync(join(versions, id));
15
+ const inside = (root: string, file: string) => {
16
+ const path = relative(root, file);
17
+ return path !== "" && !path.startsWith("..") && !isAbsolute(path);
18
+ };
19
+ if (!inside(versions, version) || readFileSync(join(version, ".gentle-shell-version"), "utf8") !== `${id}\n`) {
20
+ throw new Error("Invalid active Gentle Shell version");
21
+ }
22
+ const pkg = join(version, "node_modules", "gentle-pi");
23
+ const modulePath = realpathSync(join(pkg, "scripts", "bundled-install.mjs"));
24
+ const entry = realpathSync(join(pkg, "bin", "gentle-shell.mjs"));
25
+ if (!inside(version, modulePath) || !inside(version, entry)) throw new Error("Active launcher escaped its version");
26
+ const release = await import(pathToFileURL(modulePath).href);
27
+ const next = release.prefixLayout(input);
28
+ return { command: next.node, args: [entry, ...input.argv], env: { ...input.env, GENTLE_SHELL_SKIP_STARTUP_UPDATE: "1" } };
29
+ }
30
+
31
+ // Only an explicit update selection may cross the existing upgrade boundary.
32
+ export async function applyStartupChoice(choice: "update" | "skip" | "omit" | "cancel", effects: {
33
+ omit: () => void;
34
+ upgrade: () => Promise<number>;
35
+ relaunch: () => Promise<number>;
36
+ warn?: (message: string) => void;
37
+ }): Promise<number | undefined> {
38
+ if (choice === "cancel") return 130;
39
+ if (choice === "omit") {
40
+ try { effects.omit(); }
41
+ catch { effects.warn?.("Could not remember the skipped release; continuing without saving."); }
42
+ }
43
+ if (choice !== "update") return undefined;
44
+ if (await effects.upgrade() !== 0) return undefined;
45
+ // A failed relaunch must not fall through to the captured old packageRoot.
46
+ return effects.relaunch();
47
+ }
48
+
49
+ export function omitStartupVersion(read: () => string | undefined, write: (text: string) => void, version: string): void {
50
+ const text = read();
51
+ const config = text === undefined ? {} : JSON.parse(text);
52
+ if (config === null || typeof config !== "object" || Array.isArray(config)) throw new Error("Invalid launcher config");
53
+ write(`${JSON.stringify({ ...config, skippedStartupRelease: version }, null, 2)}\n`);
54
+ }
@@ -0,0 +1,92 @@
1
+ import type { ParsedLauncherArgs } from "./gentle-shell-launcher.ts";
2
+
3
+ export interface StartupUpdateOffer {
4
+ current: string;
5
+ latest: string;
6
+ notesUrl: string;
7
+ }
8
+
9
+ export interface StartupUpdateCheckOptions {
10
+ args: ParsedLauncherArgs;
11
+ env: Record<string, string | undefined>;
12
+ bundled: boolean;
13
+ inputTTY: boolean;
14
+ outputTTY: boolean;
15
+ }
16
+
17
+ export interface StartupUpdateLookupOptions {
18
+ current: string;
19
+ skipped?: string;
20
+ lookup: (signal: AbortSignal) => Promise<string | undefined>;
21
+ timeoutMs?: number;
22
+ }
23
+
24
+ export function shouldCheckStartupUpdate({ args, env, bundled, inputTTY, outputTTY }: StartupUpdateCheckOptions): boolean {
25
+ if (!bundled || !inputTTY || !outputTTY || args.command || args.piSubcommand || args.error || args.help || args.version || args.packageRoot !== undefined) {
26
+ return false;
27
+ }
28
+ if (env.GENTLE_PI_AGENTS_CHILD || env.PI_OFFLINE || env.GENTLE_SHELL_SKIP_STARTUP_UPDATE) {
29
+ return false;
30
+ }
31
+
32
+ // An allowlist keeps new command modes from accidentally showing a prompt.
33
+ for (let i = 0; i < args.passthrough.length; i += 1) {
34
+ const token = args.passthrough[i];
35
+ if (token === "--continue" || token === "-c") continue;
36
+ if (token.startsWith("--resume=") && token.length > "--resume=".length) continue;
37
+ if (token === "--resume" || token === "-r") {
38
+ const next = args.passthrough[i + 1];
39
+ if (next !== undefined && !next.startsWith("-")) i += 1;
40
+ continue;
41
+ }
42
+ return false;
43
+ }
44
+ return true;
45
+ }
46
+
47
+ function stableParts(version: string): string[] | undefined {
48
+ // No leading zeros, prefixes, whitespace, prereleases or build metadata.
49
+ const match = /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/.exec(version);
50
+ return match && match[0] === version ? match.slice(1) : undefined;
51
+ }
52
+
53
+ function isNewer(latest: string[], current: string[]): boolean {
54
+ for (let i = 0; i < 3; i += 1) {
55
+ if (latest[i] === current[i]) continue;
56
+ // Decimal string comparison avoids rounding even for arbitrarily large parts.
57
+ return latest[i].length !== current[i].length
58
+ ? latest[i].length > current[i].length
59
+ : latest[i] > current[i];
60
+ }
61
+ return false;
62
+ }
63
+
64
+ export async function findStartupUpdate({ current, skipped, lookup, timeoutMs = 1500 }: StartupUpdateLookupOptions): Promise<StartupUpdateOffer | undefined> {
65
+ const currentParts = stableParts(current);
66
+ if (!currentParts) return undefined;
67
+ const controller = new AbortController();
68
+ const deadline = Number.isFinite(timeoutMs) && timeoutMs >= 0 && timeoutMs <= 2_147_483_647 ? timeoutMs : 1500;
69
+ let timer: ReturnType<typeof setTimeout> | undefined;
70
+ try {
71
+ const timeout = new Promise<undefined>((resolve) => {
72
+ timer = setTimeout(() => {
73
+ resolve(undefined);
74
+ controller.abort();
75
+ }, deadline);
76
+ });
77
+ // The race bounds startup even if the injected lookup ignores cancellation.
78
+ const latest = await Promise.race([lookup(controller.signal), timeout]);
79
+ if (latest === undefined || latest === skipped) return undefined;
80
+ const latestParts = stableParts(latest);
81
+ if (!latestParts || !isNewer(latestParts, currentParts)) return undefined;
82
+ return {
83
+ current,
84
+ latest,
85
+ notesUrl: `https://github.com/Gentleman-Programming/gentle-shell/releases/tag/v${latest}`,
86
+ };
87
+ } catch {
88
+ return undefined;
89
+ } finally {
90
+ clearTimeout(timer);
91
+ }
92
+ }