@xopcai/xopc 0.0.88 → 0.0.90

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 (275) hide show
  1. package/README.md +8 -1
  2. package/README.zh-CN.md +8 -1
  3. package/dist/browser-ext/manifest.json +1 -1
  4. package/dist/extensions/telegram/xopc.extension.json +1 -1
  5. package/dist/gateway/static/root/assets/agents-cPvvYLXo.js +222 -0
  6. package/dist/gateway/static/root/assets/apps-page-Bk1_P5FJ.js +1 -0
  7. package/dist/gateway/static/root/assets/channels-settings-CZoeQwHz.js +1 -0
  8. package/dist/gateway/static/root/assets/{channels-status-swr-DIsl75Y3.js → channels-status-swr-BrtH2VzC.js} +1 -1
  9. package/dist/gateway/static/root/assets/circle-check-C23XjkUj.js +1 -0
  10. package/dist/gateway/static/root/assets/cron-api-CyqbgfHM.js +1 -0
  11. package/dist/gateway/static/root/assets/cron-dreaming-jobs-Ip703-qM.js +2 -0
  12. package/dist/gateway/static/root/assets/cron-page-BpLdiQN8.js +1 -0
  13. package/dist/gateway/static/root/assets/dist-BpAiK86n.js +1 -0
  14. package/dist/gateway/static/root/assets/{extension-debug-page-BVJohZoZ.js → extension-debug-page-D6Ak0STa.js} +1 -1
  15. package/dist/gateway/static/root/assets/{extension-page-BT2tmElC.js → extension-page-Q0P3d6DW.js} +1 -1
  16. package/dist/gateway/static/root/assets/{extension-settings-page-BSS47c2j.js → extension-settings-page-CL55LwU_.js} +1 -1
  17. package/dist/gateway/static/root/assets/eye-DAfL1U7M.js +1 -0
  18. package/dist/gateway/static/root/assets/{fetch-BaFNUtkE.js → fetch-Dqa9iTWl.js} +1 -1
  19. package/dist/gateway/static/root/assets/{field-primitives-QwYEq6Hz.js → field-primitives-HUR6JElP.js} +1 -1
  20. package/dist/gateway/static/root/assets/{heartbeat-config-api-BVSidEDJ.js → heartbeat-config-api-DusckjUX.js} +1 -1
  21. package/dist/gateway/static/root/assets/{index-qNrVJp-y.js → index-BYcGfwcE.js} +97 -97
  22. package/dist/gateway/static/root/assets/index-V7MQ7834.css +1 -0
  23. package/dist/gateway/static/root/assets/{logs-page-DDonPVLn.js → logs-page-_HcZ2fgK.js} +1 -1
  24. package/dist/gateway/static/root/assets/sessions-page-iezSMjho.js +1 -0
  25. package/dist/gateway/static/root/assets/{settings-form-section-B8N3A3Zo.js → settings-form-section-a0qGVOlr.js} +1 -1
  26. package/dist/gateway/static/root/assets/settings-page-C9_nYQwM.js +3 -0
  27. package/dist/gateway/static/root/assets/{share-preview-page-Q7KqkO-u.js → share-preview-page-DExl7CJy.js} +1 -1
  28. package/dist/gateway/static/root/assets/skills-page-BlgGD93t.js +2 -0
  29. package/dist/gateway/static/root/assets/{theme-store-BbRc5ugR.js → theme-store-C0Ehmdo5.js} +1 -1
  30. package/dist/gateway/static/root/assets/url-fxyYANfA.js +3 -0
  31. package/dist/gateway/static/root/assets/{utils-CxDGduqK.js → utils-DRQryzdn.js} +1 -1
  32. package/dist/gateway/static/root/assets/voice-api-key-field-D0viACE2.js +1 -0
  33. package/dist/gateway/static/root/assets/workflow-page.utils-DnG8JBhV.js +1 -0
  34. package/dist/gateway/static/root/assets/workflows-page-BvMobnJP.js +27 -0
  35. package/dist/gateway/static/root/index.html +7 -6
  36. package/dist/package.js +1 -1
  37. package/dist/src/agent/agent-manager.d.ts +2 -0
  38. package/dist/src/agent/agent-manager.js +1 -0
  39. package/dist/src/agent/agent-manager.js.map +1 -1
  40. package/dist/src/agent/service.js +2 -1
  41. package/dist/src/agent/service.js.map +1 -1
  42. package/dist/src/agent/service.types.d.ts +3 -1
  43. package/dist/src/agent/skills/marketplace/adapters/skillhub/adapter.js +20 -18
  44. package/dist/src/agent/skills/marketplace/adapters/skillhub/adapter.js.map +1 -1
  45. package/dist/src/agent/tools/cronjob-tool.d.ts +6 -0
  46. package/dist/src/agent/tools/cronjob-tool.js +76 -10
  47. package/dist/src/agent/tools/cronjob-tool.js.map +1 -1
  48. package/dist/src/agent/tools/edit.d.ts +5 -1
  49. package/dist/src/agent/tools/edit.js +7 -5
  50. package/dist/src/agent/tools/edit.js.map +1 -1
  51. package/dist/src/agent/tools/factory.d.ts +3 -0
  52. package/dist/src/agent/tools/factory.js +4 -25
  53. package/dist/src/agent/tools/factory.js.map +1 -1
  54. package/dist/src/agent/tools/workflow-tool.d.ts +6 -28
  55. package/dist/src/agent/tools/workflow-tool.js +60 -260
  56. package/dist/src/agent/tools/workflow-tool.js.map +1 -1
  57. package/dist/src/agent/tools/write.d.ts +5 -1
  58. package/dist/src/agent/tools/write.js +7 -5
  59. package/dist/src/agent/tools/write.js.map +1 -1
  60. package/dist/src/agent/workflow/agent-progress.js +2 -0
  61. package/dist/src/agent/workflow/agent-progress.js.map +1 -1
  62. package/dist/src/agent/workflow/builtins/client-proposal.d.ts +12 -0
  63. package/dist/src/agent/workflow/builtins/client-proposal.js +155 -0
  64. package/dist/src/agent/workflow/builtins/client-proposal.js.map +1 -0
  65. package/dist/src/agent/workflow/builtins/competitor-scan.d.ts +12 -0
  66. package/dist/src/agent/workflow/builtins/competitor-scan.js +150 -0
  67. package/dist/src/agent/workflow/builtins/competitor-scan.js.map +1 -0
  68. package/dist/src/agent/workflow/builtins/content-draft.d.ts +13 -0
  69. package/dist/src/agent/workflow/builtins/content-draft.js +146 -0
  70. package/dist/src/agent/workflow/builtins/content-draft.js.map +1 -0
  71. package/dist/src/agent/workflow/builtins/content-repurpose.d.ts +11 -0
  72. package/dist/src/agent/workflow/builtins/content-repurpose.js +137 -0
  73. package/dist/src/agent/workflow/builtins/content-repurpose.js.map +1 -0
  74. package/dist/src/agent/workflow/builtins/decision-compare.d.ts +13 -0
  75. package/dist/src/agent/workflow/builtins/decision-compare.js +173 -0
  76. package/dist/src/agent/workflow/builtins/decision-compare.js.map +1 -0
  77. package/dist/src/agent/workflow/builtins/inbox-triage.d.ts +11 -0
  78. package/dist/src/agent/workflow/builtins/inbox-triage.js +148 -0
  79. package/dist/src/agent/workflow/builtins/inbox-triage.js.map +1 -0
  80. package/dist/src/agent/workflow/builtins/index.d.ts +10 -1
  81. package/dist/src/agent/workflow/builtins/index.js +46 -1
  82. package/dist/src/agent/workflow/builtins/index.js.map +1 -1
  83. package/dist/src/agent/workflow/builtins/meeting-prep.d.ts +12 -0
  84. package/dist/src/agent/workflow/builtins/meeting-prep.js +144 -0
  85. package/dist/src/agent/workflow/builtins/meeting-prep.js.map +1 -0
  86. package/dist/src/agent/workflow/builtins/offer-design.d.ts +12 -0
  87. package/dist/src/agent/workflow/builtins/offer-design.js +161 -0
  88. package/dist/src/agent/workflow/builtins/offer-design.js.map +1 -0
  89. package/dist/src/agent/workflow/builtins/weekly-review.d.ts +12 -0
  90. package/dist/src/agent/workflow/builtins/weekly-review.js +131 -0
  91. package/dist/src/agent/workflow/builtins/weekly-review.js.map +1 -0
  92. package/dist/src/agent/workflow/step-labels.js +2 -2
  93. package/dist/src/agent/workflow/step-labels.js.map +1 -1
  94. package/dist/src/agent/workflow/subagent-runner.js +3 -1
  95. package/dist/src/agent/workflow/subagent-runner.js.map +1 -1
  96. package/dist/src/agent/workflow/types.d.ts +4 -0
  97. package/dist/src/agent/workflow/workflow-child-tools.d.ts +4 -0
  98. package/dist/src/agent/workflow/workflow-child-tools.js +21 -0
  99. package/dist/src/agent/workflow/workflow-child-tools.js.map +1 -0
  100. package/dist/src/auth/credentials.d.ts +14 -2
  101. package/dist/src/auth/credentials.js +38 -13
  102. package/dist/src/auth/credentials.js.map +1 -1
  103. package/dist/src/auth/oauth/types.d.ts +16 -0
  104. package/dist/src/chat-commands/agent-edit.d.ts +4 -0
  105. package/dist/src/chat-commands/agent-edit.js +136 -0
  106. package/dist/src/chat-commands/agent-edit.js.map +1 -0
  107. package/dist/src/chat-commands/index.d.ts +1 -0
  108. package/dist/src/chat-commands/index.js +3 -1
  109. package/dist/src/chat-commands/index.js.map +1 -1
  110. package/dist/src/cli/bin.js +2 -0
  111. package/dist/src/cli/bin.js.map +1 -1
  112. package/dist/src/cli/commands/auth.js +6 -0
  113. package/dist/src/cli/commands/auth.js.map +1 -1
  114. package/dist/src/cli/commands/cron.js +42 -3
  115. package/dist/src/cli/commands/cron.js.map +1 -1
  116. package/dist/src/cli/commands/doctor/checks/session-integrity.js +79 -56
  117. package/dist/src/cli/commands/doctor/checks/session-integrity.js.map +1 -1
  118. package/dist/src/cli/commands/onboard/model.js +6 -0
  119. package/dist/src/cli/commands/onboard/model.js.map +1 -1
  120. package/dist/src/cli/commands/update.js +86 -79
  121. package/dist/src/cli/commands/update.js.map +1 -1
  122. package/dist/src/commands/agents.config.d.ts +3 -2
  123. package/dist/src/commands/agents.config.js +5 -2
  124. package/dist/src/commands/agents.config.js.map +1 -1
  125. package/dist/src/config/agent-typed-models.d.ts +2 -7
  126. package/dist/src/config/agent-typed-models.js +3 -14
  127. package/dist/src/config/agent-typed-models.js.map +1 -1
  128. package/dist/src/config/localized-text.d.ts +6 -0
  129. package/dist/src/config/localized-text.js +42 -0
  130. package/dist/src/config/localized-text.js.map +1 -0
  131. package/dist/src/config/models-json.d.ts +6 -6
  132. package/dist/src/config/schema.d.ts +6 -21
  133. package/dist/src/config/schema.js +4 -4
  134. package/dist/src/config/schema.js.map +1 -1
  135. package/dist/src/cron/executor.d.ts +4 -0
  136. package/dist/src/cron/executor.js +169 -5
  137. package/dist/src/cron/executor.js.map +1 -1
  138. package/dist/src/cron/job-content.js +2 -1
  139. package/dist/src/cron/job-content.js.map +1 -1
  140. package/dist/src/cron/types.d.ts +28 -1
  141. package/dist/src/cron/validation.d.ts +80 -0
  142. package/dist/src/cron/validation.js +30 -4
  143. package/dist/src/cron/validation.js.map +1 -1
  144. package/dist/src/cron/workflow-run-completion.d.ts +23 -0
  145. package/dist/src/cron/workflow-run-completion.js +72 -0
  146. package/dist/src/cron/workflow-run-completion.js.map +1 -0
  147. package/dist/src/extensions/update.d.ts +51 -0
  148. package/dist/src/extensions/update.js +260 -0
  149. package/dist/src/extensions/update.js.map +1 -0
  150. package/dist/src/gateway/agents-admin.d.ts +15 -8
  151. package/dist/src/gateway/agents-admin.js +77 -28
  152. package/dist/src/gateway/agents-admin.js.map +1 -1
  153. package/dist/src/gateway/gateway-workflow-host.types.d.ts +17 -0
  154. package/dist/src/gateway/gateway-workflow-host.types.js +1 -0
  155. package/dist/src/gateway/heartbeat/service.js +1 -1
  156. package/dist/src/gateway/hono/lib/config-payload.d.ts +5 -0
  157. package/dist/src/gateway/hono/lib/config-payload.js +2 -1
  158. package/dist/src/gateway/hono/lib/config-payload.js.map +1 -1
  159. package/dist/src/gateway/hono/middleware/auth.d.ts +2 -0
  160. package/dist/src/gateway/hono/middleware/auth.js +12 -7
  161. package/dist/src/gateway/hono/middleware/auth.js.map +1 -1
  162. package/dist/src/gateway/hono/oauth-async.js +40 -15
  163. package/dist/src/gateway/hono/oauth-async.js.map +1 -1
  164. package/dist/src/gateway/hono/oauth.js +31 -6
  165. package/dist/src/gateway/hono/oauth.js.map +1 -1
  166. package/dist/src/gateway/hono/routes/agents.js +55 -12
  167. package/dist/src/gateway/hono/routes/agents.js.map +1 -1
  168. package/dist/src/gateway/hono/routes/config-patch/agents.js +1 -1
  169. package/dist/src/gateway/hono/routes/models.js +11 -5
  170. package/dist/src/gateway/hono/routes/models.js.map +1 -1
  171. package/dist/src/gateway/hono/routes/update.js +55 -107
  172. package/dist/src/gateway/hono/routes/update.js.map +1 -1
  173. package/dist/src/gateway/hono/routes/workflows.js +72 -191
  174. package/dist/src/gateway/hono/routes/workflows.js.map +1 -1
  175. package/dist/src/gateway/server.js +2 -0
  176. package/dist/src/gateway/server.js.map +1 -1
  177. package/dist/src/gateway/service.d.ts +5 -0
  178. package/dist/src/gateway/service.js +24 -3
  179. package/dist/src/gateway/service.js.map +1 -1
  180. package/dist/src/heartbeat/index.js +1 -1
  181. package/dist/src/infra/brew.d.ts +4 -0
  182. package/dist/src/infra/brew.js +20 -0
  183. package/dist/src/infra/brew.js.map +1 -0
  184. package/dist/src/infra/package-json.d.ts +2 -0
  185. package/dist/src/infra/package-json.js +23 -0
  186. package/dist/src/infra/package-json.js.map +1 -0
  187. package/dist/src/infra/package-update-steps.d.ts +35 -0
  188. package/dist/src/infra/package-update-steps.js +304 -0
  189. package/dist/src/infra/package-update-steps.js.map +1 -0
  190. package/dist/src/infra/path-env.d.ts +11 -0
  191. package/dist/src/infra/path-env.js +90 -0
  192. package/dist/src/infra/path-env.js.map +1 -0
  193. package/dist/src/infra/path-prepend.d.ts +7 -0
  194. package/dist/src/infra/path-prepend.js +44 -0
  195. package/dist/src/infra/path-prepend.js.map +1 -0
  196. package/dist/src/infra/stable-node-path.d.ts +2 -0
  197. package/dist/src/infra/stable-node-path.js +28 -0
  198. package/dist/src/infra/stable-node-path.js.map +1 -0
  199. package/dist/src/infra/update-global.d.ts +30 -23
  200. package/dist/src/infra/update-global.js +113 -64
  201. package/dist/src/infra/update-global.js.map +1 -1
  202. package/dist/src/infra/update-log.d.ts +1 -0
  203. package/dist/src/infra/update-log.js +12 -0
  204. package/dist/src/infra/update-log.js.map +1 -0
  205. package/dist/src/infra/update-restart.d.ts +20 -0
  206. package/dist/src/infra/update-restart.js +165 -0
  207. package/dist/src/infra/update-restart.js.map +1 -0
  208. package/dist/src/infra/update-runner.d.ts +89 -1
  209. package/dist/src/infra/update-runner.js +604 -173
  210. package/dist/src/infra/update-runner.js.map +1 -1
  211. package/dist/src/infra/update-startup.d.ts +3 -0
  212. package/dist/src/infra/update-startup.js +8 -4
  213. package/dist/src/infra/update-startup.js.map +1 -1
  214. package/dist/src/providers/index.d.ts +8 -0
  215. package/dist/src/providers/index.js +51 -12
  216. package/dist/src/providers/index.js.map +1 -1
  217. package/dist/src/routing/resolve-route.d.ts +3 -1
  218. package/dist/src/routing/resolve-route.js.map +1 -1
  219. package/dist/src/session/store.d.ts +5 -3
  220. package/dist/src/session/store.js +66 -20
  221. package/dist/src/session/store.js.map +1 -1
  222. package/dist/src/share/site-share-config.d.ts +3 -2
  223. package/dist/src/share/site-share-config.js.map +1 -1
  224. package/dist/src/utils/logger/stats.d.ts +1 -1
  225. package/dist/src/workflows/domain/command.d.ts +2 -1
  226. package/dist/src/workflows/domain/definition-utils.d.ts +14 -0
  227. package/dist/src/workflows/domain/definition-utils.js +50 -0
  228. package/dist/src/workflows/domain/definition-utils.js.map +1 -0
  229. package/dist/src/workflows/domain/event.d.ts +3 -0
  230. package/dist/src/workflows/domain/index.d.ts +2 -0
  231. package/dist/src/workflows/domain/index.js +3 -1
  232. package/dist/src/workflows/domain/run.d.ts +60 -0
  233. package/dist/src/workflows/domain/run.js.map +1 -1
  234. package/dist/src/workflows/domain/validation.d.ts +19 -0
  235. package/dist/src/workflows/domain/validation.js +66 -0
  236. package/dist/src/workflows/domain/validation.js.map +1 -0
  237. package/dist/src/workflows/engine/projector.js +17 -0
  238. package/dist/src/workflows/engine/projector.js.map +1 -1
  239. package/dist/src/workflows/engine/workflow-engine.d.ts +2 -1
  240. package/dist/src/workflows/engine/workflow-engine.js +128 -0
  241. package/dist/src/workflows/engine/workflow-engine.js.map +1 -1
  242. package/dist/src/workflows/index.d.ts +4 -0
  243. package/dist/src/workflows/index.js +9 -2
  244. package/dist/src/workflows/service/run-view-to-snapshot.d.ts +4 -0
  245. package/dist/src/workflows/service/run-view-to-snapshot.js +63 -0
  246. package/dist/src/workflows/service/run-view-to-snapshot.js.map +1 -0
  247. package/dist/src/workflows/service/workflow-run-service.d.ts +37 -0
  248. package/dist/src/workflows/service/workflow-run-service.js +282 -0
  249. package/dist/src/workflows/service/workflow-run-service.js.map +1 -0
  250. package/dist/src/workflows/service/workflow-run-service.types.d.ts +47 -0
  251. package/dist/src/workflows/service/workflow-run-service.types.js +1 -0
  252. package/dist/src/workflows/service/workflow-session-bridge.d.ts +29 -0
  253. package/dist/src/workflows/service/workflow-session-bridge.js +177 -0
  254. package/dist/src/workflows/service/workflow-session-bridge.js.map +1 -0
  255. package/dist/src/workflows/service/workflow-session-key.d.ts +3 -0
  256. package/dist/src/workflows/service/workflow-session-key.js +21 -0
  257. package/dist/src/workflows/service/workflow-session-key.js.map +1 -0
  258. package/dist/src/workflows/store/run-store.js +1 -0
  259. package/dist/src/workflows/store/run-store.js.map +1 -1
  260. package/package.json +1 -1
  261. package/dist/gateway/static/root/assets/agents-CRxETUZx.js +0 -222
  262. package/dist/gateway/static/root/assets/apps-page-wKWf3l57.js +0 -1
  263. package/dist/gateway/static/root/assets/channels-settings-DDbqVNkx.js +0 -1
  264. package/dist/gateway/static/root/assets/copy-SxMW6Xpc.js +0 -1
  265. package/dist/gateway/static/root/assets/cron-api-N9hvuRrn.js +0 -1
  266. package/dist/gateway/static/root/assets/cron-dreaming-jobs-DueM3rBz.js +0 -2
  267. package/dist/gateway/static/root/assets/cron-page-tlNGNxhP.js +0 -1
  268. package/dist/gateway/static/root/assets/dist-CJwfHYvT.js +0 -1
  269. package/dist/gateway/static/root/assets/index-CqZzHNEg.css +0 -1
  270. package/dist/gateway/static/root/assets/sessions-page-DKt-Wmib.js +0 -1
  271. package/dist/gateway/static/root/assets/settings-page-DcJjvvw4.js +0 -3
  272. package/dist/gateway/static/root/assets/skills-page-DuJ4BTO3.js +0 -2
  273. package/dist/gateway/static/root/assets/url-D6jvVYIA.js +0 -7
  274. package/dist/gateway/static/root/assets/voice-api-key-field-CTyHz7L_.js +0 -1
  275. package/dist/gateway/static/root/assets/workflows-page-GacJ41Fv.js +0 -27
