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,8 +1,9 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { readFileSync } from "node:fs";
3
- import { join } from "node:path";
3
+ import { join, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import test from "node:test";
6
+ import { CHILD_PACKAGE_INJECTION_ENV, encodeChildPackageInjection, parseChildPackageInjection } from "../lib/child-package-injection.ts";
6
7
  import {
7
8
  MIN_PI_VERSION,
8
9
  MIN_SETUP_GENTLE_AI_VERSION,
@@ -32,6 +33,8 @@ import {
32
33
  quoteForCmdExe,
33
34
  recordProvisioned,
34
35
  resolveHome,
36
+ USER_PI_HOME_ENV,
37
+ userPiHome,
35
38
  resolvePiRuntime,
36
39
  restoreJsonField,
37
40
  settingsDeclareGentlePi,
@@ -152,6 +155,50 @@ test("parseLauncherArgs recognises the home subcommand as argv[0] and captures t
152
155
  assert.deepEqual(parsed.passthrough, []);
153
156
  });
154
157
 
158
+ test("parseLauncherArgs recognises the upgrade subcommand as argv[0] and captures its arguments", () => {
159
+ const parsed = parseLauncherArgs(["upgrade", "--channel", "main"]);
160
+ assert.equal(parsed.command, "upgrade");
161
+ assert.deepEqual(parsed.commandArgs, ["--channel", "main"]);
162
+ assert.deepEqual(parsed.passthrough, []);
163
+ assert.equal(parsed.piSubcommand, undefined);
164
+ });
165
+
166
+ test("parseLauncherArgs recognises the self-uninstall subcommand as argv[0] and captures its arguments", () => {
167
+ const parsed = parseLauncherArgs(["self-uninstall", "--dry-run", "--include-shared"]);
168
+ assert.equal(parsed.command, "self-uninstall");
169
+ assert.deepEqual(parsed.commandArgs, ["--dry-run", "--include-shared"]);
170
+ assert.deepEqual(parsed.passthrough, []);
171
+ assert.equal(parsed.piSubcommand, undefined);
172
+ assert.equal(parsed.error, undefined);
173
+ });
174
+
175
+ test("parseLauncherArgs keeps uninstall as pi's own alias for remove", () => {
176
+ const parsed = parseLauncherArgs(["uninstall", "npm:x"]);
177
+ assert.equal(parsed.command, undefined);
178
+ assert.equal(parsed.piSubcommand, "uninstall");
179
+ assert.deepEqual(parsed.passthrough, ["uninstall", "npm:x"]);
180
+ });
181
+
182
+ test("help text documents gentle-shell self-uninstall and its flags", () => {
183
+ const text = helpText();
184
+ assert.ok(text.includes(" gentle-shell self-uninstall [--dry-run] [--yes] [--include-shared]"));
185
+ assert.ok(text.includes(" self-uninstall Remove Gentle Shell: its own data, the isolated home and the gentle-pi"));
186
+ assert.match(text, /'gentle-shell uninstall <source>' is pi's own alias for 'remove'/);
187
+ });
188
+
189
+ test("help text documents gentle-shell upgrade and its channels", () => {
190
+ const text = helpText();
191
+ assert.ok(text.includes(" gentle-shell upgrade [--channel release|main]"));
192
+ assert.ok(text.includes(" upgrade Update Gentle Shell along its channel: the latest release, or the latest"));
193
+ });
194
+
195
+ test("help text documents the bundled upgrade, its rollback and the pinned Pi update", () => {
196
+ const text = helpText();
197
+ assert.ok(text.includes(" gentle-shell upgrade --rollback"));
198
+ assert.match(text, /bundled install.*release channel only.*--rollback switches back to the previous kept version/s);
199
+ assert.match(text, /In a bundled install Pi ships pinned: 'update' without a package\n\s+runs 'gentle-shell upgrade'/);
200
+ });
201
+
155
202
  test("parseLauncherArgs treats home as a plain passthrough token when it is not argv[0]", () => {
156
203
  const parsed = parseLauncherArgs(["--isolated", "home"]);
157
204
  assert.equal(parsed.command, undefined);
@@ -308,6 +355,22 @@ test("resolveHome falls back to <homedir>/.pi/agent for --link with no override"
308
355
  assert.deepEqual(resolved, { mode: "link", dir: join("/home/alan", ".pi", "agent"), source: "flag" });
309
356
  });
310
357
 
358
+ test("--link inside an isolated session resolves the preserved original Pi home (#2015)", () => {
359
+ const nestedEnv = { PI_CODING_AGENT_DIR: "/home/alan/.gentle-shell/agent", GENTLE_SHELL_USER_PI_HOME: "/custom/pi" };
360
+ // Nested: the inherited isolated home must not be re-selected.
361
+ assert.deepEqual(
362
+ resolveHome({ args: args({ link: true }), env: nestedEnv, homedir: "/home/alan", config: undefined }),
363
+ { mode: "link", dir: "/custom/pi", source: "flag" },
364
+ );
365
+ assert.deepEqual(
366
+ resolveHome({ args: args(), env: nestedEnv, homedir: "/home/alan", config: { mode: "link" } }),
367
+ { mode: "link", dir: "/custom/pi", source: "config" },
368
+ );
369
+ // Clean terminal: a custom override and the conventional home are unchanged.
370
+ assert.equal(resolveHome({ args: args({ link: true }), env: { PI_CODING_AGENT_DIR: "/custom/pi" }, homedir: "/home/alan", config: undefined }).dir, "/custom/pi");
371
+ assert.equal(resolveHome({ args: args({ link: true }), env: {}, homedir: "/home/alan", config: undefined }).dir, join("/home/alan", ".pi", "agent"));
372
+ });
373
+
311
374
  test("resolveHome honours --isolated and reads GENTLE_SHELL_HOME", () => {
312
375
  const resolved = resolveHome({
313
376
  args: args({ isolated: true }),
@@ -620,20 +683,37 @@ test("checkPeerVersionPin passes for a matching pin", () => {
620
683
  assert.deepEqual(result, { ok: true, pinned: ">=0.85.1" });
621
684
  });
622
685
 
686
+ test("Pi 0.99.1 baseline and host peers follow the open development range policy", () => {
687
+ const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
688
+ assert.equal(MIN_PI_VERSION, "0.99.1");
689
+ assert.equal(pkg.engines.node, ">=22.19.0");
690
+ for (const name of ["@earendil-works/pi-ai", "@earendil-works/pi-tui"]) {
691
+ assert.equal(pkg.peerDependencies[name], "*");
692
+ assert.equal(pkg.peerDependenciesMeta[name].optional, true);
693
+ assert.equal(pkg.dependencies[name], undefined);
694
+ }
695
+ // Development ranges are policy specifiers; their floor never drops below
696
+ // the runtime minimum the launcher enforces.
697
+ for (const name of ["@earendil-works/pi-coding-agent", "@earendil-works/pi-ai", "@earendil-works/pi-tui"]) {
698
+ assert.equal(pkg.devDependencies[name], ">=1.0.0", name);
699
+ assert.equal(checkPiVersion(pkg.devDependencies[name].slice(2)).ok, true, name);
700
+ }
701
+ });
702
+
623
703
  test("checkPiVersion accepts a version equal to the minimum", () => {
624
- assert.deepEqual(checkPiVersion("0.85.1"), { ok: true, version: "0.85.1" });
704
+ assert.deepEqual(checkPiVersion("0.99.1"), { ok: true, version: "0.99.1" });
625
705
  });
626
706
 
627
707
  test("checkPiVersion accepts a version above the minimum", () => {
628
- assert.deepEqual(checkPiVersion("0.86.0"), { ok: true, version: "0.86.0" });
708
+ assert.deepEqual(checkPiVersion("0.100.0"), { ok: true, version: "0.100.0" });
629
709
  });
630
710
 
631
711
  test("checkPiVersion accepts a v-prefixed version", () => {
632
- assert.deepEqual(checkPiVersion("v0.85.1"), { ok: true, version: "0.85.1" });
712
+ assert.deepEqual(checkPiVersion("v0.99.1"), { ok: true, version: "0.99.1" });
633
713
  });
634
714
 
635
715
  test("checkPiVersion accepts a prerelease suffix at the minimum", () => {
636
- assert.deepEqual(checkPiVersion("pi version 0.85.1-rc.2"), { ok: true, version: "0.85.1" });
716
+ assert.deepEqual(checkPiVersion("pi version 0.99.1-rc.2"), { ok: true, version: "0.99.1" });
637
717
  });
638
718
 
639
719
  test("checkPiVersion rejects a version below the minimum and names both versions", () => {
@@ -642,7 +722,7 @@ test("checkPiVersion rejects a version below the minimum and names both versions
642
722
  if (result.ok) throw new Error("expected a failing result");
643
723
  assert.equal(result.version, "0.85.0");
644
724
  assert.match(result.message, /0\.85\.0/);
645
- assert.match(result.message, /0\.85\.1/);
725
+ assert.match(result.message, /0\.99\.1/);
646
726
  });
647
727
 
648
728
  test("checkPiVersion rejects a prerelease below the minimum", () => {
@@ -656,7 +736,7 @@ test("checkPiVersion reports unparsable output with the raw text and the minimum
656
736
  if (result.ok) throw new Error("expected a failing result");
657
737
  assert.equal(result.version, undefined);
658
738
  assert.match(result.message, /not a version/);
659
- assert.match(result.message, /0\.85\.1/);
739
+ assert.match(result.message, /0\.99\.1/);
660
740
  });
661
741
 
662
742
  test("checkPiVersion accepts a custom minimum", () => {
@@ -833,7 +913,7 @@ test("findGentlePiDeclaration detects a bare npm:gentle-pi object source entry",
833
913
  });
834
914
 
835
915
  test("findGentlePiDeclaration recognises a relative path entry whose package.json name is gentle-pi", () => {
836
- const resolvedDir = join("/agent", "..", "..", "work", "gentle-pi");
916
+ const resolvedDir = resolve("/agent", "..", "..", "work", "gentle-pi");
837
917
  const result = findGentlePiDeclaration('{"packages":["../../work/gentle-pi"]}', {
838
918
  agentDir: "/agent",
839
919
  readPackageName: readPackageNameStub({ [resolvedDir]: "gentle-pi" }),
@@ -844,17 +924,17 @@ test("findGentlePiDeclaration recognises a relative path entry whose package.jso
844
924
  test("findGentlePiDeclaration recognises an absolute path entry whose package.json name is gentle-pi", () => {
845
925
  const result = findGentlePiDeclaration('{"packages":["/checkouts/gentle-pi"]}', {
846
926
  agentDir: "/agent",
847
- readPackageName: readPackageNameStub({ "/checkouts/gentle-pi": "gentle-pi" }),
927
+ readPackageName: readPackageNameStub({ [resolve("/checkouts/gentle-pi")]: "gentle-pi" }),
848
928
  });
849
- assert.deepEqual(result, { kind: "path", dir: "/checkouts/gentle-pi" });
929
+ assert.deepEqual(result, { kind: "path", dir: resolve("/checkouts/gentle-pi") });
850
930
  });
851
931
 
852
932
  test("findGentlePiDeclaration recognises a path object source entry whose package.json name is gentle-pi", () => {
853
933
  const result = findGentlePiDeclaration('{"packages":[{"source":"../gentle-pi","extensions":[]}]}', {
854
934
  agentDir: "/agent",
855
- readPackageName: readPackageNameStub({ [join("/agent", "..", "gentle-pi")]: "gentle-pi" }),
935
+ readPackageName: readPackageNameStub({ [resolve("/agent", "..", "gentle-pi")]: "gentle-pi" }),
856
936
  });
857
- assert.deepEqual(result, { kind: "path", dir: join("/agent", "..", "gentle-pi") });
937
+ assert.deepEqual(result, { kind: "path", dir: resolve("/agent", "..", "gentle-pi") });
858
938
  });
859
939
 
860
940
  test("findGentlePiDeclaration is undefined for a path entry whose package.json name is not gentle-pi", () => {
@@ -887,9 +967,9 @@ test("findGentlePiDeclaration is undefined when packages lists unrelated entries
887
967
  test("findGentlePiDeclaration returns the first matching declaration, path or npm, in list order", () => {
888
968
  const result = findGentlePiDeclaration('{"packages":["../gentle-pi","npm:gentle-pi"]}', {
889
969
  agentDir: "/agent",
890
- readPackageName: readPackageNameStub({ [join("/agent", "..", "gentle-pi")]: "gentle-pi" }),
970
+ readPackageName: readPackageNameStub({ [resolve("/agent", "..", "gentle-pi")]: "gentle-pi" }),
891
971
  });
892
- assert.deepEqual(result, { kind: "path", dir: join("/agent", "..", "gentle-pi") });
972
+ assert.deepEqual(result, { kind: "path", dir: resolve("/agent", "..", "gentle-pi") });
893
973
  });
894
974
 
895
975
  // --- decideTakeOver -----------------------------------------------------------
@@ -973,7 +1053,7 @@ test("otherPackageInjections resolves a path entry relative to agentDir", () =>
973
1053
  agentDir: "/agent",
974
1054
  skip: { kind: "npm" },
975
1055
  });
976
- assert.deepEqual(result.paths, [join("/agent", "..", "..", "work", "pi-qwen-ambassador")]);
1056
+ assert.deepEqual(result.paths, [resolve("/agent", "..", "..", "work", "pi-qwen-ambassador")]);
977
1057
  });
978
1058
 
979
1059
  test("otherPackageInjections skips a git entry and warns", () => {
@@ -999,7 +1079,7 @@ test("otherPackageInjections warns for an object entry with extensions or autolo
999
1079
  });
1000
1080
 
1001
1081
  test("otherPackageInjections skips the entry matching a path declaration being taken over", () => {
1002
- const declaredDir = join("/agent", "..", "..", "work", "gentle-pi");
1082
+ const declaredDir = resolve("/agent", "..", "..", "work", "gentle-pi");
1003
1083
  const result = otherPackageInjections({
1004
1084
  settingsText: '{"packages":["npm:some-other","../../work/gentle-pi"]}',
1005
1085
  agentDir: "/agent",
@@ -1014,7 +1094,7 @@ test("otherPackageInjections tolerates undefined or malformed settings text", ()
1014
1094
  });
1015
1095
 
1016
1096
  test("otherPackageInjections skips every gentle-pi entry, not only the one matching the declaration kind", () => {
1017
- const declaredDir = join("/agent", "..", "..", "work", "gentle-pi");
1097
+ const declaredDir = resolve("/agent", "..", "..", "work", "gentle-pi");
1018
1098
  const result = otherPackageInjections({
1019
1099
  // The declaration being taken over is the path entry, but settings
1020
1100
  // also carries a second, unrelated npm:gentle-pi entry: both must be
@@ -1069,7 +1149,7 @@ test("otherPackageInjections keeps a declared package directory that isDirectory
1069
1149
  });
1070
1150
 
1071
1151
  test("otherPackageInjections excludes a settings path entry that resolves via realpath to the same physical directory as a realpath'd skip (R4-forced-root-symlink-double-injection)", () => {
1072
- const linkOtherDir = join("/agent", "link-other");
1152
+ const linkOtherDir = resolve("/agent", "link-other");
1073
1153
  const result = otherPackageInjections({
1074
1154
  settingsText: '{"packages":["link-other","npm:some-other"]}',
1075
1155
  agentDir: "/agent",
@@ -1088,7 +1168,7 @@ test("otherPackageInjections keeps comparing raw strings when no realpath resolv
1088
1168
  agentDir: "/agent",
1089
1169
  skip: { kind: "path", dir: "/real/other" },
1090
1170
  });
1091
- assert.deepEqual(result.paths, [join("/agent", "link-other"), join("/agent", "npm", "node_modules", "some-other")]);
1171
+ assert.deepEqual(result.paths, [resolve("/agent", "link-other"), join("/agent", "npm", "node_modules", "some-other")]);
1092
1172
  });
1093
1173
 
1094
1174
  test("otherPackageInjections defaults to including every declared package when isDirectory is not provided (existing callers keep pure string resolution)", () => {
@@ -1103,6 +1183,24 @@ test("otherPackageInjections defaults to including every declared package when i
1103
1183
 
1104
1184
  // --- buildPiInvocation -------------------------------------------------------
1105
1185
 
1186
+ test("package injection relies on Pi's -e resource discovery in isolated and takeover modes", () => {
1187
+ for (const takeOver of [false, true]) {
1188
+ const built = buildPiInvocation({
1189
+ runtime: { kind: "path", command: "/usr/bin/pi", args: [] },
1190
+ home: { mode: "isolated", dir: "/gentle-shell/agent", source: "default" },
1191
+ packageRoot: "/pkg",
1192
+ declaration: undefined,
1193
+ takeOver,
1194
+ otherPackagePaths: [],
1195
+ passthrough: [],
1196
+ baseEnv: {},
1197
+ homedir: "/home/u",
1198
+ cwd: "/work",
1199
+ });
1200
+ assert.deepEqual(built.args, takeOver ? ["--no-extensions", "-e", "/pkg"] : ["-e", "/pkg"]);
1201
+ }
1202
+ });
1203
+
1106
1204
  const linkHome: ResolvedHome = { mode: "link", dir: "/pi/agent", source: "flag" };
1107
1205
  const isolatedHomeResolved: ResolvedHome = { mode: "isolated", dir: "/gentle-shell/agent", source: "default" };
1108
1206
 
@@ -1116,8 +1214,10 @@ test("buildPiInvocation injects the launcher env into baseEnv", () => {
1116
1214
  otherPackagePaths: [],
1117
1215
  passthrough: [],
1118
1216
  baseEnv: { PATH: "/usr/bin" },
1217
+ homedir: "/home/u",
1218
+ cwd: "/work",
1119
1219
  });
1120
- assert.deepEqual(built.env, { PATH: "/usr/bin", PI_CODING_AGENT_DIR: "/pi/agent", GENTLE_PI_AGENT_HOME: "/pi/agent" });
1220
+ assert.deepEqual(built.env, { PATH: "/usr/bin", PI_CODING_AGENT_DIR: "/pi/agent", GENTLE_PI_AGENT_HOME: "/pi/agent", GENTLE_SHELL_USER_PI_HOME: join("/home/u", ".pi", "agent") });
1121
1221
  });
1122
1222
 
1123
1223
  test("buildPiInvocation skips injection when there is a declaration and no takeover (npm matches the launcher's own install)", () => {
@@ -1130,6 +1230,8 @@ test("buildPiInvocation skips injection when there is a declaration and no takeo
1130
1230
  otherPackagePaths: [],
1131
1231
  passthrough: ["--mode", "rpc"],
1132
1232
  baseEnv: {},
1233
+ homedir: "/home/u",
1234
+ cwd: "/work",
1133
1235
  });
1134
1236
  assert.deepEqual(built.command, "/usr/bin/pi");
1135
1237
  assert.deepEqual(built.args, ["--mode", "rpc"]);
@@ -1145,16 +1247,12 @@ test("buildPiInvocation adds the gentle-pi injection flags when there is no decl
1145
1247
  otherPackagePaths: [],
1146
1248
  passthrough: ["--mode", "rpc"],
1147
1249
  baseEnv: {},
1250
+ homedir: "/home/u",
1251
+ cwd: "/work",
1148
1252
  });
1149
1253
  assert.deepEqual(built.args, [
1150
1254
  "-e",
1151
1255
  "/pkg",
1152
- "--theme",
1153
- join("/pkg", "themes"),
1154
- "--skill",
1155
- join("/pkg", "skills"),
1156
- "--prompt-template",
1157
- join("/pkg", "prompts"),
1158
1256
  "--mode",
1159
1257
  "rpc",
1160
1258
  ]);
@@ -1171,6 +1269,8 @@ test("buildPiInvocation emits only runtime args and passthrough when a pi subcom
1171
1269
  passthrough: ["install", "npm:x"],
1172
1270
  piSubcommand: "install",
1173
1271
  baseEnv: {},
1272
+ homedir: "/home/u",
1273
+ cwd: "/work",
1174
1274
  });
1175
1275
  assert.deepEqual(built.args, ["install", "npm:x"]);
1176
1276
  assert.equal(built.command, "/usr/bin/pi");
@@ -1187,11 +1287,14 @@ test("buildPiInvocation still injects the launcher env for a pi subcommand", ()
1187
1287
  passthrough: ["list"],
1188
1288
  piSubcommand: "list",
1189
1289
  baseEnv: { PATH: "/usr/bin" },
1290
+ homedir: "/home/u",
1291
+ cwd: "/work",
1190
1292
  });
1191
1293
  assert.deepEqual(built.env, {
1192
1294
  PATH: "/usr/bin",
1193
1295
  PI_CODING_AGENT_DIR: "/gentle-shell/agent",
1194
1296
  GENTLE_PI_AGENT_HOME: "/gentle-shell/agent",
1297
+ GENTLE_SHELL_USER_PI_HOME: join("/home/u", ".pi", "agent"),
1195
1298
  });
1196
1299
  });
1197
1300
 
@@ -1205,11 +1308,51 @@ test("buildPiInvocation in link mode with a pi subcommand is exactly pi <subcomm
1205
1308
  otherPackagePaths: [join("/agent", "npm", "node_modules", "some-other")],
1206
1309
  passthrough: ["auth", "status"],
1207
1310
  piSubcommand: "auth",
1208
- baseEnv: {},
1311
+ baseEnv: { PI_CODING_AGENT_DIR: "/pi/agent" },
1312
+ homedir: "/home/u",
1313
+ cwd: "/work",
1209
1314
  });
1210
1315
  assert.equal(built.command, "/usr/bin/pi");
1211
1316
  assert.deepEqual(built.args, ["auth", "status"]);
1212
- assert.deepEqual(built.env, { PI_CODING_AGENT_DIR: "/pi/agent", GENTLE_PI_AGENT_HOME: "/pi/agent" });
1317
+ assert.deepEqual(built.env, { PI_CODING_AGENT_DIR: "/pi/agent", GENTLE_PI_AGENT_HOME: "/pi/agent", GENTLE_SHELL_USER_PI_HOME: "/pi/agent" });
1318
+ });
1319
+
1320
+ // The isolated home overrides PI_CODING_AGENT_DIR for the session, so the
1321
+ // user's own Pi home travels separately for read-only features such as
1322
+ // /gentle:stats. Isolation itself is unchanged.
1323
+ test("buildPiInvocation carries the user's original Pi home without weakening isolation", () => {
1324
+ const build = (baseEnv: Record<string, string | undefined>, home: ResolvedHome = isolatedHomeResolved) => buildPiInvocation({
1325
+ runtime: { kind: "path", command: "/usr/bin/pi", args: [] },
1326
+ home,
1327
+ packageRoot: "/pkg",
1328
+ declaration: undefined,
1329
+ takeOver: false,
1330
+ otherPackagePaths: [],
1331
+ passthrough: [],
1332
+ baseEnv,
1333
+ homedir: "/home/u",
1334
+ cwd: "/work",
1335
+ }).env;
1336
+ const conventional = build({});
1337
+ assert.equal(conventional.PI_CODING_AGENT_DIR, "/gentle-shell/agent");
1338
+ assert.equal(conventional.GENTLE_PI_AGENT_HOME, "/gentle-shell/agent");
1339
+ assert.equal(conventional.GENTLE_SHELL_USER_PI_HOME, join("/home/u", ".pi", "agent"));
1340
+ // A custom Pi home is preserved; an empty value falls through like Pi itself.
1341
+ const custom = build({ PI_CODING_AGENT_DIR: "/custom/pi" });
1342
+ assert.equal(custom.PI_CODING_AGENT_DIR, "/gentle-shell/agent");
1343
+ assert.equal(custom.GENTLE_SHELL_USER_PI_HOME, "/custom/pi");
1344
+ assert.equal(build({ PI_CODING_AGENT_DIR: "" }).GENTLE_SHELL_USER_PI_HOME, join("/home/u", ".pi", "agent"));
1345
+ // A nested launch inherits the outer isolated PI_CODING_AGENT_DIR; the
1346
+ // original home the outer launcher recorded must win over it.
1347
+ const nested = build({ PI_CODING_AGENT_DIR: "/gentle-shell/agent", GENTLE_SHELL_USER_PI_HOME: "/custom/pi" });
1348
+ assert.equal(nested.PI_CODING_AGENT_DIR, "/gentle-shell/agent");
1349
+ assert.equal(nested.GENTLE_SHELL_USER_PI_HOME, "/custom/pi");
1350
+ // --link: the active and original homes are the same directory.
1351
+ const linked = build({ PI_CODING_AGENT_DIR: "/pi/agent" }, linkHome);
1352
+ assert.equal(linked.PI_CODING_AGENT_DIR, "/pi/agent");
1353
+ assert.equal(linked.GENTLE_SHELL_USER_PI_HOME, "/pi/agent");
1354
+ assert.equal(USER_PI_HOME_ENV, "GENTLE_SHELL_USER_PI_HOME");
1355
+ assert.equal(userPiHome({ GENTLE_SHELL_USER_PI_HOME: "/kept" }, "/home/u"), "/kept");
1213
1356
  });
1214
1357
 
1215
1358
  test("buildPiInvocation keeps the runtime's own args ahead of the injection and passthrough", () => {
@@ -1222,6 +1365,8 @@ test("buildPiInvocation keeps the runtime's own args ahead of the injection and
1222
1365
  otherPackagePaths: [],
1223
1366
  passthrough: [],
1224
1367
  baseEnv: {},
1368
+ homedir: "/home/u",
1369
+ cwd: "/work",
1225
1370
  });
1226
1371
  assert.equal(built.args[0], "/bundled/cli.js");
1227
1372
  assert.equal(built.command, "/usr/bin/node");
@@ -1237,6 +1382,8 @@ test("buildPiInvocation takes over a conflicting path declaration: --no-extensio
1237
1382
  otherPackagePaths: [join("/agent", "npm", "node_modules", "some-other")],
1238
1383
  passthrough: ["--mode", "rpc"],
1239
1384
  baseEnv: {},
1385
+ homedir: "/home/u",
1386
+ cwd: "/work",
1240
1387
  });
1241
1388
  assert.deepEqual(built.args, [
1242
1389
  "--no-extensions",
@@ -1244,12 +1391,6 @@ test("buildPiInvocation takes over a conflicting path declaration: --no-extensio
1244
1391
  join("/agent", "npm", "node_modules", "some-other"),
1245
1392
  "-e",
1246
1393
  "/pkg",
1247
- "--theme",
1248
- join("/pkg", "themes"),
1249
- "--skill",
1250
- join("/pkg", "skills"),
1251
- "--prompt-template",
1252
- join("/pkg", "prompts"),
1253
1394
  "--mode",
1254
1395
  "rpc",
1255
1396
  ]);
@@ -1265,17 +1406,13 @@ test("buildPiInvocation takes over with --package-root even for a matching npm d
1265
1406
  otherPackagePaths: [],
1266
1407
  passthrough: [],
1267
1408
  baseEnv: {},
1409
+ homedir: "/home/u",
1410
+ cwd: "/work",
1268
1411
  });
1269
1412
  assert.deepEqual(built.args, [
1270
1413
  "--no-extensions",
1271
1414
  "-e",
1272
1415
  "/forced/root",
1273
- "--theme",
1274
- join("/forced/root", "themes"),
1275
- "--skill",
1276
- join("/forced/root", "skills"),
1277
- "--prompt-template",
1278
- join("/forced/root", "prompts"),
1279
1416
  ]);
1280
1417
  });
1281
1418
 
@@ -1289,6 +1426,8 @@ test("buildPiInvocation takes over with --package-root even when there is no dec
1289
1426
  otherPackagePaths: [join("/agent", "npm", "node_modules", "some-other")],
1290
1427
  passthrough: [],
1291
1428
  baseEnv: {},
1429
+ homedir: "/home/u",
1430
+ cwd: "/work",
1292
1431
  });
1293
1432
  assert.deepEqual(built.args, [
1294
1433
  "--no-extensions",
@@ -1296,12 +1435,6 @@ test("buildPiInvocation takes over with --package-root even when there is no dec
1296
1435
  join("/agent", "npm", "node_modules", "some-other"),
1297
1436
  "-e",
1298
1437
  "/forced/root",
1299
- "--theme",
1300
- join("/forced/root", "themes"),
1301
- "--skill",
1302
- join("/forced/root", "skills"),
1303
- "--prompt-template",
1304
- join("/forced/root", "prompts"),
1305
1438
  ]);
1306
1439
  });
1307
1440
 
@@ -1316,6 +1449,8 @@ test("buildPiInvocation injects loose extension entries during a takeover, after
1316
1449
  looseExtensionEntries: [join("/agent", "extensions", "a.ts"), join("/project", ".pi", "extensions", "b.js")],
1317
1450
  passthrough: ["--mode", "rpc"],
1318
1451
  baseEnv: {},
1452
+ homedir: "/home/u",
1453
+ cwd: "/work",
1319
1454
  });
1320
1455
  assert.deepEqual(built.args, [
1321
1456
  "--no-extensions",
@@ -1327,12 +1462,6 @@ test("buildPiInvocation injects loose extension entries during a takeover, after
1327
1462
  join("/project", ".pi", "extensions", "b.js"),
1328
1463
  "-e",
1329
1464
  "/pkg",
1330
- "--theme",
1331
- join("/pkg", "themes"),
1332
- "--skill",
1333
- join("/pkg", "skills"),
1334
- "--prompt-template",
1335
- join("/pkg", "prompts"),
1336
1465
  "--mode",
1337
1466
  "rpc",
1338
1467
  ]);
@@ -1348,8 +1477,10 @@ test("buildPiInvocation omits loose extension entry flags when the list is empty
1348
1477
  otherPackagePaths: [],
1349
1478
  passthrough: [],
1350
1479
  baseEnv: {},
1480
+ homedir: "/home/u",
1481
+ cwd: "/work",
1351
1482
  });
1352
- assert.deepEqual(withoutField.args, ["--no-extensions", "-e", "/pkg", "--theme", join("/pkg", "themes"), "--skill", join("/pkg", "skills"), "--prompt-template", join("/pkg", "prompts")]);
1483
+ assert.deepEqual(withoutField.args, ["--no-extensions", "-e", "/pkg"]);
1353
1484
 
1354
1485
  const withEmptyField = buildPiInvocation({
1355
1486
  runtime: { kind: "path", command: "/usr/bin/pi", args: [] },
@@ -1361,6 +1492,8 @@ test("buildPiInvocation omits loose extension entry flags when the list is empty
1361
1492
  looseExtensionEntries: [],
1362
1493
  passthrough: [],
1363
1494
  baseEnv: {},
1495
+ homedir: "/home/u",
1496
+ cwd: "/work",
1364
1497
  });
1365
1498
  assert.deepEqual(withEmptyField.args, withoutField.args);
1366
1499
  });
@@ -1381,6 +1514,8 @@ test("buildPiInvocation dedupes loose extension entries against other-package pa
1381
1514
  looseExtensionEntries: ["/shared/dup.ts", "/agent/extensions/a.ts", "/agent/extensions/a.ts"],
1382
1515
  passthrough: [],
1383
1516
  baseEnv: {},
1517
+ homedir: "/home/u",
1518
+ cwd: "/work",
1384
1519
  });
1385
1520
  assert.deepEqual(built.args, [
1386
1521
  "--no-extensions",
@@ -1392,12 +1527,6 @@ test("buildPiInvocation dedupes loose extension entries against other-package pa
1392
1527
  "/agent/extensions/a.ts",
1393
1528
  "-e",
1394
1529
  "/pkg",
1395
- "--theme",
1396
- join("/pkg", "themes"),
1397
- "--skill",
1398
- join("/pkg", "skills"),
1399
- "--prompt-template",
1400
- join("/pkg", "prompts"),
1401
1530
  ]);
1402
1531
  });
1403
1532
 
@@ -1415,6 +1544,8 @@ test("buildPiInvocation dedupes the launcher's own package root against an other
1415
1544
  otherPackagePaths: [join("/agent", "npm", "node_modules", "some-other"), "/pkg"],
1416
1545
  passthrough: [],
1417
1546
  baseEnv: {},
1547
+ homedir: "/home/u",
1548
+ cwd: "/work",
1418
1549
  });
1419
1550
  const eFlags = built.args.filter((arg, index) => built.args[index - 1] === "-e");
1420
1551
  assert.deepEqual(eFlags, [join("/agent", "npm", "node_modules", "some-other"), "/pkg"]);
@@ -1424,15 +1555,130 @@ test("buildPiInvocation dedupes the launcher's own package root against an other
1424
1555
  join("/agent", "npm", "node_modules", "some-other"),
1425
1556
  "-e",
1426
1557
  "/pkg",
1427
- "--theme",
1428
- join("/pkg", "themes"),
1429
- "--skill",
1430
- join("/pkg", "skills"),
1431
- "--prompt-template",
1432
- join("/pkg", "prompts"),
1433
1558
  ]);
1434
1559
  });
1435
1560
 
1561
+ // --- child package injection signal (#1690) ----------------------------------
1562
+ //
1563
+ // The launcher exports exactly the -e set it computed itself, so the subagent
1564
+ // runner can forward it to delegated children. Passthrough -e flags are not
1565
+ // part of the signal.
1566
+
1567
+ const injectionInput = {
1568
+ runtime: { kind: "path" as const, command: "/usr/bin/pi", args: [] },
1569
+ home: isolatedHomeResolved,
1570
+ packageRoot: "/pkg",
1571
+ otherPackagePaths: [],
1572
+ passthrough: ["-e", "/user/ext.ts", "--mode", "rpc"],
1573
+ homedir: "/home/u",
1574
+ cwd: "/work",
1575
+ };
1576
+
1577
+ test("buildPiInvocation signals the packageRoot injection when there is no declaration", () => {
1578
+ const built = buildPiInvocation({ ...injectionInput, declaration: undefined, takeOver: false, baseEnv: {} });
1579
+ assert.deepEqual(parseChildPackageInjection(built.env), { noExtensions: false, extensionPaths: ["/pkg"] });
1580
+ });
1581
+
1582
+ test("buildPiInvocation signals the exact deduped takeover -e set in order, without passthrough -e flags", () => {
1583
+ const built = buildPiInvocation({
1584
+ ...injectionInput,
1585
+ home: linkHome,
1586
+ declaration: { kind: "path", dir: "/other/checkout" },
1587
+ takeOver: true,
1588
+ otherPackagePaths: ["/agent/npm/node_modules/some-other", "/shared/dup.ts", "/pkg"],
1589
+ looseExtensionEntries: ["/shared/dup.ts", "/agent/extensions/a.ts", "/agent/extensions/a.ts"],
1590
+ baseEnv: {},
1591
+ });
1592
+ const launcherPaths = built.args.slice(0, built.args.indexOf("--mode") - 2).filter((_, index, all) => all[index - 1] === "-e");
1593
+ const signal = parseChildPackageInjection(built.env);
1594
+ assert.deepEqual(signal, {
1595
+ noExtensions: true,
1596
+ // /pkg keeps its first (other-package) position, exactly as in argv.
1597
+ extensionPaths: ["/agent/npm/node_modules/some-other", "/shared/dup.ts", "/pkg", "/agent/extensions/a.ts"],
1598
+ });
1599
+ assert.deepEqual(signal?.extensionPaths, launcherPaths);
1600
+ });
1601
+
1602
+ test("buildPiInvocation signals absolute paths resolved against the given cwd when a takeover receives a relative loose entry", () => {
1603
+ const cwd = resolve("/elsewhere", "launch-dir");
1604
+ assert.notEqual(cwd, process.cwd());
1605
+ const built = buildPiInvocation({
1606
+ ...injectionInput,
1607
+ cwd,
1608
+ home: linkHome,
1609
+ declaration: { kind: "npm" },
1610
+ takeOver: true,
1611
+ looseExtensionEntries: [join("relative-home", "extensions", "a.ts")],
1612
+ baseEnv: {},
1613
+ });
1614
+ assert.ok(built.args.includes(join("relative-home", "extensions", "a.ts")), "the -e argv itself is unchanged");
1615
+ assert.deepEqual(parseChildPackageInjection(built.env)?.extensionPaths, [resolve(cwd, "relative-home", "extensions", "a.ts"), "/pkg"]);
1616
+ });
1617
+
1618
+ test("buildPiInvocation signals one entry for a file spelled both relative and absolute, while argv keeps both spellings", () => {
1619
+ const cwd = resolve("/elsewhere", "launch-dir");
1620
+ const relative = join("relative-home", "extensions", "a.ts");
1621
+ const absolute = resolve(cwd, relative);
1622
+ const built = buildPiInvocation({
1623
+ ...injectionInput,
1624
+ cwd,
1625
+ home: linkHome,
1626
+ declaration: { kind: "npm" },
1627
+ takeOver: true,
1628
+ otherPackagePaths: ["/agent/npm/node_modules/some-other", absolute],
1629
+ looseExtensionEntries: [relative, "/agent/extensions/b.ts"],
1630
+ baseEnv: {},
1631
+ });
1632
+ assert.deepEqual(built.args.slice(0, built.args.indexOf("--mode") - 2), [
1633
+ "--no-extensions",
1634
+ "-e",
1635
+ "/agent/npm/node_modules/some-other",
1636
+ "-e",
1637
+ absolute,
1638
+ "-e",
1639
+ relative,
1640
+ "-e",
1641
+ "/agent/extensions/b.ts",
1642
+ "-e",
1643
+ "/pkg",
1644
+ ]);
1645
+ assert.deepEqual(parseChildPackageInjection(built.env)?.extensionPaths, [
1646
+ "/agent/npm/node_modules/some-other",
1647
+ absolute,
1648
+ "/agent/extensions/b.ts",
1649
+ "/pkg",
1650
+ ]);
1651
+ });
1652
+
1653
+ test("buildPiInvocation does not signal when settings already declare gentle-pi, and drops an inherited signal", () => {
1654
+ const stale = encodeChildPackageInjection({ noExtensions: false, extensionPaths: ["/outer/pkg"] });
1655
+ for (const baseEnv of [{}, { [CHILD_PACKAGE_INJECTION_ENV]: stale }]) {
1656
+ const built = buildPiInvocation({ ...injectionInput, home: linkHome, declaration: { kind: "npm" }, takeOver: false, baseEnv });
1657
+ assert.equal(Object.hasOwn(built.env, CHILD_PACKAGE_INJECTION_ENV), false);
1658
+ }
1659
+ });
1660
+
1661
+ test("buildPiInvocation does not signal for a pi subcommand, and drops an inherited signal", () => {
1662
+ const stale = encodeChildPackageInjection({ noExtensions: false, extensionPaths: ["/outer/pkg"] });
1663
+ for (const takeOver of [false, true]) {
1664
+ const built = buildPiInvocation({
1665
+ ...injectionInput,
1666
+ declaration: undefined,
1667
+ takeOver,
1668
+ passthrough: ["list"],
1669
+ piSubcommand: "list",
1670
+ baseEnv: { [CHILD_PACKAGE_INJECTION_ENV]: stale },
1671
+ });
1672
+ assert.equal(Object.hasOwn(built.env, CHILD_PACKAGE_INJECTION_ENV), false);
1673
+ }
1674
+ });
1675
+
1676
+ test("buildPiInvocation replaces an inherited signal with its own injection", () => {
1677
+ const stale = encodeChildPackageInjection({ noExtensions: true, extensionPaths: ["/outer/pkg"] });
1678
+ const built = buildPiInvocation({ ...injectionInput, declaration: undefined, takeOver: false, baseEnv: { [CHILD_PACKAGE_INJECTION_ENV]: stale } });
1679
+ assert.deepEqual(parseChildPackageInjection(built.env), { noExtensions: false, extensionPaths: ["/pkg"] });
1680
+ });
1681
+
1436
1682
  // --- discoverLooseExtensionEntries -------------------------------------------
1437
1683
  //
1438
1684
  // Pure mirror of pi's own discoverExtensionsInDir (packages/coding-agent/src/
@@ -1759,3 +2005,25 @@ test("helpText documents pi's own package-management subcommands", () => {
1759
2005
  assert.match(text, /\bconfig\b/);
1760
2006
  assert.match(text, /\bauth\b/);
1761
2007
  });
2008
+
2009
+ // T3 B3: the isolated-home bootstrap creates ~/.gentle-shell private (0700) even under
2010
+ // umask 002, so the bundled prefix claim (which refuses group/other-writable folders) accepts it.
2011
+ test("the isolated-home bootstrap creates ~/.gentle-shell as 0700 under umask 002", { skip: process.platform === "win32" ? "POSIX modes" : false }, async () => {
2012
+ const { chmodSync, mkdtempSync, rmSync, statSync, writeFileSync } = await import("node:fs");
2013
+ const { spawnSync } = await import("node:child_process");
2014
+ const { tmpdir } = await import("node:os");
2015
+ const root = mkdtempSync(join(tmpdir(), "gentle-shell-umask-"));
2016
+ try {
2017
+ const piScript = join(root, "fake-pi.mjs");
2018
+ writeFileSync(piScript, "#!/usr/bin/env node\nif (process.argv.includes('--version')) console.log('0.99.1');\n");
2019
+ chmodSync(piScript, 0o755);
2020
+ const binPath = fileURLToPath(new URL("../bin/gentle-shell.mjs", import.meta.url));
2021
+ const env = { HOME: root, USERPROFILE: root, PATH: process.env.PATH ?? "/usr/bin:/bin", GENTLE_SHELL_PI: piScript, GENTLE_SHELL_NO_AUTO_SETUP: "1" };
2022
+ const result = spawnSync("/bin/sh", ["-c", 'umask 002; exec "$0" "$@"', process.execPath, binPath, "hello"], { env, encoding: "utf8", input: "" });
2023
+ assert.equal(result.status, 0, result.stderr);
2024
+ assert.equal(statSync(join(root, ".gentle-shell")).mode & 0o777, 0o700);
2025
+ assert.equal(statSync(join(root, ".gentle-shell", "agent")).mode & 0o777, 0o700);
2026
+ } finally {
2027
+ rmSync(root, { recursive: true, force: true });
2028
+ }
2029
+ });