@xingular/harness 0.1.7 → 1.0.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 (683) hide show
  1. package/CHANGELOG.md +97 -1
  2. package/LICENSE +202 -0
  3. package/NOTICE.md +22 -0
  4. package/README.md +187 -85
  5. package/THIRD_PARTY_LICENSES.md +942 -0
  6. package/dist/cli.js +13701 -13498
  7. package/dist/sdk/accounts/accounts.d.ts +165 -0
  8. package/dist/sdk/accounts/accounts.js +730 -0
  9. package/dist/sdk/accounts/choices.d.ts +23 -0
  10. package/dist/sdk/accounts/choices.js +62 -0
  11. package/dist/sdk/accounts/env.d.ts +78 -0
  12. package/dist/sdk/accounts/env.js +294 -0
  13. package/dist/sdk/accounts/secrets.d.ts +16 -0
  14. package/dist/sdk/accounts/secrets.js +40 -0
  15. package/dist/sdk/accounts/store.d.ts +114 -0
  16. package/dist/sdk/accounts/store.js +255 -0
  17. package/dist/sdk/browser/config.d.ts +16 -0
  18. package/dist/sdk/browser/config.js +22 -0
  19. package/dist/sdk/browser/diagnostics.d.ts +4 -0
  20. package/dist/sdk/browser/diagnostics.js +38 -0
  21. package/dist/sdk/browser/index.d.ts +17 -0
  22. package/dist/sdk/browser/index.js +65 -0
  23. package/dist/sdk/browser/network.d.ts +10 -0
  24. package/dist/sdk/browser/network.js +59 -0
  25. package/dist/sdk/browser/session.d.ts +84 -0
  26. package/dist/sdk/browser/session.js +192 -0
  27. package/dist/sdk/browser/tools.d.ts +8 -0
  28. package/dist/sdk/browser/tools.js +329 -0
  29. package/dist/sdk/capabilities/api.d.ts +22 -0
  30. package/dist/sdk/capabilities/api.js +54 -0
  31. package/dist/sdk/capabilities/runSnapshot.d.ts +18 -0
  32. package/dist/sdk/capabilities/runSnapshot.js +47 -0
  33. package/dist/sdk/capabilities/types.d.ts +29 -0
  34. package/dist/sdk/capabilities/types.js +1 -0
  35. package/dist/sdk/cli.d.ts +2 -0
  36. package/dist/sdk/cli.js +142 -0
  37. package/dist/sdk/core/bus.d.ts +23 -0
  38. package/dist/sdk/core/bus.js +76 -0
  39. package/dist/sdk/core/caller.d.ts +22 -0
  40. package/dist/sdk/core/caller.js +16 -0
  41. package/dist/sdk/core/changes.d.ts +15 -0
  42. package/dist/sdk/core/changes.js +15 -0
  43. package/dist/sdk/core/effort.d.ts +2 -0
  44. package/dist/sdk/core/effort.js +1 -0
  45. package/dist/sdk/core/env.d.ts +18 -0
  46. package/dist/sdk/core/env.js +8 -0
  47. package/dist/sdk/core/errors.d.ts +15 -0
  48. package/dist/sdk/core/errors.js +24 -0
  49. package/dist/sdk/core/frontmatter.d.ts +12 -0
  50. package/dist/sdk/core/frontmatter.js +50 -0
  51. package/dist/sdk/core/fs.d.ts +18 -0
  52. package/dist/sdk/core/fs.js +106 -0
  53. package/dist/sdk/core/index.d.ts +24 -0
  54. package/dist/sdk/core/index.js +187 -0
  55. package/dist/sdk/core/path.d.ts +7 -0
  56. package/dist/sdk/core/path.js +79 -0
  57. package/dist/sdk/core/projects.d.ts +18 -0
  58. package/dist/sdk/core/projects.js +67 -0
  59. package/dist/sdk/core/secrets.d.ts +2 -0
  60. package/dist/sdk/core/secrets.js +5 -0
  61. package/dist/sdk/core/text.d.ts +7 -0
  62. package/dist/sdk/core/text.js +40 -0
  63. package/dist/sdk/doctor/checks.d.ts +5 -0
  64. package/dist/sdk/doctor/checks.js +208 -0
  65. package/dist/sdk/doctor/probe.d.ts +4 -0
  66. package/dist/sdk/doctor/probe.js +92 -0
  67. package/dist/sdk/doctor/service.d.ts +23 -0
  68. package/dist/sdk/doctor/service.js +144 -0
  69. package/dist/sdk/doctor/types.d.ts +141 -0
  70. package/dist/sdk/doctor/types.js +29 -0
  71. package/dist/sdk/harness.d.ts +133 -0
  72. package/dist/sdk/harness.js +299 -0
  73. package/dist/sdk/history/lessonPrompt.d.ts +2 -0
  74. package/dist/sdk/history/lessonPrompt.js +15 -0
  75. package/dist/sdk/history/lessons.d.ts +52 -0
  76. package/dist/sdk/history/lessons.js +173 -0
  77. package/dist/sdk/history/recall.d.ts +68 -0
  78. package/dist/sdk/history/recall.js +199 -0
  79. package/dist/sdk/history/trajectory.d.ts +7 -0
  80. package/dist/sdk/history/trajectory.js +216 -0
  81. package/dist/sdk/hub/catalog.json +123 -0
  82. package/dist/sdk/hub/cli.d.ts +7 -0
  83. package/dist/sdk/hub/cli.js +81 -0
  84. package/dist/sdk/hub/hub.d.ts +74 -0
  85. package/dist/sdk/hub/hub.js +474 -0
  86. package/dist/sdk/hub/schema.d.ts +280 -0
  87. package/dist/sdk/hub/schema.js +78 -0
  88. package/dist/sdk/index.d.ts +63 -0
  89. package/dist/sdk/index.js +48 -0
  90. package/dist/sdk/mcp/connections.d.ts +83 -0
  91. package/dist/sdk/mcp/connections.js +669 -0
  92. package/dist/sdk/mcp/manager.d.ts +26 -0
  93. package/dist/sdk/mcp/manager.js +124 -0
  94. package/dist/sdk/mcp/oauth.d.ts +35 -0
  95. package/dist/sdk/mcp/oauth.js +171 -0
  96. package/dist/sdk/mcp/scopeMigration.d.ts +19 -0
  97. package/dist/sdk/mcp/scopeMigration.js +198 -0
  98. package/dist/sdk/mcp/service.d.ts +160 -0
  99. package/dist/sdk/mcp/service.js +510 -0
  100. package/dist/sdk/mcp/types.d.ts +131 -0
  101. package/dist/sdk/mcp/types.js +49 -0
  102. package/dist/sdk/pi/runtime.d.ts +13 -0
  103. package/dist/sdk/pi/runtime.js +31 -0
  104. package/dist/sdk/plugins/api.d.ts +66 -0
  105. package/dist/sdk/plugins/api.js +155 -0
  106. package/dist/sdk/plugins/attachments.d.ts +46 -0
  107. package/dist/sdk/plugins/attachments.js +111 -0
  108. package/dist/sdk/plugins/builtin.d.ts +6 -0
  109. package/dist/sdk/plugins/builtin.js +68 -0
  110. package/dist/sdk/plugins/catalogue.d.ts +29 -0
  111. package/dist/sdk/plugins/catalogue.js +196 -0
  112. package/dist/sdk/plugins/cli.d.ts +13 -0
  113. package/dist/sdk/plugins/cli.js +187 -0
  114. package/dist/sdk/plugins/discover.d.ts +42 -0
  115. package/dist/sdk/plugins/discover.js +115 -0
  116. package/dist/sdk/plugins/firstParty.d.ts +50 -0
  117. package/dist/sdk/plugins/firstParty.js +113 -0
  118. package/dist/sdk/plugins/hooks.d.ts +94 -0
  119. package/dist/sdk/plugins/hooks.js +162 -0
  120. package/dist/sdk/plugins/identity.d.ts +19 -0
  121. package/dist/sdk/plugins/identity.js +48 -0
  122. package/dist/sdk/plugins/install.d.ts +20 -0
  123. package/dist/sdk/plugins/install.js +110 -0
  124. package/dist/sdk/plugins/inventory.d.ts +22 -0
  125. package/dist/sdk/plugins/inventory.js +110 -0
  126. package/dist/sdk/plugins/manifest.d.ts +13 -0
  127. package/dist/sdk/plugins/manifest.js +56 -0
  128. package/dist/sdk/plugins/plugins.d.ts +104 -0
  129. package/dist/sdk/plugins/plugins.js +164 -0
  130. package/dist/sdk/plugins/runtime.d.ts +9 -0
  131. package/dist/sdk/plugins/runtime.js +95 -0
  132. package/dist/sdk/plugins/trust.d.ts +44 -0
  133. package/dist/sdk/plugins/trust.js +120 -0
  134. package/dist/sdk/plugins/types.d.ts +159 -0
  135. package/dist/sdk/plugins/types.js +7 -0
  136. package/dist/sdk/prompt/compile.d.ts +174 -0
  137. package/dist/sdk/prompt/compile.js +396 -0
  138. package/dist/sdk/refine/cli.d.ts +4 -0
  139. package/dist/sdk/refine/cli.js +186 -0
  140. package/dist/sdk/refine/demo.d.ts +21 -0
  141. package/dist/sdk/refine/demo.js +114 -0
  142. package/dist/sdk/refine/evidence.d.ts +55 -0
  143. package/dist/sdk/refine/evidence.js +330 -0
  144. package/dist/sdk/refine/host.d.ts +51 -0
  145. package/dist/sdk/refine/host.js +76 -0
  146. package/dist/sdk/refine/ledger.d.ts +48 -0
  147. package/dist/sdk/refine/ledger.js +76 -0
  148. package/dist/sdk/refine/library.d.ts +55 -0
  149. package/dist/sdk/refine/library.js +177 -0
  150. package/dist/sdk/refine/pi.d.ts +72 -0
  151. package/dist/sdk/refine/pi.js +248 -0
  152. package/dist/sdk/refine/prompts.d.ts +45 -0
  153. package/dist/sdk/refine/prompts.js +390 -0
  154. package/dist/sdk/refine/recursion.d.ts +113 -0
  155. package/dist/sdk/refine/recursion.js +258 -0
  156. package/dist/sdk/refine/runner.d.ts +81 -0
  157. package/dist/sdk/refine/runner.js +150 -0
  158. package/dist/sdk/refine/selection.d.ts +57 -0
  159. package/dist/sdk/refine/selection.js +251 -0
  160. package/dist/sdk/refine/service.d.ts +84 -0
  161. package/dist/sdk/refine/service.js +165 -0
  162. package/dist/sdk/refine/suite.d.ts +67 -0
  163. package/dist/sdk/refine/suite.js +174 -0
  164. package/dist/sdk/refine/trajectory.d.ts +9 -0
  165. package/dist/sdk/refine/trajectory.js +68 -0
  166. package/dist/sdk/refine/types.d.ts +77 -0
  167. package/dist/sdk/refine/types.js +10 -0
  168. package/dist/sdk/server/methods.d.ts +3 -0
  169. package/dist/sdk/server/methods.js +370 -0
  170. package/dist/sdk/server/remote.d.ts +19 -0
  171. package/dist/sdk/server/remote.js +84 -0
  172. package/dist/sdk/server/rpc.d.ts +20 -0
  173. package/dist/sdk/server/rpc.js +102 -0
  174. package/dist/sdk/server/serve.d.ts +10 -0
  175. package/dist/sdk/server/serve.js +18 -0
  176. package/dist/sdk/sessions/approvals.d.ts +47 -0
  177. package/dist/sdk/sessions/approvals.js +119 -0
  178. package/dist/sdk/sessions/continuation.d.ts +48 -0
  179. package/dist/sdk/sessions/continuation.js +85 -0
  180. package/dist/sdk/sessions/contracts.d.ts +534 -0
  181. package/dist/sdk/sessions/contracts.js +299 -0
  182. package/dist/sdk/sessions/extensionUi.d.ts +19 -0
  183. package/dist/sdk/sessions/extensionUi.js +64 -0
  184. package/dist/sdk/sessions/history.d.ts +16 -0
  185. package/dist/sdk/sessions/history.js +68 -0
  186. package/dist/sdk/sessions/lineageJoin.d.ts +2 -0
  187. package/dist/sdk/sessions/lineageJoin.js +61 -0
  188. package/dist/sdk/sessions/planMode.d.ts +36 -0
  189. package/dist/sdk/sessions/planMode.js +84 -0
  190. package/dist/sdk/sessions/policy.d.ts +43 -0
  191. package/dist/sdk/sessions/policy.js +1 -0
  192. package/dist/sdk/sessions/pricing.d.ts +34 -0
  193. package/dist/sdk/sessions/pricing.js +40 -0
  194. package/dist/sdk/sessions/reasoningSummaries.d.ts +11 -0
  195. package/dist/sdk/sessions/reasoningSummaries.js +61 -0
  196. package/dist/sdk/sessions/registry.d.ts +102 -0
  197. package/dist/sdk/sessions/registry.js +383 -0
  198. package/dist/sdk/sessions/runIndex.d.ts +21 -0
  199. package/dist/sdk/sessions/runIndex.js +65 -0
  200. package/dist/sdk/sessions/runStore.d.ts +66 -0
  201. package/dist/sdk/sessions/runStore.js +335 -0
  202. package/dist/sdk/sessions/runner.d.ts +213 -0
  203. package/dist/sdk/sessions/runner.js +589 -0
  204. package/dist/sdk/sessions/sessions.d.ts +140 -0
  205. package/dist/sdk/sessions/sessions.js +394 -0
  206. package/dist/sdk/sessions/subagentService.d.ts +41 -0
  207. package/dist/sdk/sessions/subagentService.js +247 -0
  208. package/dist/sdk/sessions/subagents.d.ts +50 -0
  209. package/dist/sdk/sessions/subagents.js +68 -0
  210. package/dist/sdk/terminal/accountsMenu.d.ts +46 -0
  211. package/dist/sdk/terminal/accountsMenu.js +392 -0
  212. package/dist/sdk/terminal/cli.d.ts +20 -0
  213. package/dist/sdk/terminal/cli.js +121 -0
  214. package/dist/sdk/terminal/client.d.ts +15 -0
  215. package/dist/sdk/terminal/client.js +126 -0
  216. package/dist/sdk/terminal/control.d.ts +4 -0
  217. package/dist/sdk/terminal/control.js +25 -0
  218. package/dist/sdk/terminal/controller.d.ts +72 -0
  219. package/dist/sdk/terminal/controller.js +351 -0
  220. package/dist/sdk/terminal/hubMenu.d.ts +10 -0
  221. package/dist/sdk/terminal/hubMenu.js +34 -0
  222. package/dist/sdk/terminal/launch.d.ts +5 -0
  223. package/dist/sdk/terminal/launch.js +27 -0
  224. package/dist/sdk/terminal/logs.d.ts +14 -0
  225. package/dist/sdk/terminal/logs.js +59 -0
  226. package/dist/sdk/terminal/piExtension.d.ts +6 -0
  227. package/dist/sdk/terminal/piExtension.js +392 -0
  228. package/dist/sdk/terminal/pluginMenu.d.ts +34 -0
  229. package/dist/sdk/terminal/pluginMenu.js +101 -0
  230. package/dist/sdk/terminal/socket.d.ts +70 -0
  231. package/dist/sdk/terminal/socket.js +256 -0
  232. package/dist/sdk/terminal/standalone.d.ts +18 -0
  233. package/dist/sdk/terminal/standalone.js +146 -0
  234. package/dist/sdk/terminal/terminals.d.ts +176 -0
  235. package/dist/sdk/terminal/terminals.js +767 -0
  236. package/dist/sdk/terminal/trajectory.d.ts +10 -0
  237. package/dist/sdk/terminal/trajectory.js +30 -0
  238. package/dist/sdk/terminal/tui/chrome.d.ts +64 -0
  239. package/dist/sdk/terminal/tui/chrome.js +169 -0
  240. package/dist/sdk/terminal/tui/clipboardFiles.d.ts +11 -0
  241. package/dist/sdk/terminal/tui/clipboardFiles.js +74 -0
  242. package/dist/sdk/terminal/tui/commandPages.d.ts +7 -0
  243. package/dist/sdk/terminal/tui/commandPages.js +63 -0
  244. package/dist/sdk/terminal/tui/commands.d.ts +42 -0
  245. package/dist/sdk/terminal/tui/commands.js +276 -0
  246. package/dist/sdk/terminal/tui/dialogGeometry.d.ts +27 -0
  247. package/dist/sdk/terminal/tui/dialogGeometry.js +50 -0
  248. package/dist/sdk/terminal/tui/dialogs.d.ts +75 -0
  249. package/dist/sdk/terminal/tui/dialogs.js +363 -0
  250. package/dist/sdk/terminal/tui/keybindings.d.ts +11 -0
  251. package/dist/sdk/terminal/tui/keybindings.js +49 -0
  252. package/dist/sdk/terminal/tui/list.d.ts +73 -0
  253. package/dist/sdk/terminal/tui/list.js +196 -0
  254. package/dist/sdk/terminal/tui/modeChoices.d.ts +42 -0
  255. package/dist/sdk/terminal/tui/modeChoices.js +70 -0
  256. package/dist/sdk/terminal/tui/modelPicker.d.ts +55 -0
  257. package/dist/sdk/terminal/tui/modelPicker.js +155 -0
  258. package/dist/sdk/terminal/tui/mouse.d.ts +3 -0
  259. package/dist/sdk/terminal/tui/mouse.js +3 -0
  260. package/dist/sdk/terminal/tui/page.d.ts +14 -0
  261. package/dist/sdk/terminal/tui/page.js +1 -0
  262. package/dist/sdk/terminal/tui/pageOverlay.d.ts +27 -0
  263. package/dist/sdk/terminal/tui/pageOverlay.js +145 -0
  264. package/dist/sdk/terminal/tui/pages/accountsPage.d.ts +54 -0
  265. package/dist/sdk/terminal/tui/pages/accountsPage.js +181 -0
  266. package/dist/sdk/terminal/tui/pages/componentsPage.d.ts +47 -0
  267. package/dist/sdk/terminal/tui/pages/componentsPage.js +119 -0
  268. package/dist/sdk/terminal/tui/pages/doctorPage.d.ts +37 -0
  269. package/dist/sdk/terminal/tui/pages/doctorPage.js +145 -0
  270. package/dist/sdk/terminal/tui/pages/hubPage.d.ts +39 -0
  271. package/dist/sdk/terminal/tui/pages/hubPage.js +128 -0
  272. package/dist/sdk/terminal/tui/pages/logPage.d.ts +38 -0
  273. package/dist/sdk/terminal/tui/pages/logPage.js +189 -0
  274. package/dist/sdk/terminal/tui/pages/permissionsPage.d.ts +44 -0
  275. package/dist/sdk/terminal/tui/pages/permissionsPage.js +124 -0
  276. package/dist/sdk/terminal/tui/pages/pluginsPage.d.ts +57 -0
  277. package/dist/sdk/terminal/tui/pages/pluginsPage.js +243 -0
  278. package/dist/sdk/terminal/tui/pages/settingsPage.d.ts +55 -0
  279. package/dist/sdk/terminal/tui/pages/settingsPage.js +88 -0
  280. package/dist/sdk/terminal/tui/pages/trajectoryPage.d.ts +58 -0
  281. package/dist/sdk/terminal/tui/pages/trajectoryPage.js +377 -0
  282. package/dist/sdk/terminal/tui/piAdapter.d.ts +18 -0
  283. package/dist/sdk/terminal/tui/piAdapter.js +48 -0
  284. package/dist/sdk/terminal/tui/planMode.d.ts +2 -0
  285. package/dist/sdk/terminal/tui/planMode.js +2 -0
  286. package/dist/sdk/terminal/tui/questionPicker.d.ts +20 -0
  287. package/dist/sdk/terminal/tui/questionPicker.js +150 -0
  288. package/dist/sdk/terminal/tui/questions.d.ts +19 -0
  289. package/dist/sdk/terminal/tui/questions.js +42 -0
  290. package/dist/sdk/terminal/tui/runtime.d.ts +114 -0
  291. package/dist/sdk/terminal/tui/runtime.js +441 -0
  292. package/dist/sdk/terminal/tui/search.d.ts +22 -0
  293. package/dist/sdk/terminal/tui/search.js +60 -0
  294. package/dist/sdk/terminal/tui/selection.d.ts +16 -0
  295. package/dist/sdk/terminal/tui/selection.js +16 -0
  296. package/dist/sdk/terminal/tui/sessionUi.d.ts +3 -0
  297. package/dist/sdk/terminal/tui/sessionUi.js +30 -0
  298. package/dist/sdk/terminal/tui/settingsModel.d.ts +12 -0
  299. package/dist/sdk/terminal/tui/settingsModel.js +84 -0
  300. package/dist/sdk/terminal/tui/sideQuestion.d.ts +53 -0
  301. package/dist/sdk/terminal/tui/sideQuestion.js +197 -0
  302. package/dist/sdk/terminal/tui/skillInvocation.d.ts +9 -0
  303. package/dist/sdk/terminal/tui/skillInvocation.js +43 -0
  304. package/dist/sdk/terminal/tui/status.d.ts +18 -0
  305. package/dist/sdk/terminal/tui/status.js +67 -0
  306. package/dist/sdk/terminal/tui/theme.d.ts +45 -0
  307. package/dist/sdk/terminal/tui/theme.js +176 -0
  308. package/dist/sdk/terminal/tui/treeModel.d.ts +79 -0
  309. package/dist/sdk/terminal/tui/treeModel.js +483 -0
  310. package/dist/sdk/terminal/tui/treePicker.d.ts +49 -0
  311. package/dist/sdk/terminal/tui/treePicker.js +251 -0
  312. package/dist/sdk/terminal/tui/ui.d.ts +47 -0
  313. package/dist/sdk/terminal/tui/ui.js +148 -0
  314. package/dist/sdk/terminal/tui/workingStatus.d.ts +9 -0
  315. package/dist/sdk/terminal/tui/workingStatus.js +229 -0
  316. package/dist/sdk/tools/askUser.d.ts +6 -0
  317. package/dist/sdk/tools/askUser.js +74 -0
  318. package/dist/sdk/tools/checklist.d.ts +9 -0
  319. package/dist/sdk/tools/checklist.js +54 -0
  320. package/dist/sdk/tools/definitions.d.ts +30 -0
  321. package/dist/sdk/tools/definitions.js +117 -0
  322. package/dist/sdk/tools/dispatch.d.ts +16 -0
  323. package/dist/sdk/tools/dispatch.js +67 -0
  324. package/dist/sdk/tools/harnessTools.d.ts +6 -0
  325. package/dist/sdk/tools/harnessTools.js +20 -0
  326. package/dist/sdk/tools/sessionName.d.ts +7 -0
  327. package/dist/sdk/tools/sessionName.js +31 -0
  328. package/dist/sdk/tools/spawnSubagent.d.ts +4 -0
  329. package/dist/sdk/tools/spawnSubagent.js +49 -0
  330. package/dist/sdk/tools/types.d.ts +48 -0
  331. package/dist/sdk/tools/types.js +1 -0
  332. package/dist/sdk/variant/components.d.ts +34 -0
  333. package/dist/sdk/variant/components.js +230 -0
  334. package/dist/sdk/variant/materialise.d.ts +66 -0
  335. package/dist/sdk/variant/materialise.js +81 -0
  336. package/dist/sdk/variant/migrate.d.ts +4 -0
  337. package/dist/sdk/variant/migrate.js +41 -0
  338. package/dist/sdk/variant/seed.d.ts +20 -0
  339. package/dist/sdk/variant/seed.js +116 -0
  340. package/dist/sdk/variant/store.d.ts +82 -0
  341. package/dist/sdk/variant/store.js +151 -0
  342. package/dist/sdk/variant/surface.d.ts +56 -0
  343. package/dist/sdk/variant/surface.js +81 -0
  344. package/dist/sdk/variant/trust.d.ts +27 -0
  345. package/dist/sdk/variant/trust.js +64 -0
  346. package/dist/sdk/variant/variant.d.ts +71 -0
  347. package/dist/sdk/variant/variant.js +331 -0
  348. package/dist/sdk/web/markdown.d.ts +10 -0
  349. package/dist/sdk/web/markdown.js +66 -0
  350. package/dist/sdk/web/safeRequest.d.ts +34 -0
  351. package/dist/sdk/web/safeRequest.js +175 -0
  352. package/dist/sdk/web/search.d.ts +53 -0
  353. package/dist/sdk/web/search.js +168 -0
  354. package/dist/sdk/web/text.d.ts +2 -0
  355. package/dist/sdk/web/text.js +7 -0
  356. package/dist/sdk/web/tools.d.ts +22 -0
  357. package/dist/sdk/web/tools.js +195 -0
  358. package/dist/sdk/web/turndown-plugin-gfm.d.ts +4 -0
  359. package/dist/sdk/wiki/notes.d.ts +68 -0
  360. package/dist/sdk/wiki/notes.js +297 -0
  361. package/dist/sdk/world/actions.d.ts +23 -0
  362. package/dist/sdk/world/actions.js +31 -0
  363. package/dist/sdk/world/cli.d.ts +12 -0
  364. package/dist/sdk/world/cli.js +166 -0
  365. package/dist/sdk/world/images.d.ts +20 -0
  366. package/dist/sdk/world/images.js +48 -0
  367. package/dist/sdk/world/untrusted.d.ts +12 -0
  368. package/dist/sdk/world/untrusted.js +20 -0
  369. package/dist/sdk/world/world.d.ts +65 -0
  370. package/dist/sdk/world/world.js +80 -0
  371. package/dist/terminal-opentui.js +13612 -0
  372. package/dist/terminal-pi.js +808 -151
  373. package/docs/README.md +22 -0
  374. package/docs/architecture/00-overview.md +159 -0
  375. package/docs/architecture/01-extension-points.md +78 -0
  376. package/docs/architecture/02-sessions-and-runs.md +146 -0
  377. package/docs/architecture/03-variants.md +95 -0
  378. package/docs/architecture/04-prompt.md +102 -0
  379. package/docs/architecture/05-refinement.md +107 -0
  380. package/docs/architecture/06-plugins-and-capabilities.md +93 -0
  381. package/docs/architecture/07-tools-and-gate.md +88 -0
  382. package/docs/architecture/08-memory-and-history.md +94 -0
  383. package/docs/architecture/09-accounts-and-connectors.md +90 -0
  384. package/docs/architecture/10-server-protocol.md +84 -0
  385. package/docs/architecture/11-terminal.md +81 -0
  386. package/docs/architecture/12-storage.md +61 -0
  387. package/docs/architecture/13-world-tools.md +23 -0
  388. package/docs/architecture/README.md +114 -0
  389. package/docs/architecture/proposals/PROP-0001-reporting-section.md +106 -0
  390. package/docs/architecture/proposals/PROP-0002-retain-rollout-workspaces.md +104 -0
  391. package/docs/architecture/proposals/PROP-0003-executor-settings.md +81 -0
  392. package/docs/architecture/proposals/PROP-0004-flexible-reporting-guidance.md +71 -0
  393. package/docs/architecture/proposals/PROP-0005-xharness-cli-name.md +78 -0
  394. package/docs/architecture/proposals/PROP-0006-run-checklist.md +68 -0
  395. package/docs/architecture/proposals/PROP-0007-run-continuation.md +80 -0
  396. package/docs/architecture/proposals/PROP-0008-lessons-from-runs.md +80 -0
  397. package/docs/architecture/proposals/PROP-0009-foundation-and-variants.md +78 -0
  398. package/docs/architecture/proposals/PROP-0010-session-names.md +73 -0
  399. package/docs/architecture/proposals/PROP-0011-doctor.md +63 -0
  400. package/docs/architecture/proposals/PROP-0012-release-baseline.md +56 -0
  401. package/docs/architecture/proposals/README.md +76 -0
  402. package/docs/decisions/ADR-0002-native-subagent-lifecycle.md +49 -0
  403. package/docs/decisions/ADR-0003-shared-asynchronous-subagents.md +70 -0
  404. package/docs/decisions/ADR-0004-fullscreen-terminal-layout.md +50 -0
  405. package/docs/decisions/ADR-0005-app-provided-oauth-clients.md +54 -0
  406. package/docs/decisions/ADR-0006-opentui-only-terminal.md +47 -0
  407. package/docs/decisions/ADR-0007-reporting-section.md +33 -0
  408. package/docs/decisions/ADR-0008-flexible-reporting-guidance.md +38 -0
  409. package/docs/decisions/ADR-0009-xharness-command.md +38 -0
  410. package/docs/decisions/ADR-0010-lessons-in-the-wiki.md +31 -0
  411. package/docs/decisions/ADR-0011-foundation-and-variants.md +28 -0
  412. package/docs/decisions/ADR-0012-native-session-names.md +34 -0
  413. package/docs/decisions/ADR-0013-doctor.md +27 -0
  414. package/docs/decisions/ADR-0014-public-release-baseline.md +21 -0
  415. package/docs/doctor.md +74 -0
  416. package/docs/experiments/EXP-0001-terminal-input-latency/plan.md +81 -0
  417. package/docs/experiments/EXP-0001-terminal-input-latency/results.md +46 -0
  418. package/docs/experiments/EXP-0002-opentui-typing-latency/plan.md +70 -0
  419. package/docs/experiments/EXP-0002-opentui-typing-latency/results.md +92 -0
  420. package/docs/features/README.md +20 -0
  421. package/docs/features/automations/README.md +41 -0
  422. package/docs/features/cores/README.md +39 -0
  423. package/docs/features/google-harness2/README.md +52 -0
  424. package/docs/features/integrations/README.md +40 -0
  425. package/docs/features/interfaces/README.md +29 -0
  426. package/docs/features/memory/README.md +28 -0
  427. package/docs/releases/1.0.0.md +40 -0
  428. package/docs/releases/README.md +36 -0
  429. package/docs/study/00-index.md +78 -0
  430. package/docs/study/arms/00-matrix.md +20 -0
  431. package/docs/study/arms/01-system-prompt.md +211 -0
  432. package/docs/study/arms/02-tools.md +79 -0
  433. package/docs/study/arms/03-subagents.md +90 -0
  434. package/docs/study/arms/04-skills.md +75 -0
  435. package/docs/study/arms/05-context-management.md +90 -0
  436. package/docs/study/arms/06-memory.md +90 -0
  437. package/docs/study/arms/07-governance.md +71 -0
  438. package/docs/study/arms/08-execution.md +69 -0
  439. package/docs/study/arms/09-observability.md +60 -0
  440. package/docs/study/arms/10-refinement.md +110 -0
  441. package/docs/study/btw-comparison.md +37 -0
  442. package/docs/study/feature-gap-review.md +42 -0
  443. package/docs/study/insights.md +123 -0
  444. package/docs/study/roadmap.md +72 -0
  445. package/docs/study/sources/claude-code.md +44 -0
  446. package/docs/study/sources/deepseek-harness.md +102 -0
  447. package/docs/study/sources/harness2.md +50 -0
  448. package/docs/study/sources/kimi-cli.md +42 -0
  449. package/docs/study/sources/omp.md +93 -0
  450. package/docs/study/sources/opencode.md +43 -0
  451. package/docs/study/sources/openhands.md +46 -0
  452. package/docs/study/sources/smolagents.md +40 -0
  453. package/docs/study/sources/swe-agent.md +34 -0
  454. package/docs/system-prompt-design.md +196 -0
  455. package/docs/terminal-tui.md +473 -56
  456. package/package.json +43 -13
  457. package/docs/evidence/terminal-2026-10-17/120x40-completed-transcript.ansi.gz +0 -0
  458. package/docs/evidence/terminal-2026-10-17/40x20-settings-interaction.ansi.gz +0 -0
  459. package/docs/evidence/terminal-2026-10-17/40x20-settings-keyboard.ansi.gz +0 -0
  460. package/docs/evidence/terminal-2026-10-17/40x20-welcome.ansi.gz +0 -0
  461. package/docs/evidence/terminal-2026-10-17/80x24-approval-0.ansi.gz +0 -0
  462. package/docs/evidence/terminal-2026-10-17/80x24-question.ansi.gz +0 -0
  463. package/docs/evidence/terminal-2026-10-17/verification.json +0 -130
  464. package/docs/evidence/terminal-2026-10-17-followup/120x40-completed-transcript.ansi.gz +0 -0
  465. package/docs/evidence/terminal-2026-10-17-followup/40x20-settings-interaction.ansi.gz +0 -0
  466. package/docs/evidence/terminal-2026-10-17-followup/40x20-settings-keyboard.ansi.gz +0 -0
  467. package/docs/evidence/terminal-2026-10-17-followup/40x20-welcome.ansi.gz +0 -0
  468. package/docs/evidence/terminal-2026-10-17-followup/80x24-approval-0.ansi.gz +0 -0
  469. package/docs/evidence/terminal-2026-10-17-followup/80x24-plugin-light.ansi.gz +0 -0
  470. package/docs/evidence/terminal-2026-10-17-followup/80x24-question-batch.ansi.gz +0 -0
  471. package/docs/evidence/terminal-2026-10-17-followup/80x24-question.ansi.gz +0 -0
  472. package/docs/evidence/terminal-2026-10-17-followup/app-bundle/80x24-approval-0.ansi.gz +0 -0
  473. package/docs/evidence/terminal-2026-10-17-followup/app-bundle/80x24-plugin-light.ansi.gz +0 -0
  474. package/docs/evidence/terminal-2026-10-17-followup/app-bundle/80x24-question-batch.ansi.gz +0 -0
  475. package/docs/evidence/terminal-2026-10-17-followup/app-bundle/80x24-question.ansi.gz +0 -0
  476. package/docs/evidence/terminal-2026-10-17-followup/app-bundle/verification.json +0 -95
  477. package/docs/evidence/terminal-2026-10-17-followup/global-install/80x24-approval-0.ansi.gz +0 -0
  478. package/docs/evidence/terminal-2026-10-17-followup/global-install/80x24-plugin-light.ansi.gz +0 -0
  479. package/docs/evidence/terminal-2026-10-17-followup/global-install/80x24-question-batch.ansi.gz +0 -0
  480. package/docs/evidence/terminal-2026-10-17-followup/global-install/80x24-question.ansi.gz +0 -0
  481. package/docs/evidence/terminal-2026-10-17-followup/global-install/README.md +0 -27
  482. package/docs/evidence/terminal-2026-10-17-followup/global-install/shell-command-80x24.ansi.gz +0 -0
  483. package/docs/evidence/terminal-2026-10-17-followup/global-install/shell-command.json +0 -18
  484. package/docs/evidence/terminal-2026-10-17-followup/global-install/verification.json +0 -95
  485. package/docs/evidence/terminal-2026-10-17-followup/harness-reference-80x24.json.gz +0 -0
  486. package/docs/evidence/terminal-2026-10-17-followup/harness-reference-final-80x24.json.gz +0 -0
  487. package/docs/evidence/terminal-2026-10-17-followup/harness-reference-scenario.json +0 -60
  488. package/docs/evidence/terminal-2026-10-17-followup/live-codex/README.md +0 -24
  489. package/docs/evidence/terminal-2026-10-17-followup/live-codex/complete-80x24.ansi.gz +0 -0
  490. package/docs/evidence/terminal-2026-10-17-followup/live-codex/verification.json +0 -57
  491. package/docs/evidence/terminal-2026-10-17-followup/omp-reference-80x24-early.json.gz +0 -0
  492. package/docs/evidence/terminal-2026-10-17-followup/omp-reference-80x24.json.gz +0 -0
  493. package/docs/evidence/terminal-2026-10-17-followup/omp-reference-scenario.json +0 -58
  494. package/docs/evidence/terminal-2026-10-17-followup/omp-supplemental-static.json.gz +0 -0
  495. package/docs/evidence/terminal-2026-10-17-followup/rea-artifacts.json +0 -320
  496. package/docs/evidence/terminal-2026-10-17-followup/reference-comparison.json +0 -30
  497. package/docs/evidence/terminal-2026-10-17-followup/reference-comparison.md +0 -133
  498. package/docs/evidence/terminal-2026-10-17-followup/reference-source-observations.json +0 -20
  499. package/docs/evidence/terminal-2026-10-17-followup/verification.json +0 -207
  500. package/src/accounts/accounts.test.ts +0 -333
  501. package/src/accounts/accounts.ts +0 -658
  502. package/src/accounts/env.test.ts +0 -95
  503. package/src/accounts/env.ts +0 -338
  504. package/src/accounts/secrets.ts +0 -50
  505. package/src/accounts/store.ts +0 -338
  506. package/src/capabilities/api.ts +0 -75
  507. package/src/capabilities/runSnapshot.ts +0 -59
  508. package/src/capabilities/types.ts +0 -34
  509. package/src/cli.ts +0 -118
  510. package/src/core/bus.ts +0 -34
  511. package/src/core/changes.ts +0 -33
  512. package/src/core/env.ts +0 -27
  513. package/src/core/errors.ts +0 -32
  514. package/src/core/frontmatter.ts +0 -47
  515. package/src/core/fs.test.ts +0 -38
  516. package/src/core/fs.ts +0 -92
  517. package/src/core/index.ts +0 -193
  518. package/src/core/path.test.ts +0 -14
  519. package/src/core/path.ts +0 -79
  520. package/src/core/projects.ts +0 -90
  521. package/src/core/text.ts +0 -39
  522. package/src/harness.ts +0 -312
  523. package/src/hub/catalog.json +0 -123
  524. package/src/hub/cli.ts +0 -59
  525. package/src/hub/hub.test.ts +0 -205
  526. package/src/hub/hub.ts +0 -256
  527. package/src/hub/schema.ts +0 -81
  528. package/src/index.ts +0 -75
  529. package/src/mcp/connections.ts +0 -614
  530. package/src/mcp/manager.ts +0 -130
  531. package/src/mcp/oauth.ts +0 -195
  532. package/src/mcp/scopeMigration.test.ts +0 -79
  533. package/src/mcp/scopeMigration.ts +0 -204
  534. package/src/mcp/service.test.ts +0 -376
  535. package/src/mcp/service.ts +0 -549
  536. package/src/mcp/types.ts +0 -108
  537. package/src/memory/api.test.ts +0 -82
  538. package/src/memory/api.ts +0 -303
  539. package/src/memory/blocks.test.ts +0 -78
  540. package/src/memory/blocks.ts +0 -408
  541. package/src/memory/caller.ts +0 -69
  542. package/src/memory/curation.test.ts +0 -163
  543. package/src/memory/curation.ts +0 -64
  544. package/src/memory/dispatch.ts +0 -59
  545. package/src/memory/facts.test.ts +0 -41
  546. package/src/memory/facts.ts +0 -248
  547. package/src/memory/graph.ts +0 -103
  548. package/src/memory/health.test.ts +0 -130
  549. package/src/memory/health.ts +0 -74
  550. package/src/memory/maintainer.ts +0 -272
  551. package/src/memory/maintenance.test.ts +0 -176
  552. package/src/memory/maintenance.ts +0 -518
  553. package/src/memory/migrate.ts +0 -79
  554. package/src/memory/operations.ts +0 -150
  555. package/src/memory/proposals.test.ts +0 -168
  556. package/src/memory/proposals.ts +0 -584
  557. package/src/memory/recall.test.ts +0 -115
  558. package/src/memory/recall.ts +0 -278
  559. package/src/memory/references.ts +0 -77
  560. package/src/memory/runContext.ts +0 -93
  561. package/src/memory/secrets.ts +0 -6
  562. package/src/memory/store.test.ts +0 -66
  563. package/src/memory/store.ts +0 -385
  564. package/src/memory/wiki.test.ts +0 -47
  565. package/src/memory/wiki.ts +0 -422
  566. package/src/memory/wikiIndex.test.ts +0 -88
  567. package/src/memory/wikiIndex.ts +0 -116
  568. package/src/plugins/api.ts +0 -181
  569. package/src/plugins/attachments.test.ts +0 -84
  570. package/src/plugins/attachments.ts +0 -141
  571. package/src/plugins/builtin.ts +0 -82
  572. package/src/plugins/catalogue.test.ts +0 -254
  573. package/src/plugins/catalogue.ts +0 -251
  574. package/src/plugins/cli.ts +0 -184
  575. package/src/plugins/discover.ts +0 -151
  576. package/src/plugins/firstParty.test.ts +0 -143
  577. package/src/plugins/firstParty.ts +0 -129
  578. package/src/plugins/hooks.test.ts +0 -250
  579. package/src/plugins/hooks.ts +0 -239
  580. package/src/plugins/identity.ts +0 -37
  581. package/src/plugins/install.test.ts +0 -107
  582. package/src/plugins/install.ts +0 -106
  583. package/src/plugins/inventory.ts +0 -123
  584. package/src/plugins/manifest.ts +0 -76
  585. package/src/plugins/plugins.test.ts +0 -205
  586. package/src/plugins/plugins.ts +0 -189
  587. package/src/plugins/proposal.ts +0 -121
  588. package/src/plugins/runtime.test.ts +0 -40
  589. package/src/plugins/runtime.ts +0 -82
  590. package/src/plugins/trust.ts +0 -142
  591. package/src/plugins/types.ts +0 -149
  592. package/src/prompt/compile.test.ts +0 -261
  593. package/src/prompt/compile.ts +0 -732
  594. package/src/server/methods.ts +0 -349
  595. package/src/server/remote.ts +0 -77
  596. package/src/server/rpc.ts +0 -116
  597. package/src/server/serve.ts +0 -28
  598. package/src/server/server.test.ts +0 -174
  599. package/src/sessions/approvals.ts +0 -118
  600. package/src/sessions/contracts.ts +0 -336
  601. package/src/sessions/conversation.test.ts +0 -177
  602. package/src/sessions/extensionSession.test.ts +0 -74
  603. package/src/sessions/extensionUi.test.ts +0 -65
  604. package/src/sessions/extensionUi.ts +0 -102
  605. package/src/sessions/history.ts +0 -51
  606. package/src/sessions/lineageJoin.ts +0 -75
  607. package/src/sessions/policy.ts +0 -36
  608. package/src/sessions/pricing.ts +0 -60
  609. package/src/sessions/questionBatches.test.ts +0 -110
  610. package/src/sessions/reasoningSummaries.ts +0 -59
  611. package/src/sessions/registry.ts +0 -381
  612. package/src/sessions/runIndex.ts +0 -99
  613. package/src/sessions/runStore.test.ts +0 -152
  614. package/src/sessions/runStore.ts +0 -332
  615. package/src/sessions/runner.ts +0 -604
  616. package/src/sessions/runtimeResources.test.ts +0 -61
  617. package/src/sessions/sessions.test.ts +0 -549
  618. package/src/sessions/sessions.ts +0 -339
  619. package/src/skills/registry.test.ts +0 -104
  620. package/src/skills/registry.ts +0 -413
  621. package/src/terminal/accountsMenu.test.ts +0 -225
  622. package/src/terminal/accountsMenu.ts +0 -382
  623. package/src/terminal/cli.ts +0 -149
  624. package/src/terminal/client.ts +0 -74
  625. package/src/terminal/control.test.ts +0 -23
  626. package/src/terminal/control.ts +0 -23
  627. package/src/terminal/controller.ts +0 -291
  628. package/src/terminal/hubMenu.ts +0 -24
  629. package/src/terminal/launch.test.ts +0 -25
  630. package/src/terminal/launch.ts +0 -48
  631. package/src/terminal/piExtension.lifecycle.test.ts +0 -55
  632. package/src/terminal/piExtension.ts +0 -259
  633. package/src/terminal/pluginControl.test.ts +0 -146
  634. package/src/terminal/pluginMenu.ts +0 -112
  635. package/src/terminal/socket.test.ts +0 -71
  636. package/src/terminal/socket.ts +0 -232
  637. package/src/terminal/standalone.test.ts +0 -167
  638. package/src/terminal/standalone.ts +0 -153
  639. package/src/terminal/terminal.test.ts +0 -399
  640. package/src/terminal/terminals.ts +0 -553
  641. package/src/terminal/tui/application.test.ts +0 -114
  642. package/src/terminal/tui/application.ts +0 -491
  643. package/src/terminal/tui/commands.test.ts +0 -218
  644. package/src/terminal/tui/commands.ts +0 -361
  645. package/src/terminal/tui/dialogs.ts +0 -301
  646. package/src/terminal/tui/editor.test.ts +0 -84
  647. package/src/terminal/tui/editor.ts +0 -79
  648. package/src/terminal/tui/keybindings.test.ts +0 -22
  649. package/src/terminal/tui/keybindings.ts +0 -50
  650. package/src/terminal/tui/piAdapter.ts +0 -62
  651. package/src/terminal/tui/presentation.test.ts +0 -79
  652. package/src/terminal/tui/questions.test.ts +0 -41
  653. package/src/terminal/tui/questions.ts +0 -47
  654. package/src/terminal/tui/runtime.socket.test.ts +0 -104
  655. package/src/terminal/tui/runtime.test.ts +0 -215
  656. package/src/terminal/tui/runtime.ts +0 -388
  657. package/src/terminal/tui/sessionUi.ts +0 -24
  658. package/src/terminal/tui/settings.test.ts +0 -150
  659. package/src/terminal/tui/settings.ts +0 -188
  660. package/src/terminal/tui/status.test.ts +0 -70
  661. package/src/terminal/tui/status.ts +0 -109
  662. package/src/terminal/tui/theme.ts +0 -174
  663. package/src/terminal/tui/tools.test.ts +0 -145
  664. package/src/terminal/tui/tools.ts +0 -142
  665. package/src/terminal/tui/transcript.test.ts +0 -25
  666. package/src/terminal/tui/transcript.ts +0 -124
  667. package/src/terminal/tui/ui.test.ts +0 -313
  668. package/src/terminal/tui/ui.ts +0 -166
  669. package/src/terminal/tui/welcome.ts +0 -102
  670. package/src/tools/askUser.test.ts +0 -69
  671. package/src/tools/askUser.ts +0 -59
  672. package/src/tools/definitions.ts +0 -187
  673. package/src/tools/maintenance.ts +0 -24
  674. package/src/tools/memoryTools.ts +0 -22
  675. package/src/tools/pluginPropose.ts +0 -41
  676. package/src/tools/registryWrite.test.ts +0 -101
  677. package/src/tools/registryWrite.ts +0 -182
  678. package/src/tools/spawnSubagent.ts +0 -92
  679. package/src/tools/types.ts +0 -39
  680. package/src/web/safeRequest.ts +0 -171
  681. package/src/web/text.ts +0 -53
  682. package/src/web/tools.ts +0 -134
  683. package/src/web/web.test.ts +0 -141