@@ -1,79 +1,42 @@
1
1
  import { createLogger } from "../../utils/logger/index.js";
2
2
  import { init_logger } from "../../utils/logger.js";
3
3
  import { extractProfileAgentId } from "../../config/agent-profile.js";
4
- import { init_providers, resolveModel } from "../../providers/index.js";
5
- import { applySubagentProgress } from "../workflow/agent-progress.js";
6
4
  import { parseWorkflowScript } from "../workflow/parser.js";
7
- import { getLastWorkflowMemory } from "../workflow/last-run-memory.js";
8
- import { runWorkflow } from "../workflow/runtime.js";
9
- import { previewValue, recomputeCounts, renderWorkflowText } from "../workflow/snapshot.js";
10
- import { DelegateSubagentRunner } from "../workflow/subagent-runner.js";
11
5
  import "../workflow/index.js";
12
- import { resolveModelRef } from "../../config/agent-typed-models.js";
13
6
  import { Type } from "@sinclair/typebox";
14
7
  //#region src/agent/tools/workflow-tool.ts
15
8
  /**
16
- * `workflow` — the AgentTool the parent model calls to spawn a fan-out run.
17
- *
18
- * Shape mirrors `delegate-tool`: factory builds a closure over deps; `execute`
19
- * parses the script, instantiates the {@link DelegateSubagentRunner}, drives the
20
- * {@link runWorkflow} runtime, and pushes a live text snapshot through
21
- * `onUpdate` for streaming UIs (TUI, gateway console).
22
- *
23
- * Why this lives in `src/agent/tools/` (not under `src/agent/workflow/`):
24
- * the runtime is reusable infrastructure; the AgentTool wrapping is a
25
- * presentation concern that depends on the AgentToolsFactory wiring. Keeping
26
- * the wrapper here matches how `delegate-tool` and `execute-code-tool` are
27
- * organised today.
9
+ * `workflow` — starts a persisted workflow run in a dedicated chat session.
28
10
  */
29
11
  init_logger();
30
- init_providers();
31
12
  const log = createLogger("workflow-tool");
32
- const DEFAULT_TIMEOUT_SEC = 1800;
33
- const MAX_TIMEOUT_SEC = 14400;
34
- const DEFAULT_MAX_CONCURRENCY = 16;
35
- const DEFAULT_MAX_SUBAGENTS = 1e3;
36
- const PUSH_UPDATE_THROTTLE_MS = 300;
37
13
  const WorkflowToolSchema = Type.Object({
38
14
  name: Type.Optional(Type.String({ description: "Name of a saved workflow to run. Either `name` or `script` is required. Use `name` whenever the user references a known workflow (built-in or in ~/.xopc/workflows/)." })),
39
- script: Type.Optional(Type.String({ description: [
40
- "Raw JavaScript workflow script (no Markdown fences, no TypeScript syntax). Ignored when `name` is set.",
41
- "First statement: export const meta = { name: 'snake_case', description: 'short, human-readable' }.",
42
- "Use phase(title), agent(prompt, opts), parallel(arrayOfFunctions), pipeline(items, ...stages), log(message), args, and budget.",
43
- "The script must call agent() at least once.",
44
- "parallel() requires functions: await parallel(items.map(item => () => agent(...)))."
45
- ].join(" ") })),
46
- args: Type.Optional(Type.Any({ description: "Optional JSON value exposed to the workflow script as the global `args`." }))
15
+ script: Type.Optional(Type.String({ description: ["Raw JavaScript workflow script (no Markdown fences, no TypeScript syntax). Ignored when `name` is set.", "First statement: export const meta = { name: 'snake_case', description: 'short, human-readable' }."].join(" ") })),
16
+ args: Type.Optional(Type.Any({ description: "Optional JSON value passed as workflow input payload." })),
17
+ goal: Type.Optional(Type.String({ description: "Optional goal or task description for this workflow run (defaults to user intent in chat)." }))
47
18
  });
