@xingular/harness 1.0.0 → 1.2.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 (397) hide show
  1. package/CHANGELOG.md +46 -5
  2. package/README.md +75 -21
  3. package/dist/cli.js +17421 -8711
  4. package/dist/sdk/accounts/accounts.d.ts +27 -3
  5. package/dist/sdk/accounts/accounts.js +78 -24
  6. package/dist/sdk/accounts/secrets.d.ts +12 -0
  7. package/dist/sdk/accounts/secrets.js +114 -7
  8. package/dist/sdk/accounts/store.d.ts +26 -1
  9. package/dist/sdk/accounts/store.js +1 -1
  10. package/dist/sdk/agents/builtin.d.ts +2 -0
  11. package/dist/sdk/agents/builtin.js +48 -0
  12. package/dist/sdk/agents/narrow.d.ts +13 -0
  13. package/dist/sdk/agents/narrow.js +29 -0
  14. package/dist/sdk/agents/planResearch.d.ts +5 -0
  15. package/dist/sdk/agents/planResearch.js +19 -0
  16. package/dist/sdk/agents/profiles.d.ts +81 -0
  17. package/dist/sdk/agents/profiles.js +226 -0
  18. package/dist/sdk/annotations/bridge.d.ts +42 -0
  19. package/dist/sdk/annotations/bridge.js +264 -0
  20. package/dist/sdk/annotations/capture.d.ts +116 -0
  21. package/dist/sdk/annotations/capture.js +308 -0
  22. package/dist/sdk/annotations/delivery.d.ts +72 -0
  23. package/dist/sdk/annotations/delivery.js +130 -0
  24. package/dist/sdk/annotations/inspect.d.ts +10 -0
  25. package/dist/sdk/annotations/inspect.js +33 -0
  26. package/dist/sdk/annotations/isolated.d.ts +6 -0
  27. package/dist/sdk/annotations/isolated.js +51 -0
  28. package/dist/sdk/annotations/pageScript.d.ts +28 -0
  29. package/dist/sdk/annotations/pageScript.js +871 -0
  30. package/dist/sdk/annotations/part.d.ts +10 -0
  31. package/dist/sdk/annotations/part.js +155 -0
  32. package/dist/sdk/annotations/queue.d.ts +4 -0
  33. package/dist/sdk/annotations/queue.js +40 -0
  34. package/dist/sdk/annotations/serverMethods.d.ts +3 -0
  35. package/dist/sdk/annotations/serverMethods.js +62 -0
  36. package/dist/sdk/annotations/store.d.ts +167 -0
  37. package/dist/sdk/annotations/store.js +394 -0
  38. package/dist/sdk/browser/backend.d.ts +16 -0
  39. package/dist/sdk/browser/backend.js +17 -0
  40. package/dist/sdk/browser/contracts.d.ts +19 -0
  41. package/dist/sdk/browser/contracts.js +116 -0
  42. package/dist/sdk/browser/index.d.ts +14 -3
  43. package/dist/sdk/browser/index.js +11 -4
  44. package/dist/sdk/browser/serverMethods.d.ts +3 -0
  45. package/dist/sdk/browser/serverMethods.js +7 -0
  46. package/dist/sdk/browser/session.d.ts +1 -0
  47. package/dist/sdk/browser/session.js +45 -9
  48. package/dist/sdk/browser/tools.d.ts +1 -4
  49. package/dist/sdk/browser/tools.js +14 -91
  50. package/dist/sdk/cli.js +24 -15
  51. package/dist/sdk/commands/list.d.ts +3 -0
  52. package/dist/sdk/commands/list.js +75 -0
  53. package/dist/sdk/commands/registry.d.ts +250 -0
  54. package/dist/sdk/commands/registry.js +47 -0
  55. package/dist/sdk/commands/routes.d.ts +16 -0
  56. package/dist/sdk/commands/routes.js +33 -0
  57. package/dist/sdk/core/bus.js +1 -1
  58. package/dist/sdk/core/commandBudget.d.ts +23 -0
  59. package/dist/sdk/core/commandBudget.js +57 -0
  60. package/dist/sdk/core/env.d.ts +1 -1
  61. package/dist/sdk/core/env.js +1 -1
  62. package/dist/sdk/core/index.js +17 -1
  63. package/dist/sdk/core/interactiveProfile.d.ts +46 -0
  64. package/dist/sdk/core/interactiveProfile.js +143 -0
  65. package/dist/sdk/core/workspace.d.ts +13 -0
  66. package/dist/sdk/core/workspace.js +88 -0
  67. package/dist/sdk/core/workspaceBashTool.d.ts +4 -0
  68. package/dist/sdk/core/workspaceBashTool.js +36 -0
  69. package/dist/sdk/core/workspaceSandbox.d.ts +31 -0
  70. package/dist/sdk/core/workspaceSandbox.js +320 -0
  71. package/dist/sdk/core/workspaceWriteOperations.d.ts +22 -0
  72. package/dist/sdk/core/workspaceWriteOperations.js +206 -0
  73. package/dist/sdk/doctor/serverMethods.d.ts +3 -0
  74. package/dist/sdk/doctor/serverMethods.js +8 -0
  75. package/dist/sdk/doctor/types.d.ts +2 -2
  76. package/dist/sdk/harness.d.ts +22 -0
  77. package/dist/sdk/harness.js +84 -5
  78. package/dist/sdk/history/recall.js +8 -8
  79. package/dist/sdk/history/serverMethods.d.ts +3 -0
  80. package/dist/sdk/history/serverMethods.js +8 -0
  81. package/dist/sdk/hub/cli.d.ts +1 -1
  82. package/dist/sdk/hub/cli.js +1 -1
  83. package/dist/sdk/hub/schema.d.ts +1 -1
  84. package/dist/sdk/hub/serverMethods.d.ts +3 -0
  85. package/dist/sdk/hub/serverMethods.js +11 -0
  86. package/dist/sdk/index.d.ts +16 -1
  87. package/dist/sdk/index.js +10 -0
  88. package/dist/sdk/mcp/connections.js +14 -7
  89. package/dist/sdk/mcp/serverMethods.d.ts +3 -0
  90. package/dist/sdk/mcp/serverMethods.js +22 -0
  91. package/dist/sdk/pi/runtime.d.ts +2 -1
  92. package/dist/sdk/pi/runtime.js +2 -2
  93. package/dist/sdk/plugins/builtin.d.ts +1 -0
  94. package/dist/sdk/plugins/builtin.js +145 -33
  95. package/dist/sdk/plugins/builtinRegistry.d.ts +60 -0
  96. package/dist/sdk/plugins/builtinRegistry.js +71 -0
  97. package/dist/sdk/plugins/catalogue.js +2 -1
  98. package/dist/sdk/plugins/cli.d.ts +1 -1
  99. package/dist/sdk/plugins/cli.js +4 -3
  100. package/dist/sdk/plugins/commandPrefix.d.ts +8 -0
  101. package/dist/sdk/plugins/commandPrefix.js +21 -0
  102. package/dist/sdk/plugins/firstParty.d.ts +1 -1
  103. package/dist/sdk/plugins/firstParty.js +8 -2
  104. package/dist/sdk/plugins/inventory.js +4 -3
  105. package/dist/sdk/plugins/manifest.d.ts +2 -0
  106. package/dist/sdk/plugins/manifest.js +2 -1
  107. package/dist/sdk/plugins/surfaces.d.ts +5 -0
  108. package/dist/sdk/plugins/surfaces.js +11 -0
  109. package/dist/sdk/plugins/types.d.ts +15 -0
  110. package/dist/sdk/prompt/compile.d.ts +19 -2
  111. package/dist/sdk/prompt/compile.js +79 -26
  112. package/dist/sdk/prompt/standard.d.ts +17 -0
  113. package/dist/sdk/prompt/standard.js +21 -0
  114. package/dist/sdk/refine/cli.d.ts +1 -1
  115. package/dist/sdk/refine/cli.js +2 -2
  116. package/dist/sdk/refine/serverMethods.d.ts +3 -0
  117. package/dist/sdk/refine/serverMethods.js +20 -0
  118. package/dist/sdk/server/methods.js +27 -86
  119. package/dist/sdk/server/params.d.ts +4 -0
  120. package/dist/sdk/server/params.js +11 -0
  121. package/dist/sdk/server/remote.js +36 -11
  122. package/dist/sdk/sessions/compactionCheckpoint.d.ts +11 -0
  123. package/dist/sdk/sessions/compactionCheckpoint.js +79 -0
  124. package/dist/sdk/sessions/continuation.d.ts +3 -0
  125. package/dist/sdk/sessions/continuation.js +5 -1
  126. package/dist/sdk/sessions/contracts.d.ts +185 -0
  127. package/dist/sdk/sessions/contracts.js +101 -4
  128. package/dist/sdk/sessions/control.d.ts +72 -0
  129. package/dist/sdk/sessions/control.js +158 -0
  130. package/dist/sdk/sessions/initialBash.d.ts +7 -0
  131. package/dist/sdk/sessions/initialBash.js +7 -0
  132. package/dist/sdk/sessions/planMode.d.ts +18 -4
  133. package/dist/sdk/sessions/planMode.js +82 -16
  134. package/dist/sdk/sessions/reasoningSummaries.d.ts +2 -2
  135. package/dist/sdk/sessions/reasoningSummaries.js +3 -3
  136. package/dist/sdk/sessions/registry.d.ts +2 -1
  137. package/dist/sdk/sessions/registry.js +6 -3
  138. package/dist/sdk/sessions/runIndex.js +7 -7
  139. package/dist/sdk/sessions/runStore.d.ts +14 -0
  140. package/dist/sdk/sessions/runStore.js +38 -1
  141. package/dist/sdk/sessions/runner.d.ts +40 -2
  142. package/dist/sdk/sessions/runner.js +163 -15
  143. package/dist/sdk/sessions/sessions.d.ts +40 -8
  144. package/dist/sdk/sessions/sessions.js +279 -27
  145. package/dist/sdk/sessions/sideQuestionServerMethods.d.ts +3 -0
  146. package/dist/sdk/sessions/sideQuestionServerMethods.js +19 -0
  147. package/dist/sdk/sessions/sideQuestions.d.ts +66 -0
  148. package/dist/sdk/sessions/sideQuestions.js +290 -0
  149. package/dist/sdk/sessions/subagentServerMethods.d.ts +3 -0
  150. package/dist/sdk/sessions/subagentServerMethods.js +41 -0
  151. package/dist/sdk/sessions/subagentService.d.ts +22 -2
  152. package/dist/sdk/sessions/subagentService.js +93 -16
  153. package/dist/sdk/sessions/subagents.d.ts +9 -1
  154. package/dist/sdk/sessions/subagents.js +19 -0
  155. package/dist/sdk/sessions/workSlice.d.ts +103 -0
  156. package/dist/sdk/sessions/workSlice.js +297 -0
  157. package/dist/sdk/sessions/workspaceBoundary.d.ts +15 -0
  158. package/dist/sdk/sessions/workspaceBoundary.js +46 -0
  159. package/dist/sdk/terminal/browser.d.ts +62 -0
  160. package/dist/sdk/terminal/browser.js +41 -0
  161. package/dist/sdk/terminal/browserControl.d.ts +29 -0
  162. package/dist/sdk/terminal/browserControl.js +187 -0
  163. package/dist/sdk/terminal/commandHelp.d.ts +17 -0
  164. package/dist/sdk/terminal/commandHelp.js +20 -0
  165. package/dist/sdk/terminal/controller.d.ts +15 -1
  166. package/dist/sdk/terminal/controller.js +67 -8
  167. package/dist/sdk/terminal/opentui/subagentsWidget.d.ts +28 -0
  168. package/dist/sdk/terminal/opentui/subagentsWidget.js +52 -0
  169. package/dist/sdk/terminal/opentui/theme.d.ts +43 -0
  170. package/dist/sdk/terminal/opentui/theme.js +87 -0
  171. package/dist/sdk/terminal/piExtension.d.ts +6 -1
  172. package/dist/sdk/terminal/piExtension.js +169 -84
  173. package/dist/sdk/terminal/socket.d.ts +6 -3
  174. package/dist/sdk/terminal/socket.js +40 -9
  175. package/dist/sdk/terminal/standalone.d.ts +9 -0
  176. package/dist/sdk/terminal/standalone.js +118 -19
  177. package/dist/sdk/terminal/subagentMonitor.d.ts +47 -0
  178. package/dist/sdk/terminal/subagentMonitor.js +152 -0
  179. package/dist/sdk/terminal/terminals.d.ts +60 -7
  180. package/dist/sdk/terminal/terminals.js +598 -47
  181. package/dist/sdk/terminal/trajectory.js +2 -2
  182. package/dist/sdk/terminal/tui/annotateCommand.d.ts +3 -0
  183. package/dist/sdk/terminal/tui/annotateCommand.js +22 -0
  184. package/dist/sdk/terminal/tui/commandPages.d.ts +7 -1
  185. package/dist/sdk/terminal/tui/commandPages.js +57 -6
  186. package/dist/sdk/terminal/tui/commands.d.ts +10 -4
  187. package/dist/sdk/terminal/tui/commands.js +269 -65
  188. package/dist/sdk/terminal/tui/dialogGeometry.d.ts +2 -2
  189. package/dist/sdk/terminal/tui/dialogGeometry.js +4 -2
  190. package/dist/sdk/terminal/tui/dialogs.d.ts +6 -1
  191. package/dist/sdk/terminal/tui/dialogs.js +9 -2
  192. package/dist/sdk/terminal/tui/list.d.ts +14 -1
  193. package/dist/sdk/terminal/tui/list.js +96 -14
  194. package/dist/sdk/terminal/tui/modelPicker.js +1 -6
  195. package/dist/sdk/terminal/tui/page.d.ts +3 -0
  196. package/dist/sdk/terminal/tui/pageOverlay.js +27 -7
  197. package/dist/sdk/terminal/tui/pages/accountsPage.d.ts +26 -4
  198. package/dist/sdk/terminal/tui/pages/accountsPage.js +264 -36
  199. package/dist/sdk/terminal/tui/pages/agentsPage.d.ts +74 -0
  200. package/dist/sdk/terminal/tui/pages/agentsPage.js +190 -0
  201. package/dist/sdk/terminal/tui/pages/annotatePage.d.ts +94 -0
  202. package/dist/sdk/terminal/tui/pages/annotatePage.js +341 -0
  203. package/dist/sdk/terminal/tui/pages/browserPage.d.ts +87 -0
  204. package/dist/sdk/terminal/tui/pages/browserPage.js +738 -0
  205. package/dist/sdk/terminal/tui/pages/doctorPage.d.ts +2 -0
  206. package/dist/sdk/terminal/tui/pages/doctorPage.js +103 -15
  207. package/dist/sdk/terminal/tui/pages/notesPage.d.ts +75 -0
  208. package/dist/sdk/terminal/tui/pages/notesPage.js +201 -0
  209. package/dist/sdk/terminal/tui/pages/pluginsPage.d.ts +0 -5
  210. package/dist/sdk/terminal/tui/pages/pluginsPage.js +9 -3
  211. package/dist/sdk/terminal/tui/pages/settingsPage.d.ts +8 -0
  212. package/dist/sdk/terminal/tui/pages/settingsPage.js +16 -1
  213. package/dist/sdk/terminal/tui/pages/subagentsPage.d.ts +24 -0
  214. package/dist/sdk/terminal/tui/pages/subagentsPage.js +152 -0
  215. package/dist/sdk/terminal/tui/pages/trajectoryPage.d.ts +12 -5
  216. package/dist/sdk/terminal/tui/pages/trajectoryPage.js +156 -83
  217. package/dist/sdk/terminal/tui/pages/usagePage.d.ts +43 -0
  218. package/dist/sdk/terminal/tui/pages/usagePage.js +370 -0
  219. package/dist/sdk/terminal/tui/pages/worktreesPage.d.ts +72 -0
  220. package/dist/sdk/terminal/tui/pages/worktreesPage.js +203 -0
  221. package/dist/sdk/terminal/tui/piAdapter.d.ts +1 -2
  222. package/dist/sdk/terminal/tui/piAdapter.js +3 -5
  223. package/dist/sdk/terminal/tui/planMode.d.ts +1 -1
  224. package/dist/sdk/terminal/tui/planMode.js +1 -1
  225. package/dist/sdk/terminal/tui/pluginCommands.d.ts +16 -0
  226. package/dist/sdk/terminal/tui/pluginCommands.js +31 -0
  227. package/dist/sdk/terminal/tui/questions.d.ts +2 -0
  228. package/dist/sdk/terminal/tui/runtime.js +27 -5
  229. package/dist/sdk/terminal/tui/sessionPicker.d.ts +40 -0
  230. package/dist/sdk/terminal/tui/sessionPicker.js +141 -0
  231. package/dist/sdk/terminal/tui/settingsModel.d.ts +2 -0
  232. package/dist/sdk/terminal/tui/settingsModel.js +32 -2
  233. package/dist/sdk/terminal/tui/sideQuestion.d.ts +26 -26
  234. package/dist/sdk/terminal/tui/sideQuestion.js +131 -98
  235. package/dist/sdk/terminal/tui/ui.d.ts +2 -0
  236. package/dist/sdk/terminal/tui/ui.js +1 -0
  237. package/dist/sdk/tools/askUser.js +26 -8
  238. package/dist/sdk/tools/checklist.js +3 -2
  239. package/dist/sdk/tools/checkpoint.d.ts +27 -0
  240. package/dist/sdk/tools/checkpoint.js +154 -0
  241. package/dist/sdk/tools/getCapabilities.d.ts +31 -0
  242. package/dist/sdk/tools/getCapabilities.js +34 -0
  243. package/dist/sdk/tools/harnessTools.d.ts +2 -0
  244. package/dist/sdk/tools/harnessTools.js +1 -1
  245. package/dist/sdk/tools/spawnSubagent.js +14 -4
  246. package/dist/sdk/usage/analytics.d.ts +20 -0
  247. package/dist/sdk/usage/analytics.js +151 -0
  248. package/dist/sdk/usage/ledger.d.ts +38 -0
  249. package/dist/sdk/usage/ledger.js +142 -0
  250. package/dist/sdk/usage/limits/adapters.d.ts +11 -0
  251. package/dist/sdk/usage/limits/adapters.js +349 -0
  252. package/dist/sdk/usage/limits/capture.d.ts +8 -0
  253. package/dist/sdk/usage/limits/capture.js +47 -0
  254. package/dist/sdk/usage/limits/http.d.ts +12 -0
  255. package/dist/sdk/usage/limits/http.js +51 -0
  256. package/dist/sdk/usage/limits/normalize.d.ts +15 -0
  257. package/dist/sdk/usage/limits/normalize.js +211 -0
  258. package/dist/sdk/usage/limits/service.d.ts +30 -0
  259. package/dist/sdk/usage/limits/service.js +176 -0
  260. package/dist/sdk/usage/limits/types.d.ts +65 -0
  261. package/dist/sdk/usage/limits/types.js +1 -0
  262. package/dist/sdk/usage/serverMethods.d.ts +3 -0
  263. package/dist/sdk/usage/serverMethods.js +22 -0
  264. package/dist/sdk/usage/service.d.ts +32 -0
  265. package/dist/sdk/usage/service.js +66 -0
  266. package/dist/sdk/usage/settings.d.ts +7 -0
  267. package/dist/sdk/usage/settings.js +28 -0
  268. package/dist/sdk/usage/types.d.ts +60 -0
  269. package/dist/sdk/usage/types.js +4 -0
  270. package/dist/sdk/web/part.d.ts +10 -0
  271. package/dist/sdk/web/part.js +6 -0
  272. package/dist/sdk/web/serverMethods.d.ts +3 -0
  273. package/dist/sdk/web/serverMethods.js +7 -0
  274. package/dist/sdk/wiki/serverMethods.d.ts +3 -0
  275. package/dist/sdk/wiki/serverMethods.js +26 -0
  276. package/dist/sdk/worktrees/cli.d.ts +11 -0
  277. package/dist/sdk/worktrees/cli.js +146 -0
  278. package/dist/sdk/worktrees/copy.d.ts +9 -0
  279. package/dist/sdk/worktrees/copy.js +111 -0
  280. package/dist/sdk/worktrees/git.d.ts +16 -0
  281. package/dist/sdk/worktrees/git.js +81 -0
  282. package/dist/sdk/worktrees/serverMethods.d.ts +3 -0
  283. package/dist/sdk/worktrees/serverMethods.js +30 -0
  284. package/dist/sdk/worktrees/service.d.ts +73 -0
  285. package/dist/sdk/worktrees/service.js +483 -0
  286. package/dist/sdk/worktrees/setup.d.ts +23 -0
  287. package/dist/sdk/worktrees/setup.js +65 -0
  288. package/dist/sdk/worktrees/types.d.ts +91 -0
  289. package/dist/sdk/worktrees/types.js +1 -0
  290. package/dist/sdk/world/cli.d.ts +2 -2
  291. package/dist/sdk/world/cli.js +6 -6
  292. package/dist/sdk/world/world.d.ts +3 -1
  293. package/dist/sdk/world/world.js +8 -9
  294. package/dist/terminal-opentui.js +16456 -5966
  295. package/dist/terminal-pi.js +1579 -573
  296. package/docs/README.md +22 -2
  297. package/docs/architecture/00-overview.md +28 -16
  298. package/docs/architecture/02-sessions-and-runs.md +184 -25
  299. package/docs/architecture/03-variants.md +10 -2
  300. package/docs/architecture/04-prompt.md +65 -7
  301. package/docs/architecture/06-plugins-and-capabilities.md +57 -4
  302. package/docs/architecture/07-tools-and-gate.md +112 -17
  303. package/docs/architecture/08-memory-and-history.md +4 -2
  304. package/docs/architecture/09-accounts-and-connectors.md +39 -1
  305. package/docs/architecture/10-server-protocol.md +168 -4
  306. package/docs/architecture/11-terminal.md +251 -8
  307. package/docs/architecture/12-storage.md +45 -3
  308. package/docs/architecture/13-world-tools.md +49 -6
  309. package/docs/architecture/14-surfaces-and-parity.md +136 -0
  310. package/docs/architecture/README.md +17 -4
  311. package/docs/architecture/proposals/PROP-0006-run-checklist.md +4 -2
  312. package/docs/architecture/proposals/PROP-0013-resource-browser.md +58 -0
  313. package/docs/architecture/proposals/PROP-0014-fullscreen-browser-tabs.md +58 -0
  314. package/docs/architecture/proposals/PROP-0015-one-capability-surface.md +88 -0
  315. package/docs/architecture/proposals/PROP-0016-managed-worktrees.md +156 -0
  316. package/docs/architecture/proposals/PROP-0017-page-annotations.md +131 -0
  317. package/docs/architecture/proposals/PROP-0018-annotation-queue-and-app-targets.md +179 -0
  318. package/docs/architecture/proposals/PROP-0019-contextual-side-questions.md +122 -0
  319. package/docs/architecture/proposals/PROP-0020-builtin-feature-plugins.md +105 -0
  320. package/docs/architecture/proposals/PROP-0021-command-namespaces-and-duplicates.md +99 -0
  321. package/docs/architecture/proposals/PROP-0022-plan-only-questions.md +102 -0
  322. package/docs/architecture/proposals/PROP-0023-xagent-cli-name.md +61 -0
  323. package/docs/architecture/proposals/PROP-0024-question-sketch-previews.md +130 -0
  324. package/docs/architecture/proposals/PROP-0025-contextual-question-wireframes.md +49 -0
  325. package/docs/architecture/proposals/PROP-0026-connected-sketch-display.md +94 -0
  326. package/docs/architecture/proposals/PROP-0027-xingular-agent-product-identity.md +60 -0
  327. package/docs/architecture/proposals/PROP-0028-normalize-question-wireframes.md +55 -0
  328. package/docs/architecture/proposals/PROP-0029-agent-profiles.md +124 -0
  329. package/docs/architecture/proposals/PROP-0030-subagent-title.md +86 -0
  330. package/docs/architecture/proposals/PROP-0031-plugin-command-owner-namespaces.md +54 -0
  331. package/docs/architecture/proposals/PROP-0032-planning-quality.md +196 -0
  332. package/docs/architecture/proposals/PROP-0033-compaction-plan-checkpoints.md +75 -0
  333. package/docs/architecture/proposals/PROP-0034-conversation-fast-mode.md +120 -0
  334. package/docs/architecture/proposals/PROP-0035-retain-subagent-status.md +47 -0
  335. package/docs/architecture/proposals/PROP-0036-first-class-harness-commands.md +59 -0
  336. package/docs/architecture/proposals/PROP-0037-workspace-write-boundary.md +63 -0
  337. package/docs/architecture/proposals/PROP-0038-annotate-open-shadow-roots.md +73 -0
  338. package/docs/architecture/proposals/PROP-0039-workspace-boundary-hardening.md +139 -0
  339. package/docs/architecture/proposals/PROP-0040-one-launch-path.md +87 -0
  340. package/docs/architecture/proposals/PROP-0041-bounded-work-slices.md +247 -0
  341. package/docs/architecture/proposals/PROP-0042-usage-and-provider-limits.md +127 -0
  342. package/docs/architecture/proposals/PROP-0043-read-only-plan-bash.md +74 -0
  343. package/docs/architecture/proposals/README.md +39 -0
  344. package/docs/decisions/ADR-0001-owned-terminal.md +1 -1
  345. package/docs/decisions/ADR-0009-xharness-command.md +6 -5
  346. package/docs/decisions/ADR-0014-public-release-baseline.md +3 -2
  347. package/docs/decisions/ADR-0015-resource-browser.md +28 -0
  348. package/docs/decisions/ADR-0016-fullscreen-browser-tabs.md +31 -0
  349. package/docs/decisions/ADR-0017-one-capability-surface.md +34 -0
  350. package/docs/decisions/ADR-0018-builtin-feature-plugins.md +35 -0
  351. package/docs/decisions/ADR-0019-managed-git-worktrees.md +31 -0
  352. package/docs/decisions/ADR-0020-page-annotations.md +37 -0
  353. package/docs/decisions/ADR-0021-required-command-namespaces.md +40 -0
  354. package/docs/decisions/ADR-0022-plan-only-question-tools.md +34 -0
  355. package/docs/decisions/ADR-0023-contextual-side-question-runs.md +35 -0
  356. package/docs/decisions/ADR-0024-xagent-command.md +40 -0
  357. package/docs/decisions/ADR-0025-question-choice-sketches.md +49 -0
  358. package/docs/decisions/ADR-0026-contextual-question-wireframes.md +58 -0
  359. package/docs/decisions/ADR-0027-connected-sketch-display.md +60 -0
  360. package/docs/decisions/ADR-0028-xingular-agent-product-identity.md +56 -0
  361. package/docs/decisions/ADR-0029-normalized-question-wireframe-borders.md +33 -0
  362. package/docs/decisions/ADR-0030-agent-profiles.md +43 -0
  363. package/docs/decisions/ADR-0031-plugin-command-owner-namespaces.md +37 -0
  364. package/docs/decisions/ADR-0032-compaction-run-checkpoints.md +33 -0
  365. package/docs/decisions/ADR-0033-plan-mode-research-delegation.md +47 -0
  366. package/docs/decisions/ADR-0034-first-class-harness-terminal-commands.md +25 -0
  367. package/docs/decisions/ADR-0035-one-launch-path.md +40 -0
  368. package/docs/decisions/ADR-0036-workspace-write-operation-boundary.md +54 -0
  369. package/docs/decisions/ADR-0037-usage-ledger-and-provider-limits.md +44 -0
  370. package/docs/decisions/ADR-0038-plan-bash-os-read-only.md +29 -0
  371. package/docs/doctor.md +10 -4
  372. package/docs/experiments/EXP-0003-performance-audit/data/session-runtime-replication.json +420 -0
  373. package/docs/experiments/EXP-0003-performance-audit/data/session-runtime.json +420 -0
  374. package/docs/experiments/EXP-0003-performance-audit/data/terminal-compact.json +376 -0
  375. package/docs/experiments/EXP-0003-performance-audit/data/terminal-history.json +422 -0
  376. package/docs/experiments/EXP-0003-performance-audit/data/terminal-question.json +95 -0
  377. package/docs/experiments/EXP-0003-performance-audit/data/terminal-wide.json +1036 -0
  378. package/docs/experiments/EXP-0003-performance-audit/plan.md +109 -0
  379. package/docs/experiments/EXP-0003-performance-audit/results.md +151 -0
  380. package/docs/features/README.md +26 -0
  381. package/docs/features/interfaces/README.md +1 -1
  382. package/docs/future-work.md +48 -0
  383. package/docs/handoff-2026-10-25-app-parity-and-solo-tabs.md +202 -0
  384. package/docs/parity.md +134 -0
  385. package/docs/releases/1.0.0.md +2 -2
  386. package/docs/releases/1.1.1.md +28 -0
  387. package/docs/releases/1.2.0.md +49 -0
  388. package/docs/releases/4.0.0.md +47 -0
  389. package/docs/releases/README.md +37 -27
  390. package/docs/study/00-index.md +8 -0
  391. package/docs/study/STUDY-0003-long-horizon-runs.md +210 -0
  392. package/docs/study/autonomous-engineering-sources.md +277 -0
  393. package/docs/study/autonomy-sources.md +147 -0
  394. package/docs/study/proactive-autonomy.md +408 -0
  395. package/docs/study/tui-resource-browser.md +78 -0
  396. package/docs/terminal-tui.md +386 -113
  397. package/package.json +6 -5
