@esso0428/pi-subagents 0.17.16 → 0.17.17

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 (359) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/ROADMAP.md +87 -0
  3. package/docs/post-0.17.6-agents-roster-viewer-spec.md +42 -0
  4. package/docs/post-0.17.6-durable-history-spec.md +41 -0
  5. package/docs/post-0.17.6-feature-specs.md +50 -0
  6. package/docs/post-0.17.6-nested-agents-spec.md +38 -0
  7. package/docs/post-0.17.6-recovery-shutdown-spec.md +46 -0
  8. package/docs/post-0.17.6-ui-latency-baseline-spec.md +46 -0
  9. package/docs/post-0.17.6-workflow-rpc-lifecycle-spec.md +54 -0
  10. package/package.json +1 -1
  11. package/src/agent-history.ts +2 -54
  12. package/src/agent-manager.ts +406 -1288
  13. package/src/agent-runner.ts +27 -251
  14. package/src/agent-types.ts +32 -188
  15. package/src/cross-extension-rpc.ts +20 -96
  16. package/src/custom-agents.ts +13 -170
  17. package/src/index.ts +480 -1920
  18. package/src/invocation-config.ts +3 -118
  19. package/src/model-resolver.ts +0 -18
  20. package/src/output-file.ts +6 -61
  21. package/src/prompts.ts +2 -45
  22. package/src/schedule.ts +14 -35
  23. package/src/settings.ts +2 -301
  24. package/src/status-note.ts +1 -66
  25. package/src/types.ts +10 -177
  26. package/src/ui/agent-widget.ts +57 -278
  27. package/src/ui/conversation-blocks.ts +0 -6
  28. package/src/ui/conversation-timeline.ts +25 -139
  29. package/src/ui/conversation-viewer.ts +48 -212
  30. package/src/ui/schedule-menu.ts +8 -9
  31. package/src/usage.ts +2 -109
  32. package/src/worktree.ts +55 -69
  33. package/dist/abortable.d.ts +0 -13
  34. package/dist/abortable.d.ts.map +0 -1
  35. package/dist/abortable.js +0 -43
  36. package/dist/abortable.js.map +0 -1
  37. package/dist/agent-color.d.ts +0 -36
  38. package/dist/agent-color.d.ts.map +0 -1
  39. package/dist/agent-color.js +0 -124
  40. package/dist/agent-color.js.map +0 -1
  41. package/dist/agent-file-toggle.d.ts +0 -126
  42. package/dist/agent-file-toggle.d.ts.map +0 -1
  43. package/dist/agent-file-toggle.js +0 -259
  44. package/dist/agent-file-toggle.js.map +0 -1
  45. package/dist/agent-history-list.d.ts +0 -19
  46. package/dist/agent-history-list.d.ts.map +0 -1
  47. package/dist/agent-history-list.js +0 -69
  48. package/dist/agent-history-list.js.map +0 -1
  49. package/dist/agent-history.d.ts +0 -35
  50. package/dist/agent-history.d.ts.map +0 -1
  51. package/dist/agent-history.js +0 -188
  52. package/dist/agent-history.js.map +0 -1
  53. package/dist/agent-manager.d.ts +0 -503
  54. package/dist/agent-manager.d.ts.map +0 -1
  55. package/dist/agent-manager.js +0 -1572
  56. package/dist/agent-manager.js.map +0 -1
  57. package/dist/agent-recovery.d.ts +0 -37
  58. package/dist/agent-recovery.d.ts.map +0 -1
  59. package/dist/agent-recovery.js +0 -171
  60. package/dist/agent-recovery.js.map +0 -1
  61. package/dist/agent-runner.d.ts +0 -303
  62. package/dist/agent-runner.d.ts.map +0 -1
  63. package/dist/agent-runner.js +0 -1003
  64. package/dist/agent-runner.js.map +0 -1
  65. package/dist/agent-types.d.ts +0 -120
  66. package/dist/agent-types.d.ts.map +0 -1
  67. package/dist/agent-types.js +0 -301
  68. package/dist/agent-types.js.map +0 -1
  69. package/dist/child-context.d.ts +0 -3
  70. package/dist/child-context.d.ts.map +0 -1
  71. package/dist/child-context.js +0 -13
  72. package/dist/child-context.js.map +0 -1
  73. package/dist/context.d.ts +0 -13
  74. package/dist/context.d.ts.map +0 -1
  75. package/dist/context.js +0 -57
  76. package/dist/context.js.map +0 -1
  77. package/dist/cross-extension-rpc.d.ts +0 -67
  78. package/dist/cross-extension-rpc.d.ts.map +0 -1
  79. package/dist/cross-extension-rpc.js +0 -139
  80. package/dist/cross-extension-rpc.js.map +0 -1
  81. package/dist/custom-agents.d.ts +0 -55
  82. package/dist/custom-agents.d.ts.map +0 -1
  83. package/dist/custom-agents.js +0 -309
  84. package/dist/custom-agents.js.map +0 -1
  85. package/dist/default-agents.d.ts +0 -8
  86. package/dist/default-agents.d.ts.map +0 -1
  87. package/dist/default-agents.js +0 -123
  88. package/dist/default-agents.js.map +0 -1
  89. package/dist/enabled-models.d.ts +0 -50
  90. package/dist/enabled-models.d.ts.map +0 -1
  91. package/dist/enabled-models.js +0 -146
  92. package/dist/enabled-models.js.map +0 -1
  93. package/dist/env.d.ts +0 -7
  94. package/dist/env.d.ts.map +0 -1
  95. package/dist/env.js +0 -29
  96. package/dist/env.js.map +0 -1
  97. package/dist/group-join.d.ts +0 -33
  98. package/dist/group-join.d.ts.map +0 -1
  99. package/dist/group-join.js +0 -117
  100. package/dist/group-join.js.map +0 -1
  101. package/dist/index.d.ts +0 -51
  102. package/dist/index.d.ts.map +0 -1
  103. package/dist/index.js +0 -3687
  104. package/dist/index.js.map +0 -1
  105. package/dist/invocation-config.d.ts +0 -108
  106. package/dist/invocation-config.d.ts.map +0 -1
  107. package/dist/invocation-config.js +0 -84
  108. package/dist/invocation-config.js.map +0 -1
  109. package/dist/memory.d.ts +0 -54
  110. package/dist/memory.d.ts.map +0 -1
  111. package/dist/memory.js +0 -166
  112. package/dist/memory.js.map +0 -1
  113. package/dist/mention-clone.d.ts +0 -88
  114. package/dist/mention-clone.d.ts.map +0 -1
  115. package/dist/mention-clone.js +0 -154
  116. package/dist/mention-clone.js.map +0 -1
  117. package/dist/mention.d.ts +0 -82
  118. package/dist/mention.d.ts.map +0 -1
  119. package/dist/mention.js +0 -132
  120. package/dist/mention.js.map +0 -1
  121. package/dist/model-resolver.d.ts +0 -37
  122. package/dist/model-resolver.d.ts.map +0 -1
  123. package/dist/model-resolver.js +0 -96
  124. package/dist/model-resolver.js.map +0 -1
  125. package/dist/model-scope.d.ts +0 -50
  126. package/dist/model-scope.d.ts.map +0 -1
  127. package/dist/model-scope.js +0 -49
  128. package/dist/model-scope.js.map +0 -1
  129. package/dist/nested-tools.d.ts +0 -57
  130. package/dist/nested-tools.d.ts.map +0 -1
  131. package/dist/nested-tools.js +0 -301
  132. package/dist/nested-tools.js.map +0 -1
  133. package/dist/nico-overrides.d.ts +0 -54
  134. package/dist/nico-overrides.d.ts.map +0 -1
  135. package/dist/nico-overrides.js +0 -170
  136. package/dist/nico-overrides.js.map +0 -1
  137. package/dist/output-file.d.ts +0 -44
  138. package/dist/output-file.d.ts.map +0 -1
  139. package/dist/output-file.js +0 -156
  140. package/dist/output-file.js.map +0 -1
  141. package/dist/prompts.d.ts +0 -56
  142. package/dist/prompts.d.ts.map +0 -1
  143. package/dist/prompts.js +0 -92
  144. package/dist/prompts.js.map +0 -1
  145. package/dist/schedule-store.d.ts +0 -39
  146. package/dist/schedule-store.d.ts.map +0 -1
  147. package/dist/schedule-store.js +0 -156
  148. package/dist/schedule-store.js.map +0 -1
  149. package/dist/schedule.d.ts +0 -110
  150. package/dist/schedule.d.ts.map +0 -1
  151. package/dist/schedule.js +0 -360
  152. package/dist/schedule.js.map +0 -1
  153. package/dist/settings.d.ts +0 -354
  154. package/dist/settings.d.ts.map +0 -1
  155. package/dist/settings.js +0 -247
  156. package/dist/settings.js.map +0 -1
  157. package/dist/skill-loader.d.ts +0 -25
  158. package/dist/skill-loader.d.ts.map +0 -1
  159. package/dist/skill-loader.js +0 -94
  160. package/dist/skill-loader.js.map +0 -1
  161. package/dist/status-note.d.ts +0 -62
  162. package/dist/status-note.d.ts.map +0 -1
  163. package/dist/status-note.js +0 -86
  164. package/dist/status-note.js.map +0 -1
  165. package/dist/structured-output.d.ts +0 -62
  166. package/dist/structured-output.d.ts.map +0 -1
  167. package/dist/structured-output.js +0 -113
  168. package/dist/structured-output.js.map +0 -1
  169. package/dist/types.d.ts +0 -372
  170. package/dist/types.d.ts.map +0 -1
  171. package/dist/types.js +0 -6
  172. package/dist/types.js.map +0 -1
  173. package/dist/ui/agent-mention.d.ts +0 -83
  174. package/dist/ui/agent-mention.d.ts.map +0 -1
  175. package/dist/ui/agent-mention.js +0 -188
  176. package/dist/ui/agent-mention.js.map +0 -1
  177. package/dist/ui/agent-widget.d.ts +0 -241
  178. package/dist/ui/agent-widget.d.ts.map +0 -1
  179. package/dist/ui/agent-widget.js +0 -992
  180. package/dist/ui/agent-widget.js.map +0 -1
  181. package/dist/ui/ccstyle/diff/ansi-utils.d.ts +0 -11
  182. package/dist/ui/ccstyle/diff/ansi-utils.d.ts.map +0 -1
  183. package/dist/ui/ccstyle/diff/ansi-utils.js +0 -145
  184. package/dist/ui/ccstyle/diff/ansi-utils.js.map +0 -1
  185. package/dist/ui/ccstyle/diff/diff-presentation.d.ts +0 -12
  186. package/dist/ui/ccstyle/diff/diff-presentation.d.ts.map +0 -1
  187. package/dist/ui/ccstyle/diff/diff-presentation.js +0 -48
  188. package/dist/ui/ccstyle/diff/diff-presentation.js.map +0 -1
  189. package/dist/ui/ccstyle/diff/diff-renderer.d.ts +0 -46
  190. package/dist/ui/ccstyle/diff/diff-renderer.d.ts.map +0 -1
  191. package/dist/ui/ccstyle/diff/diff-renderer.js +0 -2049
  192. package/dist/ui/ccstyle/diff/diff-renderer.js.map +0 -1
  193. package/dist/ui/ccstyle/diff/line-width-safety.d.ts +0 -12
  194. package/dist/ui/ccstyle/diff/line-width-safety.d.ts.map +0 -1
  195. package/dist/ui/ccstyle/diff/line-width-safety.js +0 -58
  196. package/dist/ui/ccstyle/diff/line-width-safety.js.map +0 -1
  197. package/dist/ui/ccstyle/diff/render-utils.d.ts +0 -6
  198. package/dist/ui/ccstyle/diff/render-utils.d.ts.map +0 -1
  199. package/dist/ui/ccstyle/diff/render-utils.js +0 -25
  200. package/dist/ui/ccstyle/diff/render-utils.js.map +0 -1
  201. package/dist/ui/ccstyle/diff/shiki-highlight.d.ts +0 -19
  202. package/dist/ui/ccstyle/diff/shiki-highlight.d.ts.map +0 -1
  203. package/dist/ui/ccstyle/diff/shiki-highlight.js +0 -85
  204. package/dist/ui/ccstyle/diff/shiki-highlight.js.map +0 -1
  205. package/dist/ui/ccstyle/diff/types.d.ts +0 -22
  206. package/dist/ui/ccstyle/diff/types.d.ts.map +0 -1
  207. package/dist/ui/ccstyle/diff/types.js +0 -10
  208. package/dist/ui/ccstyle/diff/types.js.map +0 -1
  209. package/dist/ui/ccstyle/diff/write-display-utils.d.ts +0 -2
  210. package/dist/ui/ccstyle/diff/write-display-utils.d.ts.map +0 -1
  211. package/dist/ui/ccstyle/diff/write-display-utils.js +0 -12
  212. package/dist/ui/ccstyle/diff/write-display-utils.js.map +0 -1
  213. package/dist/ui/ccstyle/tool-renderer.d.ts +0 -17
  214. package/dist/ui/ccstyle/tool-renderer.d.ts.map +0 -1
  215. package/dist/ui/ccstyle/tool-renderer.js +0 -74
  216. package/dist/ui/ccstyle/tool-renderer.js.map +0 -1
  217. package/dist/ui/ccstyle/tool-result.d.ts +0 -44
  218. package/dist/ui/ccstyle/tool-result.d.ts.map +0 -1
  219. package/dist/ui/ccstyle/tool-result.js +0 -423
  220. package/dist/ui/ccstyle/tool-result.js.map +0 -1
  221. package/dist/ui/conversation-blocks.d.ts +0 -46
  222. package/dist/ui/conversation-blocks.d.ts.map +0 -1
  223. package/dist/ui/conversation-blocks.js +0 -313
  224. package/dist/ui/conversation-blocks.js.map +0 -1
  225. package/dist/ui/conversation-nvim.d.ts +0 -8
  226. package/dist/ui/conversation-nvim.d.ts.map +0 -1
  227. package/dist/ui/conversation-nvim.js +0 -117
  228. package/dist/ui/conversation-nvim.js.map +0 -1
  229. package/dist/ui/conversation-role.d.ts +0 -13
  230. package/dist/ui/conversation-role.d.ts.map +0 -1
  231. package/dist/ui/conversation-role.js +0 -39
  232. package/dist/ui/conversation-role.js.map +0 -1
  233. package/dist/ui/conversation-search.d.ts +0 -39
  234. package/dist/ui/conversation-search.d.ts.map +0 -1
  235. package/dist/ui/conversation-search.js +0 -124
  236. package/dist/ui/conversation-search.js.map +0 -1
  237. package/dist/ui/conversation-timeline.d.ts +0 -102
  238. package/dist/ui/conversation-timeline.d.ts.map +0 -1
  239. package/dist/ui/conversation-timeline.js +0 -555
  240. package/dist/ui/conversation-timeline.js.map +0 -1
  241. package/dist/ui/conversation-viewer.d.ts +0 -138
  242. package/dist/ui/conversation-viewer.d.ts.map +0 -1
  243. package/dist/ui/conversation-viewer.js +0 -1175
  244. package/dist/ui/conversation-viewer.js.map +0 -1
  245. package/dist/ui/schedule-menu.d.ts +0 -17
  246. package/dist/ui/schedule-menu.d.ts.map +0 -1
  247. package/dist/ui/schedule-menu.js +0 -95
  248. package/dist/ui/schedule-menu.js.map +0 -1
  249. package/dist/ui/select-item.d.ts +0 -28
  250. package/dist/ui/select-item.d.ts.map +0 -1
  251. package/dist/ui/select-item.js +0 -35
  252. package/dist/ui/select-item.js.map +0 -1
  253. package/dist/ui/viewer-keys.d.ts +0 -21
  254. package/dist/ui/viewer-keys.d.ts.map +0 -1
  255. package/dist/ui/viewer-keys.js +0 -18
  256. package/dist/ui/viewer-keys.js.map +0 -1
  257. package/dist/ui/workflow-card.d.ts +0 -176
  258. package/dist/ui/workflow-card.d.ts.map +0 -1
  259. package/dist/ui/workflow-card.js +0 -333
  260. package/dist/ui/workflow-card.js.map +0 -1
  261. package/dist/ui/workflow-dialog.d.ts +0 -306
  262. package/dist/ui/workflow-dialog.d.ts.map +0 -1
  263. package/dist/ui/workflow-dialog.js +0 -844
  264. package/dist/ui/workflow-dialog.js.map +0 -1
  265. package/dist/ui/workflow-menu.d.ts +0 -42
  266. package/dist/ui/workflow-menu.d.ts.map +0 -1
  267. package/dist/ui/workflow-menu.js +0 -127
  268. package/dist/ui/workflow-menu.js.map +0 -1
  269. package/dist/usage.d.ts +0 -136
  270. package/dist/usage.d.ts.map +0 -1
  271. package/dist/usage.js +0 -121
  272. package/dist/usage.js.map +0 -1
  273. package/dist/workflow/collisions.d.ts +0 -96
  274. package/dist/workflow/collisions.d.ts.map +0 -1
  275. package/dist/workflow/collisions.js +0 -89
  276. package/dist/workflow/collisions.js.map +0 -1
  277. package/dist/workflow/entry.d.ts +0 -33
  278. package/dist/workflow/entry.d.ts.map +0 -1
  279. package/dist/workflow/entry.js +0 -30
  280. package/dist/workflow/entry.js.map +0 -1
  281. package/dist/workflow/host.d.ts +0 -63
  282. package/dist/workflow/host.d.ts.map +0 -1
  283. package/dist/workflow/host.js +0 -363
  284. package/dist/workflow/host.js.map +0 -1
  285. package/dist/workflow/journal.d.ts +0 -98
  286. package/dist/workflow/journal.d.ts.map +0 -1
  287. package/dist/workflow/journal.js +0 -121
  288. package/dist/workflow/journal.js.map +0 -1
  289. package/dist/workflow/json-schema.d.ts +0 -52
  290. package/dist/workflow/json-schema.d.ts.map +0 -1
  291. package/dist/workflow/json-schema.js +0 -112
  292. package/dist/workflow/json-schema.js.map +0 -1
  293. package/dist/workflow/meta.d.ts +0 -68
  294. package/dist/workflow/meta.d.ts.map +0 -1
  295. package/dist/workflow/meta.js +0 -318
  296. package/dist/workflow/meta.js.map +0 -1
  297. package/dist/workflow/progress.d.ts +0 -225
  298. package/dist/workflow/progress.d.ts.map +0 -1
  299. package/dist/workflow/progress.js +0 -362
  300. package/dist/workflow/progress.js.map +0 -1
  301. package/dist/workflow/runtime.d.ts +0 -335
  302. package/dist/workflow/runtime.d.ts.map +0 -1
  303. package/dist/workflow/runtime.js +0 -831
  304. package/dist/workflow/runtime.js.map +0 -1
  305. package/dist/workflow/saved.d.ts +0 -91
  306. package/dist/workflow/saved.d.ts.map +0 -1
  307. package/dist/workflow/saved.js +0 -204
  308. package/dist/workflow/saved.js.map +0 -1
  309. package/dist/workflow/task.d.ts +0 -137
  310. package/dist/workflow/task.d.ts.map +0 -1
  311. package/dist/workflow/task.js +0 -208
  312. package/dist/workflow/task.js.map +0 -1
  313. package/dist/workflow/tool-description.d.ts +0 -39
  314. package/dist/workflow/tool-description.d.ts.map +0 -1
  315. package/dist/workflow/tool-description.js +0 -200
  316. package/dist/workflow/tool-description.js.map +0 -1
  317. package/dist/workflow/worker-source.d.ts +0 -48
  318. package/dist/workflow/worker-source.d.ts.map +0 -1
  319. package/dist/workflow/worker-source.js +0 -779
  320. package/dist/workflow/worker-source.js.map +0 -1
  321. package/dist/worktree.d.ts +0 -53
  322. package/dist/worktree.d.ts.map +0 -1
  323. package/dist/worktree.js +0 -165
  324. package/dist/worktree.js.map +0 -1
  325. package/dist/write-execution.d.ts +0 -42
  326. package/dist/write-execution.d.ts.map +0 -1
  327. package/dist/write-execution.js +0 -138
  328. package/dist/write-execution.js.map +0 -1
  329. package/dist/xml.d.ts +0 -11
  330. package/dist/xml.d.ts.map +0 -1
  331. package/dist/xml.js +0 -13
  332. package/dist/xml.js.map +0 -1
  333. package/src/abortable.ts +0 -43
  334. package/src/agent-color.ts +0 -161
  335. package/src/agent-file-toggle.ts +0 -269
  336. package/src/child-context.ts +0 -15
  337. package/src/mention-clone.ts +0 -196
  338. package/src/mention.ts +0 -141
  339. package/src/model-scope.ts +0 -70
  340. package/src/nested-tools.ts +0 -424
  341. package/src/structured-output.ts +0 -130
  342. package/src/ui/agent-mention.ts +0 -216
  343. package/src/ui/select-item.ts +0 -45
  344. package/src/ui/workflow-card.ts +0 -470
  345. package/src/ui/workflow-dialog.ts +0 -1115
  346. package/src/ui/workflow-menu.ts +0 -166
  347. package/src/workflow/collisions.ts +0 -123
  348. package/src/workflow/entry.ts +0 -47
  349. package/src/workflow/host.ts +0 -403
  350. package/src/workflow/journal.ts +0 -164
  351. package/src/workflow/json-schema.ts +0 -128
  352. package/src/workflow/meta.ts +0 -325
  353. package/src/workflow/progress.ts +0 -550
  354. package/src/workflow/runtime.ts +0 -1219
  355. package/src/workflow/saved.ts +0 -217
  356. package/src/workflow/task.ts +0 -302
  357. package/src/workflow/tool-description.ts +0 -200
  358. package/src/workflow/worker-source.ts +0 -781
  359. package/src/xml.ts +0 -13
