pi-ui-extend 0.1.1

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 (420) hide show
  1. package/README.md +307 -0
  2. package/bin/pix.mjs +219 -0
  3. package/dist/app/app.d.ts +96 -0
  4. package/dist/app/app.js +871 -0
  5. package/dist/app/blink-controller.d.ts +23 -0
  6. package/dist/app/blink-controller.js +82 -0
  7. package/dist/app/cli.d.ts +8 -0
  8. package/dist/app/cli.js +83 -0
  9. package/dist/app/clipboard.d.ts +1 -0
  10. package/dist/app/clipboard.js +24 -0
  11. package/dist/app/command-controller.d.ts +18 -0
  12. package/dist/app/command-controller.js +58 -0
  13. package/dist/app/command-host.d.ts +44 -0
  14. package/dist/app/command-host.js +1 -0
  15. package/dist/app/command-model-actions.d.ts +12 -0
  16. package/dist/app/command-model-actions.js +176 -0
  17. package/dist/app/command-navigation-actions.d.ts +19 -0
  18. package/dist/app/command-navigation-actions.js +267 -0
  19. package/dist/app/command-registry.d.ts +32 -0
  20. package/dist/app/command-registry.js +225 -0
  21. package/dist/app/command-runtime.d.ts +5 -0
  22. package/dist/app/command-runtime.js +32 -0
  23. package/dist/app/command-session-actions.d.ts +20 -0
  24. package/dist/app/command-session-actions.js +295 -0
  25. package/dist/app/constants.d.ts +52 -0
  26. package/dist/app/constants.js +103 -0
  27. package/dist/app/conversation-entry-renderer.d.ts +21 -0
  28. package/dist/app/conversation-entry-renderer.js +81 -0
  29. package/dist/app/conversation-shell-renderer.d.ts +5 -0
  30. package/dist/app/conversation-shell-renderer.js +43 -0
  31. package/dist/app/conversation-tool-renderer.d.ts +16 -0
  32. package/dist/app/conversation-tool-renderer.js +216 -0
  33. package/dist/app/conversation-viewport.d.ts +55 -0
  34. package/dist/app/conversation-viewport.js +252 -0
  35. package/dist/app/dcp-stats.d.ts +2 -0
  36. package/dist/app/dcp-stats.js +116 -0
  37. package/dist/app/editor-layout-renderer.d.ts +31 -0
  38. package/dist/app/editor-layout-renderer.js +211 -0
  39. package/dist/app/editor-panels.d.ts +4 -0
  40. package/dist/app/editor-panels.js +130 -0
  41. package/dist/app/extension-actions-controller.d.ts +22 -0
  42. package/dist/app/extension-actions-controller.js +60 -0
  43. package/dist/app/extension-event-bus.d.ts +3 -0
  44. package/dist/app/extension-event-bus.js +23 -0
  45. package/dist/app/extension-ui-controller.d.ts +57 -0
  46. package/dist/app/extension-ui-controller.js +532 -0
  47. package/dist/app/file-link-opener.d.ts +2 -0
  48. package/dist/app/file-link-opener.js +66 -0
  49. package/dist/app/file-links.d.ts +10 -0
  50. package/dist/app/file-links.js +117 -0
  51. package/dist/app/guards.d.ts +3 -0
  52. package/dist/app/guards.js +9 -0
  53. package/dist/app/icons.d.ts +37 -0
  54. package/dist/app/icons.js +97 -0
  55. package/dist/app/id.d.ts +1 -0
  56. package/dist/app/id.js +4 -0
  57. package/dist/app/image-click-targets.d.ts +5 -0
  58. package/dist/app/image-click-targets.js +32 -0
  59. package/dist/app/image-opener.d.ts +2 -0
  60. package/dist/app/image-opener.js +64 -0
  61. package/dist/app/input-action-controller.d.ts +47 -0
  62. package/dist/app/input-action-controller.js +209 -0
  63. package/dist/app/input-controller.d.ts +60 -0
  64. package/dist/app/input-controller.js +425 -0
  65. package/dist/app/input-paste-handler.d.ts +27 -0
  66. package/dist/app/input-paste-handler.js +146 -0
  67. package/dist/app/menu-items-controller.d.ts +32 -0
  68. package/dist/app/menu-items-controller.js +182 -0
  69. package/dist/app/message-content.d.ts +8 -0
  70. package/dist/app/message-content.js +115 -0
  71. package/dist/app/model-ref.d.ts +13 -0
  72. package/dist/app/model-ref.js +50 -0
  73. package/dist/app/model-usage-controller.d.ts +35 -0
  74. package/dist/app/model-usage-controller.js +99 -0
  75. package/dist/app/model-usage-status.d.ts +125 -0
  76. package/dist/app/model-usage-status.js +749 -0
  77. package/dist/app/mouse-controller.d.ts +182 -0
  78. package/dist/app/mouse-controller.js +968 -0
  79. package/dist/app/native-modifiers.d.ts +3 -0
  80. package/dist/app/native-modifiers.js +60 -0
  81. package/dist/app/nerd-font-controller.d.ts +11 -0
  82. package/dist/app/nerd-font-controller.js +90 -0
  83. package/dist/app/popup-action-controller.d.ts +44 -0
  84. package/dist/app/popup-action-controller.js +278 -0
  85. package/dist/app/popup-menu-controller.d.ts +183 -0
  86. package/dist/app/popup-menu-controller.js +1070 -0
  87. package/dist/app/prompt-enhancer-controller.d.ts +40 -0
  88. package/dist/app/prompt-enhancer-controller.js +215 -0
  89. package/dist/app/queued-message-controller.d.ts +62 -0
  90. package/dist/app/queued-message-controller.js +377 -0
  91. package/dist/app/render-controller.d.ts +41 -0
  92. package/dist/app/render-controller.js +378 -0
  93. package/dist/app/render-text.d.ts +19 -0
  94. package/dist/app/render-text.js +67 -0
  95. package/dist/app/request-history.d.ts +23 -0
  96. package/dist/app/request-history.js +131 -0
  97. package/dist/app/runtime.d.ts +39 -0
  98. package/dist/app/runtime.js +195 -0
  99. package/dist/app/screen-selection.d.ts +6 -0
  100. package/dist/app/screen-selection.js +9 -0
  101. package/dist/app/screen-styler.d.ts +34 -0
  102. package/dist/app/screen-styler.js +168 -0
  103. package/dist/app/scroll-controller.d.ts +51 -0
  104. package/dist/app/scroll-controller.js +207 -0
  105. package/dist/app/session-event-controller.d.ts +69 -0
  106. package/dist/app/session-event-controller.js +338 -0
  107. package/dist/app/session-history.d.ts +23 -0
  108. package/dist/app/session-history.js +164 -0
  109. package/dist/app/session-lifecycle-controller.d.ts +55 -0
  110. package/dist/app/session-lifecycle-controller.js +116 -0
  111. package/dist/app/session-search.d.ts +24 -0
  112. package/dist/app/session-search.js +215 -0
  113. package/dist/app/shell-command.d.ts +27 -0
  114. package/dist/app/shell-command.js +176 -0
  115. package/dist/app/shell-controller.d.ts +28 -0
  116. package/dist/app/shell-controller.js +124 -0
  117. package/dist/app/slash-commands.d.ts +6 -0
  118. package/dist/app/slash-commands.js +75 -0
  119. package/dist/app/startup-checks.d.ts +8 -0
  120. package/dist/app/startup-checks.js +59 -0
  121. package/dist/app/startup-info.d.ts +3 -0
  122. package/dist/app/startup-info.js +176 -0
  123. package/dist/app/status-controller.d.ts +35 -0
  124. package/dist/app/status-controller.js +105 -0
  125. package/dist/app/status-line-renderer.d.ts +68 -0
  126. package/dist/app/status-line-renderer.js +453 -0
  127. package/dist/app/subagents-files.d.ts +10 -0
  128. package/dist/app/subagents-files.js +193 -0
  129. package/dist/app/subagents-model.d.ts +23 -0
  130. package/dist/app/subagents-model.js +224 -0
  131. package/dist/app/subagents-widget-controller.d.ts +43 -0
  132. package/dist/app/subagents-widget-controller.js +311 -0
  133. package/dist/app/tab-line-renderer.d.ts +26 -0
  134. package/dist/app/tab-line-renderer.js +222 -0
  135. package/dist/app/tabs-controller.d.ts +100 -0
  136. package/dist/app/tabs-controller.js +885 -0
  137. package/dist/app/terminal-controller.d.ts +40 -0
  138. package/dist/app/terminal-controller.js +135 -0
  139. package/dist/app/terminal-edit-shortcuts.d.ts +23 -0
  140. package/dist/app/terminal-edit-shortcuts.js +138 -0
  141. package/dist/app/terminal-output-buffer.d.ts +20 -0
  142. package/dist/app/terminal-output-buffer.js +52 -0
  143. package/dist/app/toast-controller.d.ts +13 -0
  144. package/dist/app/toast-controller.js +40 -0
  145. package/dist/app/toast-renderer.d.ts +9 -0
  146. package/dist/app/toast-renderer.js +79 -0
  147. package/dist/app/todo-model.d.ts +22 -0
  148. package/dist/app/todo-model.js +179 -0
  149. package/dist/app/todo-widget-controller.d.ts +21 -0
  150. package/dist/app/todo-widget-controller.js +59 -0
  151. package/dist/app/tool-block-renderer.d.ts +26 -0
  152. package/dist/app/tool-block-renderer.js +439 -0
  153. package/dist/app/types.d.ts +550 -0
  154. package/dist/app/types.js +1 -0
  155. package/dist/app/update.d.ts +36 -0
  156. package/dist/app/update.js +315 -0
  157. package/dist/app/voice-controller.d.ts +52 -0
  158. package/dist/app/voice-controller.js +600 -0
  159. package/dist/app/workspace-actions-controller.d.ts +40 -0
  160. package/dist/app/workspace-actions-controller.js +215 -0
  161. package/dist/app/workspace-undo.d.ts +44 -0
  162. package/dist/app/workspace-undo.js +215 -0
  163. package/dist/config.d.ts +62 -0
  164. package/dist/config.js +527 -0
  165. package/dist/context-progress-bar.d.ts +17 -0
  166. package/dist/context-progress-bar.js +48 -0
  167. package/dist/default-pix-config.d.ts +1 -0
  168. package/dist/default-pix-config.js +375 -0
  169. package/dist/fuzzy.d.ts +23 -0
  170. package/dist/fuzzy.js +165 -0
  171. package/dist/input-editor-files.d.ts +15 -0
  172. package/dist/input-editor-files.js +62 -0
  173. package/dist/input-editor.d.ts +186 -0
  174. package/dist/input-editor.js +835 -0
  175. package/dist/main.d.ts +1 -0
  176. package/dist/main.js +12 -0
  177. package/dist/markdown-format.d.ts +22 -0
  178. package/dist/markdown-format.js +542 -0
  179. package/dist/sdk.d.ts +3 -0
  180. package/dist/sdk.js +1 -0
  181. package/dist/syntax-highlight.d.ts +22 -0
  182. package/dist/syntax-highlight.js +528 -0
  183. package/dist/terminal-width.d.ts +6 -0
  184. package/dist/terminal-width.js +245 -0
  185. package/dist/theme.d.ts +56 -0
  186. package/dist/theme.js +118 -0
  187. package/dist/tool-renderers/apply-patch.d.ts +2 -0
  188. package/dist/tool-renderers/apply-patch.js +36 -0
  189. package/dist/tool-renderers/ast.d.ts +2 -0
  190. package/dist/tool-renderers/ast.js +5 -0
  191. package/dist/tool-renderers/compress.d.ts +2 -0
  192. package/dist/tool-renderers/compress.js +126 -0
  193. package/dist/tool-renderers/index.d.ts +3 -0
  194. package/dist/tool-renderers/index.js +44 -0
  195. package/dist/tool-renderers/question.d.ts +2 -0
  196. package/dist/tool-renderers/question.js +88 -0
  197. package/dist/tool-renderers/read.d.ts +2 -0
  198. package/dist/tool-renderers/read.js +36 -0
  199. package/dist/tool-renderers/repo.d.ts +2 -0
  200. package/dist/tool-renderers/repo.js +13 -0
  201. package/dist/tool-renderers/shell.d.ts +3 -0
  202. package/dist/tool-renderers/shell.js +27 -0
  203. package/dist/tool-renderers/skill.d.ts +2 -0
  204. package/dist/tool-renderers/skill.js +132 -0
  205. package/dist/tool-renderers/subagents.d.ts +2 -0
  206. package/dist/tool-renderers/subagents.js +51 -0
  207. package/dist/tool-renderers/todo.d.ts +2 -0
  208. package/dist/tool-renderers/todo.js +9 -0
  209. package/dist/tool-renderers/types.d.ts +42 -0
  210. package/dist/tool-renderers/types.js +1 -0
  211. package/dist/tool-renderers/utils.d.ts +31 -0
  212. package/dist/tool-renderers/utils.js +230 -0
  213. package/dist/tool-renderers/web.d.ts +3 -0
  214. package/dist/tool-renderers/web.js +9 -0
  215. package/dist/tool-renderers/write.d.ts +2 -0
  216. package/dist/tool-renderers/write.js +42 -0
  217. package/dist/ui.d.ts +56 -0
  218. package/dist/ui.js +94 -0
  219. package/docs/release.md +81 -0
  220. package/extensions/question/contract.ts +100 -0
  221. package/extensions/question/index.ts +34 -0
  222. package/extensions/question/render.ts +28 -0
  223. package/extensions/question/result.ts +86 -0
  224. package/extensions/question/tool-description.ts +11 -0
  225. package/extensions/question/tui.ts +629 -0
  226. package/extensions/question/types.ts +123 -0
  227. package/extensions/session-title/config.ts +169 -0
  228. package/extensions/session-title/index.ts +459 -0
  229. package/extensions/terminal-bell/index.ts +315 -0
  230. package/external/pi-tools-suite/README.md +242 -0
  231. package/external/pi-tools-suite/index.ts +1 -0
  232. package/external/pi-tools-suite/licenses/ollama-pi-web-search.MIT +21 -0
  233. package/external/pi-tools-suite/licenses/opencode-mystatus-MIT.txt +21 -0
  234. package/external/pi-tools-suite/package.json +53 -0
  235. package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +194 -0
  236. package/external/pi-tools-suite/src/antigravity-auth/commands.ts +80 -0
  237. package/external/pi-tools-suite/src/antigravity-auth/constants.ts +26 -0
  238. package/external/pi-tools-suite/src/antigravity-auth/headers.ts +20 -0
  239. package/external/pi-tools-suite/src/antigravity-auth/index.ts +104 -0
  240. package/external/pi-tools-suite/src/antigravity-auth/models.ts +86 -0
  241. package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +305 -0
  242. package/external/pi-tools-suite/src/antigravity-auth/payload.ts +423 -0
  243. package/external/pi-tools-suite/src/antigravity-auth/status.ts +78 -0
  244. package/external/pi-tools-suite/src/antigravity-auth/stream.ts +302 -0
  245. package/external/pi-tools-suite/src/antigravity-auth/types.ts +113 -0
  246. package/external/pi-tools-suite/src/ast-grep/index.ts +6 -0
  247. package/external/pi-tools-suite/src/ast-grep/render.ts +70 -0
  248. package/external/pi-tools-suite/src/ast-grep/schema.ts +109 -0
  249. package/external/pi-tools-suite/src/ast-grep/tool.ts +345 -0
  250. package/external/pi-tools-suite/src/ast-grep/types.ts +55 -0
  251. package/external/pi-tools-suite/src/ast-grep/utils.ts +65 -0
  252. package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +222 -0
  253. package/external/pi-tools-suite/src/async-subagents/commands.ts +518 -0
  254. package/external/pi-tools-suite/src/async-subagents/constants.ts +11 -0
  255. package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +74 -0
  256. package/external/pi-tools-suite/src/async-subagents/core/attachment-bridge.ts +133 -0
  257. package/external/pi-tools-suite/src/async-subagents/core/cleanup.ts +66 -0
  258. package/external/pi-tools-suite/src/async-subagents/core/concurrency.ts +90 -0
  259. package/external/pi-tools-suite/src/async-subagents/core/config.ts +819 -0
  260. package/external/pi-tools-suite/src/async-subagents/core/log-limits.ts +166 -0
  261. package/external/pi-tools-suite/src/async-subagents/core/model-fallback.ts +133 -0
  262. package/external/pi-tools-suite/src/async-subagents/core/paths.ts +47 -0
  263. package/external/pi-tools-suite/src/async-subagents/core/pi-invocation.ts +35 -0
  264. package/external/pi-tools-suite/src/async-subagents/core/presets.ts +67 -0
  265. package/external/pi-tools-suite/src/async-subagents/core/process.ts +15 -0
  266. package/external/pi-tools-suite/src/async-subagents/core/prompt.ts +66 -0
  267. package/external/pi-tools-suite/src/async-subagents/core/registry.ts +224 -0
  268. package/external/pi-tools-suite/src/async-subagents/core/retry.ts +191 -0
  269. package/external/pi-tools-suite/src/async-subagents/core/routing.ts +259 -0
  270. package/external/pi-tools-suite/src/async-subagents/core/sessions.ts +138 -0
  271. package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +688 -0
  272. package/external/pi-tools-suite/src/async-subagents/core/state.ts +281 -0
  273. package/external/pi-tools-suite/src/async-subagents/core/stop.ts +131 -0
  274. package/external/pi-tools-suite/src/async-subagents/core/structured-result.ts +237 -0
  275. package/external/pi-tools-suite/src/async-subagents/core/tool-guard.ts +34 -0
  276. package/external/pi-tools-suite/src/async-subagents/core/types.ts +150 -0
  277. package/external/pi-tools-suite/src/async-subagents/core/ultrawork-auto.ts +184 -0
  278. package/external/pi-tools-suite/src/async-subagents/core/utils.ts +11 -0
  279. package/external/pi-tools-suite/src/async-subagents/format.ts +41 -0
  280. package/external/pi-tools-suite/src/async-subagents/index.ts +422 -0
  281. package/external/pi-tools-suite/src/async-subagents/lib.ts +88 -0
  282. package/external/pi-tools-suite/src/async-subagents/live.ts +10 -0
  283. package/external/pi-tools-suite/src/async-subagents/polling.ts +83 -0
  284. package/external/pi-tools-suite/src/async-subagents/render.ts +230 -0
  285. package/external/pi-tools-suite/src/async-subagents/subagent-overlay.ts +77 -0
  286. package/external/pi-tools-suite/src/async-subagents/tasks.ts +120 -0
  287. package/external/pi-tools-suite/src/async-subagents/tools/cleanup.ts +99 -0
  288. package/external/pi-tools-suite/src/async-subagents/tools/result.ts +179 -0
  289. package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +372 -0
  290. package/external/pi-tools-suite/src/async-subagents/tools/status.ts +60 -0
  291. package/external/pi-tools-suite/src/async-subagents/tools/stop.ts +79 -0
  292. package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +152 -0
  293. package/external/pi-tools-suite/src/async-subagents/tools/wait.ts +67 -0
  294. package/external/pi-tools-suite/src/async-subagents/types.ts +45 -0
  295. package/external/pi-tools-suite/src/async-subagents/ui.ts +5 -0
  296. package/external/pi-tools-suite/src/compress/commands.ts +440 -0
  297. package/external/pi-tools-suite/src/compress/compress-tool.ts +368 -0
  298. package/external/pi-tools-suite/src/compress/compression-blocks.ts +524 -0
  299. package/external/pi-tools-suite/src/compress/config.ts +310 -0
  300. package/external/pi-tools-suite/src/compress/dcp-tui-filter.ts +498 -0
  301. package/external/pi-tools-suite/src/compress/index.ts +397 -0
  302. package/external/pi-tools-suite/src/compress/prompts.ts +269 -0
  303. package/external/pi-tools-suite/src/compress/pruner-candidates.ts +176 -0
  304. package/external/pi-tools-suite/src/compress/pruner-compression-blocks.ts +260 -0
  305. package/external/pi-tools-suite/src/compress/pruner-message-ids.ts +147 -0
  306. package/external/pi-tools-suite/src/compress/pruner-metadata.ts +268 -0
  307. package/external/pi-tools-suite/src/compress/pruner-nudge.ts +315 -0
  308. package/external/pi-tools-suite/src/compress/pruner-tools.ts +263 -0
  309. package/external/pi-tools-suite/src/compress/pruner-types.ts +25 -0
  310. package/external/pi-tools-suite/src/compress/pruner.ts +92 -0
  311. package/external/pi-tools-suite/src/compress/state.ts +486 -0
  312. package/external/pi-tools-suite/src/compress/ui.ts +308 -0
  313. package/external/pi-tools-suite/src/config.ts +176 -0
  314. package/external/pi-tools-suite/src/context-usage.ts +31 -0
  315. package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +355 -0
  316. package/external/pi-tools-suite/src/index.ts +46 -0
  317. package/external/pi-tools-suite/src/lib/lsp.ts +62 -0
  318. package/external/pi-tools-suite/src/lib/project.ts +42 -0
  319. package/external/pi-tools-suite/src/lib/tool-args.ts +137 -0
  320. package/external/pi-tools-suite/src/lsp/_shared/config.ts +156 -0
  321. package/external/pi-tools-suite/src/lsp/_shared/glob.ts +60 -0
  322. package/external/pi-tools-suite/src/lsp/_shared/output.ts +102 -0
  323. package/external/pi-tools-suite/src/lsp/_shared/paths.ts +138 -0
  324. package/external/pi-tools-suite/src/lsp/_shared/runner.ts +64 -0
  325. package/external/pi-tools-suite/src/lsp/_shared/template.ts +23 -0
  326. package/external/pi-tools-suite/src/lsp/_shared/trust.ts +116 -0
  327. package/external/pi-tools-suite/src/lsp/_shared/types.ts +98 -0
  328. package/external/pi-tools-suite/src/lsp/async.ts +29 -0
  329. package/external/pi-tools-suite/src/lsp/child-process.ts +81 -0
  330. package/external/pi-tools-suite/src/lsp/client.ts +340 -0
  331. package/external/pi-tools-suite/src/lsp/constants.ts +9 -0
  332. package/external/pi-tools-suite/src/lsp/diagnostics-store.ts +64 -0
  333. package/external/pi-tools-suite/src/lsp/documents.ts +24 -0
  334. package/external/pi-tools-suite/src/lsp/index.ts +31 -0
  335. package/external/pi-tools-suite/src/lsp/lsp-utils.ts +37 -0
  336. package/external/pi-tools-suite/src/lsp/manager.ts +190 -0
  337. package/external/pi-tools-suite/src/lsp/mutation-events.ts +78 -0
  338. package/external/pi-tools-suite/src/lsp/renderer.ts +1 -0
  339. package/external/pi-tools-suite/src/lsp/tool-result.ts +6 -0
  340. package/external/pi-tools-suite/src/lsp/tsserver.ts +107 -0
  341. package/external/pi-tools-suite/src/lsp/types.ts +15 -0
  342. package/external/pi-tools-suite/src/model-tools/apply-patch.ts +590 -0
  343. package/external/pi-tools-suite/src/model-tools/index.ts +430 -0
  344. package/external/pi-tools-suite/src/model-tools/path-utils.ts +6 -0
  345. package/external/pi-tools-suite/src/model-tools/tool-args.ts +11 -0
  346. package/external/pi-tools-suite/src/prompt-commands/index.ts +349 -0
  347. package/external/pi-tools-suite/src/repo-discovery/index.ts +384 -0
  348. package/external/pi-tools-suite/src/repo-discovery/project.ts +7 -0
  349. package/external/pi-tools-suite/src/startup-section.ts +13 -0
  350. package/external/pi-tools-suite/src/terminal-bell/index.ts +309 -0
  351. package/external/pi-tools-suite/src/todo/index.ts +201 -0
  352. package/external/pi-tools-suite/src/todo/state/auto-clear.ts +13 -0
  353. package/external/pi-tools-suite/src/todo/state/invariants.ts +21 -0
  354. package/external/pi-tools-suite/src/todo/state/persistence.ts +94 -0
  355. package/external/pi-tools-suite/src/todo/state/replay.ts +38 -0
  356. package/external/pi-tools-suite/src/todo/state/selectors.ts +49 -0
  357. package/external/pi-tools-suite/src/todo/state/state-reducer.ts +362 -0
  358. package/external/pi-tools-suite/src/todo/state/state.ts +18 -0
  359. package/external/pi-tools-suite/src/todo/state/store.ts +52 -0
  360. package/external/pi-tools-suite/src/todo/state/task-graph.ts +57 -0
  361. package/external/pi-tools-suite/src/todo/todo.ts +487 -0
  362. package/external/pi-tools-suite/src/todo/tool/response-envelope.ts +143 -0
  363. package/external/pi-tools-suite/src/todo/tool/types.ts +188 -0
  364. package/external/pi-tools-suite/src/todo/view/format.ts +18 -0
  365. package/external/pi-tools-suite/src/todo/view/labels.ts +13 -0
  366. package/external/pi-tools-suite/src/tool-descriptions.ts +369 -0
  367. package/external/pi-tools-suite/src/usage/index.ts +152 -0
  368. package/external/pi-tools-suite/src/usage/lib/copilot.ts +535 -0
  369. package/external/pi-tools-suite/src/usage/lib/google.ts +478 -0
  370. package/external/pi-tools-suite/src/usage/lib/openai.ts +268 -0
  371. package/external/pi-tools-suite/src/usage/lib/types.ts +114 -0
  372. package/external/pi-tools-suite/src/usage/lib/utils.ts +134 -0
  373. package/external/pi-tools-suite/src/usage/lib/zhipu.ts +228 -0
  374. package/external/pi-tools-suite/src/web-search/index.ts +397 -0
  375. package/package.json +89 -0
  376. package/skills/context7/SKILL.md +69 -0
  377. package/skills/context7/scripts/context7.sh +73 -0
  378. package/skills/handoff/SKILL.md +15 -0
  379. package/skills/pdf/LICENSE.txt +30 -0
  380. package/skills/pdf/SKILL.md +314 -0
  381. package/skills/pdf/forms.md +294 -0
  382. package/skills/pdf/reference.md +612 -0
  383. package/skills/pdf/scripts/check_bounding_boxes.py +65 -0
  384. package/skills/pdf/scripts/check_fillable_fields.py +11 -0
  385. package/skills/pdf/scripts/convert_pdf_to_images.py +33 -0
  386. package/skills/pdf/scripts/create_validation_image.py +37 -0
  387. package/skills/pdf/scripts/extract_form_field_info.py +122 -0
  388. package/skills/pdf/scripts/extract_form_structure.py +115 -0
  389. package/skills/pdf/scripts/fill_fillable_fields.py +98 -0
  390. package/skills/pdf/scripts/fill_pdf_form_with_annotations.py +107 -0
  391. package/skills/playwright-cli/SKILL.md +388 -0
  392. package/skills/playwright-cli/references/element-attributes.md +23 -0
  393. package/skills/playwright-cli/references/playwright-tests.md +39 -0
  394. package/skills/playwright-cli/references/request-mocking.md +87 -0
  395. package/skills/playwright-cli/references/running-code.md +241 -0
  396. package/skills/playwright-cli/references/session-management.md +225 -0
  397. package/skills/playwright-cli/references/spec-driven-testing.md +305 -0
  398. package/skills/playwright-cli/references/storage-state.md +275 -0
  399. package/skills/playwright-cli/references/test-generation.md +134 -0
  400. package/skills/playwright-cli/references/tracing.md +139 -0
  401. package/skills/playwright-cli/references/video-recording.md +143 -0
  402. package/skills/simplify/SKILL.md +51 -0
  403. package/skills/skill-creator/LICENSE.txt +202 -0
  404. package/skills/skill-creator/SKILL.md +485 -0
  405. package/skills/skill-creator/agents/analyzer.md +274 -0
  406. package/skills/skill-creator/agents/comparator.md +202 -0
  407. package/skills/skill-creator/agents/grader.md +223 -0
  408. package/skills/skill-creator/assets/eval_review.html +146 -0
  409. package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  410. package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  411. package/skills/skill-creator/references/schemas.md +430 -0
  412. package/skills/skill-creator/scripts/__init__.py +0 -0
  413. package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  414. package/skills/skill-creator/scripts/generate_report.py +326 -0
  415. package/skills/skill-creator/scripts/improve_description.py +247 -0
  416. package/skills/skill-creator/scripts/package_skill.py +136 -0
  417. package/skills/skill-creator/scripts/quick_validate.py +103 -0
  418. package/skills/skill-creator/scripts/run_eval.py +310 -0
  419. package/skills/skill-creator/scripts/run_loop.py +328 -0
  420. package/skills/skill-creator/scripts/utils.py +47 -0