@@ -1,6 +1,11 @@
1
- import type { AgentSession, ExtensionAPI } from '@earendil-works/pi-coding-agent';
1
+ import { type AgentSession, type ExtensionAPI } from '@earendil-works/pi-coding-agent';
2
2
  export interface TerminalPlanRunState {
3
3
  active: boolean;
4
4
  allowedTools: ReadonlySet<string>;
5
+ connectors?: string[];
6
+ subagentProfiles?: {
7
+ normal: string;
8
+ plan: string;
9
+ };
5
10
  }
6
11
  export default function (pi: ExtensionAPI, planRun?: TerminalPlanRunState, getSession?: () => AgentSession): Promise<void>;
@@ -1,14 +1,29 @@
1
+ // Loaded by the `pi` command in a terminal session (`--extension`). It reports what the native
2
+ // session does to the harness, asks before each tool call, takes instructions the harness
3
+ // queues for it, and gives the session the harness's own tools. It runs in the `pi` process,
4
+ // so everything it knows about the harness comes through the session socket.
5
+ import { createBashTool, createEditTool, createWriteTool } from '@earendil-works/pi-coding-agent';
1
6
  import { Type } from 'typebox';
2
7
  import { connectionFromEnv, socketCall, terminalCall } from "./client.js";
3
- import { runAccountsMenu } from "./accountsMenu.js";
4
- import { runPluginsMenu } from "./pluginMenu.js";
5
- import { runHubMenu } from "./hubMenu.js";
6
- import { claimTerminalControl, controlCall } from "./control.js";
7
- import { SUBAGENT_OWNER, SUBAGENT_RECEIPT, SUBAGENT_RESULTS, subagentHistory } from "../sessions/subagents.js";
8
+ import { controlCall } from "./control.js";
9
+ import { SPAWN_SUBAGENT, SUBAGENT_OWNER, SUBAGENT_RECEIPT, SUBAGENT_RESULTS, subagentHistory } from "../sessions/subagents.js";
10
+ import { PLAN_RESEARCH_TOOLS } from "../agents/planResearch.js";
8
11
  import { askUserTool } from "../tools/askUser.js";