@@ -1,549 +0,0 @@
1
- import { mkdtempSync, realpathSync, utimesSync, writeFileSync } from 'node:fs'
2
- import { tmpdir } from 'node:os'
3
- import { join } from 'node:path'
4
- import { afterEach, describe, expect, it } from 'vitest'
5
-
6
- import { memorySecretStore } from '../accounts/secrets'
7
- import { createHarness, type Harness } from '../harness'
8
- import type { RunEvent } from './contracts'
9
- import { grant, type ApprovalFacts } from './approvals'
10
- import { selectHistory } from './history'
11
- import type { ToolPolicy } from './policy'
12
- import type { PiEvent, PiSessionLike, SessionFactory } from './runner'
13
- import { loadRun, readEvents } from './runStore'
14
- import type { SessionRunInput } from './sessions'
15
-
16
- const tick = (ms = 20) => new Promise((resolve) => setTimeout(resolve, ms))
17
- const open: Harness[] = []
18
- afterEach(async () => {
19
- for (const harness of open.splice(0)) await harness.close()
20
- })
21
-
22
- function setup(factory: SessionFactory, policy: ToolPolicy = {}) {
23
- const harness = createHarness({ dataRoot: mkdtempSync(join(tmpdir(), 'harness-data-')), stateDir: mkdtempSync(join(tmpdir(), 'harness-state-')), secrets: memorySecretStore(), sessionFactory: factory, policy })
24
- open.push(harness)
25
- harness.accounts.create('Main', 'apiKey')
26
- const events: RunEvent[] = []
27
- harness.runs.listen((event) => void events.push(event))
28
- const sessionDir = join(harness.env.root, 'sessions', 'agent-1')
29
- const run = (id: string, extra: Partial<SessionRunInput> = {}) =>
30
- harness.sessions.run({ run: { id, sessionId: 'agent-1' }, cwd: harness.env.root, sessionDir, text: 'hello', caller: { agentId: 'agent-1', agentName: 'Helper', actor: 'Chat' }, tools: { allow: ['read', 'bash'], gated: ['bash'] }, ...extra })
31
- return { harness, events, run, sessionDir }
32
- }
33
-
34
- function fakeSession(script: (emit: (event: PiEvent) => void, gateCheck: () => Promise<string | null>) => Promise<void>, gate: { check: (name: string, input: unknown) => Promise<string | null> }): PiSessionLike & { aborted: boolean } {
35
- let listener: (event: PiEvent) => void = () => undefined
36
- const session = {
37
- sessionId: 'native-1',
38
- aborted: false,
39
- subscribe: (fn: (event: PiEvent) => void) => {
40
- listener = fn
41
- return () => undefined
42
- },
43
- prompt: () => script((event) => listener(event), () => gate.check('bash', { command: 'ls' })),
44
- sendCustomMessage: async () => undefined,
45
- abort: async () => {
46
- session.aborted = true
47
- },
48
- dispose: () => undefined
49
- }
50
- return session
51
- }
52
-
53
- describe('session turns on Pi', () => {
54
- it('rejects image-required turns before any prompt reaches a text-only model', async () => {
55
- let requested = false
56
- let disposed = false
57
- const { run, harness } = setup(async ({ gate }) => ({
58
- ...fakeSession(async () => { requested = true }, gate),
59
- model: { input: ['text'] },
60
- dispose: () => { disposed = true }
61
- }))
62
- expect(await run('image-unsupported', { requiresImageInput: true })).toEqual({ runId: 'image-unsupported', nativeSessionId: null })
63
- expect(requested).toBe(false)
64
- expect(disposed).toBe(true)
65
- expect(loadRun(harness.env.root, 'image-unsupported')).toMatchObject({ status: 'failed', terminalReason: expect.stringContaining('supports images') })
66
- })
67
-
68
- it('passes image pixels using Pi image content fields with the existing stable prefix', async () => {
69
- let observed: unknown
70
- const { run } = setup(async ({ gate }) => ({
71
- ...fakeSession(async () => undefined, gate),
72
- model: { input: ['text', 'image'] },
73
- prompt: async (_text, options) => { observed = options }
74
- }))
75
- await run('image-supported', { requiresImageInput: true, images: [{ mediaType: 'image/png', data: 'cGl4ZWxz' }] })
76
- await tick()
77
- expect(observed).toEqual({ images: [{ type: 'image', mimeType: 'image/png', data: 'cGl4ZWxz' }] })
78
- })
79
-
80
- it('saves the context update in native history before the request, without another model turn', async () => {
81
- const delivered: unknown[] = []
82
- const { run, events, harness } = setup(async ({ gate, params }) => ({
83
- ...fakeSession(async () => undefined, gate),
84
- sendCustomMessage: async (message, options) => void delivered.push({ type: message.customType, display: message.display, options, snapshot: Object.keys(message.details as object) }),
85
- prompt: async (text) => void delivered.push({ text, system: params.system.startsWith('# Xingular Harness context\n') })
86
- }))
87
- const started = await run('context-run', { preamble: '[Group context]', text: 'The user request' })
88
- await tick()
89
- expect(started).toEqual({ runId: 'context-run', nativeSessionId: 'native-1' })
90
- expect(delivered).toEqual([
91
- { type: 'xingular-context', display: false, options: { triggerTurn: false }, snapshot: ['xingularContext'] },
92
- { text: '[Group context]\n\nThe user request', system: true }
93
- ])
94
- expect(events.filter((event) => event.type === 'usage.updated')).toHaveLength(0)
95
- expect(events.at(-1)?.type).toBe('completed')
96
- expect(loadRun(harness.env.root, 'context-run')).toMatchObject({ status: 'completed', nativeSessionId: 'native-1', mode: 'chat', harness: 'pi', goal: 'The user request', agentId: 'agent-1' })
97
- expect(readEvents(harness.env.root, 'context-run').map((event) => event.sequence)).toEqual(events.map((_, index) => index))
98
- })
99
-
100
- it('streams Pi events as contract events and completes', async () => {
101
- const { events, run } = setup(async ({ gate }) =>
102
- fakeSession(async (emit) => {
103
- emit({ type: 'message_start', message: { role: 'assistant' } })
104
- emit({ type: 'message_update', message: { role: 'assistant' }, assistantMessageEvent: { type: 'text_delta', delta: 'Hi' } })
105
- emit({ type: 'message_end', message: { role: 'assistant', content: [{ type: 'text', text: 'Hi' }], usage: { input: 3, output: 1 } } })
106
- }, gate)
107
- )
108
- await run('run-1')
109
- await tick()
110
- expect(events.map((event) => event.type)).toEqual(['started', 'status.changed', 'context.snapshot', 'message.delta', 'message.completed', 'usage.updated', 'completed'])
111
- expect(events.map((event) => event.sequence)).toEqual([0, 1, 2, 3, 4, 5, 6])
112
- })
113
-
114
- it('reports cache, reasoning, cost and context size across the requests of a reply', async () => {
115
- const { events, run } = setup(async ({ gate }) => ({
116
- ...fakeSession(async (emit) => {
117
- emit({ type: 'message_end', message: { role: 'assistant', content: [], model: 'sonnet', provider: 'anthropic', usage: { input: 10, output: 4, cacheRead: 100, cacheWrite: 20, reasoning: 1, cost: { total: 0.01 } } } })
118
- emit({ type: 'message_end', message: { role: 'assistant', content: [{ type: 'text', text: 'Done' }], model: 'sonnet', provider: 'anthropic', usage: { input: 5, output: 6, cacheRead: 130, cacheWrite: 0, reasoning: 2, cost: { total: 0.02 } } } })
119
- emit({ type: 'message_end', message: { role: 'assistant', content: [], usage: { input: 1, output: 1 } } })
120
- }, gate),
121
- model: { input: ['text'], contextWindow: 200_000 }
122
- }))
123
- await run('run-1')
124
- await tick()
125
- const usage = events.filter((event) => event.type === 'usage.updated').map((event) => event.payload as Record<string, unknown>)
126
- expect(usage[1]).toMatchObject({ inputTokens: 15, outputTokens: 10, cacheReadTokens: 230, cacheWriteTokens: 20, reasoningTokens: 3, requests: 2, contextTokens: 135, contextWindow: 200_000, model: 'sonnet', provider: 'anthropic' })
127
- expect(usage[1].costUsd).toBeCloseTo(0.03)
128
- // A request without the optional figures leaves the running totals intact.
129
- expect(usage[2]).toMatchObject({ inputTokens: 16, cacheReadTokens: 230, reasoningTokens: 3, requests: 3, contextTokens: 1 })
130
- expect(usage[2]).not.toHaveProperty('model')
131
- })
132
-
133
- it('reports each model request on its own, with its model, cost and timing', async () => {
134
- const { events, run } = setup(async ({ gate }) =>
135
- fakeSession(async (emit) => {
136
- emit({ type: 'turn_start' })
137
- emit({ type: 'message_update', message: { role: 'assistant' }, assistantMessageEvent: { type: 'thinking_delta' } })
138
- emit({ type: 'message_end', message: { role: 'assistant', content: [], model: 'sonnet', provider: 'anthropic', responseId: 'resp-1', stopReason: 'toolUse', usage: { input: 10, output: 4, cacheRead: 100, cacheWrite: 20, reasoning: 1, cost: { total: 0.01 } } } })
139
- emit({ type: 'tool_execution_start', toolCallId: 't1', toolName: 'read', args: { path: 'a.md' } })
140
- emit({ type: 'tool_execution_end', toolCallId: 't1', toolName: 'read', isError: false, result: { content: [{ type: 'text', text: 'x'.repeat(1200) }, { type: 'image' }] } })
141
- emit({ type: 'turn_start' })
142
- emit({ type: 'message_end', message: { role: 'assistant', content: [{ type: 'text', text: 'Done' }], model: 'haiku', provider: 'anthropic', usage: { input: 5, output: 6 } } })
143
- }, gate)
144
- )
145
- await run('run-1')
146
- await tick()
147
- const requests = events.filter((event) => event.type === 'usage.updated').map((event) => (event.payload as { request: Record<string, unknown> }).request)
148
- expect(requests[0]).toMatchObject({ index: 1, responseId: 'resp-1', model: 'sonnet', provider: 'anthropic', inputTokens: 10, cacheReadTokens: 100, cacheWriteTokens: 20, outputTokens: 4, reasoningTokens: 1, costUsd: 0.01, costSource: 'harness', stopReason: 'toolUse' })
149
- expect(requests[0]).not.toHaveProperty('reasoningSummary')
150
- expect(requests[0].durationMs).toBeGreaterThanOrEqual(0)
151
- expect(requests[0].ttftMs).toBeGreaterThanOrEqual(0)
152
- // The second request reported no cost and produced no streamed output.
153
- expect(requests[1]).toMatchObject({ index: 2, model: 'haiku', inputTokens: 5, outputTokens: 6, cacheReadTokens: 0 })
154
- for (const key of ['costUsd', 'costSource', 'ttftMs', 'reasoningTokens', 'responseId']) expect(requests[1]).not.toHaveProperty(key)
155
- const tool = events.find((event) => event.type === 'tool.completed')!.payload as Record<string, unknown>
156
- // Stored evidence is redacted, which rewrites JSON carried as text in its compact form.
157
- expect(events.find((event) => event.type === 'tool.started')?.payload).toMatchObject({ toolCallId: 't1', input: '{"path":"a.md"}' })
158
- expect(tool).toMatchObject({ toolCallId: 't1', ok: true, resultChars: 1200 })
159
- expect(tool.output).toContain('x'.repeat(1200))
160
- })
161
-
162
- it('persists only an explicitly captured provider summary without inventing usage figures', async () => {
163
- const { events, run } = setup(async ({ gate }) =>
164
- fakeSession(async (emit) => {
165
- emit({ type: 'turn_start' })
166
- emit({ type: 'message_end', message: { role: 'assistant', api: 'openai-codex-responses', content: [
167
- { type: 'thinking', thinkingSignature: JSON.stringify({ type: 'reasoning', summary: [{ type: 'summary_text', text: 'I will check the saved state before changing the file.' }], content: [{ text: 'private reasoning' }], encrypted_content: 'private replay payload' }) },
168
- { type: 'text', text: 'Done.' }
169
- ], model: 'gpt-6', provider: 'openai', responseId: 'response-1' } })
170
- }, gate)
171
- )
172
- await run('summary-run')
173
- await tick()
174
- const usage = events.find((event) => event.type === 'usage.updated')!.payload as Record<string, unknown>
175
- expect(usage).toMatchObject({ requests: 1, model: 'gpt-6', provider: 'openai' })
176
- expect(usage).not.toHaveProperty('inputTokens')
177
- expect(usage.request).toMatchObject({ index: 1, responseId: 'response-1', reasoningSummary: 'I will check the saved state before changing the file.' })
178
- expect(usage.request).not.toHaveProperty('outputTokens')
179
- expect(JSON.stringify(events)).not.toContain('private reasoning')
180
- expect(JSON.stringify(events)).not.toContain('private replay payload')
181
- })
182
-
183
- it('records the compiled prompt and schemas for the tools actually declared to the model', async () => {
184
- const { events, run } = setup(async ({ gate }) => ({
185
- ...fakeSession(async () => undefined, gate),
186
- sendCustomMessage: async () => undefined,
187
- getActiveToolNames: () => ['read'],
188
- getAllTools: () => [
189
- { name: 'read', description: 'Read a file', parameters: { type: 'object', properties: { path: { type: 'string' } } } },
190
- { name: 'write', description: 'Write a file', parameters: { type: 'object' } }
191
- ]
192
- }))
193
- await run('run-1')
194
- await tick()
195
- const snapshot = events.find((event) => event.type === 'context.snapshot')!.payload as { systemPrompt: string; contextUpdate: string; tools: unknown[] }
196
- expect(snapshot.systemPrompt).toContain('## Context updates')
197
- expect(snapshot.contextUpdate).toContain('<block label="persona"')
198
- expect(snapshot.tools).toEqual([{ name: 'read', description: 'Read a file', parameters: { type: 'object', properties: { path: { type: 'string' } } } }])
199
- })
200
-
201
- it('caps saved tool inputs and outputs at 24,000 characters with truncation flags', async () => {
202
- const large = 'x'.repeat(25_000)
203
- const { events, run } = setup(async ({ gate }) =>
204
- fakeSession(async (emit) => {
205
- emit({ type: 'tool_execution_start', toolCallId: 't1', toolName: 'computer_type_text', args: { app: 'Editor', text: large } })
206
- emit({ type: 'tool_execution_end', toolCallId: 't1', toolName: 'computer_type_text', isError: false, result: large })
207
- }, gate)
208
- )
209
- await run('run-1')
210
- await tick()
211
- const started = events.find((event) => event.type === 'tool.started')!.payload as { input: string; inputTruncated: boolean }
212
- const completed = events.find((event) => event.type === 'tool.completed')!.payload as { output: string; outputTruncated: boolean }
213
- expect([started.inputTruncated, completed.outputTruncated]).toEqual([true, true])
214
- expect(started.input.length).toBeLessThanOrEqual(24_000)
215
- expect(completed.output.length).toBeLessThanOrEqual(24_000)
216
- })
217
-
218
- it('records generic tool attachment references without storing their content bytes', async () => {
219
- const { events, run } = setup(async ({ gate }) => fakeSession(async (emit) => {
220
- emit({ type: 'tool_execution_end', toolCallId: 'image-1', toolName: 'capture', isError: false, result: {
221
- content: [{ type: 'text', text: 'Captured screen.' }, { type: 'image', data: 'model-image-bytes' }],
222
- details: { attachments: [{ id: 'attachment-1', name: 'Screen capture', mimeType: 'image/jpeg' }] }
223
- } })
224
- emit({ type: 'tool_execution_end', toolCallId: 'image-2', toolName: 'capture', isError: false, result: {
225
- content: [{ type: 'text', text: 'Invalid attachment metadata was ignored.' }],
226
- details: { attachments: [{ id: 'attachment-2', name: 'Capture', mimeType: 'image/jpeg', data: 'must-not-persist' }] }
227
- } })
228
- }, gate))
229
-
230
- await run('attachments-run')
231
- await tick()
232
- const completed = events.filter((event) => event.type === 'tool.completed').map((event) => event.payload as Record<string, unknown>)
233
- expect(completed[0]?.attachments).toEqual([{ id: 'attachment-1', name: 'Screen capture', mimeType: 'image/jpeg' }])
234
- expect(completed[1]).not.toHaveProperty('attachments')
235
- expect(JSON.stringify(events)).not.toContain('model-image-bytes')
236
- expect(JSON.stringify(events)).not.toContain('must-not-persist')
237
- })
238
-
239
- it('records bounded retry and compaction events without exposing thinking text', async () => {
240
- const { events, run } = setup(async ({ gate }) =>
241
- fakeSession(async (emit) => {
242
- emit({ type: 'auto_retry_start', attempt: 1, maxAttempts: 3, delayMs: 250, errorMessage: 'Temporary failure' } as unknown as PiEvent)
243
- emit({ type: 'auto_retry_end', attempt: 1, success: true } as unknown as PiEvent)
244
- emit({ type: 'compaction_start', reason: 'threshold' } as unknown as PiEvent)
245
- emit({ type: 'compaction_end', reason: 'threshold', aborted: false, willRetry: true, result: { summary: 'kept the latest messages' } } as unknown as PiEvent)
246
- }, gate)
247
- )
248
- await run('run-1')
249
- await tick()
250
- expect(events.filter((event) => event.type.startsWith('retry.') || event.type.startsWith('compaction.')).map((event) => event.type)).toEqual(['retry.started', 'retry.ended', 'compaction.started', 'compaction.ended'])
251
- expect(events.find((event) => event.type === 'compaction.ended')?.payload).toMatchObject({ aborted: false, willRetry: true, summary: '{"summary":"kept the latest messages"}' })
252
- })
253
-
254
- it('leaves out the cost and reasoning figures nobody reported', async () => {
255
- const { events, run } = setup(async ({ gate }) => fakeSession(async (emit) => emit({ type: 'message_end', message: { role: 'assistant', content: [], usage: { input: 1, output: 1 } } }), gate))
256
- await run('run-1')
257
- await tick()
258
- const usage = events.find((event) => event.type === 'usage.updated')!.payload as Record<string, unknown>
259
- for (const key of ['costUsd', 'reasoningTokens', 'contextWindow']) expect(usage).not.toHaveProperty(key)
260
- expect(usage.request).not.toHaveProperty('durationMs')
261
- })
262
-
263
- it('pauses gated tools for approval and blocks on deny', async () => {
264
- let blockReason: string | null | undefined
265
- const { events, run, harness } = setup(async ({ gate }) => fakeSession(async (_emit, check) => void (blockReason = await check()), gate))
266
- await run('run-2', { text: 'list files' })
267
- await tick()
268
- const approval = events.find((event) => event.type === 'approval.requested')!.payload as { approvalId: string }
269
- expect(approval).toMatchObject({ target: 'bash', action: 'ls', rememberable: true })
270
- expect(loadRun(harness.env.root, 'run-2').status).toBe('waiting_for_approval')
271
- harness.runs.resolveApproval('run-2', approval.approvalId, 'denied')
272
- await tick()
273
- expect(blockReason).toMatch(/denied/)
274
- expect(events.some((event) => event.type === 'approval.resolved')).toBe(true)
275
- })
276
-
277
- it("asks before a tool an extension brought, and relays the extension's question and notice as run events", async () => {
278
- let blockReason: string | null | undefined
279
- let answered: unknown
280
- const { events, run, harness } = setup(async ({ gate, extensions }) => ({
281
- ...fakeSession(async () => undefined, gate),
282
- prompt: async () => {
283
- // What a loaded extension does: registers a tool nobody switched on by name, shows a line, and asks.
284
- gate.ask?.(['kit_scan'])
285
- extensions!.notice({ kind: 'status', key: 'kit', text: 'Scanning' })
286
- answered = await extensions!.ask({ question: 'Scan the whole folder?', options: [{ label: 'Yes' }, { label: 'No' }], multiSelect: false, allowText: false })
287
- blockReason = await gate.check('kit_scan', { path: '.' })
288
- }
289
- }))
290
- await run('run-ext', { text: 'scan' })
291
- await tick()
292
- expect(events.find((event) => event.type === 'extension.notice')?.payload).toEqual({ kind: 'status', key: 'kit', text: 'Scanning' })
293
- const question = events.find((event) => event.type === 'question.requested')!.payload as { questionId: string }
294
- expect(question).toMatchObject({ question: 'Scan the whole folder?' })
295
- harness.runs.answerQuestion('run-ext', question.questionId, { selected: ['Yes'] })
296
- await tick()
297
- expect(answered).toEqual({ selected: ['Yes'] })
298
- const approval = events.find((event) => event.type === 'approval.requested')!.payload as { approvalId: string }
299
- expect(approval).toMatchObject({ target: 'kit_scan' })
300
- harness.runs.resolveApproval('run-ext', approval.approvalId, 'approved')
301
- await tick()
302
- expect(blockReason).toBeNull()
303
- })
304
-
305
- it('gives an unattended run no extension interface', async () => {
306
- let offered: unknown = 'unset'
307
- const { run } = setup(async ({ gate, extensions }) => {
308
- offered = extensions
309
- return fakeSession(async () => undefined, gate)
310
- })
311
- await run('run-auto-ext', { text: 'tidy', automatic: true })
312
- await tick()
313
- expect(offered).toBeUndefined()
314
- })
315
-
316
- it('skips the prompt for a tool the user already allowed in this session', async () => {
317
- let blockReason: string | null | undefined
318
- const { events, run, harness } = setup(async ({ gate }) => fakeSession(async (_emit, check) => void (blockReason = await check()), gate))
319
- harness.approvals.remember('agent-1', 'bash')
320
- await run('run-auto', { text: 'list files' })
321
- await tick()
322
- expect(blockReason).toBeNull()
323
- expect(events.find((event) => event.type === 'approval.requested')?.payload).toMatchObject({ target: 'bash', rememberable: true, automatic: 'remembered' })
324
- expect(events.find((event) => event.type === 'approval.resolved')?.payload).toMatchObject({ decision: 'approved', automatic: 'remembered' })
325
- expect(events.some((event) => event.type === 'status.changed' && (event.payload as { status: string }).status === 'waiting_for_approval')).toBe(false)
326
- expect(events.at(-1)?.type).toBe('completed')
327
- })
328
-
329
- it('asks a person for a humanOnly call even when the tool was remembered', async () => {
330
- let blockReason: string | null | undefined
331
- const policy: ToolPolicy = { scope: async () => ({ allowed: false, requiresApproval: true, humanOnly: true, reason: 'Run this command?' }) }
332
- const { events, run, harness } = setup(async ({ gate }) => fakeSession(async (_emit, check) => void (blockReason = await check()), gate), policy)
333
- harness.approvals.remember('agent-1', 'bash')
334
- harness.approvals.setAuto('chat', true)
335
- await run('run-human', { text: 'npm install' })
336
- await tick()
337
- const requested = events.find((event) => event.type === 'approval.requested')!.payload as { approvalId: string }
338
- expect(requested).toMatchObject({ target: 'bash', rememberable: false })
339
- expect(requested).not.toHaveProperty('automatic')
340
- expect(events.some((event) => event.type === 'status.changed' && (event.payload as { status: string }).status === 'waiting_for_approval')).toBe(true)
341
- harness.runs.resolveApproval('run-human', requested.approvalId, 'approved')
342
- await tick()
343
- expect(blockReason).toBeNull()
344
- })
345
-
346
- it('lets the Agent chat switch approve humanOnly calls without remembering the tool', async () => {
347
- let blockReason: string | null | undefined
348
- const policy: ToolPolicy = { scope: async () => ({ allowed: false, requiresApproval: true, humanOnly: true, allowGlobalApproval: true, reason: 'Run this command?' }) }
349
- const { events, run, harness } = setup(async ({ gate }) => fakeSession(async (_emit, check) => void (blockReason = await check()), gate), policy)
350
- harness.approvals.setAuto('chat', true)
351
- await run('run-human-auto', { text: 'run command' })
352
- await tick()
353
- expect(blockReason).toBeNull()
354
- expect(events.find((event) => event.type === 'approval.requested')?.payload).toMatchObject({ target: 'bash', rememberable: false, automatic: 'always' })
355
- expect(events.find((event) => event.type === 'approval.resolved')?.payload).toMatchObject({ decision: 'approved', automatic: 'always' })
356
- expect(events.some((event) => event.type === 'status.changed' && (event.payload as { status: string }).status === 'waiting_for_approval')).toBe(false)
357
- })
358
-
359
- it('still asks when the application cannot say what is allowed', async () => {
360
- const { events, run } = setup(async ({ gate }) => fakeSession(async (_emit, check) => void (await check()), gate), { approval: async () => { throw new Error('down') } })
361
- await run('run-ask', { text: 'list files' })
362
- await tick()
363
- expect(events.some((event) => event.type === 'status.changed' && (event.payload as { status: string }).status === 'waiting_for_approval')).toBe(true)
364
- expect(events.some((event) => event.type === 'approval.resolved')).toBe(false)
365
- })
366
-
367
- it('lets the application refuse a call that is outside an unattended run\'s scope, and switch a tool off', async () => {
368
- const reasons: (string | null)[] = []
369
- let authorized = 0
370
- const policy: ToolPolicy = {
371
- scope: async ({ tool }) => ({ automatic: true, allowed: tool !== 'bash', reason: 'Scheduled workers may use scoped file tools only.' }),
372
- authorize: async () => {
373
- authorized += 1
374
- if (authorized > 1) throw new Error('Tool unavailable in the app settings.')
375
- }
376
- }
377
- const { run, harness } = setup(async ({ gate }) => fakeSession(async () => {
378
- reasons.push(await gate.check('bash', {}), await gate.check('read', {}), await gate.check('read', {}))
379
- }, gate), policy)
380
- harness.approvals.setAuto('chat', true)
381
- await run('run-scope')
382
- await tick()
383
- expect(reasons).toEqual(['Scheduled workers may use scoped file tools only.', null, 'Tool unavailable in the app settings.'])
384
- })
385
-
386
- it('forwards opaque caller policy context to the application tool policy', async () => {
387
- const contexts: unknown[] = []
388
- const policy: ToolPolicy = {
389
- scope: async ({ caller }) => {
390
- contexts.push(caller.policyContext)
391
- return { allowed: true }
392
- }
393
- }
394
- const { run } = setup(async ({ gate }) => fakeSession(async (_emit, check) => void (await check()), gate), policy)
395
- const policyContext = { delegatesTo: ['worker-1'], snapshot: 'run-start' }
396
- await run('run-policy-context', {
397
- caller: { agentId: 'agent-1', agentName: 'Helper', actor: 'Chat', policyContext }
398
- })
399
- await tick()
400
- expect(contexts).toEqual([policyContext])
401
- })
402
-
403
- it('aborts the Pi session on interrupt', async () => {
404
- let session: (PiSessionLike & { aborted: boolean }) | null = null
405
- const { events, run, harness } = setup(async ({ gate }) => (session = fakeSession(() => new Promise((resolve) => setTimeout(resolve, 50)), gate)))
406
- await run('run-3')
407
- await tick()
408
- await harness.runs.interrupt('run-3')
409
- await tick(80)
410
- expect(session!.aborted).toBe(true)
411
- expect(events.at(-1)).toMatchObject({ type: 'status.changed', payload: { status: 'interrupted' } })
412
- expect(loadRun(harness.env.root, 'run-3').status).toBe('interrupted')
413
- })
414
-
415
- it('gives every session the memory, recall and Wiki tools, a question tool, and the application\'s tools in order', async () => {
416
- let received: Parameters<SessionFactory>[0] | null = null
417
- const { run, harness } = setup(async (input) => {
418
- received = input
419
- return fakeSession(async () => undefined, input.gate)
420
- })
421
- harness.sessions.provide(() => [{ name: 'react', label: 'React', description: 'React', parameters: (Type) => Type.Object({}), execute: async () => 'ok' }])
422
- await run('run-5', { tools: { allow: ['react'] } })
423
- await tick()
424
- expect(received!.allow.slice(0, 3)).toEqual(['react', 'ask_user', 'plugin_propose'])
425
- expect(received!.allow).toEqual(expect.arrayContaining(['memory_append', 'history_search', 'wiki_search', 'wiki_read', 'wiki_propose']))
426
- expect(received!.tools.map((tool) => tool.name)).toEqual(['memory_append', 'memory_replace', 'memory_rethink', 'history_search', 'history_read', 'wiki_search', 'wiki_read', 'wiki_list', 'skill_load', 'memory_facts', 'wiki_propose', 'ask_user', 'plugin_propose', 'react'])
427
- expect(await received!.tools[0].execute({ block: 'notes', text: 'Remember this.' })).toContain('Updated the notes block')
428
- expect(received!.params.system).toContain('# Xingular Harness context')
429
- })
430
-
431
- it('refuses missing continuation history before creating a run or opening a session', async () => {
432
- let called = false
433
- const { run, events, harness } = setup(async ({ gate }) => {
434
- called = true
435
- return fakeSession(async () => undefined, gate)
436
- })
437
- await expect(run('resume-run', { resume: { sessionFile: '/tmp/missing-history/saved.jsonl', nativeSessionId: 'native-1' } })).rejects.toMatchObject({ code: 'native_resume_unavailable' })
438
- expect(called).toBe(false)
439
- expect(events).toEqual([])
440
- expect(() => loadRun(harness.env.root, 'resume-run')).toThrow()
441
- })
442
-
443
- it('fails the run when the session cannot start', async () => {
444
- const { events, run, harness } = setup(async () => {
445
- throw new Error('No provider')
446
- })
447
- expect(await run('run-4')).toEqual({ runId: 'run-4', nativeSessionId: null })
448
- expect(events.at(-1)).toMatchObject({ type: 'failed', payload: { message: 'No provider' } })
449
- expect(loadRun(harness.env.root, 'run-4')).toMatchObject({ status: 'failed', terminalReason: 'No provider' })
450
- })
451
-
452
- it('needs an account before it records anything', async () => {
453
- const harness = createHarness({ dataRoot: mkdtempSync(join(tmpdir(), 'harness-data-')), stateDir: mkdtempSync(join(tmpdir(), 'harness-state-')), secrets: memorySecretStore(), sessionFactory: async () => { throw new Error('unreachable') } })
454
- open.push(harness)
455
- await expect(harness.sessions.run({ run: { id: 'r', sessionId: 's' }, cwd: '/tmp', sessionDir: join(harness.env.root, 's'), text: 'x', caller: { agentId: 'a' }, tools: { allow: [] } })).rejects.toMatchObject({ code: 'account_required' })
456
- expect(() => loadRun(harness.env.root, 'r')).toThrow()
457
- })
458
- })
459
-
460
- describe('history selection', () => {
461
- it('opens the newest history, or exactly the recorded one for a continuation', () => {
462
- const dir = mkdtempSync(join(tmpdir(), 'context-history-'))
463
- expect(selectHistory({ sessionDir: dir })).toBeNull()
464
- expect(selectHistory({ sessionDir: join(dir, 'missing') })).toBeNull()
465
- const first = join(dir, 'first.jsonl')
466
- const newer = join(dir, 'newer.jsonl')
467
- writeFileSync(first, `${JSON.stringify({ type: 'session', id: 'first' })}\n`)
468
- writeFileSync(newer, `${JSON.stringify({ type: 'session', id: 'newer' })}\n`)
469
- writeFileSync(join(dir, 'notes.txt'), '')
470
- utimesSync(newer, new Date(Date.now() + 1000), new Date(Date.now() + 1000))
471
- utimesSync(join(dir, 'notes.txt'), new Date(Date.now() + 5000), new Date(Date.now() + 5000))
472
- expect(selectHistory({ sessionDir: dir })).toBe(realpathSync(newer))
473
- expect(selectHistory({ sessionDir: dir, resume: { sessionFile: first, nativeSessionId: 'first' } })).toBe(realpathSync(first))
474
- expect(() => selectHistory({ sessionDir: dir, resume: { sessionFile: first, nativeSessionId: 'newer' } })).toThrow(/saved conversation/)
475
- expect(() => selectHistory({ sessionDir: dir, resume: { sessionFile: join(dir, 'missing'), nativeSessionId: 'first' } })).toThrow(/saved conversation/)
476
- expect(() => selectHistory({ sessionDir: dir, resume: { sessionFile: first } })).toThrow(/saved conversation/)
477
- const outside = join(mkdtempSync(join(tmpdir(), 'other-history-')), 'outside.jsonl')
478
- writeFileSync(outside, `${JSON.stringify({ type: 'session', id: 'outside' })}\n`)
479
- expect(() => selectHistory({ sessionDir: dir, resume: { sessionFile: outside, nativeSessionId: 'outside' } })).toThrow(/saved conversation/)
480
- })
481
-
482
- it('never opens a newest file that is not a native history', () => {
483
- const dir = mkdtempSync(join(tmpdir(), 'context-history-'))
484
- writeFileSync(join(dir, 'broken.jsonl'), 'not json\n')
485
- expect(() => selectHistory({ sessionDir: dir })).toThrow(/saved conversation/)
486
- })
487
- })
488
-
489
- describe('subagents', () => {
490
- it('starts a child run of the same caller with no more tools than its parent, and returns its result', async () => {
491
- const seen = new Map<string, { allow: string[]; tools: string[]; depth: string | null }>()
492
- let parentResult: unknown = null
493
- const { harness, run } = setup(async ({ gate, params, allow, tools }) => {
494
- seen.set(params.runId, { allow: [...allow], tools: tools.map((tool) => tool.name), depth: null })
495
- return fakeSession(async (emit) => {
496
- if (params.runId === 'parent') {
497
- const spawn = tools.find((tool) => tool.name === 'spawn_subagent')!
498
- expect(await spawn.execute({ brief: 'Find the bug', tools: ['bash', 'web'] })).toMatch(/cannot have tools you do not have: web/)
499
- parentResult = await spawn.execute({ brief: 'Find the bug', tools: ['read'] })
500
- return
501
- }
502
- emit({ type: 'message_start', message: { role: 'assistant' } })
503
- emit({ type: 'message_end', message: { role: 'assistant', content: [{ type: 'text', text: 'Found it in parser.ts' }] } })
504
- }, gate)
505
- })
506
- await run('parent', { subagents: { maxDepth: 1 } })
507
- for (let attempt = 0; attempt < 50 && !parentResult; attempt++) await tick()
508
- expect(parentResult).toMatch(/Subagent run .* · completed\nResult:\nFound it in parser.ts/)
509
- expect(seen.get('parent')!.allow).toContain('spawn_subagent')
510
- const [childId] = [...seen.keys()].filter((id) => id !== 'parent')
511
- const child = seen.get(childId)!
512
- expect(child.allow).toContain('read')
513
- expect(child.allow).not.toContain('bash')
514
- // At the depth limit the child cannot start subagents of its own.
515
- expect(child.allow).not.toContain('spawn_subagent')
516
- expect(loadRun(harness.env.root, childId)).toMatchObject({ parentRunId: 'parent', relation: 'subagent', agentId: 'agent-1', status: 'completed' })
517
- })
518
-
519
- it('offers no subagent tool unless the caller asks for it, and can run without the application\'s tool providers', async () => {
520
- const offered: string[][] = []
521
- const { harness, run } = setup(async ({ gate, tools }) => {
522
- offered.push(tools.map((tool) => tool.name))
523
- return fakeSession(async () => undefined, gate)
524
- })
525
- harness.sessions.provide(() => [{ name: 'app_tool', label: 'App', description: 'An app tool', parameters: (Type) => Type.Object({}), execute: async () => 'ok' }])
526
- await run('plain', { tools: { allow: ['app_tool'] } })
527
- await run('solo', { tools: { allow: ['app_tool'] }, providers: false })
528
- await tick()
529
- expect(offered[0]).toContain('app_tool')
530
- expect(offered[0]).not.toContain('spawn_subagent')
531
- expect(offered[1]).not.toContain('app_tool')
532
- })
533
- })
534
-
535
- describe('standing approvals', () => {
536
- const policy = { auto: { pi: true }, remembered: { 'pane-1': ['shell'] } }
537
- const facts = (unattended: boolean, readOnly: boolean): ApprovalFacts => ({ unattended, readOnly })
538
-
539
- it('the switch reaches attended sessions but not unattended or read-only ones', () => {
540
- expect(grant(policy, 'pi', 'pane-1', 'edit files', facts(false, false))).toBe('always')
541
- expect(grant(policy, 'pi', 'pane-1', 'edit files', facts(true, false))).toBeNull()
542
- expect(grant(policy, 'pi', 'pane-1', 'edit files', facts(false, true))).toBeNull()
543
- expect(grant(policy, 'pi', 'pane-1', 'shell', facts(false, true))).toBe('remembered')
544
- expect(grant(policy, 'pi', 'pane-1', 'shell', facts(true, false))).toBeNull()
545
- expect(grant(policy, 'chat', 'pane-1', 'edit files', facts(false, false))).toBeNull()
546
- expect(grant(policy, 'pi', 'pane-2', 'shell', {})).toBe('always')
547
- expect(grant({ auto: {}, remembered: policy.remembered }, 'pi', 'pane-2', 'shell', {})).toBeNull()
548
- })
549
- })