@@ -0,0 +1,224 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { getRunRoot, isDir, resolveRunDir } from "./paths.js";
4
+
5
+ export const SUBAGENT_REGISTRY_FILE = "registry.json";
6
+
7
+ export interface SubagentRegistryRun {
8
+ runId: string;
9
+ runDir: string;
10
+ agentIds: string[];
11
+ createdAt: string;
12
+ updatedAt: string;
13
+ }
14
+
15
+ export interface SubagentRegistryAgent {
16
+ agentId: string;
17
+ runId: string;
18
+ runDir: string;
19
+ updatedAt: string;
20
+ }
21
+
22
+ export interface SubagentRegistry {
23
+ version: 1;
24
+ latestRunId?: string;
25
+ latestRunDir?: string;
26
+ runs: Record<string, SubagentRegistryRun>;
27
+ agents: Record<string, SubagentRegistryAgent>;
28
+ }
29
+
30
+ function emptyRegistry(): SubagentRegistry {
31
+ return { version: 1, runs: {}, agents: {} };
32
+ }
33
+
34
+ export function getSubagentRegistryPath(cwd: string): string {
35
+ return path.join(getRunRoot(cwd), SUBAGENT_REGISTRY_FILE);
36
+ }
37
+
38
+ export function loadSubagentRegistry(cwd: string): SubagentRegistry {
39
+ try {
40
+ const parsed = JSON.parse(fs.readFileSync(getSubagentRegistryPath(cwd), "utf-8"));
41
+ if (!isRecord(parsed)) return emptyRegistry();
42
+ const registry = emptyRegistry();
43
+ if (typeof parsed.latestRunId === "string") registry.latestRunId = parsed.latestRunId;
44
+ if (typeof parsed.latestRunDir === "string") registry.latestRunDir = parsed.latestRunDir;
45
+ if (isRecord(parsed.runs)) {
46
+ for (const [runId, value] of Object.entries(parsed.runs)) {
47
+ const run = normalizeRegistryRun(runId, value);
48
+ if (run) registry.runs[runId] = run;
49
+ }
50
+ }
51
+ if (isRecord(parsed.agents)) {
52
+ for (const [agentId, value] of Object.entries(parsed.agents)) {
53
+ const agent = normalizeRegistryAgent(agentId, value);
54
+ if (agent) registry.agents[agentId] = agent;
55
+ }
56
+ }
57
+ return registry;
58
+ } catch {
59
+ return emptyRegistry();
60
+ }
61
+ }
62
+
63
+ export function saveSubagentRegistry(cwd: string, registry: SubagentRegistry): void {
64
+ const registryPath = getSubagentRegistryPath(cwd);
65
+ fs.mkdirSync(path.dirname(registryPath), { recursive: true });
66
+ fs.writeFileSync(registryPath, `${JSON.stringify(registry, null, 2)}\n`, "utf-8");
67
+ }
68
+
69
+ export function recordSubagentRun(cwd: string, runDir: string, agentIds: string[]): SubagentRegistry {
70
+ const registry = loadSubagentRegistry(cwd);
71
+ const resolvedRunDir = path.resolve(runDir);
72
+ const runId = path.basename(resolvedRunDir);
73
+ const now = new Date().toISOString();
74
+ const uniqueAgentIds = [...new Set(agentIds.filter((id) => id.trim().length > 0))];
75
+ const previous = registry.runs[runId];
76
+ registry.runs[runId] = {
77
+ runId,
78
+ runDir: resolvedRunDir,
79
+ agentIds: uniqueAgentIds,
80
+ createdAt: previous?.createdAt ?? now,
81
+ updatedAt: now,
82
+ };
83
+ registry.latestRunId = runId;
84
+ registry.latestRunDir = resolvedRunDir;
85
+ for (const agentId of uniqueAgentIds) {
86
+ registry.agents[agentId] = { agentId, runId, runDir: resolvedRunDir, updatedAt: now };
87
+ }
88
+ saveSubagentRegistry(cwd, registry);
89
+ return registry;
90
+ }
91
+
92
+ export function removeSubagentRunsFromRegistry(cwd: string, runDirs: string[]): SubagentRegistry {
93
+ const registry = loadSubagentRegistry(cwd);
94
+ const removed = new Set(runDirs.map((runDir) => normalizePath(runDir)));
95
+ for (const [runId, run] of Object.entries(registry.runs)) {
96
+ if (removed.has(normalizePath(run.runDir))) delete registry.runs[runId];
97
+ }
98
+ for (const [agentId, agent] of Object.entries(registry.agents)) {
99
+ if (removed.has(normalizePath(agent.runDir))) delete registry.agents[agentId];
100
+ }
101
+ refreshLatestRun(registry);
102
+ saveSubagentRegistry(cwd, registry);
103
+ return registry;
104
+ }
105
+
106
+ export function resolveSubagentRunDir(cwd: string, runDir?: string): string {
107
+ if (hasText(runDir)) return resolveRunDir(cwd, runDir);
108
+ const latest = findLatestSubagentRunDir(cwd);
109
+ if (latest) return latest;
110
+ throw new Error(`runDir was omitted and no sub-agent runs were found under ${getRunRoot(cwd)}.`);
111
+ }
112
+
113
+ export function resolveSubagentAgentRunDir(cwd: string, agentId: string, runDir?: string): string {
114
+ if (hasText(runDir)) return resolveRunDir(cwd, runDir);
115
+ const registered = loadSubagentRegistry(cwd).agents[agentId]?.runDir;
116
+ if (registered && hasAgentRecord(registered, agentId)) return registered;
117
+ const scanned = findSubagentRunDirsForAgent(cwd, agentId)[0];
118
+ if (scanned) return scanned;
119
+ throw new Error(`runDir was omitted and agent "${agentId}" was not found under ${getRunRoot(cwd)}.`);
120
+ }
121
+
122
+ export function findLatestSubagentRunDir(cwd: string): string | undefined {
123
+ const registry = loadSubagentRegistry(cwd);
124
+ const latest = registry.latestRunDir;
125
+ if (latest && isDir(latest)) return latest;
126
+ const registered = Object.values(registry.runs)
127
+ .filter((run) => isDir(run.runDir))
128
+ .sort((a, b) => Date.parse(b.updatedAt) - Date.parse(a.updatedAt));
129
+ if (registered[0]) return registered[0].runDir;
130
+ return listSubagentRunDirs(cwd)[0];
131
+ }
132
+
133
+ export function findSubagentRunDirsForAgent(cwd: string, agentId: string): string[] {
134
+ return listSubagentRunDirs(cwd).filter((runDir) => hasAgentRecord(runDir, agentId));
135
+ }
136
+
137
+ export function listSubagentRunDirs(cwd: string): string[] {
138
+ const root = getRunRoot(cwd);
139
+ if (!isDir(root)) return [];
140
+ return fs.readdirSync(root, { withFileTypes: true })
141
+ .filter((entry) => entry.isDirectory())
142
+ .map((entry) => path.join(root, entry.name))
143
+ .filter(looksLikeRunDir)
144
+ .sort((a, b) => statMtimeMs(b) - statMtimeMs(a));
145
+ }
146
+
147
+ function refreshLatestRun(registry: SubagentRegistry): void {
148
+ const latest = Object.values(registry.runs)
149
+ .filter((run) => isDir(run.runDir))
150
+ .sort((a, b) => Date.parse(b.updatedAt) - Date.parse(a.updatedAt))[0];
151
+ if (latest) {
152
+ registry.latestRunId = latest.runId;
153
+ registry.latestRunDir = latest.runDir;
154
+ return;
155
+ }
156
+ delete registry.latestRunId;
157
+ delete registry.latestRunDir;
158
+ }
159
+
160
+ function looksLikeRunDir(runDir: string): boolean {
161
+ if (isDir(path.join(runDir, "prompts"))) return true;
162
+ try {
163
+ return fs.readdirSync(runDir, { withFileTypes: true })
164
+ .some((entry) => entry.isDirectory() && fs.existsSync(path.join(runDir, entry.name, "prompt.md")));
165
+ } catch {
166
+ return false;
167
+ }
168
+ }
169
+
170
+ function hasAgentRecord(runDir: string, agentId: string): boolean {
171
+ return fs.existsSync(path.join(runDir, agentId, "prompt.md"))
172
+ || fs.existsSync(path.join(runDir, "prompts", `${agentId}.md`));
173
+ }
174
+
175
+ function normalizeRegistryRun(runId: string, value: unknown): SubagentRegistryRun | undefined {
176
+ if (!isRecord(value)) return undefined;
177
+ if (typeof value.runDir !== "string") return undefined;
178
+ const agentIds = Array.isArray(value.agentIds)
179
+ ? value.agentIds.filter((id): id is string => typeof id === "string" && id.trim().length > 0)
180
+ : [];
181
+ return {
182
+ runId: typeof value.runId === "string" ? value.runId : runId,
183
+ runDir: value.runDir,
184
+ agentIds,
185
+ createdAt: typeof value.createdAt === "string" ? value.createdAt : new Date(0).toISOString(),
186
+ updatedAt: typeof value.updatedAt === "string" ? value.updatedAt : new Date(0).toISOString(),
187
+ };
188
+ }
189
+
190
+ function normalizeRegistryAgent(agentId: string, value: unknown): SubagentRegistryAgent | undefined {
191
+ if (!isRecord(value)) return undefined;
192
+ if (typeof value.runDir !== "string" || typeof value.runId !== "string") return undefined;
193
+ return {
194
+ agentId: typeof value.agentId === "string" ? value.agentId : agentId,
195
+ runId: value.runId,
196
+ runDir: value.runDir,
197
+ updatedAt: typeof value.updatedAt === "string" ? value.updatedAt : new Date(0).toISOString(),
198
+ };
199
+ }
200
+
201
+ function isRecord(value: unknown): value is Record<string, unknown> {
202
+ return typeof value === "object" && value !== null;
203
+ }
204
+
205
+ function hasText(value: string | undefined): value is string {
206
+ return typeof value === "string" && value.trim().length > 0;
207
+ }
208
+
209
+ function statMtimeMs(filePath: string): number {
210
+ try {
211
+ return fs.statSync(filePath).mtimeMs;
212
+ } catch {
213
+ return 0;
214
+ }
215
+ }
216
+
217
+ function normalizePath(filePath: string): string {
218
+ const resolved = path.resolve(filePath);
219
+ try {
220
+ return fs.realpathSync.native(resolved);
221
+ } catch {
222
+ return resolved;
223
+ }
224
+ }
@@ -0,0 +1,191 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import type { AgentTask, RetryConfig } from "./types.js";
4
+ import type { SpawnedAgent, AgentCompletionHandler } from "./types.js";
5
+ import { isQuotaLimitCompletion, nextFallbackModel, rememberSessionModelFallback, selectSessionModelWithFallback } from "./model-fallback.js";
6
+ import { spawnAgent, type SpawnAgentOptions } from "./spawn.js";
7
+ import { getAgentState } from "./state.js";
8
+ import { isoNow } from "./utils.js";
9
+
10
+ export interface RetryableSpawnOptions extends SpawnAgentOptions {
11
+ retry: RetryConfig;
12
+ extraArgs?: string[];
13
+ /** Ordered fallback models for quota/rate-limit failures. Applies to this Pi process/session. */
14
+ fallbackModels?: string[];
15
+ signal?: AbortSignal;
16
+ onRpcEvent?: (event: import("./types.js").RpcEventRecord) => void;
17
+ /** Called each time a retry attempt starts. */
18
+ onRetry?: (attempt: number, maxRetries: number, delayMs: number) => void;
19
+ }
20
+
21
+ /**
22
+ * Spawn an agent with automatic retry on failure.
23
+ *
24
+ * Returns a promise that resolves with the final SpawnedAgent (possibly after retries).
25
+ * The `onComplete` callback is only called for the *final* attempt (success or all retries exhausted).
26
+ */
27
+ export function spawnAgentWithRetry(
28
+ runDir: string,
29
+ task: AgentTask,
30
+ cwd: string,
31
+ onComplete: AgentCompletionHandler | undefined,
32
+ options: RetryableSpawnOptions,
33
+ ): { initial: SpawnedAgent; done: Promise<void> } {
34
+ const { retry, extraArgs = [], fallbackModels = [], signal, onRpcEvent, onRetry, ...spawnOpts } = options;
35
+ if (signal?.aborted) throw new Error("Aborted");
36
+ const maxRetries = retry.maxRetries > 0 ? retry.maxRetries : 0;
37
+ const initialModel = selectSessionModelWithFallback(task.model, fallbackModels);
38
+ let currentTask = initialModel?.fellBack ? { ...task, model: initialModel.model } : task;
39
+
40
+ let currentAttempt = 0;
41
+ let settled = false;
42
+ let retryTimer: NodeJS.Timeout | undefined;
43
+ let abortRetry: (() => void) | undefined;
44
+ let resolveAllDone: () => void;
45
+ let rejectAllDone: (error: Error) => void;
46
+ const allDone = new Promise<void>((resolve, reject) => {
47
+ resolveAllDone = resolve;
48
+ rejectAllDone = reject;
49
+ });
50
+
51
+ const settle = (completion: Parameters<AgentCompletionHandler>[0], error?: Error) => {
52
+ if (settled) return;
53
+ settled = true;
54
+ if (retryTimer) clearTimeout(retryTimer);
55
+ abortRetry?.();
56
+ clearRetryPending(completion.agentDir);
57
+ writeRetryCount(completion.agentDir, currentAttempt);
58
+ onComplete?.(completion);
59
+ if (error) rejectAllDone!(error);
60
+ else resolveAllDone!();
61
+ };
62
+
63
+ const handleCompletion: AgentCompletionHandler = (completion) => {
64
+ if (settled) return;
65
+ const fallbackModel = isQuotaLimitCompletion(completion, currentTask.model)
66
+ ? nextFallbackModel(currentTask.model, fallbackModels)
67
+ : undefined;
68
+ if (fallbackModel) {
69
+ const failedModel = currentTask.model;
70
+ rememberSessionModelFallback(failedModel, fallbackModel);
71
+ writeModelFallbackLog(completion.agentDir, failedModel, fallbackModel, completion.exitCode);
72
+ currentTask = { ...currentTask, model: fallbackModel };
73
+ if (signal?.aborted) {
74
+ settle(completion);
75
+ return;
76
+ }
77
+ if (isStopRequested(completion.agentDir)) {
78
+ settle(refreshedCompletion(completion));
79
+ return;
80
+ }
81
+ try {
82
+ spawnAgent(runDir, currentTask, cwd, extraArgs, onRpcEvent, handleCompletion, spawnOpts);
83
+ } catch (error) {
84
+ settle(completion, error instanceof Error ? error : new Error(String(error)));
85
+ }
86
+ return;
87
+ }
88
+ const isRetryable = maxRetries > currentAttempt && shouldRetry(completion, retry);
89
+
90
+ if (!isRetryable) {
91
+ settle(completion);
92
+ return;
93
+ }
94
+
95
+ // Schedule retry with exponential backoff.
96
+ currentAttempt++;
97
+ const delayMs = retry.backoffMs * Math.pow(2, currentAttempt - 1);
98
+ onRetry?.(currentAttempt, maxRetries, delayMs);
99
+
100
+ // Write retry metadata.
101
+ writeRetryCount(completion.agentDir, currentAttempt);
102
+ writeRetryPending(completion.agentDir, delayMs);
103
+ writeRetryLog(completion.agentDir, currentAttempt, delayMs, completion.exitCode);
104
+
105
+ if (signal?.aborted) {
106
+ settle(completion);
107
+ return;
108
+ }
109
+
110
+ retryTimer = setTimeout(() => {
111
+ retryTimer = undefined;
112
+ abortRetry?.();
113
+ abortRetry = undefined;
114
+ if (settled) return;
115
+ if (signal?.aborted || isStopRequested(completion.agentDir)) {
116
+ settle(refreshedCompletion(completion));
117
+ return;
118
+ }
119
+ clearRetryPending(completion.agentDir);
120
+ try {
121
+ spawnAgent(runDir, currentTask, cwd, extraArgs, onRpcEvent, handleCompletion, spawnOpts);
122
+ } catch (error) {
123
+ settle(completion, error instanceof Error ? error : new Error(String(error)));
124
+ }
125
+ }, delayMs);
126
+ retryTimer.unref?.();
127
+
128
+ if (signal) {
129
+ const onAbort = () => settle(refreshedCompletion(completion));
130
+ signal.addEventListener("abort", onAbort, { once: true });
131
+ abortRetry = () => signal.removeEventListener("abort", onAbort);
132
+ }
133
+ };
134
+
135
+ const initial = spawnAgent(runDir, currentTask, cwd, extraArgs, onRpcEvent, handleCompletion, spawnOpts);
136
+ return { initial, done: allDone };
137
+ }
138
+
139
+ function shouldRetry(completion: Parameters<AgentCompletionHandler>[0], retry: RetryConfig): boolean {
140
+ if (completion.state.status === "stopped") return false;
141
+ if (completion.exitCode === 0) return false;
142
+ if (retry.retryableExitCodes === undefined) return true;
143
+ if (retry.retryableExitCodes.length === 0) return false;
144
+ return retry.retryableExitCodes.includes(completion.exitCode);
145
+ }
146
+
147
+ function writeRetryCount(agentDir: string, count: number): void {
148
+ try {
149
+ fs.writeFileSync(path.join(agentDir, "retry_count"), String(count), "utf-8");
150
+ } catch { /* best-effort */ }
151
+ }
152
+
153
+ function writeRetryPending(agentDir: string, delayMs: number): void {
154
+ try {
155
+ const nextRetryAt = new Date(Date.now() + delayMs).toISOString().replace(/\.\d{3}Z$/, "Z");
156
+ fs.writeFileSync(path.join(agentDir, "retry_pending"), isoNow(), "utf-8");
157
+ fs.writeFileSync(path.join(agentDir, "next_retry_at"), nextRetryAt, "utf-8");
158
+ } catch { /* best-effort */ }
159
+ }
160
+
161
+ function clearRetryPending(agentDir: string): void {
162
+ try {
163
+ fs.rmSync(path.join(agentDir, "retry_pending"), { force: true });
164
+ fs.rmSync(path.join(agentDir, "next_retry_at"), { force: true });
165
+ } catch { /* best-effort */ }
166
+ }
167
+
168
+ function writeRetryLog(agentDir: string, attempt: number, delayMs: number, exitCode: number): void {
169
+ try {
170
+ const line = `${isoNow()} retry=${attempt} delay=${delayMs}ms exitCode=${exitCode}\n`;
171
+ fs.appendFileSync(path.join(agentDir, "retry.log"), line, "utf-8");
172
+ } catch { /* best-effort */ }
173
+ }
174
+
175
+ function writeModelFallbackLog(agentDir: string, failedModel: string | undefined, fallbackModel: string, exitCode: number): void {
176
+ try {
177
+ const line = `${isoNow()} modelFallback ${failedModel ?? "(default)"} -> ${fallbackModel} exitCode=${exitCode}\n`;
178
+ fs.appendFileSync(path.join(agentDir, "model_fallback.log"), line, "utf-8");
179
+ fs.writeFileSync(path.join(agentDir, "model_fallback_from"), failedModel ?? "", "utf-8");
180
+ fs.writeFileSync(path.join(agentDir, "model_fallback_to"), fallbackModel, "utf-8");
181
+ } catch { /* best-effort */ }
182
+ }
183
+
184
+ function isStopRequested(agentDir: string): boolean {
185
+ return fs.existsSync(path.join(agentDir, "stop_requested"));
186
+ }
187
+
188
+ function refreshedCompletion(completion: Parameters<AgentCompletionHandler>[0]): Parameters<AgentCompletionHandler>[0] {
189
+ const refreshed = getAgentState(completion.runDir, completion.agentId, { includeLineCounts: false });
190
+ return refreshed ? { ...completion, exitCode: refreshed.exitCode ?? completion.exitCode, state: refreshed } : completion;
191
+ }
@@ -0,0 +1,259 @@
1
+ import { complete } from "@earendil-works/pi-ai";
2
+ import type { Api, Model } from "@earendil-works/pi-ai";
3
+ import type { AgentTask } from "./types.js";
4
+ import {
5
+ currentModelRef,
6
+ defaultSubagentType,
7
+ resolveSubagentRoutingConfig,
8
+ type ResolvedSubagentRoutingConfig,
9
+ type SubagentConfig,
10
+ } from "./config.js";
11
+
12
+ export interface SubagentRoutingContext {
13
+ model?: unknown;
14
+ modelRegistry?: {
15
+ find(provider: string, modelId: string): Model<Api> | undefined;
16
+ getApiKeyAndHeaders(model: Model<Api>): Promise<
17
+ | { ok?: true; apiKey?: string; headers?: Record<string, string> }
18
+ | { ok: false; error: string }
19
+ >;
20
+ };
21
+ hasUI?: boolean;
22
+ ui?: { notify?(message: string, level?: string): void };
23
+ }
24
+
25
+ export interface RoutedSubagentTasks {
26
+ tasks: AgentTask[];
27
+ usedLlm: boolean;
28
+ routes: Record<string, string>;
29
+ warnings: string[];
30
+ }
31
+
32
+ const ROUTER_SYSTEM_PROMPT = [
33
+ "You route Pi async sub-agent tasks to the best configured subagentType.",
34
+ "Choose exactly one allowed type for each task. Use the allowed type descriptions as the source of truth.",
35
+ "Prefer the most specific matching type over generic quick/deep. Use frontend for UI/UX implementation or visual frontend polish; use vision only for attached images/screenshots or image inspection.",
36
+ "Return only strict JSON with this shape: {\"routes\":[{\"id\":\"task-id\",\"subagentType\":\"type\"}]}",
37
+ "Do not include markdown, comments, explanations, or unknown types.",
38
+ ].join("\n");
39
+
40
+ export async function routeSubagentTasks(
41
+ tasks: AgentTask[],
42
+ config: SubagentConfig,
43
+ ctx: SubagentRoutingContext,
44
+ signal?: AbortSignal,
45
+ ): Promise<RoutedSubagentTasks> {
46
+ const fallbackTasks = () => tasks.map((task) => withFallbackType(task, config));
47
+ const autoTasks = tasks.filter((task) => !hasText(task.subagentType));
48
+ if (autoTasks.length === 0) return { tasks, usedLlm: false, routes: {}, warnings: [] };
49
+
50
+ const routing = resolveSubagentRoutingConfig(config);
51
+ if (!routing.enabled) {
52
+ return { tasks: fallbackTasks(), usedLlm: false, routes: {}, warnings: ["LLM sub-agent routing is disabled; used defaultType fallback."] };
53
+ }
54
+ if (signal?.aborted) throw new Error("Aborted");
55
+
56
+ try {
57
+ const resolved = await resolveRoutingModel(ctx, routing);
58
+ if (!resolved) {
59
+ const warning = `LLM sub-agent routing model unavailable (${routing.model}); used defaultType fallback.`;
60
+ notifyRoutingWarning(ctx, routing, warning);
61
+ return { tasks: fallbackTasks(), usedLlm: false, routes: {}, warnings: [warning] };
62
+ }
63
+
64
+ const response = await complete(
65
+ resolved.model,
66
+ {
67
+ systemPrompt: ROUTER_SYSTEM_PROMPT,
68
+ messages: [
69
+ {
70
+ role: "user" as const,
71
+ content: [{ type: "text" as const, text: buildRoutingPrompt(autoTasks, config, routing) }],
72
+ timestamp: Date.now(),
73
+ },
74
+ ],
75
+ },
76
+ {
77
+ apiKey: resolved.apiKey,
78
+ headers: resolved.headers,
79
+ cacheRetention: "none",
80
+ maxRetries: routing.maxRetries,
81
+ maxTokens: routing.maxTokens,
82
+ signal,
83
+ temperature: routing.temperature,
84
+ timeoutMs: routing.timeoutMs,
85
+ },
86
+ );
87
+ const routes = parseRoutingResponse(responseText(response), config, autoTasks);
88
+ const warnings = Object.keys(routes).length === autoTasks.length
89
+ ? []
90
+ : [`LLM sub-agent routing returned ${Object.keys(routes).length}/${autoTasks.length} valid route(s); missing tasks used defaultType fallback.`];
91
+ for (const warning of warnings) notifyRoutingWarning(ctx, routing, warning);
92
+ return {
93
+ usedLlm: true,
94
+ routes,
95
+ warnings,
96
+ tasks: tasks.map((task) => applyRoute(task, config, routes)),
97
+ };
98
+ } catch (error) {
99
+ if (signal?.aborted || isAbortError(error)) throw error;
100
+ const warning = `LLM sub-agent routing failed (${errorMessage(error)}); used defaultType fallback.`;
101
+ notifyRoutingWarning(ctx, routing, warning);
102
+ return { tasks: fallbackTasks(), usedLlm: false, routes: {}, warnings: [warning] };
103
+ }
104
+ }
105
+
106
+ function applyRoute(task: AgentTask, config: SubagentConfig, routes: Record<string, string>): AgentTask {
107
+ if (hasText(task.subagentType)) return task;
108
+ const fallback = withFallbackType(task, config);
109
+ return routes[task.id] ? { ...fallback, subagentType: routes[task.id] } : fallback;
110
+ }
111
+
112
+ function withFallbackType(task: AgentTask, config: SubagentConfig): AgentTask {
113
+ if (hasText(task.subagentType)) return task;
114
+ const fallback = defaultSubagentType(config);
115
+ return fallback ? { ...task, subagentType: fallback } : task;
116
+ }
117
+
118
+ function buildRoutingPrompt(tasks: AgentTask[], config: SubagentConfig, routing: ResolvedSubagentRoutingConfig): string {
119
+ return [
120
+ "Choose subagentType for each task.",
121
+ "",
122
+ "Allowed types (type: description):",
123
+ ...Object.entries(config.types).map(([name, profile]) => {
124
+ return `- ${name}: ${profile.description ?? "No description; use only when the task explicitly names this type."}`;
125
+ }),
126
+ "",
127
+ `Default fallback if genuinely ambiguous: ${defaultSubagentType(config) ?? "none"}`,
128
+ "",
129
+ "Tasks:",
130
+ JSON.stringify(tasks.map((task) => ({
131
+ id: task.id,
132
+ task: truncate(task.task, routing.maxTaskChars),
133
+ scope: truncate(task.scope, routing.maxTaskChars),
134
+ parentObjective: truncate(task.parentObjective, routing.maxTaskChars),
135
+ hasImages: Array.isArray(task.imagePaths) && task.imagePaths.length > 0,
136
+ focus: truncate(task.focus, routing.maxTaskChars),
137
+ })), null, 2),
138
+ ].join("\n");
139
+ }
140
+
141
+ async function resolveRoutingModel(ctx: SubagentRoutingContext, routing: ResolvedSubagentRoutingConfig): Promise<{
142
+ model: Model<Api>;
143
+ apiKey?: string;
144
+ headers?: Record<string, string>;
145
+ } | undefined> {
146
+ const configured = await resolveModelRef(ctx, routing.model);
147
+ if (configured) return configured;
148
+ const parentModel = currentModelRef(ctx.model);
149
+ return parentModel && parentModel !== routing.model ? resolveModelRef(ctx, parentModel) : undefined;
150
+ }
151
+
152
+ async function resolveModelRef(ctx: SubagentRoutingContext, modelRef: string): Promise<{
153
+ model: Model<Api>;
154
+ apiKey?: string;
155
+ headers?: Record<string, string>;
156
+ } | undefined> {
157
+ const parsed = parseModelRef(modelRef);
158
+ if (!parsed || !ctx.modelRegistry) return undefined;
159
+ const model = ctx.modelRegistry.find(parsed.provider, parsed.modelId);
160
+ if (!model) return undefined;
161
+ const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
162
+ if (auth.ok === false) return undefined;
163
+ return { model, apiKey: auth.apiKey, headers: auth.headers };
164
+ }
165
+
166
+ function parseModelRef(modelRef: string): { provider: string; modelId: string } | undefined {
167
+ const trimmed = modelRef.trim();
168
+ const slash = trimmed.indexOf("/");
169
+ if (slash <= 0 || slash === trimmed.length - 1) return undefined;
170
+ return { provider: trimmed.slice(0, slash), modelId: trimmed.slice(slash + 1) };
171
+ }
172
+
173
+ function parseRoutingResponse(raw: string, config: SubagentConfig, tasks: AgentTask[]): Record<string, string> {
174
+ const parsed = parseJsonObject(raw);
175
+ const allowedTypes = new Map(Object.keys(config.types).map((name) => [name.toLowerCase(), name]));
176
+ const taskIds = new Set(tasks.map((task) => task.id));
177
+ const routes: Record<string, string> = {};
178
+ if (Array.isArray(parsed)) collectRouteArray(parsed, taskIds, allowedTypes, routes);
179
+ else if (typeof parsed === "string" && tasks.length === 1) addRoute(routes, taskIds, allowedTypes, tasks[0]!.id, parsed);
180
+ else if (isRecord(parsed)) {
181
+ if (Array.isArray(parsed.routes)) collectRouteArray(parsed.routes, taskIds, allowedTypes, routes);
182
+ else {
183
+ for (const [id, value] of Object.entries(parsed)) {
184
+ if (typeof value === "string") addRoute(routes, taskIds, allowedTypes, id, value);
185
+ else if (isRecord(value)) addRoute(routes, taskIds, allowedTypes, id, value.subagentType ?? value.type);
186
+ }
187
+ }
188
+ }
189
+ if (Object.keys(routes).length === 0 && tasks.length === 1) addRoute(routes, taskIds, allowedTypes, tasks[0]!.id, raw.trim());
190
+ return routes;
191
+ }
192
+
193
+ function collectRouteArray(items: unknown[], taskIds: Set<string>, allowedTypes: Map<string, string>, routes: Record<string, string>): void {
194
+ for (const item of items) {
195
+ if (!isRecord(item)) continue;
196
+ addRoute(routes, taskIds, allowedTypes, item.id ?? item.taskId ?? item.agentId, item.subagentType ?? item.type);
197
+ }
198
+ }
199
+
200
+ function addRoute(routes: Record<string, string>, taskIds: Set<string>, allowedTypes: Map<string, string>, rawId: unknown, rawType: unknown): void {
201
+ if (typeof rawId !== "string" || typeof rawType !== "string") return;
202
+ const id = rawId.trim();
203
+ if (!taskIds.has(id)) return;
204
+ const type = allowedTypes.get(rawType.trim().toLowerCase());
205
+ if (type) routes[id] = type;
206
+ }
207
+
208
+ function parseJsonObject(raw: string): unknown {
209
+ const cleaned = raw.trim()
210
+ .replace(/^```(?:json)?\s*/iu, "")
211
+ .replace(/```$/u, "")
212
+ .trim();
213
+ try {
214
+ return JSON.parse(cleaned) as unknown;
215
+ } catch {
216
+ const match = /(?:\[[\s\S]*\]|\{[\s\S]*\})/.exec(cleaned);
217
+ if (!match) return undefined;
218
+ try {
219
+ return JSON.parse(match[0]) as unknown;
220
+ } catch {
221
+ return undefined;
222
+ }
223
+ }
224
+ }
225
+
226
+ function responseText(response: { content: Array<{ type: string; text?: string }> }): string {
227
+ return response.content
228
+ .filter((block): block is { type: "text"; text: string } => block.type === "text" && typeof block.text === "string")
229
+ .map((block) => block.text)
230
+ .join("\n");
231
+ }
232
+
233
+ function notifyRoutingWarning(ctx: SubagentRoutingContext, routing: ResolvedSubagentRoutingConfig, message: string): void {
234
+ if (!routing.debug || !ctx.hasUI) return;
235
+ ctx.ui?.notify?.(message, "warning");
236
+ }
237
+
238
+ function truncate(value: unknown, maxChars: number): string | undefined {
239
+ if (typeof value !== "string") return undefined;
240
+ const trimmed = value.trim();
241
+ if (!trimmed) return undefined;
242
+ return trimmed.length <= maxChars ? trimmed : `${trimmed.slice(0, maxChars).trimEnd()}…`;
243
+ }
244
+
245
+ function hasText(value: unknown): value is string {
246
+ return typeof value === "string" && value.trim().length > 0;
247
+ }
248
+
249
+ function isRecord(value: unknown): value is Record<string, unknown> {
250
+ return typeof value === "object" && value !== null && !Array.isArray(value);
251
+ }
252
+
253
+ function isAbortError(error: unknown): boolean {
254
+ return error instanceof Error && /abort/i.test(error.name || error.message);
255
+ }
256
+
257
+ function errorMessage(error: unknown): string {
258
+ return error instanceof Error ? error.message : String(error);
259
+ }