12
+ import { checklistTool } from "../tools/checklist.js";
13
+ import { checkpointTool } from "../tools/checkpoint.js";
14
+ import { createSliceState, parseWorkSlice, workSliceGuard } from "../sessions/workSlice.js";
15
+ import { DEFAULT_COMMAND_TIMEOUT_SECONDS, withCommandBudget } from "../core/commandBudget.js";
9
16
  import { sessionNameTool } from "../tools/sessionName.js";
17
+ import { CAPABILITY_DISCOVERY, describeCapabilities } from "../tools/getCapabilities.js";
10
18
  import { PLAN_INSTRUCTIONS, planRefusal } from "../sessions/planMode.js";
19
+ import { compactionCheckpoint } from "../sessions/compactionCheckpoint.js";
11
20
  import { pruneToolImages } from "../world/images.js";
21
+ import { parseInitialBashCommand } from "../sessions/initialBash.js";
22
+ import { inspectWorkspaceWrite } from "../sessions/workspaceBoundary.js";
23
+ import { withWorkspaceWriteScope } from "../core/workspaceBashTool.js";
24
+ import { createWorkspaceBashOperations, runWithWorkspaceWriteScope } from "../core/workspaceSandbox.js";
25
+ import { withWorkspaceWriteAuthorization, workspaceEditOperations, workspaceWriteOperations } from "../core/workspaceWriteOperations.js";
26
+ import { BUDGET_GUARD, OUTPUT_TRIM, budgetGuard, outputTrim } from "../plugins/firstParty.js";
12
27
  /** How much text a tool handed back to the model. */
