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
@@ -0,0 +1,231 @@
1
+ import type { Readable, Writable } from "node:stream";
2
+
3
+ export type StartupUpdateChoice = "update" | "skip" | "omit" | "cancel";
4
+ type TerminalInput = Readable & { isRaw?: boolean; setRawMode?: (raw: boolean) => unknown };
5
+ type TerminalOutput = Writable & { columns?: number; rows?: number; isTTY?: boolean };
6
+ type UpdateOffer = { current: string; latest: string; notesUrl: string };
7
+ type PromptOptions = { input?: TerminalInput; output?: TerminalOutput; color?: boolean };
8
+
9
+ const choices: StartupUpdateChoice[] = ["update", "skip", "omit"];
10
+ const labels = ["Update now (gentle-shell upgrade)", "Not now", "Skip this version"];
11
+
12
+ function sanitize(value: string, limit: number): string {
13
+ // Bound work before parsing, remove whole escape sequences (including OSC titles),
14
+ // then remove remaining control and bidi characters. Never emit an OSC hyperlink.
15
+ return value.slice(0, 2048)
16
+ .replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\|$)/g, "")
17
+ .replace(/(?:\x1b\[|\x9b)[0-?]*[ -/]*[@-~]/g, "")
18
+ .replace(/\x1b[ -/]*[@-~]/g, "")
19
+ .replace(/[\x00-\x1f\x7f-\x9f\u2028\u2029\u202a-\u202e\u2066-\u2069]/g, "")
20
+ .slice(0, limit);
21
+ }
22
+
23
+ function cells(char: string): number {
24
+ const code = char.codePointAt(0)!;
25
+ if (/\p{Mark}/u.test(char) || code === 0x200d || code === 0xfe0f) return 0;
26
+ return code >= 0x1100 && (
27
+ code <= 0x115f || code === 0x2329 || code === 0x232a ||
28
+ (code >= 0x2e80 && code <= 0xa4cf) || (code >= 0xac00 && code <= 0xd7a3) ||
29
+ (code >= 0xf900 && code <= 0xfaff) || (code >= 0xfe10 && code <= 0xfe6f) ||
30
+ (code >= 0xff00 && code <= 0xff60) || (code >= 0xffe0 && code <= 0xffe6) ||
31
+ code >= 0x1f000
32
+ ) ? 2 : 1;
33
+ }
34
+
35
+ function wrap(text: string, width: number): string[] {
36
+ const lines: string[] = [];
37
+ let line = "";
38
+ let used = 0;
39
+ for (const original of text) {
40
+ const char = cells(original) > width ? "?" : original;
41
+ const size = cells(char);
42
+ if (used + size > width) {
43
+ lines.push(line);
44
+ line = "";
45
+ used = 0;
46
+ }
47
+ line += char;
48
+ used += size;
49
+ }
50
+ lines.push(line);
51
+ return lines;
52
+ }
53
+
54
+ /** A pre-Pi chooser: returns intent only; it never invokes upgrade or persists policy. */
55
+ export function promptStartupUpdate(offer: UpdateOffer, options: PromptOptions = {}): Promise<StartupUpdateChoice> {
56
+ const input = options.input ?? process.stdin;
57
+ const output = options.output ?? process.stdout;
58
+ const color = options.color ?? output.isTTY === true;
59
+ const rose = (text: string) => color ? `\x1b[38;2;255;118;195m${text}\x1b[39m` : text;
60
+ const current = sanitize(offer.current, 40);
61
+ const latest = sanitize(offer.latest, 40);
62
+ const notes = sanitize(offer.notesUrl, 160);
63
+
64
+ return new Promise((resolve, reject) => {
65
+ const wasRaw = input.isRaw === true;
66
+ const wasFlowing = input.readableFlowing === true;
67
+ let rawAttempted = false;
68
+ let cursorHidden = false;
69
+ let selected = 1;
70
+ let done = false;
71
+ let failure: unknown;
72
+ let outcome: StartupUpdateChoice = "skip";
73
+ let pendingWrites = 0;
74
+ let settlementScheduled = false;
75
+ let escape = "";
76
+ let escapeTimer: ReturnType<typeof setTimeout> | undefined;
77
+ let previousMenuLines = 0;
78
+ let previousWidth = 0;
79
+
80
+ function settle() {
81
+ if (!done || pendingWrites || settlementScheduled) return;
82
+ settlementScheduled = true;
83
+ // Writable callback errors can be followed by an error event. Keep our
84
+ // listener until that event is drained, including during cursor cleanup.
85
+ setImmediate(() => {
86
+ output.removeListener("error", onError);
87
+ if (failure) reject(failure);
88
+ else resolve(outcome);
89
+ });
90
+ }
91
+
92
+ function write(text: string) {
93
+ pendingWrites++;
94
+ try {
95
+ output.write(text, (error?: Error | null) => {
96
+ pendingWrites--;
97
+ if (error) onError(error);
98
+ settle();
99
+ });
100
+ } catch (error) {
101
+ pendingWrites--;
102
+ onError(error);
103
+ }
104
+ }
105
+
106
+ function finish(choice: StartupUpdateChoice, error?: unknown) {
107
+ if (error && !failure) failure = error;
108
+ if (done) return;
109
+ done = true;
110
+ outcome = choice;
111
+ clearTimeout(escapeTimer);
112
+ input.removeListener("data", onData);
113
+ input.removeListener("end", onEnd);
114
+ input.removeListener("close", onEnd);
115
+ input.removeListener("error", onError);
116
+ try {
117
+ if (rawAttempted) input.setRawMode?.(wasRaw);
118
+ } catch (error) {
119
+ failure ??= error;
120
+ }
121
+ if (!wasFlowing) input.pause();
122
+ if (cursorHidden) write("\x1b[?25h");
123
+ settle();
124
+ }
125
+
126
+ function onError(error: unknown) {
127
+ if (!failure) failure = error;
128
+ finish("skip", error);
129
+ }
130
+ function onEnd() { finish("skip"); }
131
+
132
+ function terminalWidth(): number {
133
+ const width = output.columns;
134
+ return typeof width === "number" && Number.isFinite(width) ? Math.max(1, Math.min(76, Math.floor(width))) : 76;
135
+ }
136
+
137
+ function render(initial = false) {
138
+ const width = terminalWidth();
139
+ const bordered = width >= 6;
140
+ const inner = bordered ? width - 4 : width;
141
+ const row = (text: string) => wrap(text, inner).map(line => {
142
+ const used = [...line].reduce((sum, char) => sum + cells(char), 0);
143
+ return bordered ? `| ${line}${" ".repeat(Math.max(0, inner - used))} |` : line;
144
+ });
145
+ const border = bordered ? rose(`+${"-".repeat(width - 2)}+`) : "";
146
+ const menu = labels.flatMap((label, index) => row(`${index === selected ? ">" : " "} ${index + 1}. ${label}`));
147
+ if (bordered) menu.push(border);
148
+ let prefix = "";
149
+ if (initial) {
150
+ // Start on a fresh line so the existing shell prompt is never erased.
151
+ const header = [
152
+ ...(bordered ? [border] : []),
153
+ ...row("Gentle Shell update available").map(rose),
154
+ ...row(`${current} -> ${latest}`),
155
+ ...row("Release notes:"),
156
+ ...row(notes),
157
+ ...row("Arrows / 1-3: select; Enter: confirm"),
158
+ ...row("Esc: not now; Ctrl+C: cancel"),
159
+ ];
160
+ prefix = `\r\n${header.join("\r\n")}\r\n`;
161
+ } else {
162
+ const rows = output.rows ?? 24;
163
+ // On resize or a very short terminal, append rather than moving above
164
+ // our visible menu. Never clear a screen or enter an alternate screen.
165
+ if (width === previousWidth && previousMenuLines < rows) {
166
+ prefix = `\x1b[${previousMenuLines}A\r`;
167
+ }
168
+ }
169
+ const clear = !initial && prefix.startsWith("\x1b[") ? "\x1b[2K" : "";
170
+ write(prefix + menu.map(line => `${clear}${line}\r\n`).join(""));
171
+ previousMenuLines = menu.length;
172
+ previousWidth = width;
173
+ }
174
+
175
+ function select(index: number) {
176
+ selected = (index + choices.length) % choices.length;
177
+ render();
178
+ }
179
+
180
+ function onData(chunk: Buffer | string) {
181
+ for (const char of chunk.toString()) {
182
+ if (done) break;
183
+ if (char === "\x03") { finish("cancel"); break; }
184
+ if (escape) {
185
+ clearTimeout(escapeTimer);
186
+ escape += char;
187
+ if (escape === "\x1b[" || escape === "\x1bO") {
188
+ escapeTimer = setTimeout(() => finish("skip"), 40);
189
+ continue;
190
+ }
191
+ if (/^\x1b[\[O][ABCD]$/.test(escape)) {
192
+ select(selected + (char === "A" || char === "D" ? -1 : 1));
193
+ escape = "";
194
+ continue;
195
+ }
196
+ finish("skip");
197
+ break;
198
+ }
199
+ if (char === "\x1b") {
200
+ escape = char;
201
+ escapeTimer = setTimeout(() => finish("skip"), 40);
202
+ } else if (/^[123]$/.test(char)) select(Number(char) - 1);
203
+ else if (char === "\r" || char === "\n") finish(choices[selected]);
204
+ }
205
+ }
206
+
207
+ output.on("error", onError);
208
+ input.on("error", onError);
209
+ input.on("end", onEnd);
210
+ input.on("close", onEnd);
211
+ try {
212
+ if (input.readableEnded || input.destroyed || output.destroyed) {
213
+ finish("skip");
214
+ return;
215
+ }
216
+ if (input.setRawMode) {
217
+ rawAttempted = true;
218
+ input.setRawMode(true);
219
+ }
220
+ cursorHidden = true;
221
+ write("\x1b[?25l");
222
+ if (!done) render(true);
223
+ if (!done) {
224
+ input.on("data", onData);
225
+ input.resume();
226
+ }
227
+ } catch (error) {
228
+ finish("skip", error);
229
+ }
230
+ });
231
+ }
@@ -0,0 +1,183 @@
1
+ import { execFile } from "node:child_process";
2
+ import { closeSync, constants, fstatSync, openSync, readSync } from "node:fs";
3
+ import { dirname, isAbsolute, join } from "node:path";
4
+ import { stripTerminalSequences, visibleWidth } from "@earendil-works/pi-tui";
5
+
6
+ const graphemes = new Intl.Segmenter(undefined, { granularity: "grapheme" });
7
+
8
+ const FALLBACK_COLUMNS = 24;
9
+ const MAX_SNAPSHOT_BYTES = 256 * 1024;
10
+
11
+ /** Read only a bounded local snapshot, never poll Herdr's CLI/socket for geometry. */
12
+ export function createActivityColumns(socket: string, now: () => number = Date.now): () => number {
13
+ const path = isAbsolute(socket) ? join(dirname(socket), "session.json") : undefined;
14
+ let refreshAt = -Infinity;
15
+ let columns = FALLBACK_COLUMNS;
16
+ return () => {
17
+ const time = now();
18
+ if (time < refreshAt) return columns;
19
+ refreshAt = time + 5000;
20
+ columns = FALLBACK_COLUMNS;
21
+ if (!path) return columns;
22
+ let fd: number | undefined;
23
+ try {
24
+ // Reject FIFOs after opening without waiting for a writer; unsupported flags are omitted.
25
+ fd = openSync(path, constants.O_RDONLY | (constants.O_NONBLOCK ?? 0));
26
+ const stat = fstatSync(fd);
27
+ if (!stat.isFile() || stat.size > MAX_SNAPSHOT_BYTES) return columns;
28
+ // One extra byte rejects files that grew after fstat, without unlimited reads.
29
+ const buffer = Buffer.alloc(MAX_SNAPSHOT_BYTES + 1);
30
+ const size = readSync(fd, buffer, 0, buffer.length, 0);
31
+ if (size > MAX_SNAPSHOT_BYTES) return columns;
32
+ const snapshot = JSON.parse(buffer.toString("utf8", 0, size));
33
+ const width: unknown = snapshot?.sidebar_width;
34
+ if (typeof width === "number" && Number.isInteger(width) && width >= 6 && width <= 512) {
35
+ // Divider + possible scrollbar + three-space agent detail prefix.
36
+ columns = width - 5;
37
+ }
38
+ } catch { /* Missing/malformed snapshots use conservative geometry. */ }
39
+ finally {
40
+ try { if (fd !== undefined) closeSync(fd); } catch { /* Best effort close. */ }
41
+ }
42
+ return columns;
43
+ };
44
+ }
45
+
46
+ /** Fit whole graphemes within cell, UTF-8 byte and Unicode scalar budgets. */
47
+ function fit(parts: string[], columns: number, bytes: number, characters: number, preferWords = true): number {
48
+ let cells = 0;
49
+ let used = 0;
50
+ let scalars = 0;
51
+ let count = 0;
52
+ let space = -1;
53
+ for (const part of parts) {
54
+ const width = visibleWidth(part);
55
+ const size = Buffer.byteLength(part);
56
+ const length = Array.from(part).length;
57
+ if (cells + width > columns || used + size > bytes || scalars + length > characters) break;
58
+ if (part === " ") space = count;
59
+ cells += width;
60
+ used += size;
61
+ scalars += length;
62
+ count++;
63
+ }
64
+ if (preferWords && count < parts.length && parts[count] !== " " && space > 0) return space;
65
+ return count;
66
+ }
67
+
68
+ /** Only the active Todo title is free text. LF is private framing, not a token. */
69
+ export function activitySummary(tasks: unknown, columns = FALLBACK_COLUMNS): string | null {
70
+ if (!Array.isArray(tasks)) return null;
71
+ const title: unknown = tasks.find((task) => task?.status === "in_progress")?.title;
72
+ if (typeof title !== "string") return null;
73
+ const clipped = title.length > 4096;
74
+ // Bound sanitization/segmentation work, preserve ZWJ emoji but strip bidi controls.
75
+ /* eslint-disable no-control-regex */
76
+ const clean = stripTerminalSequences(title.slice(0, 4096))
77
+ .replace(/[\x00-\x1f\x7f-\x9f\u061c\u200b-\u200c\u200e-\u200f\u202a-\u202e\u2060-\u206f\ufeff]/g, " ")
78
+ .replace(/\s+/g, " ").trim();
79
+ /* eslint-enable no-control-regex */
80
+ if (!clean) return null;
81
+ const parts = Array.from(graphemes.segment(clean), ({ segment }) => segment);
82
+ // The bounded sample may end inside a grapheme; never publish that fragment.
83
+ if (clipped) parts.pop();
84
+ columns = Number.isFinite(columns) ? Math.max(1, Math.min(512, Math.floor(columns))) : FALLBACK_COLUMNS;
85
+ if (columns <= 2) return columns === 1 ? "…" : "◐…";
86
+ // Herdr caps each token at 80 scalars; count our icon/indent before its trim.
87
+ const whole = fit(parts, columns - 2, 252, 78, false);
88
+ if (!clipped && whole === parts.length) return `◐ ${parts.join("")}`;
89
+ // Reserve the second-row indent and ellipsis; private LF framing is not a token byte.
90
+ let firstCount = fit(parts, columns - 2, 247, 78);
91
+ if (!firstCount) return columns === 3 ? "◐…" : "◐ …";
92
+ const restFits = (count: number): boolean => {
93
+ const first = `◐ ${parts.slice(0, count).join("").trimEnd()}`;
94
+ const rest = parts.slice(count);
95
+ while (rest[0] === " ") rest.shift();
96
+ return fit(rest, columns - 2, 256 - Buffer.byteLength(first) - 2, 78, false) === rest.length;
97
+ };
98
+ // Prefer words unless splitting one would preserve the entire two-row title.
99
+ if (!clipped && !restFits(firstCount)) {
100
+ const hardCount = fit(parts, columns - 2, 247, 78, false);
101
+ if (restFits(hardCount)) firstCount = hardCount;
102
+ }
103
+ const first = `◐ ${parts.slice(0, firstCount).join("").trimEnd()}`;
104
+ const rest = parts.slice(firstCount);
105
+ while (rest[0] === " ") rest.shift();
106
+ const bytes = 256 - Buffer.byteLength(first) - 2; // Two-space indent.
107
+ let count = fit(rest, columns - 2, bytes, 78, false);
108
+ const overflow = clipped || count < rest.length;
109
+ if (overflow) count = fit(rest, columns - 3, bytes - 3, 77);
110
+ const second = rest.slice(0, count).join("").trimEnd();
111
+ return `${first}\n ${second}${overflow ? "…" : ""}`;
112
+ }
113
+
114
+ export function metadataArgs(pane: string, summary: string | null, seq: number): string[] {
115
+ const rows = summary?.split("\n") ?? [];
116
+ const tokens = ["summary", "summary2"].flatMap((token, index) => rows[index]
117
+ ? ["--token", `${token}=${rows[index]}`] : ["--clear-token", token]);
118
+ return ["pane", "report-metadata", pane, "--source", "gentle:activity", "--agent", "pi",
119
+ "--seq", String(seq), "--ttl-ms", "30000", ...tokens];
120
+ }
121
+
122
+ export function metadataTransport(env: NodeJS.ProcessEnv): (summary: string | null, seq: number) => Promise<void> {
123
+ return (summary, seq) => new Promise((resolve) => {
124
+ // No shell, bounded output/time, and failure never blocks Pi's handlers.
125
+ execFile(env.HERDR_BIN_PATH ?? "herdr", metadataArgs(env.HERDR_PANE_ID!, summary, seq), {
126
+ env, timeout: 1500, maxBuffer: 4096, windowsHide: true,
127
+ }, () => resolve());
128
+ });
129
+ }
130
+
131
+ /** One in flight, one pending value; a clear supersedes queued active work. */
132
+ export class ActivityPublisher {
133
+ private pending: string | null | undefined;
134
+ private last: string | null | undefined;
135
+ private running = false;
136
+ private closed = false;
137
+ private cancel: (() => void) | undefined;
138
+ private seq = Date.now();
139
+ private readonly send: (summary: string | null, seq: number) => Promise<void>;
140
+ private readonly schedule: (fn: () => void) => () => void;
141
+ constructor(send: (summary: string | null, seq: number) => Promise<void>,
142
+ schedule: (fn: () => void) => () => void = (fn) => {
143
+ const timer = setTimeout(fn, 150);
144
+ timer.unref();
145
+ return () => clearTimeout(timer);
146
+ }) {
147
+ this.send = send;
148
+ this.schedule = schedule;
149
+ }
150
+
151
+ update(summary: string | null, force = false): void {
152
+ if (this.closed || (!force && (this.pending !== undefined
153
+ ? summary === this.pending : !this.running && summary === this.last))) return;
154
+ this.pending = summary;
155
+ if (summary === null) {
156
+ this.cancel?.();
157
+ this.cancel = undefined;
158
+ if (!this.running) void this.flush();
159
+ return;
160
+ }
161
+ this.enqueue();
162
+ }
163
+ private enqueue(): void {
164
+ if (this.running || this.cancel || this.pending === undefined) return;
165
+ this.cancel = this.schedule(() => { this.cancel = undefined; void this.flush(); });
166
+ }
167
+ private async flush(): Promise<void> {
168
+ if (this.pending === undefined) return;
169
+ const summary = this.pending;
170
+ this.pending = undefined;
171
+ this.running = true;
172
+ try { await this.send(summary, ++this.seq); this.last = summary; }
173
+ catch { /* Best effort; a refresh retries active state. */ }
174
+ finally { this.running = false; this.enqueue(); }
175
+ }
176
+ close(): void {
177
+ if (this.closed) return;
178
+ this.update(null, true);
179
+ this.closed = true;
180
+ this.cancel?.(); this.cancel = undefined;
181
+ if (!this.running) void this.flush();
182
+ }
183
+ }
@@ -0,0 +1,95 @@
1
+ import { mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
2
+ import { randomUUID } from "node:crypto";
3
+ import { join } from "node:path";
4
+ import { gentlePiConfigHome } from "./agent-home.ts";
5
+
6
+ // Prompt history capture is default-off. An explicit GENTLE_PI_HISTORY_CAPTURE
7
+ // value wins, then the Gentle → Customize preference, then off. Any missing,
8
+ // malformed or unreadable preference fails closed. Turning capture off only
9
+ // stops new captures; stored history is never deleted here.
10
+ export const HISTORY_CAPTURE_POLICY = { ON: "on", OFF: "off" } as const;
11
+ export type HistoryCapturePolicy = (typeof HISTORY_CAPTURE_POLICY)[keyof typeof HISTORY_CAPTURE_POLICY];
12
+ export const HISTORY_CAPTURE_SCHEMA = "gentle-pi.history-capture/v1";
13
+ export const HISTORY_CAPTURE_ENV = "GENTLE_PI_HISTORY_CAPTURE";
14
+ interface HistoryCaptureOptions { gentlePiConfigHome?: string }
15
+ export interface HistoryCapturePolicyResolution {
16
+ policy: HistoryCapturePolicy;
17
+ source: "global_file" | "default";
18
+ malformed: boolean;
19
+ globalFile: string;
20
+ }
21
+ export interface HistoryCaptureResolution {
22
+ enabled: boolean;
23
+ source: "env" | "global_file" | "default";
24
+ /** The persisted Customize preference, reported even while the env overrides it. */
25
+ preference: HistoryCapturePolicy;
26
+ envOverride: HistoryCapturePolicy | undefined;
27
+ malformed: boolean;
28
+ globalFile: string;
29
+ }
30
+
31
+ /** Only explicit on/off values override; anything else defers to the preference. */
32
+ export function historyCaptureEnvOverride(env: NodeJS.ProcessEnv = process.env): HistoryCapturePolicy | undefined {
33
+ const value = env[HISTORY_CAPTURE_ENV]?.trim().toLowerCase();
34
+ if (value === "1" || value === "true" || value === "on") return "on";
35
+ if (value === "0" || value === "false" || value === "off") return "off";
36
+ return undefined;
37
+ }
38
+
39
+ export function parseHistoryCaptureFile(raw: string): HistoryCapturePolicy | undefined {
40
+ try {
41
+ const value: unknown = JSON.parse(raw);
42
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined;
43
+ if (Object.keys(value).length !== 2 || !("schema" in value) || value.schema !== HISTORY_CAPTURE_SCHEMA || !("policy" in value)) return undefined;
44
+ return value.policy === "on" || value.policy === "off" ? value.policy : undefined;
45
+ } catch { return undefined; }
46
+ }
47
+
48
+ export function resolveHistoryCapturePolicy(options: HistoryCaptureOptions = {}): HistoryCapturePolicyResolution {
49
+ const globalFile = join(options.gentlePiConfigHome ?? gentlePiConfigHome(), "history-capture.json");
50
+ try {
51
+ const policy = parseHistoryCaptureFile(readFileSync(globalFile, "utf8"));
52
+ return { policy: policy ?? "off", source: "global_file", malformed: policy === undefined, globalFile };
53
+ } catch (error) {
54
+ const missing = typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
55
+ return { policy: "off", source: missing ? "default" : "global_file", malformed: !missing, globalFile };
56
+ }
57
+ }
58
+
59
+ /** Full resolution for status surfaces; always reads the preference. */
60
+ export function resolveHistoryCapture(options: { env?: NodeJS.ProcessEnv; gentlePiConfigHome?: string } = {}): HistoryCaptureResolution {
61
+ const env = options.env ?? process.env;
62
+ const saved = resolveHistoryCapturePolicy({ gentlePiConfigHome: options.gentlePiConfigHome ?? gentlePiConfigHome(env) });
63
+ const envOverride = historyCaptureEnvOverride(env);
64
+ return {
65
+ enabled: (envOverride ?? saved.policy) === "on",
66
+ source: envOverride ? "env" : saved.source,
67
+ preference: saved.policy,
68
+ envOverride,
69
+ malformed: saved.malformed,
70
+ globalFile: saved.globalFile,
71
+ };
72
+ }
73
+
74
+ /** Per-prompt gate: an explicit env value never reads the preference file. */
75
+ export function historyCaptureEnabled(options: { env?: NodeJS.ProcessEnv; gentlePiConfigHome?: string } = {}): boolean {
76
+ const env = options.env ?? process.env;
77
+ const override = historyCaptureEnvOverride(env);
78
+ if (override) return override === "on";
79
+ return resolveHistoryCapturePolicy({ gentlePiConfigHome: options.gentlePiConfigHome ?? gentlePiConfigHome(env) }).policy === "on";
80
+ }
81
+
82
+ export function writeHistoryCapturePolicy(policy: HistoryCapturePolicy, options: HistoryCaptureOptions = {}): string {
83
+ if (policy !== "on" && policy !== "off") throw new TypeError("Invalid history capture policy");
84
+ const home = options.gentlePiConfigHome ?? gentlePiConfigHome();
85
+ const path = join(home, "history-capture.json");
86
+ const temporary = `${path}.${randomUUID()}.tmp`;
87
+ mkdirSync(home, { recursive: true });
88
+ try {
89
+ writeFileSync(temporary, `${JSON.stringify({ schema: HISTORY_CAPTURE_SCHEMA, policy })}\n`, { flag: "wx", mode: 0o600 });
90
+ renameSync(temporary, path);
91
+ } finally {
92
+ try { unlinkSync(temporary); } catch { /* Rename consumed the temporary file. */ }
93
+ }
94
+ return path;
95
+ }
@@ -73,6 +73,8 @@ export interface InProcessReviewerRequest {
73
73
  readonly selection: string;
74
74
  /** Routing thinking label: off | minimal | low | medium | high | xhigh | max. Omitted is treated as "off". */
75
75
  readonly thinking?: string;
76
+ /** Effective Pi transport preference; absent preserves the provider default. */
77
+ readonly transport?: SimpleStreamOptions["transport"];
76
78
  /** Frozen Go-materialized prompt bytes, submitted verbatim as the one user message. */
77
79
  readonly prompt: Buffer;
78
80
  readonly timeoutMs: number;
@@ -272,6 +274,7 @@ export async function runInProcessReviewer(request: InProcessReviewerRequest, de
272
274
  ...(auth.apiKey === undefined ? {} : { apiKey: auth.apiKey }),
273
275
  ...(auth.headers === undefined && attributionHeaders === undefined ? {} : { headers: attributionHeaders === undefined ? auth.headers : { ...attributionHeaders, ...auth.headers } }),
274
276
  ...(reasoning.reasoning === undefined ? {} : { reasoning: reasoning.reasoning }),
277
+ ...(request.transport === undefined ? {} : { transport: request.transport }),
275
278
  };
276
279
 
277
280
  // An abort is classified by which signal actually fired, never by the
@@ -0,0 +1,20 @@
1
+ /** Session-scoped display data only; never command output or process control. */
2
+ export const JOBS_SIDEBAR_EVENT = "gentle-ai:jobs-sidebar";
3
+ export const JOBS_STATUS_KEY = "gentle-jobs";
4
+
5
+ export interface RunningJobDisplay {
6
+ id: string;
7
+ label: string;
8
+ startedAt: number;
9
+ }
10
+
11
+ export interface JobsSidebarSnapshot {
12
+ jobs: RunningJobDisplay[];
13
+ }
14
+
15
+ export function isJobsSidebarSnapshot(value: unknown): value is JobsSidebarSnapshot {
16
+ if (!value || typeof value !== "object" || !Array.isArray((value as JobsSidebarSnapshot).jobs)) return false;
17
+ return (value as JobsSidebarSnapshot).jobs.every((job) => job && typeof job === "object" &&
18
+ typeof job.id === "string" && job.id.length > 0 && typeof job.label === "string" &&
19
+ typeof job.startedAt === "number" && Number.isFinite(job.startedAt));
20
+ }