48
19
  function createWorkflowTool(deps) {
49
20
  return {
50
21
  name: "workflow",
51
22
  label: "◆ Workflow",
52
23
  description: [
53
- "Run a deterministic JavaScript workflow that orchestrates multiple isolated subagents through agent(), parallel(), and pipeline().",
54
- "Two ways to invoke:",
55
- " 1. `name`: run a saved workflow from the catalog (built-in or ~/.xopc/workflows/). Prefer this when the user references a known name.",
56
- " 2. `script`: provide a raw JS workflow inline. Use when no saved workflow fits. Header is required: export const meta = { name, description }.",
57
- "Named-workflow triggers — call this tool with `{ name: \"<name>\" }` IMMEDIATELY when the user message is any of:",
58
- " • a bare workflow name like \"/audit_repo\", \"/pr_review\", \"/research\", or \"audit_repo\"",
59
- " • \"run the audit_repo workflow\", \"review this PR\", \"debug this error\", \"kick off research\", \"do a multi_perspective_review on X\" (extract args when natural: target, question, error, diff)",
60
- " • after /workflows lists saved workflows and the user picks one",
61
- "Use phase(title) at runtime to mark progress groups. Each agent() returns a string, or a schema-validated object when opts.schema is set.",
62
- "Prefer for decomposable work: repo audits, PR review, incident triage, multi-perspective review, fan-out research, large refactors. Do not use for a single quick read/edit.",
63
- "parallel() takes thunks, not promises: parallel(items.map(item => () => agent(...))).",
64
- "pipeline(items, ...stages) interleaves items across stages — fastest path by default; only use parallel() when you genuinely need a cross-item barrier.",
65
- "Failed agent()/parallel()/pipeline() entries resolve to null; check before synthesizing.",
66
- "Do not use Date.now(), Math.random(), new Date(), require, import, fs, or network APIs — they are unavailable for determinism.",
67
- "Always end with a synthesis agent() that consolidates findings, especially when you fan out for review or research."
68
- ].join("\n\n"),
24
+ "Start a multi-agent workflow run in its own chat session.",
25
+ "Use `name` for catalog workflows, or `script` for an inline workflow (saved under meta.name before run).",
26
+ "Returns immediately with runId + sessionKey track progress in the linked chat session."
27
+ ].join(" "),
69
28
  parameters: WorkflowToolSchema,
70
- async execute(_toolCallId, params, signal, onUpdate) {
71
- let script;
72
- let resolvedSource = "script";
29
+ async execute(_toolCallId, params) {
30
+ if (!deps.startWorkflowRun) return {
31
+ content: [{
32
+ type: "text",
33
+ text: "workflow: gateway workflow runs are not available in this context"
34
+ }],
35
+ details: { error: "workflow_run_unavailable" }
36
+ };
37
+ let definitionId;
73
38
  try {
74
- const resolved = resolveScript(params, deps.catalog);
75
- script = resolved.script;
76
- resolvedSource = resolved.source;
39
+ definitionId = resolveDefinitionId(params, deps.catalog);
77
40
  } catch (e) {
78
41
  const message = e instanceof Error ? e.message : String(e);
79
42
  return {
@@ -84,241 +47,78 @@ function createWorkflowTool(deps) {
84
47
  details: { error: message }
85
48
  };
86
49
  }
87
- const wfCfg = deps.getConfig()?.agents?.defaults?.workflow;
88
- const concurrency = wfCfg?.maxConcurrency ?? DEFAULT_MAX_CONCURRENCY;
89
- const maxSubagents = wfCfg?.maxSubagents ?? DEFAULT_MAX_SUBAGENTS;
90
- const timeoutSec = clampTimeout(wfCfg?.defaultTimeoutSec);
91
- let meta;
50
+ const config = deps.getConfig();
51
+ const parentSessionKey = deps.getCurrentSessionKey?.()?.trim();
52
+ const agentId = extractProfileAgentId(parentSessionKey, config);
53
+ const goal = params.goal?.trim() || "";
54
+ const source = parentSessionKey ? {
55
+ kind: "chat",
56
+ sessionKey: parentSessionKey
57
+ } : { kind: "api" };
92
58
  try {
93
- meta = parseWorkflowScript(script).meta;
94
- } catch (e) {
95
- const message = e instanceof Error ? e.message : String(e);
96
- return {
59
+ const result = await deps.startWorkflowRun({
60
+ agentId,
61
+ definitionId,
62
+ goal,
63
+ input: params.args,
64
+ parentSessionKey,
65
+ source
66
+ });
67
+ if (result.ok === false) return {
97
68
  content: [{
98
69
  type: "text",
99
- text: resolvedSource === "name" ? `workflow "${params.name}" failed to parse: ${message}` : `workflow parse error: ${message}`
70
+ text: `workflow: ${result.message}`
100
71
  }],
101
- details: { error: message }
72
+ details: { error: result.message }
102
73
  };
103
- }
104
- const snapshot = {
105
- name: meta.name,
106
- description: meta.description,
107
- phases: meta.phases?.map((p) => p.title) ?? [],
108
- logs: [],
109
- agents: [],
110
- agentCount: 0,
111
- runningCount: 0,
112
- doneCount: 0,
113
- errorCount: 0,
114
- skippedCount: 0
115
- };
116
- let lastUpdatePushedAtMs = Number.NEGATIVE_INFINITY;
117
- let liveUpdatesDisabled = false;
118
- const pushUpdate = (completed = false, immediate = false) => {
119
- if (liveUpdatesDisabled) return;
120
- recomputeCounts(snapshot);
121
- const nowMs = Date.now();
122
- if (!(completed || immediate || nowMs - lastUpdatePushedAtMs >= PUSH_UPDATE_THROTTLE_MS)) return;
123
- lastUpdatePushedAtMs = nowMs;
124
- try {
125
- onUpdate?.({
126
- content: [{
127
- type: "text",
128
- text: renderWorkflowText(snapshot, completed, { showResultPreviews: false })
129
- }],
130
- details: snapshot
131
- });
132
- } catch (e) {
133
- liveUpdatesDisabled = true;
134
- const message = e instanceof Error ? e.message : String(e);
135
- log.warn({
136
- err: e,
137
- errorMessage: message,
138
- workflow: meta.name
139
- }, `workflow live progress disabled: ${message}`);
140
- }
141
- };
142
- const subagentStream = wfCfg?.subagentStream ?? "steps";
143
- const runner = new DelegateSubagentRunner({
144
- workspace: deps.workspace,
145
- bus: deps.bus,
146
- getDefaultModel: deps.getSubagentModel,
147
- getConfig: deps.getConfig,
148
- toolExecutorConfig: deps.toolExecutorConfig,
149
- buildChildTools: deps.buildChildTools
150
- });
151
- const resolveModelId = (modelRef) => {
152
- const config = deps.getConfig();
153
- if (!config) throw new Error("workflow model resolution requires config");
154
- const sessionKey = deps.getCurrentSessionKey?.();
155
- return resolveModel(resolveModelRef(config, extractProfileAgentId(sessionKey, config), modelRef));
156
- };
157
- const controller = new AbortController();
158
- const onParentAbort = () => controller.abort();
159
- signal?.addEventListener("abort", onParentAbort, { once: true });
160
- const timeoutHandle = timeoutSec > 0 ? setTimeout(() => controller.abort(), timeoutSec * 1e3) : void 0;
161
- pushUpdate();
162
- try {
163
- const result = await runWorkflow(script, {
164
- runner,
165
- resolveModelId
166
- }, {
167
- cwd: deps.workspace,
168
- args: params.args,
169
- signal: controller.signal,
170
- concurrency,
171
- maxSubagents,
172
- onLog: (message) => {
173
- snapshot.logs.push(message);
174
- pushUpdate();
175
- },
176
- onPhase: (title) => {
177
- snapshot.currentPhase = title;
178
- if (!snapshot.phases.includes(title)) snapshot.phases.push(title);
179
- pushUpdate();
180
- },
181
- onAgentQueued: (event) => {
182
- snapshot.agents.push({
183
- id: event.id,
184
- label: event.label,
185
- phase: event.phase,
186
- prompt: event.prompt,
187
- status: "queued"
188
- });
189
- pushUpdate(false, true);
190
- },
191
- onAgentStart: (event) => {
192
- const agent = findAgentById(snapshot.agents, event.id);
193
- if (agent) {
194
- agent.status = "running";
195
- agent.startedAtMs = Date.now();
196
- } else snapshot.agents.push({
197
- id: event.id,
198
- label: event.label,
199
- phase: event.phase,
200
- prompt: event.prompt,
201
- status: "running",
202
- startedAtMs: Date.now()
203
- });
204
- pushUpdate(false, true);
205
- },
206
- onAgentEnd: (event) => {
207
- const agent = findAgentById(snapshot.agents, event.id);
208
- if (agent) {
209
- agent.status = event.status;
210
- agent.resultPreview = previewValue(event.result);
211
- if (agent.startedAtMs != null) agent.durationMs = Date.now() - agent.startedAtMs;
212
- agent.currentStep = void 0;
213
- }
214
- pushUpdate(false, true);
215
- },
216
- enhanceSubagentRun: subagentStream === "off" ? void 0 : ({ id }) => ({ onProgress: (event) => {
217
- const agent = findAgentById(snapshot.agents, id);
218
- if (!agent) return;
219
- if (applySubagentProgress(agent, event)) pushUpdate();
220
- } })
221
- });
222
- if (result.agentCount === 0) {
223
- const reason = "workflow scripts must call agent() at least once; this workflow declared phases but never ran a subagent.";
224
- snapshot.logs.push(reason);
225
- pushUpdate(true);
226
- return {
227
- content: [{
228
- type: "text",
229
- text: reason
230
- }],
231
- details: snapshot
232
- };
233
- }
234
- snapshot.result = result.result;
235
- snapshot.durationMs = result.durationMs;
236
- pushUpdate(true);
237
- try {
238
- getLastWorkflowMemory().record(deps.getCurrentSessionKey?.(), {
239
- script,
240
- metaName: result.meta.name,
241
- source: resolvedSource,
242
- recordedAt: Date.now()
243
- });
244
- } catch {}
245
74
  return {
246
75
  content: [{
247
76
  type: "text",
248
- text: `workflow ${result.meta.name} completed: ${result.agentCount} subagent(s), ${snapshot.errorCount} error(s).\n\nResult:\n${safeStringify(result.result)}`
77
+ text: `${goal ? `Started workflow \`${definitionId}\` (run ${result.runId}). Open chat session to track progress and continue.` : `Started workflow \`${definitionId}\` (run ${result.runId}). Open the workflow chat session to track progress.`}\n\nsessionKey: ${result.sessionKey}`
249
78
  }],
250
- details: snapshot
79
+ details: {
80
+ runId: result.runId,
81
+ sessionKey: result.sessionKey,
82
+ definitionId,
83
+ parentSessionKey: parentSessionKey ?? null
84
+ }
251
85
  };
252
86
  } catch (e) {
253
- if (controller.signal.aborted) {
254
- for (const a of snapshot.agents) if (a.status === "running") {
255
- a.status = "skipped";
256
- a.error = "aborted";
257
- }
258
- pushUpdate(true);
259
- return {
260
- content: [{
261
- type: "text",
262
- text: signal?.aborted ? "workflow aborted" : `workflow timed out after ${timeoutSec}s`
263
- }],
264
- details: snapshot
265
- };
266
- }
267
87
  const message = e instanceof Error ? e.message : String(e);
268
88
  log.warn({
269
89
  err: e,
270
90
  errorMessage: message,
271
- workflow: meta.name
272
- }, `workflow failed: ${message}`);
273
- snapshot.logs.push(`workflow failed: ${message}`);
274
- pushUpdate(true);
91
+ workflow: definitionId
92
+ }, `workflow start failed: ${message}`);
275
93
  return {
276
94
  content: [{
277
95
  type: "text",
278
- text: `workflow failed: ${message}`
96
+ text: `workflow: ${message}`
279
97
  }],
280
- details: snapshot
98
+ details: { error: message }
281
99
  };
282
- } finally {
283
- if (timeoutHandle) clearTimeout(timeoutHandle);
284
- signal?.removeEventListener("abort", onParentAbort);
285
100
  }
286
101
  }
287
102
  };
288
103
  }
104
+ function resolveDefinitionId(params, catalog) {
105
+ const name = params.name?.trim();
106
+ if (name) {
107
+ catalog.load(name);
108
+ return name;
109
+ }
110
+ if (!params.script?.trim()) throw new Error("either `name` or `script` is required.");
111
+ const script = normalizeScript(params.script);
112
+ const meta = parseWorkflowScript(script).meta;
113
+ catalog.save(meta.name, script);
114
+ return meta.name;
115
+ }
289
116
  function normalizeScript(script) {
290
117
  let text = script.trim();
291
118
  const fence = text.match(/^```(?:js|javascript)?\s*\n([\s\S]*?)\n```$/i);
292
119
  if (fence) text = fence[1].trim();
293
120
  return text;
294
121
  }
295
- function resolveScript(params, catalog) {
296
- const name = params.name?.trim();
297
- if (name) return {
298
- script: catalog.load(name).script,
299
- source: "name"
300
- };
301
- if (!params.script || !params.script.trim()) throw new Error("either `name` or `script` is required.");
302
- return {
303
- script: normalizeScript(params.script),
304
- source: "script"
305
- };
306
- }
307
- function clampTimeout(requested) {
308
- const v = typeof requested === "number" && Number.isFinite(requested) ? requested : DEFAULT_TIMEOUT_SEC;
309
- if (v <= 0) return 0;
310
- return Math.min(MAX_TIMEOUT_SEC, Math.max(1, Math.floor(v)));
311
- }
312
- function findAgentById(agents, id) {
313
- for (let i = agents.length - 1; i >= 0; i--) if (agents[i].id === id) return agents[i];
314
- }
315
- function safeStringify(value) {
316
- try {
317
- return JSON.stringify(value, null, 2);
318
- } catch {
319
- return String(value);
320
- }
321
- }
322
122
  //#endregion
323
123
  export { createWorkflowTool };
324
124
 
@@ -1 +1 @@
1
- {"version":3,"file":"workflow-tool.js","names":["resolveModelById"],"sources":["../../../../src/agent/tools/workflow-tool.ts"],"sourcesContent":["/**\n * `workflow` — the AgentTool the parent model calls to spawn a fan-out run.\n *\n * Shape mirrors `delegate-tool`: factory builds a closure over deps; `execute`\n * parses the script, instantiates the {@link DelegateSubagentRunner}, drives the\n * {@link runWorkflow} runtime, and pushes a live text snapshot through\n * `onUpdate` for streaming UIs (TUI, gateway console).\n *\n * Why this lives in `src/agent/tools/` (not under `src/agent/workflow/`):\n * the runtime is reusable infrastructure; the AgentTool wrapping is a\n * presentation concern that depends on the AgentToolsFactory wiring. Keeping\n * the wrapper here matches how `delegate-tool` and `execute-code-tool` are\n * organised today.\n */\n\nimport { Type } from '@sinclair/typebox';\nimport type { AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core';\nimport type { Api, Model } from '@earendil-works/pi-ai';\n\nimport type { Config } from '../../config/schema.js';\nimport type { MessageBus } from '../../infra/bus/index.js';\nimport { createLogger } from '../../utils/logger.js';\n\nimport type { BuildChildToolsOptions } from '../child-agent-factory.js';\nimport {\n DelegateSubagentRunner,\n getLastWorkflowMemory,\n parseWorkflowScript,\n previewValue,\n recomputeCounts,\n renderWorkflowText,\n runWorkflow,\n applySubagentProgress,\n type WorkflowAgentSnapshot,\n type WorkflowCatalog,\n type WorkflowMeta,\n type WorkflowSnapshot,\n type SubagentProgressEvent,\n} from '../workflow/index.js';\nimport { resolveModel as resolveModelById } from '../../providers/index.js';\nimport { extractProfileAgentId } from '../../config/agent-profile.js';\nimport { resolveModelRef } from '../../config/agent-typed-models.js';\nimport type { ToolExecutorConfig } from './executor.js';\n\nconst log = createLogger('workflow-tool');\n\nconst DEFAULT_TIMEOUT_SEC = 30 * 60;\nconst MAX_TIMEOUT_SEC = 4 * 60 * 60;\nconst DEFAULT_MAX_CONCURRENCY = 16;\nconst DEFAULT_MAX_SUBAGENTS = 1000;\nconst PUSH_UPDATE_THROTTLE_MS = 300;\n\nconst WorkflowToolSchema = Type.Object({\n name: Type.Optional(\n Type.String({\n description:\n 'Name of a saved workflow to run. Either `name` or `script` is required. ' +\n 'Use `name` whenever the user references a known workflow (built-in or in ~/.xopc/workflows/).',\n }),\n ),\n script: Type.Optional(\n Type.String({\n description: [\n 'Raw JavaScript workflow script (no Markdown fences, no TypeScript syntax). Ignored when `name` is set.',\n \"First statement: export const meta = { name: 'snake_case', description: 'short, human-readable' }.\",\n 'Use phase(title), agent(prompt, opts), parallel(arrayOfFunctions), pipeline(items, ...stages), log(message), args, and budget.',\n 'The script must call agent() at least once.',\n 'parallel() requires functions: await parallel(items.map(item => () => agent(...))).',\n ].join(' '),\n }),\n ),\n args: Type.Optional(\n Type.Any({\n description: 'Optional JSON value exposed to the workflow script as the global `args`.',\n }),\n ),\n});\n\nexport type WorkflowToolInput = {\n name?: string;\n script?: string;\n args?: unknown;\n};\n\nexport interface WorkflowToolDeps {\n workspace: string;\n bus: MessageBus;\n /** Returns the parent agent's primary model — subagents default to this. */\n getSubagentModel: () => Model<Api>;\n getConfig: () => Config | undefined;\n /** Same injection point delegate-tool uses; supplied by AgentToolsFactory. */\n buildChildTools: (opts: BuildChildToolsOptions) => AgentTool<any, any>[];\n toolExecutorConfig?: Partial<ToolExecutorConfig>;\n /** Catalog for `name` lookups (built-in + ~/.xopc/workflows/). */\n catalog: WorkflowCatalog;\n /** Per-call sessionKey lookup — used to record \"last successful workflow\" for /workflow save. */\n getCurrentSessionKey?: () => string | undefined;\n}\n\nexport function createWorkflowTool(deps: WorkflowToolDeps): AgentTool {\n return {\n name: 'workflow',\n label: '◆ Workflow',\n description: [\n 'Run a deterministic JavaScript workflow that orchestrates multiple isolated subagents through agent(), parallel(), and pipeline().',\n 'Two ways to invoke:',\n ' 1. `name`: run a saved workflow from the catalog (built-in or ~/.xopc/workflows/). Prefer this when the user references a known name.',\n ' 2. `script`: provide a raw JS workflow inline. Use when no saved workflow fits. Header is required: export const meta = { name, description }.',\n 'Named-workflow triggers — call this tool with `{ name: \"<name>\" }` IMMEDIATELY when the user message is any of:',\n ' • a bare workflow name like \"/audit_repo\", \"/pr_review\", \"/research\", or \"audit_repo\"',\n ' • \"run the audit_repo workflow\", \"review this PR\", \"debug this error\", \"kick off research\", \"do a multi_perspective_review on X\" (extract args when natural: target, question, error, diff)',\n ' • after /workflows lists saved workflows and the user picks one',\n 'Use phase(title) at runtime to mark progress groups. Each agent() returns a string, or a schema-validated object when opts.schema is set.',\n 'Prefer for decomposable work: repo audits, PR review, incident triage, multi-perspective review, fan-out research, large refactors. Do not use for a single quick read/edit.',\n 'parallel() takes thunks, not promises: parallel(items.map(item => () => agent(...))).',\n 'pipeline(items, ...stages) interleaves items across stages — fastest path by default; only use parallel() when you genuinely need a cross-item barrier.',\n 'Failed agent()/parallel()/pipeline() entries resolve to null; check before synthesizing.',\n 'Do not use Date.now(), Math.random(), new Date(), require, import, fs, or network APIs — they are unavailable for determinism.',\n 'Always end with a synthesis agent() that consolidates findings, especially when you fan out for review or research.',\n ].join('\\n\\n'),\n parameters: WorkflowToolSchema,\n\n async execute(\n _toolCallId: string,\n params: WorkflowToolInput,\n signal?: AbortSignal,\n onUpdate?: (update: AgentToolResult<WorkflowSnapshot | undefined>) => void,\n ): Promise<AgentToolResult<WorkflowSnapshot | { error: string }>> {\n let script: string;\n let resolvedSource: 'name' | 'script' = 'script';\n try {\n const resolved = resolveScript(params, deps.catalog);\n script = resolved.script;\n resolvedSource = resolved.source;\n } catch (e) {\n const message = e instanceof Error ? e.message : String(e);\n return {\n content: [{ type: 'text', text: `workflow: ${message}` }],\n details: { error: message },\n };\n }\n\n const cfg = deps.getConfig();\n const wfCfg = cfg?.agents?.defaults?.workflow;\n const concurrency = wfCfg?.maxConcurrency ?? DEFAULT_MAX_CONCURRENCY;\n const maxSubagents = wfCfg?.maxSubagents ?? DEFAULT_MAX_SUBAGENTS;\n const timeoutSec = clampTimeout(wfCfg?.defaultTimeoutSec);\n\n // Parse early so a bad script returns an error result instead of throwing\n // through the agent loop. The runtime parses again, but that's cheap.\n let meta: WorkflowMeta;\n try {\n meta = parseWorkflowScript(script).meta;\n } catch (e) {\n const message = e instanceof Error ? e.message : String(e);\n return {\n content: [\n {\n type: 'text',\n text:\n resolvedSource === 'name'\n ? `workflow \"${params.name}\" failed to parse: ${message}`\n : `workflow parse error: ${message}`,\n },\n ],\n details: { error: message },\n };\n }\n\n const snapshot: WorkflowSnapshot = {\n name: meta.name,\n description: meta.description,\n phases: meta.phases?.map((p) => p.title) ?? [],\n logs: [],\n agents: [],\n agentCount: 0,\n runningCount: 0,\n doneCount: 0,\n errorCount: 0,\n skippedCount: 0,\n };\n\n let lastUpdatePushedAtMs = Number.NEGATIVE_INFINITY;\n let liveUpdatesDisabled = false;\n const pushUpdate = (completed = false, immediate = false) => {\n if (liveUpdatesDisabled) return;\n\n recomputeCounts(snapshot);\n const nowMs = Date.now();\n const shouldEmit =\n completed || immediate || nowMs - lastUpdatePushedAtMs >= PUSH_UPDATE_THROTTLE_MS;\n if (!shouldEmit) return;\n\n lastUpdatePushedAtMs = nowMs;\n try {\n onUpdate?.({\n content: [\n {\n type: 'text',\n text: renderWorkflowText(snapshot, completed, { showResultPreviews: false }),\n },\n ],\n details: snapshot,\n });\n } catch (e) {\n liveUpdatesDisabled = true;\n const message = e instanceof Error ? e.message : String(e);\n log.warn(\n { err: e, errorMessage: message, workflow: meta.name },\n `workflow live progress disabled: ${message}`,\n );\n }\n };\n\n const subagentStream = wfCfg?.subagentStream ?? 'steps';\n\n const runner = new DelegateSubagentRunner({\n workspace: deps.workspace,\n bus: deps.bus,\n getDefaultModel: deps.getSubagentModel,\n getConfig: deps.getConfig,\n toolExecutorConfig: deps.toolExecutorConfig,\n buildChildTools: deps.buildChildTools,\n });\n\n const resolveModelId = (modelRef: string): Model<Api> => {\n const config = deps.getConfig();\n if (!config) {\n throw new Error('workflow model resolution requires config');\n }\n const sessionKey = deps.getCurrentSessionKey?.();\n const agentId = extractProfileAgentId(sessionKey, config);\n const realRef = resolveModelRef(config, agentId, modelRef);\n return resolveModelById(realRef);\n };\n\n // Combined abort: parent signal + per-run timeout.\n const controller = new AbortController();\n const onParentAbort = () => controller.abort();\n signal?.addEventListener('abort', onParentAbort, { once: true });\n const timeoutHandle =\n timeoutSec > 0\n ? setTimeout(() => controller.abort(), timeoutSec * 1000)\n : undefined;\n\n pushUpdate();\n\n try {\n const result = await runWorkflow(script, { runner, resolveModelId }, {\n cwd: deps.workspace,\n args: params.args,\n signal: controller.signal,\n concurrency,\n maxSubagents,\n onLog: (message) => {\n snapshot.logs.push(message);\n pushUpdate();\n },\n onPhase: (title) => {\n snapshot.currentPhase = title;\n if (!snapshot.phases.includes(title)) snapshot.phases.push(title);\n pushUpdate();\n },\n onAgentQueued: (event) => {\n snapshot.agents.push({\n id: event.id,\n label: event.label,\n phase: event.phase,\n prompt: event.prompt,\n status: 'queued',\n });\n pushUpdate(false, true);\n },\n onAgentStart: (event) => {\n const agent = findAgentById(snapshot.agents, event.id);\n if (agent) {\n agent.status = 'running';\n agent.startedAtMs = Date.now();\n } else {\n snapshot.agents.push({\n id: event.id,\n label: event.label,\n phase: event.phase,\n prompt: event.prompt,\n status: 'running',\n startedAtMs: Date.now(),\n });\n }\n pushUpdate(false, true);\n },\n onAgentEnd: (event) => {\n const agent = findAgentById(snapshot.agents, event.id);\n if (agent) {\n agent.status = event.status;\n agent.resultPreview = previewValue(event.result);\n if (agent.startedAtMs != null) {\n agent.durationMs = Date.now() - agent.startedAtMs;\n }\n agent.currentStep = undefined;\n }\n pushUpdate(false, true);\n },\n enhanceSubagentRun:\n subagentStream === 'off'\n ? undefined\n : ({ id }) => ({\n onProgress: (event: SubagentProgressEvent) => {\n const agent = findAgentById(snapshot.agents, id);\n if (!agent) return;\n if (applySubagentProgress(agent, event)) {\n pushUpdate();\n }\n },\n }),\n });\n\n if (result.agentCount === 0) {\n const reason =\n 'workflow scripts must call agent() at least once; this workflow declared phases but never ran a subagent.';\n snapshot.logs.push(reason);\n pushUpdate(true);\n return {\n content: [{ type: 'text', text: reason }],\n details: snapshot,\n };\n }\n\n snapshot.result = result.result;\n snapshot.durationMs = result.durationMs;\n pushUpdate(true);\n\n // Record for /workflow save — last successful run per session.\n // Failures are intentionally skipped so users do not save broken scripts.\n try {\n getLastWorkflowMemory().record(deps.getCurrentSessionKey?.(), {\n script,\n metaName: result.meta.name,\n source: resolvedSource,\n recordedAt: Date.now(),\n });\n } catch {\n // Memory recording is best-effort; never break a successful run on it.\n }\n\n return {\n content: [\n {\n type: 'text',\n text: `workflow ${result.meta.name} completed: ${result.agentCount} subagent(s), ${snapshot.errorCount} error(s).\\n\\nResult:\\n${safeStringify(result.result)}`,\n },\n ],\n details: snapshot,\n };\n } catch (e) {\n if (controller.signal.aborted) {\n for (const a of snapshot.agents) {\n if (a.status === 'running') {\n a.status = 'skipped';\n a.error = 'aborted';\n }\n }\n pushUpdate(true);\n const reason = signal?.aborted ? 'workflow aborted' : `workflow timed out after ${timeoutSec}s`;\n return {\n content: [{ type: 'text', text: reason }],\n details: snapshot,\n };\n }\n const message = e instanceof Error ? e.message : String(e);\n log.warn({ err: e, errorMessage: message, workflow: meta.name }, `workflow failed: ${message}`);\n snapshot.logs.push(`workflow failed: ${message}`);\n pushUpdate(true);\n return {\n content: [{ type: 'text', text: `workflow failed: ${message}` }],\n details: snapshot,\n };\n } finally {\n if (timeoutHandle) clearTimeout(timeoutHandle);\n signal?.removeEventListener('abort', onParentAbort);\n }\n },\n } as unknown as AgentTool;\n}\n\n// ---------------------------------------------------------------------------\n\nfunction normalizeScript(script: string): string {\n let text = script.trim();\n const fence = text.match(/^```(?:js|javascript)?\\s*\\n([\\s\\S]*?)\\n```$/i);\n if (fence) text = fence[1].trim();\n return text;\n}\n\nfunction resolveScript(\n params: WorkflowToolInput,\n catalog: WorkflowCatalog,\n): { script: string; source: 'name' | 'script' } {\n const name = params.name?.trim();\n if (name) {\n const loaded = catalog.load(name);\n return { script: loaded.script, source: 'name' };\n }\n if (!params.script || !params.script.trim()) {\n throw new Error('either `name` or `script` is required.');\n }\n return { script: normalizeScript(params.script), source: 'script' };\n}\n\nfunction clampTimeout(requested: number | undefined): number {\n const v = typeof requested === 'number' && Number.isFinite(requested) ? requested : DEFAULT_TIMEOUT_SEC;\n if (v <= 0) return 0;\n return Math.min(MAX_TIMEOUT_SEC, Math.max(1, Math.floor(v)));\n}\n\nfunction findAgentById(agents: WorkflowAgentSnapshot[], id: number): WorkflowAgentSnapshot | undefined {\n // Linear scan — agent lists are small in practice (capped at maxSubagents).\n for (let i = agents.length - 1; i >= 0; i--) {\n if (agents[i].id === id) return agents[i];\n }\n return undefined;\n}\n\nfunction safeStringify(value: unknown): string {\n try {\n return JSON.stringify(value, null, 2);\n } catch {\n return String(value);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;aAqBqD;gBAkBuB;AAK5E,MAAM,MAAM,aAAa,gBAAgB;AAEzC,MAAM,sBAAsB;AAC5B,MAAM,kBAAkB;AACxB,MAAM,0BAA0B;AAChC,MAAM,wBAAwB;AAC9B,MAAM,0BAA0B;AAEhC,MAAM,qBAAqB,KAAK,OAAO;CACrC,MAAM,KAAK,SACT,KAAK,OAAO,EACV,aACE,yKAEH,CAAC,CACH;CACD,QAAQ,KAAK,SACX,KAAK,OAAO,EACV,aAAa;EACX;EACA;EACA;EACA;EACA;EACD,CAAC,KAAK,IAAI,EACZ,CAAC,CACH;CACD,MAAM,KAAK,SACT,KAAK,IAAI,EACP,aAAa,4EACd,CAAC,CACH;CACF,CAAC;AAuBF,SAAgB,mBAAmB,MAAmC;AACpE,QAAO;EACL,MAAM;EACN,OAAO;EACP,aAAa;GACX;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD,CAAC,KAAK,OAAO;EACd,YAAY;EAEZ,MAAM,QACJ,aACA,QACA,QACA,UACgE;GAChE,IAAI;GACJ,IAAI,iBAAoC;AACxC,OAAI;IACF,MAAM,WAAW,cAAc,QAAQ,KAAK,QAAQ;AACpD,aAAS,SAAS;AAClB,qBAAiB,SAAS;YACnB,GAAG;IACV,MAAM,UAAU,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AAC1D,WAAO;KACL,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,aAAa;MAAW,CAAC;KACzD,SAAS,EAAE,OAAO,SAAS;KAC5B;;GAIH,MAAM,QADM,KAAK,WACA,EAAE,QAAQ,UAAU;GACrC,MAAM,cAAc,OAAO,kBAAkB;GAC7C,MAAM,eAAe,OAAO,gBAAgB;GAC5C,MAAM,aAAa,aAAa,OAAO,kBAAkB;GAIzD,IAAI;AACJ,OAAI;AACF,WAAO,oBAAoB,OAAO,CAAC;YAC5B,GAAG;IACV,MAAM,UAAU,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AAC1D,WAAO;KACL,SAAS,CACP;MACE,MAAM;MACN,MACE,mBAAmB,SACf,aAAa,OAAO,KAAK,qBAAqB,YAC9C,yBAAyB;MAChC,CACF;KACD,SAAS,EAAE,OAAO,SAAS;KAC5B;;GAGH,MAAM,WAA6B;IACjC,MAAM,KAAK;IACX,aAAa,KAAK;IAClB,QAAQ,KAAK,QAAQ,KAAK,MAAM,EAAE,MAAM,IAAI,EAAE;IAC9C,MAAM,EAAE;IACR,QAAQ,EAAE;IACV,YAAY;IACZ,cAAc;IACd,WAAW;IACX,YAAY;IACZ,cAAc;IACf;GAED,IAAI,uBAAuB,OAAO;GAClC,IAAI,sBAAsB;GAC1B,MAAM,cAAc,YAAY,OAAO,YAAY,UAAU;AAC3D,QAAI,oBAAqB;AAEzB,oBAAgB,SAAS;IACzB,MAAM,QAAQ,KAAK,KAAK;AAGxB,QAAI,EADF,aAAa,aAAa,QAAQ,wBAAwB,yBAC3C;AAEjB,2BAAuB;AACvB,QAAI;AACF,gBAAW;MACT,SAAS,CACP;OACE,MAAM;OACN,MAAM,mBAAmB,UAAU,WAAW,EAAE,oBAAoB,OAAO,CAAC;OAC7E,CACF;MACD,SAAS;MACV,CAAC;aACK,GAAG;AACV,2BAAsB;KACtB,MAAM,UAAU,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AAC1D,SAAI,KACF;MAAE,KAAK;MAAG,cAAc;MAAS,UAAU,KAAK;MAAM,EACtD,oCAAoC,UACrC;;;GAIL,MAAM,iBAAiB,OAAO,kBAAkB;GAEhD,MAAM,SAAS,IAAI,uBAAuB;IACxC,WAAW,KAAK;IAChB,KAAK,KAAK;IACV,iBAAiB,KAAK;IACtB,WAAW,KAAK;IAChB,oBAAoB,KAAK;IACzB,iBAAiB,KAAK;IACvB,CAAC;GAEF,MAAM,kBAAkB,aAAiC;IACvD,MAAM,SAAS,KAAK,WAAW;AAC/B,QAAI,CAAC,OACH,OAAM,IAAI,MAAM,4CAA4C;IAE9D,MAAM,aAAa,KAAK,wBAAwB;AAGhD,WAAOA,aADS,gBAAgB,QADhB,sBAAsB,YAAY,OACH,EAAE,SAClB,CAAC;;GAIlC,MAAM,aAAa,IAAI,iBAAiB;GACxC,MAAM,sBAAsB,WAAW,OAAO;AAC9C,WAAQ,iBAAiB,SAAS,eAAe,EAAE,MAAM,MAAM,CAAC;GAChE,MAAM,gBACJ,aAAa,IACT,iBAAiB,WAAW,OAAO,EAAE,aAAa,IAAK,GACvD,KAAA;AAEN,eAAY;AAEZ,OAAI;IACF,MAAM,SAAS,MAAM,YAAY,QAAQ;KAAE;KAAQ;KAAgB,EAAE;KACnE,KAAK,KAAK;KACV,MAAM,OAAO;KACb,QAAQ,WAAW;KACnB;KACA;KACA,QAAQ,YAAY;AAClB,eAAS,KAAK,KAAK,QAAQ;AAC3B,kBAAY;;KAEd,UAAU,UAAU;AAClB,eAAS,eAAe;AACxB,UAAI,CAAC,SAAS,OAAO,SAAS,MAAM,CAAE,UAAS,OAAO,KAAK,MAAM;AACjE,kBAAY;;KAEd,gBAAgB,UAAU;AACxB,eAAS,OAAO,KAAK;OACnB,IAAI,MAAM;OACV,OAAO,MAAM;OACb,OAAO,MAAM;OACb,QAAQ,MAAM;OACd,QAAQ;OACT,CAAC;AACF,iBAAW,OAAO,KAAK;;KAEzB,eAAe,UAAU;MACvB,MAAM,QAAQ,cAAc,SAAS,QAAQ,MAAM,GAAG;AACtD,UAAI,OAAO;AACT,aAAM,SAAS;AACf,aAAM,cAAc,KAAK,KAAK;YAE9B,UAAS,OAAO,KAAK;OACnB,IAAI,MAAM;OACV,OAAO,MAAM;OACb,OAAO,MAAM;OACb,QAAQ,MAAM;OACd,QAAQ;OACR,aAAa,KAAK,KAAK;OACxB,CAAC;AAEJ,iBAAW,OAAO,KAAK;;KAEzB,aAAa,UAAU;MACrB,MAAM,QAAQ,cAAc,SAAS,QAAQ,MAAM,GAAG;AACtD,UAAI,OAAO;AACT,aAAM,SAAS,MAAM;AACrB,aAAM,gBAAgB,aAAa,MAAM,OAAO;AAChD,WAAI,MAAM,eAAe,KACvB,OAAM,aAAa,KAAK,KAAK,GAAG,MAAM;AAExC,aAAM,cAAc,KAAA;;AAEtB,iBAAW,OAAO,KAAK;;KAEzB,oBACE,mBAAmB,QACf,KAAA,KACC,EAAE,UAAU,EACX,aAAa,UAAiC;MAC5C,MAAM,QAAQ,cAAc,SAAS,QAAQ,GAAG;AAChD,UAAI,CAAC,MAAO;AACZ,UAAI,sBAAsB,OAAO,MAAM,CACrC,aAAY;QAGjB;KACR,CAAC;AAEF,QAAI,OAAO,eAAe,GAAG;KAC3B,MAAM,SACJ;AACF,cAAS,KAAK,KAAK,OAAO;AAC1B,gBAAW,KAAK;AAChB,YAAO;MACL,SAAS,CAAC;OAAE,MAAM;OAAQ,MAAM;OAAQ,CAAC;MACzC,SAAS;MACV;;AAGH,aAAS,SAAS,OAAO;AACzB,aAAS,aAAa,OAAO;AAC7B,eAAW,KAAK;AAIhB,QAAI;AACF,4BAAuB,CAAC,OAAO,KAAK,wBAAwB,EAAE;MAC5D;MACA,UAAU,OAAO,KAAK;MACtB,QAAQ;MACR,YAAY,KAAK,KAAK;MACvB,CAAC;YACI;AAIR,WAAO;KACL,SAAS,CACP;MACE,MAAM;MACN,MAAM,YAAY,OAAO,KAAK,KAAK,cAAc,OAAO,WAAW,gBAAgB,SAAS,WAAW,yBAAyB,cAAc,OAAO,OAAO;MAC7J,CACF;KACD,SAAS;KACV;YACM,GAAG;AACV,QAAI,WAAW,OAAO,SAAS;AAC7B,UAAK,MAAM,KAAK,SAAS,OACvB,KAAI,EAAE,WAAW,WAAW;AAC1B,QAAE,SAAS;AACX,QAAE,QAAQ;;AAGd,gBAAW,KAAK;AAEhB,YAAO;MACL,SAAS,CAAC;OAAE,MAAM;OAAQ,MAFb,QAAQ,UAAU,qBAAqB,4BAA4B,WAAW;OAEnD,CAAC;MACzC,SAAS;MACV;;IAEH,MAAM,UAAU,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AAC1D,QAAI,KAAK;KAAE,KAAK;KAAG,cAAc;KAAS,UAAU,KAAK;KAAM,EAAE,oBAAoB,UAAU;AAC/F,aAAS,KAAK,KAAK,oBAAoB,UAAU;AACjD,eAAW,KAAK;AAChB,WAAO;KACL,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,oBAAoB;MAAW,CAAC;KAChE,SAAS;KACV;aACO;AACR,QAAI,cAAe,cAAa,cAAc;AAC9C,YAAQ,oBAAoB,SAAS,cAAc;;;EAGxD;;AAKH,SAAS,gBAAgB,QAAwB;CAC/C,IAAI,OAAO,OAAO,MAAM;CACxB,MAAM,QAAQ,KAAK,MAAM,+CAA+C;AACxE,KAAI,MAAO,QAAO,MAAM,GAAG,MAAM;AACjC,QAAO;;AAGT,SAAS,cACP,QACA,SAC+C;CAC/C,MAAM,OAAO,OAAO,MAAM,MAAM;AAChC,KAAI,KAEF,QAAO;EAAE,QADM,QAAQ,KAAK,KACL,CAAC;EAAQ,QAAQ;EAAQ;AAElD,KAAI,CAAC,OAAO,UAAU,CAAC,OAAO,OAAO,MAAM,CACzC,OAAM,IAAI,MAAM,yCAAyC;AAE3D,QAAO;EAAE,QAAQ,gBAAgB,OAAO,OAAO;EAAE,QAAQ;EAAU;;AAGrE,SAAS,aAAa,WAAuC;CAC3D,MAAM,IAAI,OAAO,cAAc,YAAY,OAAO,SAAS,UAAU,GAAG,YAAY;AACpF,KAAI,KAAK,EAAG,QAAO;AACnB,QAAO,KAAK,IAAI,iBAAiB,KAAK,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC,CAAC;;AAG9D,SAAS,cAAc,QAAiC,IAA+C;AAErG,MAAK,IAAI,IAAI,OAAO,SAAS,GAAG,KAAK,GAAG,IACtC,KAAI,OAAO,GAAG,OAAO,GAAI,QAAO,OAAO;;AAK3C,SAAS,cAAc,OAAwB;AAC7C,KAAI;AACF,SAAO,KAAK,UAAU,OAAO,MAAM,EAAE;SAC/B;AACN,SAAO,OAAO,MAAM"}
1
+ {"version":3,"file":"workflow-tool.js","names":[],"sources":["../../../../src/agent/tools/workflow-tool.ts"],"sourcesContent":["/**\n * `workflow` — starts a persisted workflow run in a dedicated chat session.\n */\n\nimport { Type } from '@sinclair/typebox';\nimport type { AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core';\n\nimport { extractProfileAgentId } from '../../config/agent-profile.js';\nimport { createLogger } from '../../utils/logger.js';\nimport { parseWorkflowScript } from '../workflow/index.js';\nimport type { WorkflowCatalog } from '../workflow/catalog.js';\nimport type {\n StartWorkflowRunServiceParams,\n WorkflowRunServiceResult,\n} from '../../workflows/service/workflow-run-service.types.js';\n\nconst log = createLogger('workflow-tool');\n\nconst WorkflowToolSchema = Type.Object({\n name: Type.Optional(\n Type.String({\n description:\n 'Name of a saved workflow to run. Either `name` or `script` is required. ' +\n 'Use `name` whenever the user references a known workflow (built-in or in ~/.xopc/workflows/).',\n }),\n ),\n script: Type.Optional(\n Type.String({\n description: [\n 'Raw JavaScript workflow script (no Markdown fences, no TypeScript syntax). Ignored when `name` is set.',\n \"First statement: export const meta = { name: 'snake_case', description: 'short, human-readable' }.\",\n ].join(' '),\n }),\n ),\n args: Type.Optional(\n Type.Any({\n description: 'Optional JSON value passed as workflow input payload.',\n }),\n ),\n goal: Type.Optional(\n Type.String({\n description: 'Optional goal or task description for this workflow run (defaults to user intent in chat).',\n }),\n ),\n});\n\nexport type WorkflowToolInput = {\n name?: string;\n script?: string;\n args?: unknown;\n goal?: string;\n};\n\nexport interface WorkflowToolDeps {\n catalog: WorkflowCatalog;\n getCurrentSessionKey?: () => string | undefined;\n getConfig: () => import('../../config/schema.js').Config | undefined;\n startWorkflowRun?: (params: StartWorkflowRunServiceParams) => Promise<WorkflowRunServiceResult>;\n}\n\nexport function createWorkflowTool(deps: WorkflowToolDeps): AgentTool {\n return {\n name: 'workflow',\n label: '◆ Workflow',\n description: [\n 'Start a multi-agent workflow run in its own chat session.',\n 'Use `name` for catalog workflows, or `script` for an inline workflow (saved under meta.name before run).',\n 'Returns immediately with runId + sessionKey — track progress in the linked chat session.',\n ].join(' '),\n parameters: WorkflowToolSchema,\n\n async execute(\n _toolCallId: string,\n params: WorkflowToolInput,\n ): Promise<AgentToolResult<{ runId: string; sessionKey: string } | { error: string }>> {\n if (!deps.startWorkflowRun) {\n return {\n content: [{ type: 'text', text: 'workflow: gateway workflow runs are not available in this context' }],\n details: { error: 'workflow_run_unavailable' },\n };\n }\n\n let definitionId: string;\n try {\n definitionId = resolveDefinitionId(params, deps.catalog);\n } catch (e) {\n const message = e instanceof Error ? e.message : String(e);\n return {\n content: [{ type: 'text', text: `workflow: ${message}` }],\n details: { error: message },\n };\n }\n\n const config = deps.getConfig();\n const parentSessionKey = deps.getCurrentSessionKey?.()?.trim();\n const agentId = extractProfileAgentId(parentSessionKey, config);\n\n const goal = params.goal?.trim() || '';\n const source = parentSessionKey\n ? ({ kind: 'chat' as const, sessionKey: parentSessionKey })\n : ({ kind: 'api' as const });\n\n try {\n const result = await deps.startWorkflowRun({\n agentId,\n definitionId,\n goal,\n input: params.args,\n parentSessionKey,\n source,\n });\n\n if (result.ok === false) {\n return {\n content: [{ type: 'text', text: `workflow: ${result.message}` }],\n details: { error: result.message },\n };\n }\n\n const summary = goal\n ? `Started workflow \\`${definitionId}\\` (run ${result.runId}). Open chat session to track progress and continue.`\n : `Started workflow \\`${definitionId}\\` (run ${result.runId}). Open the workflow chat session to track progress.`;\n\n return {\n content: [\n {\n type: 'text',\n text: `${summary}\\n\\nsessionKey: ${result.sessionKey}`,\n },\n ],\n details: {\n runId: result.runId,\n sessionKey: result.sessionKey,\n definitionId,\n parentSessionKey: parentSessionKey ?? null,\n } as { runId: string; sessionKey: string },\n };\n } catch (e) {\n const message = e instanceof Error ? e.message : String(e);\n log.warn({ err: e, errorMessage: message, workflow: definitionId }, `workflow start failed: ${message}`);\n return {\n content: [{ type: 'text', text: `workflow: ${message}` }],\n details: { error: message },\n };\n }\n },\n } as unknown as AgentTool;\n}\n\nfunction resolveDefinitionId(params: WorkflowToolInput, catalog: WorkflowCatalog): string {\n const name = params.name?.trim();\n if (name) {\n catalog.load(name);\n return name;\n }\n if (!params.script?.trim()) {\n throw new Error('either `name` or `script` is required.');\n }\n const script = normalizeScript(params.script);\n const meta = parseWorkflowScript(script).meta;\n catalog.save(meta.name, script);\n return meta.name;\n}\n\nfunction normalizeScript(script: string): string {\n let text = script.trim();\n const fence = text.match(/^```(?:js|javascript)?\\s*\\n([\\s\\S]*?)\\n```$/i);\n if (fence) text = fence[1].trim();\n return text;\n}\n"],"mappings":";;;;;;;;;;aAQqD;AAQrD,MAAM,MAAM,aAAa,gBAAgB;AAEzC,MAAM,qBAAqB,KAAK,OAAO;CACrC,MAAM,KAAK,SACT,KAAK,OAAO,EACV,aACE,yKAEH,CAAC,CACH;CACD,QAAQ,KAAK,SACX,KAAK,OAAO,EACV,aAAa,CACX,0GACA,qGACD,CAAC,KAAK,IAAI,EACZ,CAAC,CACH;CACD,MAAM,KAAK,SACT,KAAK,IAAI,EACP,aAAa,yDACd,CAAC,CACH;CACD,MAAM,KAAK,SACT,KAAK,OAAO,EACV,aAAa,8FACd,CAAC,CACH;CACF,CAAC;AAgBF,SAAgB,mBAAmB,MAAmC;AACpE,QAAO;EACL,MAAM;EACN,OAAO;EACP,aAAa;GACX;GACA;GACA;GACD,CAAC,KAAK,IAAI;EACX,YAAY;EAEZ,MAAM,QACJ,aACA,QACqF;AACrF,OAAI,CAAC,KAAK,iBACR,QAAO;IACL,SAAS,CAAC;KAAE,MAAM;KAAQ,MAAM;KAAqE,CAAC;IACtG,SAAS,EAAE,OAAO,4BAA4B;IAC/C;GAGH,IAAI;AACJ,OAAI;AACF,mBAAe,oBAAoB,QAAQ,KAAK,QAAQ;YACjD,GAAG;IACV,MAAM,UAAU,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AAC1D,WAAO;KACL,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,aAAa;MAAW,CAAC;KACzD,SAAS,EAAE,OAAO,SAAS;KAC5B;;GAGH,MAAM,SAAS,KAAK,WAAW;GAC/B,MAAM,mBAAmB,KAAK,wBAAwB,EAAE,MAAM;GAC9D,MAAM,UAAU,sBAAsB,kBAAkB,OAAO;GAE/D,MAAM,OAAO,OAAO,MAAM,MAAM,IAAI;GACpC,MAAM,SAAS,mBACV;IAAE,MAAM;IAAiB,YAAY;IAAkB,GACvD,EAAE,MAAM,OAAgB;AAE7B,OAAI;IACF,MAAM,SAAS,MAAM,KAAK,iBAAiB;KACzC;KACA;KACA;KACA,OAAO,OAAO;KACd;KACA;KACD,CAAC;AAEF,QAAI,OAAO,OAAO,MAChB,QAAO;KACL,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,aAAa,OAAO;MAAW,CAAC;KAChE,SAAS,EAAE,OAAO,OAAO,SAAS;KACnC;AAOH,WAAO;KACL,SAAS,CACP;MACE,MAAM;MACN,MAAM,GARI,OACZ,sBAAsB,aAAa,UAAU,OAAO,MAAM,wDAC1D,sBAAsB,aAAa,UAAU,OAAO,MAAM,sDAMvC,kBAAkB,OAAO;MAC3C,CACF;KACD,SAAS;MACP,OAAO,OAAO;MACd,YAAY,OAAO;MACnB;MACA,kBAAkB,oBAAoB;MACvC;KACF;YACM,GAAG;IACV,MAAM,UAAU,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AAC1D,QAAI,KAAK;KAAE,KAAK;KAAG,cAAc;KAAS,UAAU;KAAc,EAAE,0BAA0B,UAAU;AACxG,WAAO;KACL,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,aAAa;MAAW,CAAC;KACzD,SAAS,EAAE,OAAO,SAAS;KAC5B;;;EAGN;;AAGH,SAAS,oBAAoB,QAA2B,SAAkC;CACxF,MAAM,OAAO,OAAO,MAAM,MAAM;AAChC,KAAI,MAAM;AACR,UAAQ,KAAK,KAAK;AAClB,SAAO;;AAET,KAAI,CAAC,OAAO,QAAQ,MAAM,CACxB,OAAM,IAAI,MAAM,yCAAyC;CAE3D,MAAM,SAAS,gBAAgB,OAAO,OAAO;CAC7C,MAAM,OAAO,oBAAoB,OAAO,CAAC;AACzC,SAAQ,KAAK,KAAK,MAAM,OAAO;AAC/B,QAAO,KAAK;;AAGd,SAAS,gBAAgB,QAAwB;CAC/C,IAAI,OAAO,OAAO,MAAM;CACxB,MAAM,QAAQ,KAAK,MAAM,+CAA+C;AACxE,KAAI,MAAO,QAAO,MAAM,GAAG,MAAM;AACjC,QAAO"}
@@ -1,3 +1,7 @@
1
1
  import type { AgentTool } from '@earendil-works/pi-agent-core';
2
- export declare function createWriteFileTool(workspace: string): AgentTool;
2
+ export interface CreateWriteFileToolOptions {
3
+ /** When set and the path is a bare profile filename (e.g. SOUL.md), write to this root. */
4
+ profileMarkdownRoot?: string;
5
+ }
6
+ export declare function createWriteFileTool(workspace: string, options?: CreateWriteFileToolOptions): AgentTool;
3
7
  export declare const writeFileTool: AgentTool;
@@ -1,5 +1,5 @@
1
1
  import { checkFileSafety } from "../prompt/safety.js";
2
- import { resolvePathUnderWorkspace } from "./tool-paths.js";
2
+ import { isBareProfileMarkdownFileName, resolvePathUnderWorkspace, resolveProfileMarkdownPathIfBareName } from "./tool-paths.js";
3
3
  import { evaluateFilePolicy } from "../sandbox/exec-policy.js";
4
4
  import { mkdir, writeFile } from "fs/promises";
5
5
  import { dirname } from "path";
@@ -10,10 +10,10 @@ const WriteFileSchema = Type.Object({
10
10
  path: Type.String({ description: "File path to write" }),
11
11
  content: Type.String({ description: "Content to write" })
12
12
  });
13
- function createWriteFileTool(workspace) {
13
+ function createWriteFileTool(workspace, options) {
14
14
  return {
15
15
  name: "write_file",
16
- description: "Create or overwrite a file. Relative paths are under the current agent workspace.",
16
+ description: "Create or overwrite a file. Relative paths are under the current agent workspace; profile Markdown (SOUL.md, etc.) is written automatically when given by filename.",
17
17
  parameters: WriteFileSchema,
18
18
  label: "📝 Write",
19
19
  async execute(_toolCallId, params, _signal) {
@@ -27,10 +27,12 @@ function createWriteFileTool(workspace) {
27
27
  }],
28
28
  details: {}
29
29
  };
30
+ const writesProfileFile = Boolean(options?.profileMarkdownRoot && isBareProfileMarkdownFileName(p.path));
31
+ const workspaceRoot = writesProfileFile ? options.profileMarkdownRoot : workspace;
30
32
  const pathPolicy = evaluateFilePolicy({
31
33
  operation: "write",
32
34
  path: p.path,
33
- workspaceRoot: workspace
35
+ workspaceRoot
34
36
  });
35
37
  if (!pathPolicy.allowed) return {
36
38
  content: [{
@@ -47,7 +49,7 @@ function createWriteFileTool(workspace) {
47
49
  }],
48
50
  details: {}
49
51
  };
50
- const target = resolvePathUnderWorkspace(p.path, workspace);
52
+ const target = writesProfileFile ? resolveProfileMarkdownPathIfBareName(p.path, options.profileMarkdownRoot) : resolvePathUnderWorkspace(p.path, workspace);
51
53
  await mkdir(dirname(target), { recursive: true });
52
54
  await writeFile(target, p.content, "utf-8");
53
55
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"write.js","names":[],"sources":["../../../../src/agent/tools/write.ts"],"sourcesContent":["// Write file tool\nimport { Type } from '@sinclair/typebox';\nimport type { AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core';\nimport { writeFile, mkdir } from 'fs/promises';\nimport { dirname } from 'path';\nimport { checkFileSafety } from '../prompt/safety.js';\nimport { resolvePathUnderWorkspace } from './tool-paths.js';\nimport { evaluateFilePolicy } from '../sandbox/exec-policy.js';\n\nconst MAX_FILE_SIZE = 10 * 1024 * 1024;\n\nconst WriteFileSchema = Type.Object({\n path: Type.String({ description: 'File path to write' }),\n content: Type.String({ description: 'Content to write' }),\n});\n\ntype WriteFileParams = { path: string; content: string };\n\nexport function createWriteFileTool(workspace: string): AgentTool {\n return {\n name: 'write_file',\n description: 'Create or overwrite a file. Relative paths are under the current agent workspace.',\n parameters: WriteFileSchema,\n label: '📝 Write',\n\n async execute(\n _toolCallId: string,\n params: any,\n _signal?: AbortSignal,\n ): Promise<AgentToolResult<{}>> {\n try {\n const p = params as WriteFileParams;\n const safety = checkFileSafety('write', p.path);\n if (!safety.allowed) {\n return { content: [{ type: 'text', text: `🚫 ${safety.message}` }], details: {} };\n }\n\n // Sandbox path-policy check (blocked dirs, symlink escape, config protection)\n const pathPolicy = evaluateFilePolicy({\n operation: 'write',\n path: p.path,\n workspaceRoot: workspace,\n });\n if (!pathPolicy.allowed) {\n return { content: [{ type: 'text', text: `🚫 Sandbox: ${pathPolicy.reason}` }], details: {} };\n }\n\n const contentBytes = Buffer.byteLength(p.content, 'utf-8');\n if (contentBytes > MAX_FILE_SIZE) {\n return { content: [{ type: 'text', text: `🚫 File too large: ${contentBytes} bytes` }], details: {} };\n }\n\n const target = resolvePathUnderWorkspace(p.path, workspace);\n await mkdir(dirname(target), { recursive: true });\n await writeFile(target, p.content, 'utf-8');\n return { content: [{ type: 'text', text: `File written: ${target}` }], details: { size: contentBytes } };\n } catch (error) {\n return { content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }], details: {} };\n }\n },\n } as any;\n}\n\nexport const writeFileTool: AgentTool = createWriteFileTool(process.cwd());\n"],"mappings":";;;;;;;AASA,MAAM,gBAAgB,KAAK,OAAO;AAElC,MAAM,kBAAkB,KAAK,OAAO;CAClC,MAAM,KAAK,OAAO,EAAE,aAAa,sBAAsB,CAAC;CACxD,SAAS,KAAK,OAAO,EAAE,aAAa,oBAAoB,CAAC;CAC1D,CAAC;AAIF,SAAgB,oBAAoB,WAA8B;AAChE,QAAO;EACL,MAAM;EACN,aAAa;EACb,YAAY;EACZ,OAAO;EAEP,MAAM,QACJ,aACA,QACA,SAC8B;AAC9B,OAAI;IACF,MAAM,IAAI;IACV,MAAM,SAAS,gBAAgB,SAAS,EAAE,KAAK;AAC/C,QAAI,CAAC,OAAO,QACV,QAAO;KAAE,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,MAAM,OAAO;MAAW,CAAC;KAAE,SAAS,EAAE;KAAE;IAInF,MAAM,aAAa,mBAAmB;KACpC,WAAW;KACX,MAAM,EAAE;KACR,eAAe;KAChB,CAAC;AACF,QAAI,CAAC,WAAW,QACd,QAAO;KAAE,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,eAAe,WAAW;MAAU,CAAC;KAAE,SAAS,EAAE;KAAE;IAG/F,MAAM,eAAe,OAAO,WAAW,EAAE,SAAS,QAAQ;AAC1D,QAAI,eAAe,cACjB,QAAO;KAAE,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,sBAAsB,aAAa;MAAS,CAAC;KAAE,SAAS,EAAE;KAAE;IAGvG,MAAM,SAAS,0BAA0B,EAAE,MAAM,UAAU;AAC3D,UAAM,MAAM,QAAQ,OAAO,EAAE,EAAE,WAAW,MAAM,CAAC;AACjD,UAAM,UAAU,QAAQ,EAAE,SAAS,QAAQ;AAC3C,WAAO;KAAE,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,iBAAiB;MAAU,CAAC;KAAE,SAAS,EAAE,MAAM,cAAc;KAAE;YACjG,OAAO;AACd,WAAO;KAAE,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;MAAI,CAAC;KAAE,SAAS,EAAE;KAAE;;;EAGlI;;AAGH,MAAa,gBAA2B,oBAAoB,QAAQ,KAAK,CAAC"}
1
+ {"version":3,"file":"write.js","names":[],"sources":["../../../../src/agent/tools/write.ts"],"sourcesContent":["// Write file tool\nimport { Type } from '@sinclair/typebox';\nimport type { AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core';\nimport { writeFile, mkdir } from 'fs/promises';\nimport { dirname } from 'path';\nimport { checkFileSafety } from '../prompt/safety.js';\nimport {\n isBareProfileMarkdownFileName,\n resolveProfileMarkdownPathIfBareName,\n resolvePathUnderWorkspace,\n} from './tool-paths.js';\nimport { evaluateFilePolicy } from '../sandbox/exec-policy.js';\n\nconst MAX_FILE_SIZE = 10 * 1024 * 1024;\n\nconst WriteFileSchema = Type.Object({\n path: Type.String({ description: 'File path to write' }),\n content: Type.String({ description: 'Content to write' }),\n});\n\ntype WriteFileParams = { path: string; content: string };\n\nexport interface CreateWriteFileToolOptions {\n /** When set and the path is a bare profile filename (e.g. SOUL.md), write to this root. */\n profileMarkdownRoot?: string;\n}\n\nexport function createWriteFileTool(\n workspace: string,\n options?: CreateWriteFileToolOptions,\n): AgentTool {\n return {\n name: 'write_file',\n description:\n 'Create or overwrite a file. Relative paths are under the current agent workspace; profile Markdown (SOUL.md, etc.) is written automatically when given by filename.',\n parameters: WriteFileSchema,\n label: '📝 Write',\n\n async execute(\n _toolCallId: string,\n params: any,\n _signal?: AbortSignal,\n ): Promise<AgentToolResult<{}>> {\n try {\n const p = params as WriteFileParams;\n const safety = checkFileSafety('write', p.path);\n if (!safety.allowed) {\n return { content: [{ type: 'text', text: `🚫 ${safety.message}` }], details: {} };\n }\n\n const writesProfileFile = Boolean(\n options?.profileMarkdownRoot && isBareProfileMarkdownFileName(p.path),\n );\n const workspaceRoot = writesProfileFile ? options!.profileMarkdownRoot! : workspace;\n\n // Sandbox path-policy check (blocked dirs, symlink escape, config protection)\n const pathPolicy = evaluateFilePolicy({\n operation: 'write',\n path: p.path,\n workspaceRoot,\n });\n if (!pathPolicy.allowed) {\n return { content: [{ type: 'text', text: `🚫 Sandbox: ${pathPolicy.reason}` }], details: {} };\n }\n\n const contentBytes = Buffer.byteLength(p.content, 'utf-8');\n if (contentBytes > MAX_FILE_SIZE) {\n return { content: [{ type: 'text', text: `🚫 File too large: ${contentBytes} bytes` }], details: {} };\n }\n\n const target = writesProfileFile\n ? resolveProfileMarkdownPathIfBareName(p.path, options!.profileMarkdownRoot!)\n : resolvePathUnderWorkspace(p.path, workspace);\n await mkdir(dirname(target), { recursive: true });\n await writeFile(target, p.content, 'utf-8');\n return { content: [{ type: 'text', text: `File written: ${target}` }], details: { size: contentBytes } };\n } catch (error) {\n return { content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }], details: {} };\n }\n },\n } as any;\n}\n\nexport const writeFileTool: AgentTool = createWriteFileTool(process.cwd());\n"],"mappings":";;;;;;;AAaA,MAAM,gBAAgB,KAAK,OAAO;AAElC,MAAM,kBAAkB,KAAK,OAAO;CAClC,MAAM,KAAK,OAAO,EAAE,aAAa,sBAAsB,CAAC;CACxD,SAAS,KAAK,OAAO,EAAE,aAAa,oBAAoB,CAAC;CAC1D,CAAC;AASF,SAAgB,oBACd,WACA,SACW;AACX,QAAO;EACL,MAAM;EACN,aACE;EACF,YAAY;EACZ,OAAO;EAEP,MAAM,QACJ,aACA,QACA,SAC8B;AAC9B,OAAI;IACF,MAAM,IAAI;IACV,MAAM,SAAS,gBAAgB,SAAS,EAAE,KAAK;AAC/C,QAAI,CAAC,OAAO,QACV,QAAO;KAAE,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,MAAM,OAAO;MAAW,CAAC;KAAE,SAAS,EAAE;KAAE;IAGnF,MAAM,oBAAoB,QACxB,SAAS,uBAAuB,8BAA8B,EAAE,KAAK,CACtE;IACD,MAAM,gBAAgB,oBAAoB,QAAS,sBAAuB;IAG1E,MAAM,aAAa,mBAAmB;KACpC,WAAW;KACX,MAAM,EAAE;KACR;KACD,CAAC;AACF,QAAI,CAAC,WAAW,QACd,QAAO;KAAE,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,eAAe,WAAW;MAAU,CAAC;KAAE,SAAS,EAAE;KAAE;IAG/F,MAAM,eAAe,OAAO,WAAW,EAAE,SAAS,QAAQ;AAC1D,QAAI,eAAe,cACjB,QAAO;KAAE,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,sBAAsB,aAAa;MAAS,CAAC;KAAE,SAAS,EAAE;KAAE;IAGvG,MAAM,SAAS,oBACX,qCAAqC,EAAE,MAAM,QAAS,oBAAqB,GAC3E,0BAA0B,EAAE,MAAM,UAAU;AAChD,UAAM,MAAM,QAAQ,OAAO,EAAE,EAAE,WAAW,MAAM,CAAC;AACjD,UAAM,UAAU,QAAQ,EAAE,SAAS,QAAQ;AAC3C,WAAO;KAAE,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,iBAAiB;MAAU,CAAC;KAAE,SAAS,EAAE,MAAM,cAAc;KAAE;YACjG,OAAO;AACd,WAAO;KAAE,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;MAAI,CAAC;KAAE,SAAS,EAAE;KAAE;;;EAGlI;;AAGH,MAAa,gBAA2B,oBAAoB,QAAQ,KAAK,CAAC"}
@@ -25,6 +25,8 @@ function applySubagentProgress(agent, event) {
25
25
  const step = findStep(agent, event.toolCallId);
26
26
  if (!step) return false;
27
27
  step.status = event.isError ? "error" : "done";
28
+ step.resultPreview = event.resultPreview;
29
+ step.error = event.error;
28
30
  if (step.startedAtMs != null) step.durationMs = Date.now() - step.startedAtMs;
29
31
  if (agent.currentStep === formatCurrentStep(step)) agent.currentStep = void 0;
30
32
  return true;
@@ -1 +1 @@
1
- {"version":3,"file":"agent-progress.js","names":[],"sources":["../../../../src/agent/workflow/agent-progress.ts"],"sourcesContent":["/**\n * Apply live subagent progress events onto a {@link WorkflowAgentSnapshot}.\n */\n\nimport type {\n SubagentProgressEvent,\n WorkflowAgentSnapshot,\n WorkflowAgentStep,\n} from './types.js';\nimport { workflowStepLabel } from './step-labels.js';\n\nconst MAX_STEPS = 50;\nconst MAX_STREAM_TEXT = 32_000;\n\nexport function applySubagentProgress(\n agent: WorkflowAgentSnapshot,\n event: SubagentProgressEvent,\n): boolean {\n switch (event.type) {\n case 'tool_start': {\n if (!agent.steps) agent.steps = [];\n const { label, detail } = workflowStepLabel(event.toolName, event.args);\n const step: WorkflowAgentStep = {\n id: event.toolCallId,\n kind: 'tool',\n toolName: event.toolName,\n label,\n detail,\n status: 'running',\n startedAtMs: Date.now(),\n };\n agent.steps.push(step);\n trimSteps(agent);\n agent.currentStep = formatCurrentStep(step);\n return true;\n }\n case 'tool_end': {\n const step = findStep(agent, event.toolCallId);\n if (!step) return false;\n step.status = event.isError ? 'error' : 'done';\n if (step.startedAtMs != null) step.durationMs = Date.now() - step.startedAtMs;\n if (agent.currentStep === formatCurrentStep(step)) {\n agent.currentStep = undefined;\n }\n return true;\n }\n case 'iteration': {\n agent.iteration = event.count;\n agent.maxIterations = event.max;\n return true;\n }\n case 'text_delta': {\n if (!event.delta) return false;\n agent.streamText = appendStreamText(agent.streamText, event.delta);\n return true;\n }\n case 'thinking_delta': {\n if (!event.delta) return false;\n agent.streamText = appendStreamText(agent.streamText, event.delta);\n return true;\n }\n default:\n return false;\n }\n}\n\nfunction findStep(agent: WorkflowAgentSnapshot, id: string): WorkflowAgentStep | undefined {\n return agent.steps?.find((s) => s.id === id);\n}\n\nfunction formatCurrentStep(step: WorkflowAgentStep): string {\n return step.detail ? `${step.label}: ${step.detail}` : step.label;\n}\n\nfunction trimSteps(agent: WorkflowAgentSnapshot): void {\n if (!agent.steps || agent.steps.length <= MAX_STEPS) return;\n agent.steps = agent.steps.slice(-MAX_STEPS);\n}\n\nfunction appendStreamText(existing: string | undefined, delta: string): string {\n const next = (existing ?? '') + delta;\n if (next.length <= MAX_STREAM_TEXT) return next;\n return next.slice(-MAX_STREAM_TEXT);\n}\n"],"mappings":";;AAWA,MAAM,YAAY;AAClB,MAAM,kBAAkB;AAExB,SAAgB,sBACd,OACA,OACS;AACT,SAAQ,MAAM,MAAd;EACE,KAAK,cAAc;AACjB,OAAI,CAAC,MAAM,MAAO,OAAM,QAAQ,EAAE;GAClC,MAAM,EAAE,OAAO,WAAW,kBAAkB,MAAM,UAAU,MAAM,KAAK;GACvE,MAAM,OAA0B;IAC9B,IAAI,MAAM;IACV,MAAM;IACN,UAAU,MAAM;IAChB;IACA;IACA,QAAQ;IACR,aAAa,KAAK,KAAK;IACxB;AACD,SAAM,MAAM,KAAK,KAAK;AACtB,aAAU,MAAM;AAChB,SAAM,cAAc,kBAAkB,KAAK;AAC3C,UAAO;;EAET,KAAK,YAAY;GACf,MAAM,OAAO,SAAS,OAAO,MAAM,WAAW;AAC9C,OAAI,CAAC,KAAM,QAAO;AAClB,QAAK,SAAS,MAAM,UAAU,UAAU;AACxC,OAAI,KAAK,eAAe,KAAM,MAAK,aAAa,KAAK,KAAK,GAAG,KAAK;AAClE,OAAI,MAAM,gBAAgB,kBAAkB,KAAK,CAC/C,OAAM,cAAc,KAAA;AAEtB,UAAO;;EAET,KAAK;AACH,SAAM,YAAY,MAAM;AACxB,SAAM,gBAAgB,MAAM;AAC5B,UAAO;EAET,KAAK;AACH,OAAI,CAAC,MAAM,MAAO,QAAO;AACzB,SAAM,aAAa,iBAAiB,MAAM,YAAY,MAAM,MAAM;AAClE,UAAO;EAET,KAAK;AACH,OAAI,CAAC,MAAM,MAAO,QAAO;AACzB,SAAM,aAAa,iBAAiB,MAAM,YAAY,MAAM,MAAM;AAClE,UAAO;EAET,QACE,QAAO;;;AAIb,SAAS,SAAS,OAA8B,IAA2C;AACzF,QAAO,MAAM,OAAO,MAAM,MAAM,EAAE,OAAO,GAAG;;AAG9C,SAAS,kBAAkB,MAAiC;AAC1D,QAAO,KAAK,SAAS,GAAG,KAAK,MAAM,IAAI,KAAK,WAAW,KAAK;;AAG9D,SAAS,UAAU,OAAoC;AACrD,KAAI,CAAC,MAAM,SAAS,MAAM,MAAM,UAAU,UAAW;AACrD,OAAM,QAAQ,MAAM,MAAM,MAAM,CAAC,UAAU;;AAG7C,SAAS,iBAAiB,UAA8B,OAAuB;CAC7E,MAAM,QAAQ,YAAY,MAAM;AAChC,KAAI,KAAK,UAAU,gBAAiB,QAAO;AAC3C,QAAO,KAAK,MAAM,CAAC,gBAAgB"}
1
+ {"version":3,"file":"agent-progress.js","names":[],"sources":["../../../../src/agent/workflow/agent-progress.ts"],"sourcesContent":["/**\n * Apply live subagent progress events onto a {@link WorkflowAgentSnapshot}.\n */\n\nimport type {\n SubagentProgressEvent,\n WorkflowAgentSnapshot,\n WorkflowAgentStep,\n} from './types.js';\nimport { workflowStepLabel } from './step-labels.js';\n\nconst MAX_STEPS = 50;\nconst MAX_STREAM_TEXT = 32_000;\n\nexport function applySubagentProgress(\n agent: WorkflowAgentSnapshot,\n event: SubagentProgressEvent,\n): boolean {\n switch (event.type) {\n case 'tool_start': {\n if (!agent.steps) agent.steps = [];\n const { label, detail } = workflowStepLabel(event.toolName, event.args);\n const step: WorkflowAgentStep = {\n id: event.toolCallId,\n kind: 'tool',\n toolName: event.toolName,\n label,\n detail,\n status: 'running',\n startedAtMs: Date.now(),\n };\n agent.steps.push(step);\n trimSteps(agent);\n agent.currentStep = formatCurrentStep(step);\n return true;\n }\n case 'tool_end': {\n const step = findStep(agent, event.toolCallId);\n if (!step) return false;\n step.status = event.isError ? 'error' : 'done';\n step.resultPreview = event.resultPreview;\n step.error = event.error;\n if (step.startedAtMs != null) step.durationMs = Date.now() - step.startedAtMs;\n if (agent.currentStep === formatCurrentStep(step)) {\n agent.currentStep = undefined;\n }\n return true;\n }\n case 'iteration': {\n agent.iteration = event.count;\n agent.maxIterations = event.max;\n return true;\n }\n case 'text_delta': {\n if (!event.delta) return false;\n agent.streamText = appendStreamText(agent.streamText, event.delta);\n return true;\n }\n case 'thinking_delta': {\n if (!event.delta) return false;\n agent.streamText = appendStreamText(agent.streamText, event.delta);\n return true;\n }\n default:\n return false;\n }\n}\n\nfunction findStep(agent: WorkflowAgentSnapshot, id: string): WorkflowAgentStep | undefined {\n return agent.steps?.find((s) => s.id === id);\n}\n\nfunction formatCurrentStep(step: WorkflowAgentStep): string {\n return step.detail ? `${step.label}: ${step.detail}` : step.label;\n}\n\nfunction trimSteps(agent: WorkflowAgentSnapshot): void {\n if (!agent.steps || agent.steps.length <= MAX_STEPS) return;\n agent.steps = agent.steps.slice(-MAX_STEPS);\n}\n\nfunction appendStreamText(existing: string | undefined, delta: string): string {\n const next = (existing ?? '') + delta;\n if (next.length <= MAX_STREAM_TEXT) return next;\n return next.slice(-MAX_STREAM_TEXT);\n}\n"],"mappings":";;AAWA,MAAM,YAAY;AAClB,MAAM,kBAAkB;AAExB,SAAgB,sBACd,OACA,OACS;AACT,SAAQ,MAAM,MAAd;EACE,KAAK,cAAc;AACjB,OAAI,CAAC,MAAM,MAAO,OAAM,QAAQ,EAAE;GAClC,MAAM,EAAE,OAAO,WAAW,kBAAkB,MAAM,UAAU,MAAM,KAAK;GACvE,MAAM,OAA0B;IAC9B,IAAI,MAAM;IACV,MAAM;IACN,UAAU,MAAM;IAChB;IACA;IACA,QAAQ;IACR,aAAa,KAAK,KAAK;IACxB;AACD,SAAM,MAAM,KAAK,KAAK;AACtB,aAAU,MAAM;AAChB,SAAM,cAAc,kBAAkB,KAAK;AAC3C,UAAO;;EAET,KAAK,YAAY;GACf,MAAM,OAAO,SAAS,OAAO,MAAM,WAAW;AAC9C,OAAI,CAAC,KAAM,QAAO;AAClB,QAAK,SAAS,MAAM,UAAU,UAAU;AACxC,QAAK,gBAAgB,MAAM;AAC3B,QAAK,QAAQ,MAAM;AACnB,OAAI,KAAK,eAAe,KAAM,MAAK,aAAa,KAAK,KAAK,GAAG,KAAK;AAClE,OAAI,MAAM,gBAAgB,kBAAkB,KAAK,CAC/C,OAAM,cAAc,KAAA;AAEtB,UAAO;;EAET,KAAK;AACH,SAAM,YAAY,MAAM;AACxB,SAAM,gBAAgB,MAAM;AAC5B,UAAO;EAET,KAAK;AACH,OAAI,CAAC,MAAM,MAAO,QAAO;AACzB,SAAM,aAAa,iBAAiB,MAAM,YAAY,MAAM,MAAM;AAClE,UAAO;EAET,KAAK;AACH,OAAI,CAAC,MAAM,MAAO,QAAO;AACzB,SAAM,aAAa,iBAAiB,MAAM,YAAY,MAAM,MAAM;AAClE,UAAO;EAET,QACE,QAAO;;;AAIb,SAAS,SAAS,OAA8B,IAA2C;AACzF,QAAO,MAAM,OAAO,MAAM,MAAM,EAAE,OAAO,GAAG;;AAG9C,SAAS,kBAAkB,MAAiC;AAC1D,QAAO,KAAK,SAAS,GAAG,KAAK,MAAM,IAAI,KAAK,WAAW,KAAK;;AAG9D,SAAS,UAAU,OAAoC;AACrD,KAAI,CAAC,MAAM,SAAS,MAAM,MAAM,UAAU,UAAW;AACrD,OAAM,QAAQ,MAAM,MAAM,MAAM,CAAC,UAAU;;AAG7C,SAAS,iBAAiB,UAA8B,OAAuB;CAC7E,MAAM,QAAQ,YAAY,MAAM;AAChC,KAAI,KAAK,UAAU,gBAAiB,QAAO;AAC3C,QAAO,KAAK,MAAM,CAAC,gBAAgB"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Built-in workflow: `client_proposal`
3
+ *
4
+ * Draft a client-facing proposal from a brief — scope, timeline, pricing logic,
5
+ * risks, and next steps. For freelancers, consultants, and solo service providers.
6
+ *
7
+ * Args:
8
+ * - client_brief: what the client wants
9
+ * - offer: what you provide
10
+ * - budget_hint: budget range or constraints (optional)
11
+ */
12
+ export declare const CLIENT_PROPOSAL_SCRIPT = "export const meta = {\n name: 'client_proposal',\n description: 'Turn a client brief into a structured proposal with scope, timeline, pricing logic, and risks.',\n whenToUse: 'Freelancer or solo consultant needs a client-ready proposal or SOW draft.',\n examplePrompts: [\n { field: 'client_brief', text: 'SaaS startup wants 3-month growth consulting, budget 50\u201380k CNY' },\n { field: 'offer', text: 'Audit funnel, weekly strategy calls, async Slack support' },\n ],\n i18n: {\n zh: {\n description: '\u5C06\u5BA2\u6237\u9700\u6C42\u8F6C\u5316\u4E3A\u542B\u8303\u56F4\u3001\u65F6\u95F4\u7EBF\u3001\u62A5\u4EF7\u903B\u8F91\u4E0E\u98CE\u9669\u8BF4\u660E\u7684\u5BA2\u6237\u65B9\u6848\u3002',\n whenToUse: '\u81EA\u7531\u804C\u4E1A\u8005\u6216\u72EC\u7ACB\u987E\u95EE\u9700\u8981\u8D77\u8349\u5BA2\u6237\u65B9\u6848 / SOW \u65F6\u3002',\n examplePrompts: [\n { field: 'client_brief', text: 'SaaS \u521B\u4E1A\u516C\u53F8\u8981 3 \u4E2A\u6708\u589E\u957F\u54A8\u8BE2\uFF0C\u9884\u7B97 5\u20138 \u4E07' },\n { field: 'offer', text: '\u6F0F\u6597\u5BA1\u8BA1\u3001\u6BCF\u5468\u7B56\u7565\u4F1A\u3001Slack \u5F02\u6B65\u652F\u6301' },\n ],\n },\n },\n tags: ['writing', 'content', 'document'],\n estimatedAgents: { min: 4, max: 5 },\n phases: [\n { title: 'Understand' },\n { title: 'Structure' },\n { title: 'Draft' },\n { title: 'Polish' },\n ],\n}\n\nconst clientBrief = args && typeof args === 'object' && args.client_brief\n ? String(args.client_brief)\n : 'Infer the client brief from the most recent user turn.'\n\nconst offer = args && typeof args === 'object' && args.offer\n ? String(args.offer)\n : 'Infer your offer from context.'\n\nconst budgetHint = args && typeof args === 'object' && args.budget_hint\n ? String(args.budget_hint)\n : ''\n\nphase('Understand')\nconst understanding = await agent(\n 'Extract client needs, implicit constraints, success criteria, red flags, and what is out of scope.\\n\\n' +\n 'CLIENT BRIEF:\\n' + clientBrief + '\\nYOUR OFFER:\\n' + offer +\n (budgetHint ? '\\nBUDGET HINT:\\n' + budgetHint : ''),\n {\n label: 'understand',\n schema: {\n type: 'object',\n properties: {\n clientGoals: { type: 'array', items: { type: 'string' } },\n constraints: { type: 'array', items: { type: 'string' } },\n successCriteria: { type: 'array', items: { type: 'string' } },\n redFlags: { type: 'array', items: { type: 'string' } },\n outOfScope: { type: 'array', items: { type: 'string' } },\n },\n required: ['clientGoals', 'successCriteria'],\n },\n },\n)\n\nphase('Structure')\nconst structure = await agent(\n 'Design proposal structure: deliverables, milestones, timeline, pricing tiers or logic, assumptions, and exclusions. Fit a solo operator capacity.\\n\\n' +\n JSON.stringify({ clientBrief, offer, budgetHint, understanding }, null, 2),\n {\n label: 'structure',\n schema: {\n type: 'object',\n properties: {\n deliverables: { type: 'array', items: { type: 'string' } },\n milestones: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n name: { type: 'string' },\n duration: { type: 'string' },\n output: { type: 'string' },\n },\n required: ['name', 'output'],\n },\n },\n pricingApproach: { type: 'string' },\n assumptions: { type: 'array', items: { type: 'string' } },\n },\n required: ['deliverables', 'milestones', 'pricingApproach'],\n },\n },\n)\n\nphase('Draft')\nconst draft = await agent(\n 'Write a client-ready proposal draft in professional but warm tone. Include executive summary, scope, timeline, pricing section (with rationale), risks, and next steps.\\n\\n' +\n JSON.stringify({ understanding, structure }, null, 2),\n {\n label: 'draft',\n schema: {\n type: 'object',\n properties: {\n title: { type: 'string' },\n executiveSummary: { type: 'string' },\n scope: { type: 'string' },\n timeline: { type: 'string' },\n pricing: { type: 'string' },\n risks: { type: 'array', items: { type: 'string' } },\n nextSteps: { type: 'array', items: { type: 'string' } },\n },\n required: ['executiveSummary', 'scope', 'pricing', 'nextSteps'],\n },\n },\n)\n\nphase('Polish')\nconst polished = await agent(\n 'Polish the proposal: tighten language, add a one-line value prop, flag anything that could scare the client, and suggest 2 negotiation flex points.\\n\\n' +\n JSON.stringify(draft, null, 2),\n {\n label: 'polish',\n schema: {\n type: 'object',\n properties: {\n valueProp: { type: 'string' },\n fullProposal: { type: 'string' },\n clientConcerns: { type: 'array', items: { type: 'string' } },\n flexPoints: { type: 'array', items: { type: 'string' } },\n },\n required: ['valueProp', 'fullProposal'],\n },\n },\n)\n\nreturn {\n ok: true,\n understanding,\n structure,\n ...(polished ?? { valueProp: '', fullProposal: 'draft failed' }),\n}\n";