13
28
  function resultSize(result) {
14
29
  const content = result?.content;
@@ -28,8 +43,6 @@ async function withParentCancellation(signal, work) {
28
43
  signal?.removeEventListener('abort', stop);
29
44
  }
30
45
  }
31
- /** /hub, /plugins and /accounts are pages: the owned terminal fills the screen, and Pi's own dialogs ignore the field. */
32
- const PAGE = { page: true };
33
46
  export default async function (pi, planRun, getSession) {
34
47
  await terminalCall('startup', { phase: 'Pi extension loaded' });
35
48
  let context = null;
@@ -45,50 +58,13 @@ export default async function (pi, planRun, getSession) {
45
58
  let requestStartedAt = null;
46
59
  const toolStartedAt = new Map();
47
60
  const fileTools = new Map();
61
+ const writeAuthorizations = new Map();
62
+ let workspaceRoot = null;
48
63
  // The harness's own tools for this session (connected apps and what the application offers).
49
64
  let stage = 'Tool discovery';
50
65
  try {
51
66
  await terminalCall('startup', { phase: stage });
52
67
  const connection = connectionFromEnv();
53
- // Claimed here, before the model has run anything, and kept in this process's memory: a
54
- // command the model runs can read the session token from its environment, never this key.
55
- const control = await claimTerminalControl(connection).catch(() => null);
56
- pi.registerCommand('hub', {
57
- description: 'Browse and install reviewed skills, plugins, and reusable documents',
58
- handler: async (_args, ctx) => {
59
- if (!control)
60
- return ctx.ui.notify('This session cannot install hub items.', 'warning');
61
- await runHubMenu({ select: (title, options) => ctx.ui.select(title, options, PAGE), confirm: (title, message) => ctx.ui.confirm(title, message), notify: (message, level) => ctx.ui.notify(message, level) }, (method, params) => socketCall(connection, method, params ?? {}, 300_000, control));
62
- }
63
- });
64
- pi.registerCommand('plugins', {
65
- description: 'See what this session loads, and switch a plugin on or off or trust its code',
66
- handler: async (_args, ctx) => {
67
- if (!control)
68
- return ctx.ui.notify('This session cannot change plugins.', 'warning');
69
- const { projectId } = await socketCall(connection, 'plugins.session', {}, 15_000, control).catch(() => ({ projectId: null }));
70
- await runPluginsMenu({ select: (title, options) => ctx.ui.select(title, options, PAGE), confirm: (title, message) => ctx.ui.confirm(title, message), notify: (message, level) => ctx.ui.notify(message, level) }, (method, params) => socketCall(connection, method, params ?? {}, 60_000, control), projectId);
71
- }
72
- });
73
- pi.registerCommand('accounts', {
74
- description: 'Add, sign in to, check, switch, or remove the Pi accounts this harness uses',
75
- handler: async (_args, ctx) => {
76
- if (!control)
77
- return ctx.ui.notify('This session cannot manage accounts.', 'warning');
78
- const session = await socketCall(connection, 'accounts.session', {}, 15_000, control).catch(() => null);
79
- if (!session)
80
- return ctx.ui.notify('This session has no account to manage.', 'warning');
81
- const outcome = await runAccountsMenu({
82
- select: (title, options) => ctx.ui.select(title, options, PAGE),
83
- confirm: (title, message) => ctx.ui.confirm(title, message),
84
- input: (title, placeholder) => ctx.ui.input(title, placeholder),
85
- notify: (message, level) => ctx.ui.notify(message, level)
86
- }, (method, params) => socketCall(connection, method, params ?? {}, 120_000, control), session);
87
- // The session restarts on the account it chose: Pi ends here, and the launcher starts it again.
88
- if (outcome === 'relaunch')
89
- ctx.shutdown();
90
- }
91
- });
92
68
  const { tools } = await socketCall(connection, 'tools.list', {}, 15_000);
93
69
  stage = 'Tool registration';
94
70
  for (const tool of tools) {
@@ -104,8 +80,19 @@ export default async function (pi, planRun, getSession) {
104
80
  }
105
81
  });
106
82
  }
83
+ // The harness's own extensions a person switched on, as a structured session loads them inline.
84
+ const own = await socketCall(connection, 'firstParty.list', {}, 15_000)
85
+ .then((reply) => reply?.plugins ?? [], () => []);
86
+ for (const plugin of own) {
87
+ if (plugin.id === OUTPUT_TRIM)
88
+ outputTrim(plugin.settings)(pi);
89
+ if (plugin.id === BUDGET_GUARD)
90
+ budgetGuard(plugin.settings)(pi);
91
+ }
107
92
  const native = await controlCall('terminal.subagents.tools');
108
93
  nativeSubagents = native.tools.length > 0;
94
+ if (planRun && native.profiles)
95
+ planRun.subagentProfiles = native.profiles;
109
96
  for (const tool of native.tools)
110
97
  pi.registerTool({
111
98
  name: tool.name, label: tool.label, description: tool.description, parameters: Type.Unsafe(tool.parameters),
@@ -119,6 +106,46 @@ export default async function (pi, planRun, getSession) {
119
106
  await terminalCall('startupFailed', { reason: `${stage} failed: ${error instanceof Error ? error.message : 'Unavailable'}` }).catch(() => { });
120
107
  throw error;
121
108
  }
109
+ pi.registerTool({
110
+ ...CAPABILITY_DISCOVERY,
111
+ parameters: Type.Unsafe(CAPABILITY_DISCOVERY.parameters),
112
+ async execute(_id, input) {
113
+ const active = pi.getActiveTools();
114
+ const tools = pi.getAllTools();
115
+ const selected = planRun?.active ? [...planRun.allowedTools] : active;
116
+ const name = input && typeof input === 'object' && typeof input.tool === 'string'
117
+ ? input.tool
118
+ : undefined;
119
+ const text = JSON.stringify(describeCapabilities({
120
+ capabilities: { version: 1, tools: selected, connectors: planRun?.connectors ?? [], contributions: [] },
121
+ active,
122
+ registered: tools
123
+ }, name));
124
+ return { content: [{ type: 'text', text }], details: undefined };
125
+ }
126
+ });
127
+ const workspaceBash = createWorkspaceBashOperations(() => workspaceRoot, {
128
+ bypassWorkspaceBoundary: async (request) => {
129
+ const result = await controlCall('terminal.workspaceBoundaryBypass', { tool: 'bash', args: { command: request.command, cwd: request.cwd, workspaceRoot: request.workspaceRoot } });
130
+ return result.bypass;
131
+ },
132
+ requestWorkspaceWriteScope: async (request) => {
133
+ const result = await controlCall('terminal.workspaceEscape', { tool: 'bash', args: { command: request.command, workspaceWriteScope: 'outside' }, reason: request.reason });
134
+ return result.approved;
135
+ },
136
+ workspaceWriteScope: () => planRun?.active ? 'read-only' : undefined
137
+ });
138
+ pi.registerTool(withWorkspaceWriteScope(createBashTool(process.cwd(), { operations: workspaceBash })));
139
+ pi.registerTool(withWorkspaceWriteAuthorization(createWriteTool(process.cwd(), { operations: workspaceWriteOperations }), writeAuthorizations));
140
+ pi.registerTool(withWorkspaceWriteAuthorization(createEditTool(process.cwd(), { operations: workspaceEditOperations }), writeAuthorizations));
141
+ compactionCheckpoint({ planInstructions: () => planRun?.active ? PLAN_INSTRUCTIONS : null })(pi);
142
+ // A terminal reply is bounded like any other (PROP-0041): a checkpoint at the boundary, then the
143
+ // person decides whether to continue.
144
+ const slicePolicy = parseWorkSlice(undefined);
145
+ const sliceState = createSliceState(slicePolicy);
146
+ // `XINGULAR_WORK_SLICE=off` at launch leaves terminal replies unbounded, as before PROP-0041.
147
+ if (slicePolicy.enabled)
148
+ workSliceGuard({ policy: slicePolicy, state: sliceState })(pi);
122
149
  // Only the latest screenshots stay in what the model sees (world/images.ts).
123
150
  pi.on('context', (event) => {
124
151
  const messages = pruneToolImages(event.messages);
@@ -162,9 +189,14 @@ export default async function (pi, planRun, getSession) {
162
189
  costReported = false;
163
190
  // A run a plugin starts with a custom message has no user prompt to record; it still begins a turn.
164
191
  const begun = await terminalCall('begin', { text: event.messageType === 'user' ? event.prompt : '' });
192
+ workspaceRoot = typeof begun.workspaceRoot === 'string' ? begun.workspaceRoot : null;
193
+ if (!workspaceRoot)
194
+ throw new Error('The active project workspace could not be resolved; the turn was not started.');
165
195
  if (nativeSubagents) {
166
196
  pi.appendEntry(SUBAGENT_OWNER, { runId: begun.runId });
167
- await controlCall('terminal.subagents.register', { tools: pi.getActiveTools(), model: ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : null, effort: pi.getThinkingLevel(), history: subagentHistory(ctx.sessionManager.getBranch()) });
197
+ const snapshot = await controlCall('terminal.subagents.register', { tools: pi.getActiveTools(), model: ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : null, effort: pi.getThinkingLevel(), plan: planRun?.active === true, history: subagentHistory(ctx.sessionManager.getBranch()) });
198
+ if (planRun && Array.isArray(snapshot.connectors))
199
+ planRun.connectors = snapshot.connectors;
168
200
  subagentParent = true;
169
201
  }
170
202
  if (planRun && event.messageType === 'user') {
@@ -172,24 +204,31 @@ export default async function (pi, planRun, getSession) {
172
204
  event.systemPromptOptions.sections.plan = PLAN_INSTRUCTIONS;
173
205
  else
174
206
  delete event.systemPromptOptions.sections.plan;
207
+ if (planRun.subagentProfiles)
208
+ event.systemPromptOptions.sections.subagents = planRun.active ? planRun.subagentProfiles.plan : planRun.subagentProfiles.normal;
175
209
  }
176
210
  const bang = event.messageType === 'user' ? /^\s*!\s*([\s\S]+)$/u.exec(event.prompt) : null;
177
211
  if (!bang)
178
212
  return;
179
- const command = bang[1];
213
+ const initial = parseInitialBashCommand(bang[1]);
214
+ const command = initial.command;
215
+ const args = { command, ...(initial.workspaceWriteScope === 'outside' ? { workspaceWriteScope: 'outside' } : {}) };
180
216
  const id = `initial-bash:${begun.runId}`;
181
217
  const started = Date.now();
182
- await terminalCall('tool', { phase: 'start', id, name: 'bash', summary: command.slice(0, 200), input: JSON.stringify({ command }) });
218
+ await terminalCall('tool', { phase: 'start', id, name: 'bash', summary: command.slice(0, 200), input: JSON.stringify(args) });
183
219
  let refusal;
184
220
  let result;
185
- if (planRun?.active) {
221
+ if (!command) {
222
+ refusal = 'Enter a Bash command after !outside.';
223
+ }
224
+ else if (planRun?.active) {
186
225
  refusal = 'Bash commands are disabled while a plan request is running.';
187
226
  }
188
227
  else {
189
228
  try {
190
- const policy = await terminalCall('policy', { tool: 'bash', args: { command } });
229
+ const policy = await terminalCall('policy', { tool: 'bash', args });
191
230
  if (policy.requiresApproval) {
192
- const approval = await terminalCall('approval', { requestId: id, tool: 'bash', args: { command } });
231
+ const approval = await terminalCall('approval', { requestId: id, tool: 'bash', args });
193
232
  if (approval.decision !== 'approved')
194
233
  refusal = 'The user denied this Bash command.';
195
234
  }
@@ -201,7 +240,7 @@ export default async function (pi, planRun, getSession) {
201
240
  if (!session)
202
241
  refusal = 'The native session is unavailable.';
203
242
  else
204
- result = await session.executeBash(command, undefined, { id });
243
+ result = await runWithWorkspaceWriteScope(initial.workspaceWriteScope, () => withCommandBudget(() => session.executeBash(command, undefined, { id, operations: workspaceBash }), () => session.abortBash(), DEFAULT_COMMAND_TIMEOUT_SECONDS));
205
244
  }
206
245
  }
207
246
  catch (error) {
@@ -282,21 +321,61 @@ export default async function (pi, planRun, getSession) {
282
321
  pi.on('turn_start', async () => {
283
322
  requestStartedAt = Date.now();
284
323
  });
285
- pi.on('tool_call', async (event) => {
324
+ pi.on('tool_call', async (event, ctx) => {
286
325
  if (planRun?.active && !planRun.allowedTools.has(event.toolName)) {
287
- const reason = planRefusal(event.toolName, new Set()) ?? `Plan mode is research-only: ${event.toolName} is not available in this session.`;
326
+ const reason = planRefusal(event.toolName, new Set(), event.input) ?? `Plan mode is research-only: ${event.toolName} is not available in this session.`;
288
327
  return { block: true, reason };
289
328
  }
329
+ if (planRun?.active && event.toolName === SPAWN_SUBAGENT) {
330
+ try {
331
+ const eligibility = await controlCall('terminal.subagents.planSafe', { input: event.input });
332
+ if (!eligibility.allowed)
333
+ return { block: true, reason: eligibility.reason ?? 'This profile is not eligible for plan-mode research.' };
334
+ }
335
+ catch (error) {
336
+ return { block: true, reason: `Plan-mode research eligibility could not be verified: ${error instanceof Error ? error.message : 'controller unavailable'}` };
337
+ }
338
+ }
339
+ else if (planRun?.active) {
340
+ const reason = planRefusal(event.toolName, new Set(PLAN_RESEARCH_TOOLS), event.input);
341
+ if (reason)
342
+ return { block: true, reason };
343
+ }
344
+ let authorization;
345
+ if ((event.toolName === 'edit' || event.toolName === 'write') && typeof event.input.path === 'string') {
346
+ try {
347
+ if (!workspaceRoot)
348
+ throw new Error('The active project workspace is unavailable.');
349
+ const inspection = inspectWorkspaceWrite(workspaceRoot, ctx.cwd, event.toolName, event.input);
350
+ Object.assign(event.input, inspection.input);
351
+ if (!inspection.path)
352
+ throw new Error('The native file-write target could not be verified.');
353
+ authorization = {
354
+ workspaceRoot,
355
+ cwd: ctx.cwd,
356
+ path: inspection.path,
357
+ outside: inspection.outside,
358
+ authorized: false
359
+ };
360
+ }
361
+ catch (error) {
362
+ return { block: true, reason: error instanceof Error ? error.message : 'The active project workspace could not be verified.' };
363
+ }
364
+ }
290
365
  try {
291
366
  const policy = await controlCall('terminal.policy', { tool: event.toolName, args: event.input });
292
367
  if (policy.requiresApproval) {
293
368
  const approval = await controlCall('terminal.approval', { requestId: event.toolCallId, tool: event.toolName, args: event.input });
294
- if (approval.decision === 'approved')
295
- return;
296
- return { block: true, reason: 'The user denied this action. Ask how they would like to proceed.' };
369
+ if (approval.decision !== 'approved')
370
+ return { block: true, reason: 'The user denied this action. Ask how they would like to proceed.' };
297
371
  }
298
- if (!policy.allowed)
372
+ else if (!policy.allowed) {
299
373
  return { block: true, reason: policy.reason ?? 'Outside what this session may do.' };
374
+ }
375
+ if (authorization) {
376
+ authorization.authorized = true;
377
+ writeAuthorizations.set(event.toolCallId, authorization);
378
+ }
300
379
  }
301
380
  catch (error) {
302
381
  const detail = error instanceof Error ? error.message : String(error);
@@ -329,7 +408,10 @@ export default async function (pi, planRun, getSession) {
329
408
  }
330
409
  }
331
410
  subagentParent = false;
332
- await terminalCall('settled', { error: failure, aborted: event.aborted });
411
+ // A reply that stopped at its work-slice boundary is not a completed task: the record says so,
412
+ // so a host can offer the continuation instead of treating it as done (PROP-0041).
413
+ const reason = sliceState.endedBy ? sliceState.endedBy === 'stuck' ? 'stalled' : 'slice_complete' : undefined;
414
+ await terminalCall('settled', { error: failure, aborted: event.aborted, ...(reason ? { reason } : {}) });
333
415
  });
334
416
  pi.on('user_bash', async () => {
335
417
  throw new Error('Use the ! command composer so this action can be checked.');
@@ -355,6 +437,32 @@ export default async function (pi, planRun, getSession) {
355
437
  return { content: [{ type: 'text', text: typeof result === 'string' ? result : result.text }], details: undefined };
356
438
  }
357
439
  });
440
+ // The checklist is recorded on the harness run; the shared tool validates and the socket carries the event.
441
+ let recorded = Promise.resolve();
442
+ const checklist = checklistTool({ emit: (_type, payload) => { recorded = terminalCall('checklist', payload); } });
443
+ pi.registerTool({
444
+ name: checklist.name, label: checklist.label, description: checklist.description,
445
+ parameters: Type.Unsafe(checklist.parameters(Type)),
446
+ async execute(_id, input) {
447
+ recorded = Promise.resolve();
448
+ const result = await checklist.execute(input);
449
+ await recorded;
450
+ return { content: [{ type: 'text', text: typeof result === 'string' ? result : result.text }], details: undefined };
451
+ }
452
+ });
453
+ // A bounded reply records its handoff here; the terminal reply is its own slice.
454
+ let checkpointRecorded = Promise.resolve();
455
+ const checkpoint = checkpointTool({ emit: (_type, payload) => { checkpointRecorded = terminalCall('checkpoint', payload); } }, 1);
456
+ pi.registerTool({
457
+ name: checkpoint.name, label: checkpoint.label, description: checkpoint.description,
458
+ parameters: Type.Unsafe(checkpoint.parameters(Type)),
459
+ async execute(_id, input) {
460
+ checkpointRecorded = Promise.resolve();
461
+ const result = await checkpoint.execute(input);
462
+ await checkpointRecorded;
463
+ return { content: [{ type: 'text', text: typeof result === 'string' ? result : result.text }], details: undefined };
464
+ }
465
+ });
358
466
  const ask = askUserTool({ requestAnswers: questions => terminalCall('questions', { questions }, 1_230_000) });
359
467
  pi.registerTool({
360
468
  name: ask.name, label: ask.label, description: ask.description,
@@ -364,29 +472,6 @@ export default async function (pi, planRun, getSession) {
364
472
  return { content: [{ type: 'text', text: typeof result === 'string' ? result : result.text }], details: undefined };
365
473
  }
366
474
  });
367
- pi.registerTool({
368
- name: 'ask_user_question',
369
- label: 'Ask a question',
370
- description: 'Ask the user a question and wait for their answer. Every question must include at least one choice and at least one choice must be recommended. Recommendations are visual only and never preselect answers; submitted choices keep their labels. Mark at most one choice recommended for single-choice questions or several when multiSelect is true.',
371
- parameters: Type.Object({
372
- question: Type.String(),
373
- options: Type.Array(Type.Object({ label: Type.String(), description: Type.Optional(Type.String()), recommended: Type.Optional(Type.Boolean()) }), {
374
- minItems: 1,
375
- contains: Type.Object({ recommended: Type.Literal(true) })
376
- }),
377
- multiSelect: Type.Optional(Type.Boolean())
378
- }),
379
- async execute(id, input) {
380
- if (!Array.isArray(input.options) || input.options.length === 0)
381
- throw new Error('Every question needs at least one choice.');
382
- const recommendedCount = input.options.filter(option => option.recommended).length;
383
- if (recommendedCount === 0)
384
- throw new Error('Every question needs at least one recommended choice.');
385
- if (!input.multiSelect && recommendedCount > 1)
386
- throw new Error('A single-choice question may recommend at most one option.');
387
- const answer = await terminalCall('question', { requestId: id, ...input });
388
- return { content: [{ type: 'text', text: JSON.stringify(answer) }], details: undefined };
389
- }
390
- });
475
+ // One question tool, as in every structured session: the terminal has no ask_user_question alias (PROP-0039).
391
476
  await terminalCall('startup', { phase: 'Pi hooks registered' });
392
477
  }
@@ -28,9 +28,12 @@ export interface SocketHandlers {
28
28
  accounts?(grant: SocketGrant, call: string, params: Record<string, unknown>, control: boolean): Promise<unknown>;
29
29
  }
30
30
  /**
31
- * Windows has no socket files for Node's `net`: it listens on a named pipe, which lives in a
32
- * machine-wide namespace. A random name keeps another local process from guessing it and standing
33
- * in front of the session; the token still decides what a caller may do.
31
+ * Each Harness process listens on its own path. Windows has no socket files for Node's `net`: it
32
+ * listens on a named pipe in a machine-wide namespace, and a random name keeps another local process
33
+ * from guessing it. On Unix the file is named for this process, because several Harness processes
34
+ * may share one state folder: closing a server removes its socket file, so a shared name would let
35
+ * one process delete, and the next one take over, the path another's sessions still call. The token
36
+ * still decides what a caller may do.
34
37
  */
35
38
  export declare function socketPath(stateDir: string): string;
36
39
  export declare class SessionSocket {
@@ -5,8 +5,8 @@
5
5
  // reply leaves it open for the next request. Each terminal session holds a random token bound to
6
6
  // its caller, its connected-apps grant, and its controller; the token stops working when the
7
7
  // terminal ends.
8
- import { createHash, randomBytes } from 'node:crypto';
9
- import { chmodSync, mkdirSync, rmSync, statSync } from 'node:fs';
8
+ import { randomBytes } from 'node:crypto';
9
+ import { chmodSync, mkdirSync, readdirSync, rmSync, statSync } from 'node:fs';
10
10
  import { createServer } from 'node:net';
11
11
  import { tmpdir } from 'node:os';
12
12
  import { dirname, join } from 'node:path';
@@ -17,21 +17,50 @@ const MAX_REQUEST_BYTES = 1024 * 1024;
17
17
  /** macOS refuses a socket path longer than 103 bytes. */
18
18
  const MAX_SOCKET_PATH = 100;
19
19
  /**
20
- * Windows has no socket files for Node's `net`: it listens on a named pipe, which lives in a
21
- * machine-wide namespace. A random name keeps another local process from guessing it and standing
22
- * in front of the session; the token still decides what a caller may do.
20
+ * Each Harness process listens on its own path. Windows has no socket files for Node's `net`: it
21
+ * listens on a named pipe in a machine-wide namespace, and a random name keeps another local process
22
+ * from guessing it. On Unix the file is named for this process, because several Harness processes
23
+ * may share one state folder: closing a server removes its socket file, so a shared name would let
24
+ * one process delete, and the next one take over, the path another's sessions still call. The token
25
+ * still decides what a caller may do.
23
26
  */
24
27
  export function socketPath(stateDir) {
25
28
  if (process.platform === 'win32')
26
29
  return `\\\\.\\pipe\\xingular-harness-${randomBytes(12).toString('hex')}`;
27
- const path = join(stateDir, 'run', 'harness.sock');
30
+ const path = join(stateDir, 'run', `harness-${process.pid}-${randomBytes(8).toString('hex')}.sock`);
28
31
  if (Buffer.byteLength(path) <= MAX_SOCKET_PATH)
29
32
  return path;
30
- return join(tmpdir(), `xh-${createHash('sha256').update(path).digest('hex').slice(0, 16)}.sock`);
33
+ return join(tmpdir(), `xh-${process.pid}-${randomBytes(8).toString('hex')}.sock`);
34
+ }
35
+ const OWN_SOCKET = /^harness-(\d+)-[0-9a-f]+\.sock$/;
36
+ /** Removes socket files left in `folder` by Harness processes that are no longer running. */
37
+ function pruneDeadSockets(folder) {
38
+ let names;
39
+ try {
40
+ names = readdirSync(folder);
41
+ }
42
+ catch {
43
+ return;
44
+ }
45
+ for (const name of names) {
46
+ const pid = Number(OWN_SOCKET.exec(name)?.[1]);
47
+ if (!pid || pid === process.pid || isRunning(pid))
48
+ continue;
49
+ rmSync(join(folder, name), { force: true });
50
+ }
51
+ }
52
+ function isRunning(pid) {
53
+ try {
54
+ process.kill(pid, 0);
55
+ return true;
56
+ }
57
+ catch (error) {
58
+ return error.code === 'EPERM';
59
+ }
31
60
  }
32
61
  function alternateSocketPath(path) {
33
62
  const candidate = join(dirname(path), `harness-${process.pid}-${randomBytes(8).toString('hex')}.sock`);
34
- return Buffer.byteLength(candidate) <= MAX_SOCKET_PATH ? candidate : join(tmpdir(), `xh-${randomBytes(16).toString('hex')}.sock`);
63
+ return Buffer.byteLength(candidate) <= MAX_SOCKET_PATH ? candidate : join(tmpdir(), `xh-${process.pid}-${randomBytes(8).toString('hex')}.sock`);
35
64
  }
36
65
  /** A named pipe has no folder, file mode, or owner to check or clean up. */
37
66
  const isPipePath = (path) => path.startsWith('\\\\.\\pipe\\');
@@ -216,8 +245,10 @@ export class SessionSocket {
216
245
  }
217
246
  /** Binds the usual path, or an instance-specific path if another Harness owns it. */
218
247
  async bindAvailablePath() {
219
- if (!isPipePath(this.path))
248
+ if (!isPipePath(this.path)) {
220
249
  mkdirSync(dirname(this.path), { recursive: true, mode: 0o700 });
250
+ pruneDeadSockets(dirname(this.path));
251
+ }
221
252
  try {
222
253
  await this.bind(this.path);
223
254
  }
@@ -1,8 +1,14 @@
1
1
  import { spawn as nodeSpawn } from 'node:child_process';
2
+ import type { SecretStore } from "../accounts/secrets.js";
3
+ import { type SecretBackendDescriptor } from "../core/interactiveProfile.js";
2
4
  import { type Harness, type HarnessOptions } from "../harness.js";
3
5
  export interface StandalonePaths {
4
6
  dataRoot: string;
5
7
  stateDir: string;
8
+ /** `--worktree [name]`: start in a new managed worktree (true) or the named one. */
9
+ worktree?: string | true | null;
10
+ /** `--variant <scope>`: run on that scope's head, as a host's agent of that scope does. */
11
+ variant?: string | null;
6
12
  }
7
13
  export interface StandaloneDependencies {
8
14
  home?: string;
@@ -11,6 +17,9 @@ export interface StandaloneDependencies {
11
17
  stdoutIsTTY?: boolean;
12
18
  createSessionId?: () => string;
13
19
  createHarness?: (options: HarnessOptions) => Harness;
20
+ hostSecretStore?: (descriptor: Extract<SecretBackendDescriptor, {
21
+ kind: 'host';
22
+ }>) => SecretStore | Promise<SecretStore>;
14
23
  spawn?: typeof nodeSpawn;
15
24
  }
16
25
  export declare function standaloneArguments(argv: string[]): string[] | null;