@@ -1,130 +0,0 @@
1
- /**
2
- * structured-output.ts — the synthetic tool behind `agent(prompt, { schema })`.
3
- *
4
- * A workflow script that passes a `schema` wants an *object* back, not prose it
5
- * has to parse. Claude Code does this by giving the child a `StructuredOutput`
6
- * tool whose input schema is the caller's schema, so the provider fills the
7
- * fields, and returning the validated payload as the agent's result.
8
- *
9
- * We do the same, with one gap named up front: Claude Code *forces* the call,
10
- * and we cannot. `toolChoice` exists in pi-ai's provider layer but is not
11
- * plumbed through `AgentSession`, so an extension has no way to require a
12
- * particular tool. What we have instead is three softer pressures —
13
- *
14
- * 1. `constrainedSampling`, so providers that support it hold the payload to
15
- * the schema at sampling time;
16
- * 2. the tool's description, snippet and guideline, which say the answer must
17
- * come through this call;
18
- * 3. validation here, answering a bad payload with `isError` so the model
19
- * sees what was wrong and calls again inside the same run.
20
- *
21
- * — and, when all three fail, one more prompt from `runAgent`. See
22
- * {@link structuredRetryPrompt}.
23
- *
24
- * The name matches Claude Code's exactly, so a ported prompt that mentions
25
- * `StructuredOutput` is still telling the truth.
26
- */
27
-
28
- import { defineTool, type ToolDefinition } from "@earendil-works/pi-coding-agent";
29
- import type { CompiledSchema } from "./workflow/json-schema.js";
30
-
31
- /**
32
- * Deliberately NOT added to `SUBAGENT_TOOL_NAMES`: that list becomes
33
- * `EXCLUDED_TOOL_NAMES`, which is exactly the denial this tool has to avoid.
34
- * Nor to `BUILTIN_TOOL_NAMES` — it is ours to inject, never a name a user may
35
- * ask for in an agent's `tools:` frontmatter.
36
- */
37
- export const STRUCTURED_OUTPUT_TOOL_NAME = "StructuredOutput";
38
-
39
- /** What the child produced, filled in as the tool is called. */
40
- export interface StructuredCapture {
41
- /** The last payload that validated, canonicalised. Absent until one does. */
42
- json?: string;
43
- /** Why the most recent attempt was rejected, for the retry prompt. */
44
- lastError?: string;
45
- /** Whether the tool was called at all — "never tried" reads differently. */
46
- called: boolean;
47
- }
48
-
49
- export function createStructuredCapture(): StructuredCapture {
50
- return { called: false };
51
- }
52
-
53
- /**
54
- * Build the tool for one child.
55
- *
56
- * `capture` is the box the caller reads afterwards. It is passed in rather than
57
- * returned so `runAgent` owns its lifetime and can consult it on every exit
58
- * path, including the ones where the tool was never reached.
59
- */
60
- export function createStructuredOutputTool(
61
- compiled: CompiledSchema,
62
- capture: StructuredCapture,
63
- ): ToolDefinition {
64
- return defineTool({
65
- name: STRUCTURED_OUTPUT_TOOL_NAME,
66
- label: "Structured Output",
67
- description:
68
- "Report your final answer. Call this exactly once, with the complete result, and put everything the "
69
- + "caller needs inside the arguments — text written outside this call is discarded. If a call is "
70
- + "rejected for not matching the schema, fix the reported fields and call it again.",
71
- promptSnippet: "Report your final answer as structured data",
72
- promptGuidelines: [
73
- "Your final answer MUST be reported by calling StructuredOutput. Prose outside that call is discarded.",
74
- ],
75
- // The caller's schema *is* the tool's input schema, verbatim — that is what
76
- // makes the provider fill the fields. pi types this as TypeBox's `TSchema`,
77
- // which v1 defines as an open interface, so a plain JSON Schema satisfies
78
- // it without a cast at runtime or a conversion at author time.
79
- parameters: compiled.schema as never,
80
- // "prefer", not "require": a provider that cannot constrain sampling should
81
- // fall through to validation-and-retry rather than fail the call outright.
82
- constrainedSampling: { type: "json_schema", strict: "prefer" },
83
- // Models occasionally send the whole payload as one JSON string instead of
84
- // an object. Recovering that costs nothing and saves a whole retry.
85
- prepareArguments: (args: unknown) => {
86
- if (typeof args !== "string") return args as never;
87
- try {
88
- return JSON.parse(args) as never;
89
- } catch {
90
- return args as never;
91
- }
92
- },
93
- execute: async (_toolCallId, params) => {
94
- capture.called = true;
95
- const verdict = compiled.check(params);
96
- if (verdict !== true) {
97
- capture.lastError = verdict;
98
- // `isError` puts the reason in front of the model as a tool result, so
99
- // it can correct itself inside this same run. This is where most
100
- // mismatches are resolved; the prompt-level retry is the backstop.
101
- return {
102
- content: [{
103
- type: "text",
104
- text: `StructuredOutput did not match the required schema:\n${verdict}\nCall it again with a corrected value.`,
105
- }],
106
- isError: true,
107
- details: {},
108
- };
109
- }
110
- // Last valid call wins: a model that calls twice meant the second one.
111
- capture.json = JSON.stringify(params);
112
- capture.lastError = undefined;
113
- return { content: [{ type: "text", text: "Recorded." }], details: {} };
114
- },
115
- }) as ToolDefinition;
116
- }
117
-
118
- /**
119
- * The one extra prompt sent when a run ended with nothing captured.
120
- *
121
- * Distinguishes "never called it" from "called it wrongly" — the two need
122
- * different corrections, and telling a model it got the shape wrong when it
123
- * never answered at all sends it looking for a mistake it did not make.
124
- */
125
- export function structuredRetryPrompt(capture: StructuredCapture): string {
126
- const reason = capture.called && capture.lastError !== undefined
127
- ? `Your last ${STRUCTURED_OUTPUT_TOOL_NAME} call did not match the required schema: ${capture.lastError}`
128
- : `You did not call ${STRUCTURED_OUTPUT_TOOL_NAME}, so your answer was not recorded.`;
129
- return `${reason}\n\nCall ${STRUCTURED_OUTPUT_TOOL_NAME} now with your complete final answer. Do not reply with prose.`;
130
- }
@@ -1,216 +0,0 @@
1
- /**
2
- * agent-mention.ts — what `@` can address, and the suggestions pi renders for it.
3
- *
4
- * A subagent is addressable whether or not it is currently running: a live
5
- * record is messaged or resumed, an evicted one whose session is still on disk
6
- * is reopened, and an agent *type* with no instance at all is started. That is
7
- * the point of the handle — `@explore` means the Explore agent, not "the
8
- * Explore process that happens to exist right now" — so the roster below unions
9
- * all three, and the dispatcher and the popup read the same list.
10
- *
11
- * Rows are per *agent*, not per handle. An agent given a `name` holds two names
12
- * (its alias and its type-derived handle) and both resolve, but it lists once,
13
- * under the alias, with its type moved into the description so the row still
14
- * says what it is.
15
- *
16
- * pi's `CombinedAutocompleteProvider` already owns `@`, where it means "attach a
17
- * file". Extensions can wrap it (`ctx.ui.addAutocompleteProvider`), so this
18
- * provider adds the `@` tokens that name an agent and delegates everything else
19
- * — including all of `applyCompletion`, whose `@`-branch already inserts
20
- * `item.value` plus a trailing space, which is exactly what a handle needs.
21
- *
22
- * Matching mirrors Claude Code: case-insensitive prefix, not fuzzy. What it does
23
- * NOT mirror is Claude Code dropping files whenever an agent matches. Here `@` is
24
- * pi's file picker first, and the handles are additive, so a token matching both
25
- * lists both — agents first. Suppressing on any match sounds narrow and is not:
26
- * an empty token prefix-matches every handle, so a bare `@` — the gesture people
27
- * use to browse files — would offer no files at all, and a single letter
28
- * beginning any handle would do the same.
29
- *
30
- * Both halves ship under ONE `prefix`, which is sound because wherever BOTH sides
31
- * produce rows they measured the same span. pi's `extractAtPrefix` takes the
32
- * token after the last of `{space, tab, ", ', =}` and keeps it only if it starts
33
- * with `@`; `MENTION_TRIGGER` matches `@[\w-]*` at the cursor, after start-of-line
34
- * or `[\s。、?!]`. Where those two disagree, exactly one side answers and there
35
- * is nothing to merge: `@src/index.ts` and `@"my file` are pi's alone (no handle
36
- * matches), `=@ex` is pi's alone (`=` is a delimiter to pi, not a boundary to us),
37
- * and `。@ex` is ours alone (the reverse). A merged response therefore never
38
- * carries a prefix from one side and an item from the other.
39
- *
40
- * Offering never-started types is a deliberate step beyond Claude Code, whose
41
- * registry holds only live tasks, so an agent you had not launched yet was
42
- * unaddressable.
43
- */
44
-
45
- import type { AutocompleteItem, AutocompleteProvider, AutocompleteSuggestions } from "@earendil-works/pi-tui";
46
- import type { AgentManager } from "../agent-manager.js";
47
- import { handleBase, MENTION_TRIGGER } from "../mention.js";
48
- import type { AgentRecord, AgentTombstone } from "../types.js";
49
-
50
- /**
51
- * One thing `@` can address, and what sending to it will do. `typeLabel` is the
52
- * agent's `display_name`, resolved by the caller: this module stays independent
53
- * of the type registry, but the popup must agree with the Agents widget,
54
- * which renders the label rather than the raw type.
55
- */
56
- export type MentionTarget =
57
- | { kind: "record"; handle: string; record: AgentRecord; typeLabel: string }
58
- | { kind: "tombstone"; handle: string; entry: AgentTombstone; typeLabel: string }
59
- | { kind: "type"; handle: string; type: string; description: string };
60
-
61
- /** The registry facts the roster needs, so it stays independent of agent-types. */
62
- export type TypeInfo = { name: string; description: string };
63
-
64
- /**
65
- * Everything `@` can reach, in the order the popup lists it: steerable agents
66
- * first, then the other live ones earliest-launched, then agent types with no
67
- * live instance. A type whose handle a record already holds is omitted — that
68
- * name addresses the existing agent, which is what makes `@explore` mean
69
- * "message the one that's running" and only otherwise "start one".
70
- */
71
- export function mentionRoster(
72
- manager: AgentManager,
73
- types: readonly TypeInfo[],
74
- // Identity by default: a caller with no registry to consult gets the raw
75
- // type, which is also what `getConfig` falls back to when no label is set.
76
- displayNameOf: (type: string) => string = type => type,
77
- ): MentionTarget[] {
78
- const live = (r: AgentRecord) => r.status === "running" || r.status === "queued";
79
- const records = manager.listAgents()
80
- .filter(r => r.handle !== undefined && r.parentAgentId === undefined)
81
- .sort((a, b) => (Number(live(b)) - Number(live(a))) || (a.startedAt - b.startedAt));
82
-
83
- const taken = new Set<string>();
84
- const targets: MentionTarget[] = [];
85
-
86
- // One row per agent, not per handle. An aliased agent lists under its alias
87
- // only — both names resolve, but showing two rows for one agent reads as two
88
- // agents. The type handle stays addressable whether or not it is listed.
89
- for (const record of records) {
90
- const handle = record.alias ?? record.handle!;
91
- taken.add(handle.toLowerCase());
92
- if (record.handle) taken.add(record.handle.toLowerCase());
93
- targets.push({ kind: "record", handle, record, typeLabel: displayNameOf(record.type) });
94
- }
95
-
96
- // Then agents that are gone but whose conversation can be reopened. After the
97
- // live ones: a running agent is the likelier target, and this keeps the
98
- // ordering "what exists now, then what can be brought back, then what can be
99
- // started".
100
- for (const entry of manager.listTombstones()) {
101
- const handle = entry.alias ?? entry.handle;
102
- if (taken.has(handle.toLowerCase())) continue;
103
- taken.add(handle.toLowerCase());
104
- taken.add(entry.handle.toLowerCase());
105
- targets.push({ kind: "tombstone", handle, entry, typeLabel: displayNameOf(entry.type) });
106
- }
107
-
108
- for (const type of types) {
109
- const handle = handleBase(type.name);
110
- if (taken.has(handle)) continue;
111
- taken.add(handle);
112
- targets.push({ kind: "type", handle, type: type.name, description: type.description });
113
- }
114
- return targets;
115
- }
116
-
117
- export function createMentionProvider(
118
- current: AutocompleteProvider,
119
- roster: () => MentionTarget[],
120
- isEnabled: () => boolean,
121
- ): AutocompleteProvider {
122
- // One warning per provider, not per keystroke: `getSuggestions` runs on every
123
- // character typed after `@`, so an unguarded log would bury the terminal in
124
- // the time it takes to finish a word.
125
- let warnedInnerFailure = false;
126
- return {
127
- // Only `@` — the contract is "characters that should naturally trigger
128
- // THIS provider", and pi unions each wrapper's own set onto the outermost
129
- // one itself (interactive-mode.js:432), so re-declaring the wrapped
130
- // provider's characters here would both misreport us and duplicate that.
131
- triggerCharacters: ["@"],
132
-
133
- async getSuggestions(lines, cursorLine, cursorCol, options): Promise<AutocompleteSuggestions | null> {
134
- const mine = isEnabled() ? mentionItems(roster(), lines[cursorLine] ?? "", cursorCol) : null;
135
- // Asked unconditionally: pi owns `@` and must keep answering for it even
136
- // when a handle matches too. That is the same work vanilla pi does on any
137
- // `@` keystroke — a capped `fd` search, or nothing at all when the host
138
- // configured no `fd` path — but we now do it on tokens we used to answer
139
- // alone, so it must not be able to take the popup down with it. The
140
- // wrapped provider is not always pi's: another extension may sit inside
141
- // us, and before this it was never called for a token naming an agent.
142
- // try/catch, not `.catch()`: a provider that throws SYNCHRONOUSLY never
143
- // returns the promise a `.catch()` would attach to, and the throw escapes
144
- // this method as a rejection — which pi does not handle either
145
- // (components/editor.js:1892 awaits with no catch of its own).
146
- let theirs: AutocompleteSuggestions | null = null;
147
- try {
148
- theirs = await current.getSuggestions(lines, cursorLine, cursorCol, options);
149
- } catch (err) {
150
- // Safe to treat as "no files": pi discards any response whose request is
151
- // no longer current, so an aborted search that surfaces as a rejection
152
- // cannot leave a stale popup behind (`isAutocompleteRequestCurrent`).
153
- // Warned rather than swallowed outright — the failure is invisible in
154
- // the popup, and the same `console.warn` channel already carries this
155
- // extension's other non-fatal failures.
156
- if (!warnedInnerFailure) {
157
- warnedInnerFailure = true;
158
- console.warn("[pi-subagents] the autocomplete provider below us failed; showing agent rows only:", err);
159
- }
160
- theirs = null;
161
- }
162
- if (!mine) return theirs;
163
- if (!theirs) return mine;
164
- // Agents first: there are a handful of them against pi's 20 file rows, and
165
- // a handle buried under fuzzy path matches is a handle nobody finds. The
166
- // prefix is ours by the span argument in the header — identical to pi's
167
- // whenever both sides have something to say.
168
- return { items: [...mine.items, ...theirs.items], prefix: mine.prefix };
169
- },
170
-
171
- applyCompletion(lines, cursorLine, cursorCol, item, prefix) {
172
- return current.applyCompletion(lines, cursorLine, cursorCol, item, prefix);
173
- },
174
-
175
- shouldTriggerFileCompletion(lines, cursorLine, cursorCol) {
176
- return current.shouldTriggerFileCompletion?.(lines, cursorLine, cursorCol) ?? true;
177
- },
178
- };
179
- }
180
-
181
- /** Suggestions for the `@…` token under the cursor, or null when it names no agent. */
182
- function mentionItems(roster: MentionTarget[], line: string, cursorCol: number): AutocompleteSuggestions | null {
183
- const match = MENTION_TRIGGER.exec(line.slice(0, cursorCol));
184
- if (!match) return null;
185
-
186
- const typed = match[2].toLowerCase();
187
- const items: AutocompleteItem[] = [];
188
- for (const target of roster) {
189
- if (!target.handle.toLowerCase().startsWith(typed)) continue;
190
- items.push({ value: `@${target.handle}`, label: `@${target.handle}`, description: describeTarget(target) });
191
- }
192
- return items.length > 0 ? { items, prefix: `@${match[2]}` } : null;
193
- }
194
-
195
- /** Name the action that will actually happen, so the list never mispromises. */
196
- function describeTarget(target: MentionTarget): string {
197
- if (target.kind === "type") return `start agent · ${summarize(target.description)}`;
198
- if (target.kind === "tombstone") {
199
- // No status: the record is gone, and "completed" would imply one is still
200
- // being tracked. The type carries the identity the handle may not.
201
- return `resume · ${target.typeLabel} · ${target.entry.description}`;
202
- }
203
- const { status, description, alias } = target.record;
204
- const action = status === "running" || status === "queued" ? "send message" : "resume";
205
- // A row listed under its alias has lost the type its handle would have shown,
206
- // so name it — `@auth-audit` alone says nothing about what the agent is.
207
- // A type-derived row already reads as its type and would just repeat itself.
208
- const identity = alias ? `${target.typeLabel} · ` : "";
209
- return `${action} · ${identity}${status} · ${description}`;
210
- }
211
-
212
- /** First sentence of an agent description, clipped — these run to paragraphs. */
213
- function summarize(description: string): string {
214
- const first = (description.match(/^.*?[.!?](?=\s|$)/s)?.[0] ?? description).replace(/\s+/g, " ").trim();
215
- return first.length > 60 ? `${first.slice(0, 59).trimEnd()}…` : first;
216
- }
@@ -1,45 +0,0 @@
1
- /**
2
- * select-item.ts — pick an item from a list via `ctx.ui.select`, safely.
3
- *
4
- * Pi's dialog API is `select(title, options: string[]) => Promise<string | undefined>`:
5
- * strings in, string out, with no index or value form. Callers therefore have to
6
- * map the returned string back to the item it came from, and the obvious way —
7
- * `labels.indexOf(choice)` over a parallel array — silently resolves to the
8
- * FIRST match whenever two rows format identically. Row formatters here truncate
9
- * (job names to 18 chars, agent descriptions to whatever fits), and the text they
10
- * truncate is LLM-authored, so collisions are ordinary rather than exotic.
11
- *
12
- * This numbers every row, which makes the labels unique by construction — no
13
- * data-dependent branch that only executes in the case nobody exercises — and
14
- * keeps each label paired with its item so a later edit that sorts or filters
15
- * between building and resolving cannot desync them.
16
- */
17
-
18
- /** Minimal shape of the `ctx.ui` surface this needs. */
19
- export interface SelectUI {
20
- select(title: string, options: string[]): Promise<string | undefined>;
21
- }
22
-
23
- /**
24
- * Show a numbered picker and return the chosen item (not its label).
25
- *
26
- * Returns undefined when the user escapes, or when the returned string is not
27
- * one we offered.
28
- */
29
- export async function selectItem<T>(
30
- ui: SelectUI,
31
- title: string,
32
- items: readonly T[],
33
- format: (item: T, index: number) => string,
34
- ): Promise<T | undefined> {
35
- // Pad the number so a 10+ item list keeps its columns aligned.
36
- const width = String(items.length).length;
37
- const rows = items.map((item, i) => ({
38
- item,
39
- label: `${String(i + 1).padStart(width)}. ${format(item, i)}`,
40
- }));
41
-
42
- const choice = await ui.select(title, rows.map(r => r.label));
43
- if (!choice) return undefined;
44
- return rows.find(r => r.label === choice)?.item;
45
- }