@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
@@ -6,6 +6,12 @@ export type CronjobToolParams = {
6
6
  name?: string;
7
7
  schedule?: string;
8
8
  message?: string;
9
+ workflowDefinitionId?: string;
10
+ workflowGoal?: string;
11
+ workflowInputJson?: string;
12
+ waitForCompletion?: boolean;
13
+ deliveryChannel?: string;
14
+ deliveryTo?: string;
9
15
  timezone?: string;
10
16
  sessionTarget?: 'main' | 'isolated';
11
17
  agentId?: string;
@@ -1,3 +1,5 @@
1
+ import { getCronPayloadText } from "../../cron/job-content.js";
2
+ import { DEFAULT_WORKFLOW_CRON_WAIT_MS } from "../../cron/workflow-run-completion.js";
1
3
  import { Type } from "@sinclair/typebox";
2
4
  //#region src/agent/tools/cronjob-tool.ts
3
5
  const CRON_THREAT_PATTERNS = [
@@ -25,6 +27,12 @@ const CronjobSchema = Type.Object({
25
27
  name: Type.Optional(Type.String({ description: "Human-readable job name" })),
26
28
  schedule: Type.Optional(Type.String({ description: "Cron schedule expression. Examples:\n \"0 9 * * *\" = every day at 9:00 AM\n \"*/30 * * * *\" = every 30 minutes\n \"0 9 * * 1-5\" = weekdays at 9:00 AM\n \"0 0 1 * *\" = first day of each month" })),
27
29
  message: Type.Optional(Type.String({ description: "Instruction for the agent when the job runs (agentTurn payload; typically a fresh session)." })),
30
+ workflowDefinitionId: Type.Optional(Type.String({ description: "Workflow definition id for a direct workflowRun job (mutually exclusive with message on create)." })),
31
+ workflowGoal: Type.Optional(Type.String({ description: "Optional goal override when creating a workflowRun job." })),
32
+ workflowInputJson: Type.Optional(Type.String({ description: "JSON object for workflow input payload (workflowRun create/update)." })),
33
+ waitForCompletion: Type.Optional(Type.Boolean({ description: "For workflowRun jobs: when true (default), cron waits for terminal status before succeeding." })),
34
+ deliveryChannel: Type.Optional(Type.String({ description: "Delivery channel when workflow completes (e.g. telegram)." })),
35
+ deliveryTo: Type.Optional(Type.String({ description: "Delivery recipient chat id when workflow completes." })),
28
36
  timezone: Type.Optional(Type.String({ description: "IANA timezone (e.g. Asia/Shanghai, America/New_York). Must be one supported by the cron store." })),
29
37
  sessionTarget: Type.Optional(Type.Union([Type.Literal("main"), Type.Literal("isolated")], { description: "\"main\" uses the main session context; \"isolated\" (default on create) uses a separate session per run." })),
30
38
  agentId: Type.Optional(Type.String({ description: "Agent profile id for isolated jobs (session key). Omit to use the configured default agent (usually `main`)." })),
@@ -42,13 +50,14 @@ function textResult(text) {
42
50
  }
43
51
  function formatJob(job) {
44
52
  const status = job.enabled ? "▶️ active" : "⏸️ disabled";
45
- const payloadText = job.payload.kind === "agentTurn" ? job.payload.message : job.payload.text;
53
+ const payloadText = getCronPayloadText(job);
46
54
  const truncatedPayload = payloadText.length > 100 ? `${payloadText.slice(0, 100)}...` : payloadText;
55
+ const payloadLine = job.payload.kind === "workflowRun" ? ` Workflow: ${job.payload.definitionId}${job.payload.goal ? ` — ${job.payload.goal}` : ""}` : ` Message: ${truncatedPayload}`;
47
56
  return [
48
57
  `${status} ${job.name ?? "(unnamed)"} (${job.id})`,
49
58
  ` Schedule: ${job.schedule}${job.timezone ? ` (${job.timezone})` : ""}`,
50
59
  ` Type: ${job.payload.kind}`,
51
- ` Message: ${truncatedPayload}`,
60
+ payloadLine,
52
61
  ` Next run: ${job.next_run ?? "N/A"}`,
53
62
  ` Session: ${job.sessionTarget ?? "main"}`,
54
63
  ` Agent: ${job.agentId?.trim() || "(default)"}`,
@@ -66,7 +75,7 @@ function createCronjobTool(deps) {
66
75
  return {
67
76
  name: "cronjob",
68
77
  label: "⏰ Cronjob",
69
- description: "Manage scheduled tasks (cron jobs) that run automatically.\n\nEach job has a cron schedule and a message the agent runs when triggered (agent turn).\n\nACTIONS:\n- list: Show all scheduled jobs with status and next run time\n- create: Create a job (requires schedule and message; optional name, timezone, sessionTarget, agentId, workingDirectory)\n- update: Change schedule, message, name, timezone, sessionTarget, agentId, or workingDirectory (requires jobId)\n- remove: Delete a job (requires jobId)\n- enable / disable: Toggle a job (requires jobId)\n- history: Recent executions for a job (requires jobId)",
78
+ description: "Manage scheduled tasks (cron jobs) that run automatically.\n\nJobs can run an agent message (agentTurn) or a workflow directly (workflowRun).\n\nACTIONS:\n- list: Show all scheduled jobs with status and next run time\n- create: Create a job (schedule + message OR workflowDefinitionId; optional goal, workflowInputJson, deliveryChannel/deliveryTo, waitForCompletion)\n- update: Change schedule, message, workflow fields, name, timezone, sessionTarget, agentId, or workingDirectory (requires jobId)\n- remove: Delete a job (requires jobId)\n- enable / disable: Toggle a job (requires jobId)\n- history: Recent executions for a job (requires jobId)",
70
79
  parameters: CronjobSchema,
71
80
  async execute(_toolCallId, params, _signal) {
72
81
  const cron = deps.getCronService();
@@ -85,22 +94,55 @@ function createCronjobTool(deps) {
85
94
  };
86
95
  }
87
96
  case "create": {
88
- if (!params.schedule?.trim() || !params.message?.trim()) return textResult("Error: create requires schedule and message.");
89
- const scanResult = scanCronPrompt(params.message);
90
- if (scanResult) return textResult(`Error: ${scanResult}`);
91
- const payload = {
97
+ const workflowId = params.workflowDefinitionId?.trim();
98
+ const hasMessage = Boolean(params.message?.trim());
99
+ if (!params.schedule?.trim() || !workflowId && !hasMessage || workflowId && hasMessage) return textResult("Error: create requires schedule and exactly one of message or workflowDefinitionId.");
100
+ if (hasMessage) {
101
+ const scanResult = scanCronPrompt(params.message);
102
+ if (scanResult) return textResult(`Error: ${scanResult}`);
103
+ }
104
+ let payload;
105
+ let timeout;
106
+ let sessionTarget = params.sessionTarget ?? "isolated";
107
+ let delivery;
108
+ if (workflowId) {
109
+ let inputEnvelope;
110
+ if (params.workflowInputJson?.trim()) try {
111
+ inputEnvelope = { payload: JSON.parse(params.workflowInputJson) };
112
+ } catch {
113
+ return textResult("Error: workflowInputJson must be valid JSON.");
114
+ }
115
+ const agentId = params.agentId?.trim() || void 0;
116
+ payload = {
117
+ kind: "workflowRun",
118
+ definitionId: workflowId,
119
+ ...params.workflowGoal?.trim() ? { goal: params.workflowGoal.trim() } : {},
120
+ ...inputEnvelope ? { inputEnvelope } : {},
121
+ ...agentId ? { agentId } : {},
122
+ ...params.waitForCompletion === false ? { waitForCompletion: false } : {}
123
+ };
124
+ sessionTarget = "isolated";
125
+ timeout = DEFAULT_WORKFLOW_CRON_WAIT_MS;
126
+ if (params.deliveryChannel?.trim() && params.deliveryTo?.trim()) delivery = {
127
+ mode: "direct",
128
+ channel: params.deliveryChannel.trim(),
129
+ to: params.deliveryTo.trim()
130
+ };
131
+ } else payload = {
92
132
  kind: "agentTurn",
93
133
  message: params.message.trim()
94
134
  };
95
135
  const result = await cron.addJob(params.schedule.trim(), {
96
136
  name: params.name?.trim() || void 0,
97
137
  timezone: params.timezone?.trim() || void 0,
98
- sessionTarget: params.sessionTarget ?? "isolated",
138
+ sessionTarget,
139
+ ...timeout ? { timeout } : {},
140
+ ...delivery ? { delivery } : {},
99
141
  ...params.agentId?.trim() ? { agentId: params.agentId.trim() } : {},
100
142
  ...params.workingDirectory?.trim() ? { workingDirectory: params.workingDirectory.trim() } : {},
101
143
  payload
102
144
  });
103
- return textResult(`Created job${params.name ? ` "${params.name.trim()}"` : ""} (${result.id})\nSchedule: ${result.schedule}`);
145
+ return textResult(`Created ${workflowId ? `workflow job (${workflowId})` : "job"}${params.name ? ` "${params.name.trim()}"` : ""} (${result.id})\nSchedule: ${result.schedule}`);
104
146
  }
105
147
  case "update": {
106
148
  if (!params.jobId?.trim()) return textResult("Error: update requires jobId.");
@@ -114,12 +156,36 @@ function createCronjobTool(deps) {
114
156
  message: params.message.trim()
115
157
  };
116
158
  }
159
+ if (params.workflowDefinitionId?.trim()) {
160
+ let inputEnvelope;
161
+ if (params.workflowInputJson?.trim()) try {
162
+ inputEnvelope = { payload: JSON.parse(params.workflowInputJson) };
163
+ } catch {
164
+ return textResult("Error: workflowInputJson must be valid JSON.");
165
+ }
166
+ const agentId = params.agentId?.trim() || void 0;
167
+ updates.payload = {
168
+ kind: "workflowRun",
169
+ definitionId: params.workflowDefinitionId.trim(),
170
+ ...params.workflowGoal?.trim() ? { goal: params.workflowGoal.trim() } : {},
171
+ ...inputEnvelope ? { inputEnvelope } : {},
172
+ ...agentId ? { agentId } : {},
173
+ ...params.waitForCompletion === false ? { waitForCompletion: false } : {}
174
+ };
175
+ updates.sessionTarget = "isolated";
176
+ updates.timeout = DEFAULT_WORKFLOW_CRON_WAIT_MS;
177
+ }
178
+ if (params.deliveryChannel?.trim() && params.deliveryTo?.trim()) updates.delivery = {
179
+ mode: "direct",
180
+ channel: params.deliveryChannel.trim(),
181
+ to: params.deliveryTo.trim()
182
+ };
117
183
  if (params.timezone?.trim()) updates.timezone = params.timezone.trim();
118
184
  if (params.name !== void 0) updates.name = params.name.trim() || void 0;
119
185
  if (params.sessionTarget !== void 0) updates.sessionTarget = params.sessionTarget;
120
186
  if (params.agentId !== void 0) updates.agentId = params.agentId.trim() || null;
121
187
  if (params.workingDirectory !== void 0) updates.workingDirectory = params.workingDirectory.trim() || null;
122
- if (Object.keys(updates).length === 0) return textResult("Error: update requires at least one of schedule, message, name, timezone, sessionTarget, agentId, workingDirectory.");
188
+ if (Object.keys(updates).length === 0) return textResult("Error: update requires at least one of schedule, message, workflowDefinitionId, name, timezone, sessionTarget, agentId, workingDirectory, deliveryChannel/deliveryTo.");
123
189
  return textResult(await cron.updateJob(params.jobId.trim(), updates) ? `Updated job ${params.jobId.trim()}.` : `Job ${params.jobId.trim()} not found.`);
124
190
  }
125
191
  case "remove":
@@ -1 +1 @@
1
- {"version":3,"file":"cronjob-tool.js","names":[],"sources":["../../../../src/agent/tools/cronjob-tool.ts"],"sourcesContent":["// Agent tool for managing scheduled cron jobs (CronService-backed)\nimport { Type } from '@sinclair/typebox';\nimport type { AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core';\n\nimport type { CronService } from '../../cron/index.js';\nimport type { CronPayload, JobData, JobExecution, JobWithNextRun } from '../../cron/types.js';\n\nconst CRON_THREAT_PATTERNS: Array<[RegExp, string]> = [\n [/ignore\\s+(previous|all|above)\\s+instructions/i, 'prompt_injection'],\n [/do\\s+not\\s+tell\\s+the\\s+user/i, 'deception'],\n [/system\\s+prompt\\s+override/i, 'sys_prompt_override'],\n [/curl\\s+[^\\n]*\\$\\{?\\w*(KEY|TOKEN|SECRET|PASSWORD)/i, 'exfil_curl'],\n [/cat\\s+[^\\n]*(\\.env|credentials|\\.netrc)/i, 'read_secrets'],\n [/rm\\s+-rf\\s+\\//i, 'destructive_rm'],\n];\n\nexport function scanCronPrompt(prompt: string): string | null {\n for (const [pattern, id] of CRON_THREAT_PATTERNS) {\n if (pattern.test(prompt)) {\n return (\n `Blocked: prompt matches threat pattern '${id}'. ` +\n 'Cron prompts must not contain injection or exfiltration payloads.'\n );\n }\n }\n return null;\n}\n\nconst CronjobSchema = Type.Object({\n action: Type.Union(\n [\n Type.Literal('list'),\n Type.Literal('create'),\n Type.Literal('update'),\n Type.Literal('remove'),\n Type.Literal('enable'),\n Type.Literal('disable'),\n Type.Literal('history'),\n ],\n { description: 'Action to perform on cron jobs' },\n ),\n\n name: Type.Optional(Type.String({ description: 'Human-readable job name' })),\n schedule: Type.Optional(\n Type.String({\n description:\n 'Cron schedule expression. Examples:\\n' +\n ' \"0 9 * * *\" = every day at 9:00 AM\\n' +\n ' \"*/30 * * * *\" = every 30 minutes\\n' +\n ' \"0 9 * * 1-5\" = weekdays at 9:00 AM\\n' +\n ' \"0 0 1 * *\" = first day of each month',\n }),\n ),\n message: Type.Optional(\n Type.String({\n description:\n 'Instruction for the agent when the job runs (agentTurn payload; typically a fresh session).',\n }),\n ),\n timezone: Type.Optional(\n Type.String({\n description:\n 'IANA timezone (e.g. Asia/Shanghai, America/New_York). Must be one supported by the cron store.',\n }),\n ),\n sessionTarget: Type.Optional(\n Type.Union([Type.Literal('main'), Type.Literal('isolated')], {\n description:\n '\"main\" uses the main session context; \"isolated\" (default on create) uses a separate session per run.',\n }),\n ),\n agentId: Type.Optional(\n Type.String({\n description:\n 'Agent profile id for isolated jobs (session key). Omit to use the configured default agent (usually `main`).',\n }),\n ),\n workingDirectory: Type.Optional(\n Type.String({\n description:\n 'Absolute workspace path on the gateway host for isolated jobs. Omit to use the agent default workspace.',\n }),\n ),\n\n jobId: Type.Optional(Type.String({ description: 'Job ID (from list output)' })),\n});\n\nexport type CronjobToolParams = {\n action: 'list' | 'create' | 'update' | 'remove' | 'enable' | 'disable' | 'history';\n name?: string;\n schedule?: string;\n message?: string;\n timezone?: string;\n sessionTarget?: 'main' | 'isolated';\n agentId?: string;\n workingDirectory?: string;\n jobId?: string;\n};\n\nexport interface CronjobToolDeps {\n getCronService: () => CronService | undefined;\n}\n\nfunction textResult(text: string): AgentToolResult<{}> {\n return { content: [{ type: 'text', text }], details: {} };\n}\n\nfunction formatJob(job: JobWithNextRun): string {\n const status = job.enabled ? '▶️ active' : '⏸️ disabled';\n const payloadText =\n job.payload.kind === 'agentTurn' ? job.payload.message : job.payload.text;\n const truncatedPayload =\n payloadText.length > 100 ? `${payloadText.slice(0, 100)}...` : payloadText;\n\n return [\n `${status} ${job.name ?? '(unnamed)'} (${job.id})`,\n ` Schedule: ${job.schedule}${job.timezone ? ` (${job.timezone})` : ''}`,\n ` Type: ${job.payload.kind}`,\n ` Message: ${truncatedPayload}`,\n ` Next run: ${job.next_run ?? 'N/A'}`,\n ` Session: ${job.sessionTarget ?? 'main'}`,\n ` Agent: ${job.agentId?.trim() || '(default)'}`,\n ` Workspace: ${job.workingDirectory?.trim() || '(agent default)'}`,\n ].join('\\n');\n}\n\nfunction formatExecution(exec: JobExecution): string {\n const dur = exec.duration != null ? `${(exec.duration / 1000).toFixed(1)}s` : 'N/A';\n const lines = [`[${exec.status}] ${exec.startedAt} (${dur})`];\n if (exec.summary) {\n lines.push(` Summary: ${exec.summary.slice(0, 200)}`);\n }\n if (exec.error) {\n lines.push(` Error: ${exec.error.slice(0, 200)}`);\n }\n return lines.join('\\n');\n}\n\nexport function createCronjobTool(deps: CronjobToolDeps): AgentTool {\n return {\n name: 'cronjob',\n label: '⏰ Cronjob',\n description:\n 'Manage scheduled tasks (cron jobs) that run automatically.\\n\\n' +\n 'Each job has a cron schedule and a message the agent runs when triggered (agent turn).\\n\\n' +\n 'ACTIONS:\\n' +\n '- list: Show all scheduled jobs with status and next run time\\n' +\n '- create: Create a job (requires schedule and message; optional name, timezone, sessionTarget, agentId, workingDirectory)\\n' +\n '- update: Change schedule, message, name, timezone, sessionTarget, agentId, or workingDirectory (requires jobId)\\n' +\n '- remove: Delete a job (requires jobId)\\n' +\n '- enable / disable: Toggle a job (requires jobId)\\n' +\n '- history: Recent executions for a job (requires jobId)',\n parameters: CronjobSchema,\n\n async execute(_toolCallId, params: CronjobToolParams, _signal) {\n const cron = deps.getCronService();\n if (!cron) {\n return textResult('Cron service is not available in this environment.');\n }\n\n try {\n switch (params.action) {\n case 'list': {\n const jobs = await cron.listJobs();\n if (jobs.length === 0) {\n return textResult('No scheduled jobs.');\n }\n const formatted = jobs.map(formatJob).join('\\n\\n');\n return { content: [{ type: 'text', text: formatted }], details: {} };\n }\n\n case 'create': {\n if (!params.schedule?.trim() || !params.message?.trim()) {\n return textResult('Error: create requires schedule and message.');\n }\n\n const scanResult = scanCronPrompt(params.message);\n if (scanResult) {\n return textResult(`Error: ${scanResult}`);\n }\n\n const payload: CronPayload = {\n kind: 'agentTurn',\n message: params.message.trim(),\n };\n\n const result = await cron.addJob(params.schedule.trim(), {\n name: params.name?.trim() || undefined,\n timezone: params.timezone?.trim() || undefined,\n sessionTarget: params.sessionTarget ?? 'isolated',\n ...(params.agentId?.trim() ? { agentId: params.agentId.trim() } : {}),\n ...(params.workingDirectory?.trim()\n ? { workingDirectory: params.workingDirectory.trim() }\n : {}),\n payload,\n });\n\n return textResult(\n `Created job${params.name ? ` \"${params.name.trim()}\"` : ''} (${result.id})\\n` +\n `Schedule: ${result.schedule}`,\n );\n }\n\n case 'update': {\n if (!params.jobId?.trim()) {\n return textResult('Error: update requires jobId.');\n }\n\n const updates: Partial<Omit<JobData, 'id' | 'created_at' | 'updated_at'>> = {};\n if (params.schedule?.trim()) {\n updates.schedule = params.schedule.trim();\n }\n if (params.message != null && params.message.trim()) {\n const scanResult = scanCronPrompt(params.message);\n if (scanResult) {\n return textResult(`Error: ${scanResult}`);\n }\n updates.payload = { kind: 'agentTurn', message: params.message.trim() };\n }\n if (params.timezone?.trim()) {\n updates.timezone = params.timezone.trim();\n }\n if (params.name !== undefined) {\n updates.name = params.name.trim() || undefined;\n }\n if (params.sessionTarget !== undefined) {\n updates.sessionTarget = params.sessionTarget;\n }\n if (params.agentId !== undefined) {\n const t = params.agentId.trim();\n updates.agentId = t || null;\n }\n if (params.workingDirectory !== undefined) {\n const t = params.workingDirectory.trim();\n updates.workingDirectory = t || null;\n }\n\n if (Object.keys(updates).length === 0) {\n return textResult(\n 'Error: update requires at least one of schedule, message, name, timezone, sessionTarget, agentId, workingDirectory.',\n );\n }\n\n const success = await cron.updateJob(params.jobId.trim(), updates);\n return textResult(\n success ? `Updated job ${params.jobId.trim()}.` : `Job ${params.jobId.trim()} not found.`,\n );\n }\n\n case 'remove': {\n if (!params.jobId?.trim()) {\n return textResult('Error: remove requires jobId.');\n }\n const removed = await cron.removeJob(params.jobId.trim());\n return textResult(\n removed ? `Removed job ${params.jobId.trim()}.` : `Job ${params.jobId.trim()} not found.`,\n );\n }\n\n case 'disable': {\n if (!params.jobId?.trim()) {\n return textResult('Error: disable requires jobId.');\n }\n const toggled = await cron.toggleJob(params.jobId.trim(), false);\n return textResult(\n toggled ? `Disabled job ${params.jobId.trim()}.` : `Job ${params.jobId.trim()} not found.`,\n );\n }\n\n case 'enable': {\n if (!params.jobId?.trim()) {\n return textResult('Error: enable requires jobId.');\n }\n const toggled = await cron.toggleJob(params.jobId.trim(), true);\n return textResult(\n toggled ? `Enabled job ${params.jobId.trim()}.` : `Job ${params.jobId.trim()} not found.`,\n );\n }\n\n case 'history': {\n if (!params.jobId?.trim()) {\n return textResult('Error: history requires jobId.');\n }\n const history = await cron.getJobHistory(params.jobId.trim(), 5);\n if (history.length === 0) {\n return textResult(`No execution history for job ${params.jobId.trim()}.`);\n }\n const formatted = history.map(formatExecution).join('\\n\\n');\n return textResult(formatted);\n }\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return textResult(`Error: ${message}`);\n }\n },\n } as any;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"cronjob-tool.js","names":[],"sources":["../../../../src/agent/tools/cronjob-tool.ts"],"sourcesContent":["// Agent tool for managing scheduled cron jobs (CronService-backed)\nimport { Type } from '@sinclair/typebox';\nimport type { AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core';\n\nimport type { CronService } from '../../cron/index.js';\nimport { getCronPayloadText } from '../../cron/job-content.js';\nimport { DEFAULT_WORKFLOW_CRON_WAIT_MS } from '../../cron/workflow-run-completion.js';\nimport type { CronPayload, JobData, JobExecution, JobWithNextRun } from '../../cron/types.js';\n\nconst CRON_THREAT_PATTERNS: Array<[RegExp, string]> = [\n [/ignore\\s+(previous|all|above)\\s+instructions/i, 'prompt_injection'],\n [/do\\s+not\\s+tell\\s+the\\s+user/i, 'deception'],\n [/system\\s+prompt\\s+override/i, 'sys_prompt_override'],\n [/curl\\s+[^\\n]*\\$\\{?\\w*(KEY|TOKEN|SECRET|PASSWORD)/i, 'exfil_curl'],\n [/cat\\s+[^\\n]*(\\.env|credentials|\\.netrc)/i, 'read_secrets'],\n [/rm\\s+-rf\\s+\\//i, 'destructive_rm'],\n];\n\nexport function scanCronPrompt(prompt: string): string | null {\n for (const [pattern, id] of CRON_THREAT_PATTERNS) {\n if (pattern.test(prompt)) {\n return (\n `Blocked: prompt matches threat pattern '${id}'. ` +\n 'Cron prompts must not contain injection or exfiltration payloads.'\n );\n }\n }\n return null;\n}\n\nconst CronjobSchema = Type.Object({\n action: Type.Union(\n [\n Type.Literal('list'),\n Type.Literal('create'),\n Type.Literal('update'),\n Type.Literal('remove'),\n Type.Literal('enable'),\n Type.Literal('disable'),\n Type.Literal('history'),\n ],\n { description: 'Action to perform on cron jobs' },\n ),\n\n name: Type.Optional(Type.String({ description: 'Human-readable job name' })),\n schedule: Type.Optional(\n Type.String({\n description:\n 'Cron schedule expression. Examples:\\n' +\n ' \"0 9 * * *\" = every day at 9:00 AM\\n' +\n ' \"*/30 * * * *\" = every 30 minutes\\n' +\n ' \"0 9 * * 1-5\" = weekdays at 9:00 AM\\n' +\n ' \"0 0 1 * *\" = first day of each month',\n }),\n ),\n message: Type.Optional(\n Type.String({\n description:\n 'Instruction for the agent when the job runs (agentTurn payload; typically a fresh session).',\n }),\n ),\n workflowDefinitionId: Type.Optional(\n Type.String({\n description:\n 'Workflow definition id for a direct workflowRun job (mutually exclusive with message on create).',\n }),\n ),\n workflowGoal: Type.Optional(\n Type.String({ description: 'Optional goal override when creating a workflowRun job.' }),\n ),\n workflowInputJson: Type.Optional(\n Type.String({\n description: 'JSON object for workflow input payload (workflowRun create/update).',\n }),\n ),\n waitForCompletion: Type.Optional(\n Type.Boolean({\n description:\n 'For workflowRun jobs: when true (default), cron waits for terminal status before succeeding.',\n }),\n ),\n deliveryChannel: Type.Optional(\n Type.String({ description: 'Delivery channel when workflow completes (e.g. telegram).' }),\n ),\n deliveryTo: Type.Optional(\n Type.String({ description: 'Delivery recipient chat id when workflow completes.' }),\n ),\n timezone: Type.Optional(\n Type.String({\n description:\n 'IANA timezone (e.g. Asia/Shanghai, America/New_York). Must be one supported by the cron store.',\n }),\n ),\n sessionTarget: Type.Optional(\n Type.Union([Type.Literal('main'), Type.Literal('isolated')], {\n description:\n '\"main\" uses the main session context; \"isolated\" (default on create) uses a separate session per run.',\n }),\n ),\n agentId: Type.Optional(\n Type.String({\n description:\n 'Agent profile id for isolated jobs (session key). Omit to use the configured default agent (usually `main`).',\n }),\n ),\n workingDirectory: Type.Optional(\n Type.String({\n description:\n 'Absolute workspace path on the gateway host for isolated jobs. Omit to use the agent default workspace.',\n }),\n ),\n\n jobId: Type.Optional(Type.String({ description: 'Job ID (from list output)' })),\n});\n\nexport type CronjobToolParams = {\n action: 'list' | 'create' | 'update' | 'remove' | 'enable' | 'disable' | 'history';\n name?: string;\n schedule?: string;\n message?: string;\n workflowDefinitionId?: string;\n workflowGoal?: string;\n workflowInputJson?: string;\n waitForCompletion?: boolean;\n deliveryChannel?: string;\n deliveryTo?: string;\n timezone?: string;\n sessionTarget?: 'main' | 'isolated';\n agentId?: string;\n workingDirectory?: string;\n jobId?: string;\n};\n\nexport interface CronjobToolDeps {\n getCronService: () => CronService | undefined;\n}\n\nfunction textResult(text: string): AgentToolResult<{}> {\n return { content: [{ type: 'text', text }], details: {} };\n}\n\nfunction formatJob(job: JobWithNextRun): string {\n const status = job.enabled ? '▶️ active' : '⏸️ disabled';\n const payloadText = getCronPayloadText(job);\n const truncatedPayload =\n payloadText.length > 100 ? `${payloadText.slice(0, 100)}...` : payloadText;\n const payloadLine =\n job.payload.kind === 'workflowRun'\n ? ` Workflow: ${job.payload.definitionId}${job.payload.goal ? ` — ${job.payload.goal}` : ''}`\n : ` Message: ${truncatedPayload}`;\n\n return [\n `${status} ${job.name ?? '(unnamed)'} (${job.id})`,\n ` Schedule: ${job.schedule}${job.timezone ? ` (${job.timezone})` : ''}`,\n ` Type: ${job.payload.kind}`,\n payloadLine,\n ` Next run: ${job.next_run ?? 'N/A'}`,\n ` Session: ${job.sessionTarget ?? 'main'}`,\n ` Agent: ${job.agentId?.trim() || '(default)'}`,\n ` Workspace: ${job.workingDirectory?.trim() || '(agent default)'}`,\n ].join('\\n');\n}\n\nfunction formatExecution(exec: JobExecution): string {\n const dur = exec.duration != null ? `${(exec.duration / 1000).toFixed(1)}s` : 'N/A';\n const lines = [`[${exec.status}] ${exec.startedAt} (${dur})`];\n if (exec.summary) {\n lines.push(` Summary: ${exec.summary.slice(0, 200)}`);\n }\n if (exec.error) {\n lines.push(` Error: ${exec.error.slice(0, 200)}`);\n }\n return lines.join('\\n');\n}\n\nexport function createCronjobTool(deps: CronjobToolDeps): AgentTool {\n return {\n name: 'cronjob',\n label: '⏰ Cronjob',\n description:\n 'Manage scheduled tasks (cron jobs) that run automatically.\\n\\n' +\n 'Jobs can run an agent message (agentTurn) or a workflow directly (workflowRun).\\n\\n' +\n 'ACTIONS:\\n' +\n '- list: Show all scheduled jobs with status and next run time\\n' +\n '- create: Create a job (schedule + message OR workflowDefinitionId; optional goal, workflowInputJson, deliveryChannel/deliveryTo, waitForCompletion)\\n' +\n '- update: Change schedule, message, workflow fields, name, timezone, sessionTarget, agentId, or workingDirectory (requires jobId)\\n' +\n '- remove: Delete a job (requires jobId)\\n' +\n '- enable / disable: Toggle a job (requires jobId)\\n' +\n '- history: Recent executions for a job (requires jobId)',\n parameters: CronjobSchema,\n\n async execute(_toolCallId, params: CronjobToolParams, _signal) {\n const cron = deps.getCronService();\n if (!cron) {\n return textResult('Cron service is not available in this environment.');\n }\n\n try {\n switch (params.action) {\n case 'list': {\n const jobs = await cron.listJobs();\n if (jobs.length === 0) {\n return textResult('No scheduled jobs.');\n }\n const formatted = jobs.map(formatJob).join('\\n\\n');\n return { content: [{ type: 'text', text: formatted }], details: {} };\n }\n\n case 'create': {\n const workflowId = params.workflowDefinitionId?.trim();\n const hasMessage = Boolean(params.message?.trim());\n if (!params.schedule?.trim() || (!workflowId && !hasMessage) || (workflowId && hasMessage)) {\n return textResult(\n 'Error: create requires schedule and exactly one of message or workflowDefinitionId.',\n );\n }\n\n if (hasMessage) {\n const scanResult = scanCronPrompt(params.message!);\n if (scanResult) {\n return textResult(`Error: ${scanResult}`);\n }\n }\n\n let payload: CronPayload;\n let timeout: number | undefined;\n let sessionTarget = params.sessionTarget ?? 'isolated';\n let delivery: JobData['delivery'];\n\n if (workflowId) {\n let inputEnvelope: { payload: unknown } | undefined;\n if (params.workflowInputJson?.trim()) {\n try {\n inputEnvelope = { payload: JSON.parse(params.workflowInputJson) as unknown };\n } catch {\n return textResult('Error: workflowInputJson must be valid JSON.');\n }\n }\n const agentId = params.agentId?.trim() || undefined;\n payload = {\n kind: 'workflowRun',\n definitionId: workflowId,\n ...(params.workflowGoal?.trim() ? { goal: params.workflowGoal.trim() } : {}),\n ...(inputEnvelope ? { inputEnvelope } : {}),\n ...(agentId ? { agentId } : {}),\n ...(params.waitForCompletion === false ? { waitForCompletion: false } : {}),\n };\n sessionTarget = 'isolated';\n timeout = DEFAULT_WORKFLOW_CRON_WAIT_MS;\n if (params.deliveryChannel?.trim() && params.deliveryTo?.trim()) {\n delivery = {\n mode: 'direct',\n channel: params.deliveryChannel.trim(),\n to: params.deliveryTo.trim(),\n };\n }\n } else {\n payload = {\n kind: 'agentTurn',\n message: params.message!.trim(),\n };\n }\n\n const result = await cron.addJob(params.schedule.trim(), {\n name: params.name?.trim() || undefined,\n timezone: params.timezone?.trim() || undefined,\n sessionTarget,\n ...(timeout ? { timeout } : {}),\n ...(delivery ? { delivery } : {}),\n ...(params.agentId?.trim() ? { agentId: params.agentId.trim() } : {}),\n ...(params.workingDirectory?.trim()\n ? { workingDirectory: params.workingDirectory.trim() }\n : {}),\n payload,\n });\n\n const kindLabel = workflowId ? `workflow job (${workflowId})` : 'job';\n return textResult(\n `Created ${kindLabel}${params.name ? ` \"${params.name.trim()}\"` : ''} (${result.id})\\n` +\n `Schedule: ${result.schedule}`,\n );\n }\n\n case 'update': {\n if (!params.jobId?.trim()) {\n return textResult('Error: update requires jobId.');\n }\n\n const updates: Partial<Omit<JobData, 'id' | 'created_at' | 'updated_at'>> = {};\n if (params.schedule?.trim()) {\n updates.schedule = params.schedule.trim();\n }\n if (params.message != null && params.message.trim()) {\n const scanResult = scanCronPrompt(params.message);\n if (scanResult) {\n return textResult(`Error: ${scanResult}`);\n }\n updates.payload = { kind: 'agentTurn', message: params.message.trim() };\n }\n if (params.workflowDefinitionId?.trim()) {\n let inputEnvelope: { payload: unknown } | undefined;\n if (params.workflowInputJson?.trim()) {\n try {\n inputEnvelope = { payload: JSON.parse(params.workflowInputJson) as unknown };\n } catch {\n return textResult('Error: workflowInputJson must be valid JSON.');\n }\n }\n const agentId = params.agentId?.trim() || undefined;\n updates.payload = {\n kind: 'workflowRun',\n definitionId: params.workflowDefinitionId.trim(),\n ...(params.workflowGoal?.trim() ? { goal: params.workflowGoal.trim() } : {}),\n ...(inputEnvelope ? { inputEnvelope } : {}),\n ...(agentId ? { agentId } : {}),\n ...(params.waitForCompletion === false ? { waitForCompletion: false } : {}),\n };\n updates.sessionTarget = 'isolated';\n updates.timeout = DEFAULT_WORKFLOW_CRON_WAIT_MS;\n }\n if (params.deliveryChannel?.trim() && params.deliveryTo?.trim()) {\n updates.delivery = {\n mode: 'direct',\n channel: params.deliveryChannel.trim(),\n to: params.deliveryTo.trim(),\n };\n }\n if (params.timezone?.trim()) {\n updates.timezone = params.timezone.trim();\n }\n if (params.name !== undefined) {\n updates.name = params.name.trim() || undefined;\n }\n if (params.sessionTarget !== undefined) {\n updates.sessionTarget = params.sessionTarget;\n }\n if (params.agentId !== undefined) {\n const t = params.agentId.trim();\n updates.agentId = t || null;\n }\n if (params.workingDirectory !== undefined) {\n const t = params.workingDirectory.trim();\n updates.workingDirectory = t || null;\n }\n\n if (Object.keys(updates).length === 0) {\n return textResult(\n 'Error: update requires at least one of schedule, message, workflowDefinitionId, name, timezone, sessionTarget, agentId, workingDirectory, deliveryChannel/deliveryTo.',\n );\n }\n\n const success = await cron.updateJob(params.jobId.trim(), updates);\n return textResult(\n success ? `Updated job ${params.jobId.trim()}.` : `Job ${params.jobId.trim()} not found.`,\n );\n }\n\n case 'remove': {\n if (!params.jobId?.trim()) {\n return textResult('Error: remove requires jobId.');\n }\n const removed = await cron.removeJob(params.jobId.trim());\n return textResult(\n removed ? `Removed job ${params.jobId.trim()}.` : `Job ${params.jobId.trim()} not found.`,\n );\n }\n\n case 'disable': {\n if (!params.jobId?.trim()) {\n return textResult('Error: disable requires jobId.');\n }\n const toggled = await cron.toggleJob(params.jobId.trim(), false);\n return textResult(\n toggled ? `Disabled job ${params.jobId.trim()}.` : `Job ${params.jobId.trim()} not found.`,\n );\n }\n\n case 'enable': {\n if (!params.jobId?.trim()) {\n return textResult('Error: enable requires jobId.');\n }\n const toggled = await cron.toggleJob(params.jobId.trim(), true);\n return textResult(\n toggled ? `Enabled job ${params.jobId.trim()}.` : `Job ${params.jobId.trim()} not found.`,\n );\n }\n\n case 'history': {\n if (!params.jobId?.trim()) {\n return textResult('Error: history requires jobId.');\n }\n const history = await cron.getJobHistory(params.jobId.trim(), 5);\n if (history.length === 0) {\n return textResult(`No execution history for job ${params.jobId.trim()}.`);\n }\n const formatted = history.map(formatExecution).join('\\n\\n');\n return textResult(formatted);\n }\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return textResult(`Error: ${message}`);\n }\n },\n } as any;\n}\n"],"mappings":"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"}
@@ -4,5 +4,9 @@ export interface EditToolDetails {
4
4
  firstChangedLine?: number;
5
5
  fuzzyMatchUsed?: boolean;
6
6
  }
7
- export declare function createEditFileTool(workspace: string): AgentTool;
7
+ export interface CreateEditFileToolOptions {
8
+ /** When set and the path is a bare profile filename (e.g. SOUL.md), edit under this root. */
9
+ profileMarkdownRoot?: string;
10
+ }
11
+ export declare function createEditFileTool(workspace: string, options?: CreateEditFileToolOptions): AgentTool;
8
12
  export declare const editFileTool: 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 { fuzzyFindText, generateDiffString, normalizeForFuzzyMatch, normalizeToLF, restoreLineEndings, stripBom } from "./edit-diff.js";
5
5
  import { readFile, stat, writeFile } from "fs/promises";
@@ -11,10 +11,10 @@ const EditFileSchema = Type.Object({
11
11
  oldText: Type.String({ description: "Text to replace" }),
12
12
  newText: Type.String({ description: "Replacement text" })
13
13
  });
14
- function createEditFileTool(workspace) {
14
+ function createEditFileTool(workspace, options) {
15
15
  return {
16
16
  name: "edit_file",
17
- description: "Edit file by replacing text. Relative paths are under the current agent workspace.",
17
+ description: "Edit file by replacing text. Relative paths are under the current agent workspace; profile Markdown (SOUL.md, etc.) is edited automatically when given by filename.",
18
18
  parameters: EditFileSchema,
19
19
  label: "✏️ Edit",
20
20
  async execute(_toolCallId, params, _signal) {
@@ -28,10 +28,12 @@ function createEditFileTool(workspace) {
28
28
  }],
29
29
  details: {}
30
30
  };
31
+ const editsProfileFile = Boolean(options?.profileMarkdownRoot && isBareProfileMarkdownFileName(p.path));
32
+ const workspaceRoot = editsProfileFile ? options.profileMarkdownRoot : workspace;
31
33
  const pathPolicy = evaluateFilePolicy({
32
34
  operation: "edit",
33
35
  path: p.path,
34
- workspaceRoot: workspace
36
+ workspaceRoot
35
37
  });
36
38
  if (!pathPolicy.allowed) return {
37
39
  content: [{
@@ -40,7 +42,7 @@ function createEditFileTool(workspace) {
40
42
  }],
41
43
  details: {}
42
44
  };
43
- const normalized = resolvePathUnderWorkspace(p.path, workspace);
45
+ const normalized = editsProfileFile ? resolveProfileMarkdownPathIfBareName(p.path, options.profileMarkdownRoot) : resolvePathUnderWorkspace(p.path, workspace);
44
46
  if ((await stat(normalized)).size > MAX_FILE_SIZE) return {
45
47
  content: [{
46
48
  type: "text",
@@ -1 +1 @@
1
- {"version":3,"file":"edit.js","names":[],"sources":["../../../../src/agent/tools/edit.ts"],"sourcesContent":["// Edit file tool\nimport { Type } from '@sinclair/typebox';\nimport type { AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core';\nimport { readFile, writeFile, stat } from 'fs/promises';\nimport { checkFileSafety } from '../prompt/safety.js';\nimport { resolvePathUnderWorkspace } from './tool-paths.js';\nimport { evaluateFilePolicy } from '../sandbox/exec-policy.js';\nimport { normalizeToLF, restoreLineEndings, normalizeForFuzzyMatch, fuzzyFindText, stripBom, generateDiffString } from './edit-diff.js';\n\nconst MAX_FILE_SIZE = 10 * 1024 * 1024;\n\nconst EditFileSchema = Type.Object({\n path: Type.String({ description: 'File path to edit' }),\n oldText: Type.String({ description: 'Text to replace' }),\n newText: Type.String({ description: 'Replacement text' }),\n});\n\nexport interface EditToolDetails {\n diff?: string;\n firstChangedLine?: number;\n fuzzyMatchUsed?: boolean;\n}\n\ntype EditFileParams = { path: string; oldText: string; newText: string };\n\nexport function createEditFileTool(workspace: string): AgentTool {\n return {\n name: 'edit_file',\n description: 'Edit file by replacing text. Relative paths are under the current agent workspace.',\n parameters: EditFileSchema,\n label: '✏️ Edit',\n\n async execute(\n _toolCallId: string,\n params: any,\n _signal?: AbortSignal,\n ): Promise<AgentToolResult<EditToolDetails>> {\n try {\n const p = params as EditFileParams;\n const safety = checkFileSafety('write', p.path);\n if (!safety.allowed) return { content: [{ type: 'text', text: `🚫 ${safety.message}` }], details: {} };\n\n // Sandbox path-policy check (blocked dirs, symlink escape, config protection)\n const pathPolicy = evaluateFilePolicy({\n operation: 'edit',\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 normalized = resolvePathUnderWorkspace(p.path, workspace);\n const stats = await stat(normalized);\n if (stats.size > MAX_FILE_SIZE) return { content: [{ type: 'text', text: `🚫 File too large` }], details: {} };\n\n const rawContent = await readFile(normalized, 'utf-8');\n const content = stripBom(rawContent);\n const lineEnding = detectLineEnding(rawContent);\n\n const normalizedContent = normalizeToLF(content);\n const normalizedOldText = normalizeToLF(p.oldText);\n const normalizedNewText = normalizeToLF(p.newText);\n\n const matchResult = fuzzyFindText(normalizedContent, normalizedOldText);\n if (!matchResult.found) return { content: [{ type: 'text', text: `Error: oldText not found` }], details: {} };\n\n const fuzzyContent = normalizeForFuzzyMatch(normalizedContent);\n const fuzzyOldText = normalizeForFuzzyMatch(normalizedOldText);\n const occurrences = fuzzyContent.split(fuzzyOldText).length - 1;\n if (occurrences > 1)\n return { content: [{ type: 'text', text: `Error: ${occurrences} occurrences found, text must be unique` }], details: {} };\n\n const baseContent = matchResult.contentForReplacement;\n const newContent =\n baseContent.substring(0, matchResult.index) +\n normalizedNewText +\n baseContent.substring(matchResult.index + matchResult.matchLength);\n const finalContent = restoreLineEndings(newContent, lineEnding);\n const originalWithReplacement = restoreLineEndings(baseContent, lineEnding);\n\n if (originalWithReplacement === finalContent)\n return { content: [{ type: 'text', text: `Error: No changes` }], details: {} };\n\n const diffResult = generateDiffString(originalWithReplacement, finalContent, normalized);\n await writeFile(normalized, finalContent, 'utf-8');\n\n return {\n content: [{ type: 'text', text: `File edited: ${normalized}` }],\n details: { diff: diffResult, fuzzyMatchUsed: matchResult.usedFuzzyMatch },\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],\n details: {},\n };\n }\n },\n } as any;\n}\n\nexport const editFileTool: AgentTool = createEditFileTool(process.cwd());\n\nfunction detectLineEnding(content: string): '\\r\\n' | '\\n' {\n const crlfIdx = content.indexOf('\\r\\n');\n const lfIdx = content.indexOf('\\n');\n if (lfIdx === -1) return '\\n';\n if (crlfIdx === -1) return '\\n';\n return crlfIdx < lfIdx ? '\\r\\n' : '\\n';\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"edit.js","names":[],"sources":["../../../../src/agent/tools/edit.ts"],"sourcesContent":["// Edit file tool\nimport { Type } from '@sinclair/typebox';\nimport type { AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core';\nimport { readFile, writeFile, stat } from 'fs/promises';\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';\nimport { normalizeToLF, restoreLineEndings, normalizeForFuzzyMatch, fuzzyFindText, stripBom, generateDiffString } from './edit-diff.js';\n\nconst MAX_FILE_SIZE = 10 * 1024 * 1024;\n\nconst EditFileSchema = Type.Object({\n path: Type.String({ description: 'File path to edit' }),\n oldText: Type.String({ description: 'Text to replace' }),\n newText: Type.String({ description: 'Replacement text' }),\n});\n\nexport interface EditToolDetails {\n diff?: string;\n firstChangedLine?: number;\n fuzzyMatchUsed?: boolean;\n}\n\ntype EditFileParams = { path: string; oldText: string; newText: string };\n\nexport interface CreateEditFileToolOptions {\n /** When set and the path is a bare profile filename (e.g. SOUL.md), edit under this root. */\n profileMarkdownRoot?: string;\n}\n\nexport function createEditFileTool(\n workspace: string,\n options?: CreateEditFileToolOptions,\n): AgentTool {\n return {\n name: 'edit_file',\n description:\n 'Edit file by replacing text. Relative paths are under the current agent workspace; profile Markdown (SOUL.md, etc.) is edited automatically when given by filename.',\n parameters: EditFileSchema,\n label: '✏️ Edit',\n\n async execute(\n _toolCallId: string,\n params: any,\n _signal?: AbortSignal,\n ): Promise<AgentToolResult<EditToolDetails>> {\n try {\n const p = params as EditFileParams;\n const safety = checkFileSafety('write', p.path);\n if (!safety.allowed) return { content: [{ type: 'text', text: `🚫 ${safety.message}` }], details: {} };\n\n const editsProfileFile = Boolean(\n options?.profileMarkdownRoot && isBareProfileMarkdownFileName(p.path),\n );\n const workspaceRoot = editsProfileFile ? options!.profileMarkdownRoot! : workspace;\n\n // Sandbox path-policy check (blocked dirs, symlink escape, config protection)\n const pathPolicy = evaluateFilePolicy({\n operation: 'edit',\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 normalized = editsProfileFile\n ? resolveProfileMarkdownPathIfBareName(p.path, options!.profileMarkdownRoot!)\n : resolvePathUnderWorkspace(p.path, workspace);\n const stats = await stat(normalized);\n if (stats.size > MAX_FILE_SIZE) return { content: [{ type: 'text', text: `🚫 File too large` }], details: {} };\n\n const rawContent = await readFile(normalized, 'utf-8');\n const content = stripBom(rawContent);\n const lineEnding = detectLineEnding(rawContent);\n\n const normalizedContent = normalizeToLF(content);\n const normalizedOldText = normalizeToLF(p.oldText);\n const normalizedNewText = normalizeToLF(p.newText);\n\n const matchResult = fuzzyFindText(normalizedContent, normalizedOldText);\n if (!matchResult.found) return { content: [{ type: 'text', text: `Error: oldText not found` }], details: {} };\n\n const fuzzyContent = normalizeForFuzzyMatch(normalizedContent);\n const fuzzyOldText = normalizeForFuzzyMatch(normalizedOldText);\n const occurrences = fuzzyContent.split(fuzzyOldText).length - 1;\n if (occurrences > 1)\n return { content: [{ type: 'text', text: `Error: ${occurrences} occurrences found, text must be unique` }], details: {} };\n\n const baseContent = matchResult.contentForReplacement;\n const newContent =\n baseContent.substring(0, matchResult.index) +\n normalizedNewText +\n baseContent.substring(matchResult.index + matchResult.matchLength);\n const finalContent = restoreLineEndings(newContent, lineEnding);\n const originalWithReplacement = restoreLineEndings(baseContent, lineEnding);\n\n if (originalWithReplacement === finalContent)\n return { content: [{ type: 'text', text: `Error: No changes` }], details: {} };\n\n const diffResult = generateDiffString(originalWithReplacement, finalContent, normalized);\n await writeFile(normalized, finalContent, 'utf-8');\n\n return {\n content: [{ type: 'text', text: `File edited: ${normalized}` }],\n details: { diff: diffResult, fuzzyMatchUsed: matchResult.usedFuzzyMatch },\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],\n details: {},\n };\n }\n },\n } as any;\n}\n\nexport const editFileTool: AgentTool = createEditFileTool(process.cwd());\n\nfunction detectLineEnding(content: string): '\\r\\n' | '\\n' {\n const crlfIdx = content.indexOf('\\r\\n');\n const lfIdx = content.indexOf('\\n');\n if (lfIdx === -1) return '\\n';\n if (crlfIdx === -1) return '\\n';\n return crlfIdx < lfIdx ? '\\r\\n' : '\\n';\n}\n"],"mappings":"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"}
@@ -15,6 +15,7 @@ import type { MemoryManager } from '../memory/manager.js';
15
15
  import type { SessionStore } from '../../session/store.js';
16
16
  import type { GatewayClarifyRequestFn } from './clarify-tool.js';
17
17
  import type { CronService } from '../../cron/index.js';
18
+ import type { WorkflowRunServiceLike } from '../../workflows/service/workflow-run-service.types.js';
18
19
  import type { SkillManager } from '../skills/skill-manager.js';
19
20
  import { type ToolExecutorConfig } from './executor.js';
20
21
  export interface ToolFactoryDeps {
@@ -44,6 +45,8 @@ export interface ToolFactoryDeps {
44
45
  };
45
46
  /** Gateway: enables the `cronjob` tool. */
46
47
  getCronService?: () => CronService | undefined;
48
+ /** Gateway: starts persisted workflow runs (dedicated chat session per run). */
49
+ getWorkflowRunService?: () => WorkflowRunServiceLike | undefined;
47
50
  /** Current session skill indexing (tool gating + allowlist); used by skills_list / skill_view. */
48
51
  getSkillIndexingContext?: () => {
49
52
  registeredToolNames: string[];
@@ -167,8 +167,8 @@ var AgentToolsFactory = class AgentToolsFactory {
167
167
  workspace
168
168
  })].filter((t) => t != null);
169
169
  const readTool = createReadFileTool(workspace, { profileMarkdownRoot: options?.profileMarkdownRoot });
170
- const writeTool = createWriteFileTool(workspace);
171
- const editTool = createEditFileTool(workspace);
170
+ const writeTool = createWriteFileTool(workspace, { profileMarkdownRoot: options?.profileMarkdownRoot });
171
+ const editTool = createEditFileTool(workspace, { profileMarkdownRoot: options?.profileMarkdownRoot });
172
172
  const listDir = createListDirTool(workspace);
173
173
  const grep = createGrepTool(workspace);
174
174
  const find = createFindTool(workspace);
@@ -255,31 +255,10 @@ var AgentToolsFactory = class AgentToolsFactory {
255
255
  }
256
256
  })] : [],
257
257
  ...cfg?.agents?.defaults?.workflow?.enabled !== false && primary ? [createWorkflowTool({
258
- workspace,
259
- bus: this.deps.bus,
260
- getConfig: () => this.deps.getConfig?.(),
261
258
  catalog: createWorkflowCatalog(),
262
259
  getCurrentSessionKey: () => this.deps.getCurrentContext()?.sessionKey,
263
- getSubagentModel: () => {
264
- const m = (options?.getPrimaryModel ?? this.deps.getPrimaryModel)?.();
265
- if (!m) throw new Error("No primary model configured for workflow");
266
- return m;
267
- },
268
- toolExecutorConfig: this.deps.toolExecutorConfig,
269
- buildChildTools: (childOpts) => {
270
- return new AgentToolsFactory({
271
- workspace: childOpts.workspace,
272
- bus: childOpts.bus,
273
- getCurrentContext: () => null,
274
- getConfig: childOpts.getConfig,
275
- getPrimaryModel: () => childOpts.model,
276
- toolExecutorConfig: childOpts.toolExecutorConfig
277
- }).createAllTools({
278
- workspace: childOpts.workspace,
279
- getPrimaryModel: () => childOpts.model,
280
- disabledTools: new Set(["extensions"])
281
- });
282
- }
260
+ getConfig: () => this.deps.getConfig?.(),
261
+ startWorkflowRun: this.deps.getWorkflowRunService ? (params) => this.deps.getWorkflowRunService().startWorkflowRun(params) : void 0
283
262
  })] : [],
284
263
  ...cfg?.agents?.defaults?.delegate?.enabled === true && primary ? [createDelegateTool({
285
264
  workspace,
@@ -1 +1 @@
1
- {"version":3,"file":"factory.js","names":["parseRoutingSessionKey"],"sources":["../../../../src/agent/tools/factory.ts"],"sourcesContent":["/**\n * Agent Tools Factory - Creates and configures agent tools\n *\n * Centralizes tool creation logic to keep service.ts focused on orchestration.\n *\n * TTS: auto TTS is applied at the ChannelManager via maybeApplyTtsToPayload().\n * Optional \\`text_to_speech\\` tool sends explicit voice when TTS is enabled.\n */\n\nimport type { AgentTool } from '@earendil-works/pi-agent-core';\nimport type { Model, Api } from '@earendil-works/pi-ai';\nimport type { Page } from 'playwright-core';\nimport type { Config } from '../../config/schema.js';\nimport type { MessageBus } from '../../infra/bus/index.js';\nimport {\n createReadFileTool,\n createWriteFileTool,\n createEditFileTool,\n createListDirTool,\n createGrepTool,\n createFindTool,\n createShellTool,\n createWebSearchTool,\n createWebFetchTool,\n createWebExtractTool,\n createMessageTool,\n createSendMediaTool,\n createCreateShareTool,\n isShareToolAvailable,\n createMemorySearchTool,\n createMemoryGetTool,\n createTodoTool,\n createSessionStatusTool,\n createDreamingTool,\n createClarifyTool,\n} from './index.js';\nimport { createCuratedMemoryTool } from './curated-memory-tool.js';\nimport { createSessionSearchTool } from './session-search-tool.js';\nimport type { BuiltinMemoryStore } from '../memory/builtin-memory-store.js';\nimport type { MemoryManager } from '../memory/manager.js';\nimport { shouldRegisterCuratedMemoryTool } from '../memory/memory-config.js';\nimport type { SessionStore } from '../../session/store.js';\nimport { parseSessionKey as parseRoutingSessionKey } from '../../routing/session-key.js';\nimport type { GatewayClarifyRequestFn } from './clarify-tool.js';\nimport { createImageTool } from './image-tool.js';\nimport { createImageGenerateTool } from './image-generate-tool.js';\nimport {\n BrowserManager,\n BrowserNotReadyError,\n CdpSupervisor,\n checkBrowserReadiness,\n resolveBrowserBackendFromConfig,\n} from '../../browser/index.js';\nimport { createBrowserUseTool } from './browser/tool/browser-use-tool.js';\nimport { createDelegateTool } from './delegate-tool.js';\nimport { createWorkflowTool } from './workflow-tool.js';\nimport { createWorkflowCatalog } from '../workflow/catalog.js';\nimport { buildSandboxToolMap, createExecuteCodeTool } from './execute-code-tool.js';\nimport { createCronjobTool } from './cronjob-tool.js';\nimport type { CronService } from '../../cron/index.js';\nimport { createLogger } from '../../utils/logger.js';\nimport type { SkillManager } from '../skills/skill-manager.js';\nimport { wrapToolsWithProtection, type ToolExecutorConfig } from './executor.js';\nimport { createSkillsListTool, createSkillViewTool } from './skills-tools.js';\nimport { createSkillManageTool } from './skill-manage-tool.js';\nimport { createTextToSpeechTool } from './tts-tool.js';\nimport { mergeTtsConfigFromAppConfig } from '../../voice/tts/merge-config.js';\n\nconst log = createLogger('AgentToolsFactory');\n\n/** Channels where `clarify` can block for a user answer (web UI, Telegram, CLI readline). */\nconst CLARIFY_SUPPORTED_CHANNELS = new Set(['webchat', 'telegram', 'cli']);\n\nfunction clarifyTransportSource(sessionKey: string): string | undefined {\n const parsed = parseRoutingSessionKey(sessionKey);\n if (parsed) return parsed.source;\n // Fallback for simple `<channel>:<chatId>` keys used by webchat and CLI.\n const first = sessionKey.split(':').filter(Boolean)[0] ?? '';\n if (first === 'cli' || first === 'webchat') return first;\n return undefined;\n}\n\nexport interface ToolFactoryDeps {\n workspace: string;\n extensionRegistry?: any;\n getCurrentContext: () => { channel: string; chatId: string; sessionKey: string } | null;\n hookRunner?: import('../../extensions/index.js').ExtensionHookRunner;\n bus: MessageBus;\n toolExecutorConfig?: Partial<ToolExecutorConfig>;\n /** Agent defaults (image tools, etc.); use getter so hot-reloaded config applies. */\n getConfig?: () => Config | undefined;\n /** Session / default chat model for vision tool description. */\n getPrimaryModel?: () => Model<Api>;\n /** Built-in curated memory store (agent home `memories/`). */\n getBuiltinMemoryStore?: () => BuiltinMemoryStore;\n /** Memory orchestration (prefetch/sync + external tools). */\n getMemoryManager?: () => MemoryManager;\n /** Session store for `session_search`. */\n getSessionStore?: () => SessionStore;\n /** When set (gateway webchat), enables the `clarify` tool. */\n gatewayClarify?: { requestClarification: GatewayClarifyRequestFn };\n /** Gateway: enables the `cronjob` tool. */\n getCronService?: () => CronService | undefined;\n /** Current session skill indexing (tool gating + allowlist); used by skills_list / skill_view. */\n getSkillIndexingContext?: () =>\n | { registeredToolNames: string[]; skillAllowlist?: string[] }\n | undefined;\n /** After skill_manage mutates disk, reload skills + refresh agent prompts (optional). */\n onSkillsFilesystemMutate?: () => void;\n /** Names registered via skill_view for shell env passthrough. */\n getSkillPassthroughEnvVarNames?: () => string[];\n /** Add declared env names for the current session (no values stored). */\n registerSkillEnvPassthrough?: (names: string[]) => void;\n}\n\nexport interface CreateCoreToolsOptions {\n /** Workspace root for file/shell tools (defaults to factory workspace). */\n workspace?: string;\n /** Canonical `agents/<id>/profile/`: bare SOUL.md / IDENTITY.md resolve here after the workspace. */\n profileMarkdownRoot?: string;\n /** Tool `name` values to omit (e.g. `shell`, `extensions` for extension tools). */\n disabledTools?: Set<string>;\n /** Optional primary model for image tool heuristics. */\n getPrimaryModel?: () => Model<Api>;\n getBuiltinMemoryStore?: () => BuiltinMemoryStore;\n getMemoryManager?: () => MemoryManager;\n /** When set, registers `skills_list` and `skill_view` bound to this workspace\\'s skills. */\n getSkillManager?: () => SkillManager;\n}\n\nexport class AgentToolsFactory {\n private browserManager: BrowserManager | null = null;\n /** One dialog/console supervisor per chat session (browser tab). */\n private readonly browserTaskSupervisors = new Map<string, CdpSupervisor>();\n /** Cached readiness probe — keyed by backend mode + extension host:port. */\n private browserReadinessCache: {\n key: string;\n expiresAt: number;\n inflight?: Promise<BrowserNotReadyError | null>;\n result?: BrowserNotReadyError | null;\n } | null = null;\n\n constructor(private deps: ToolFactoryDeps) {}\n\n private browserReadinessKey(): string {\n const cfg = this.deps.getConfig?.();\n const backend = resolveBrowserBackendFromConfig(cfg);\n const ext = cfg?.agents?.defaults?.browser?.extension;\n const host = typeof ext?.host === 'string' && ext.host.trim() ? ext.host.trim() : '127.0.0.1';\n const port = typeof ext?.port === 'number' ? ext.port : 19820;\n const cdpUrl = backend.mode === 'cdp' ? backend.config.wsEndpoint : '';\n const cloudKind = backend.mode === 'cloud' ? backend.config.type : '';\n return `${backend.mode}@${host}:${port}|${cdpUrl}|${cloudKind}`;\n }\n\n private async checkBrowserReadinessCached(): Promise<BrowserNotReadyError | null> {\n const key = this.browserReadinessKey();\n const now = Date.now();\n const cached = this.browserReadinessCache;\n if (cached && cached.key === key && cached.expiresAt > now && cached.inflight === undefined) {\n return cached.result ?? null;\n }\n if (cached && cached.key === key && cached.inflight) {\n return cached.inflight;\n }\n const inflight = checkBrowserReadiness(this.deps.getConfig?.());\n this.browserReadinessCache = { key, expiresAt: now + 30_000, inflight };\n try {\n const result = await inflight;\n this.browserReadinessCache = { key, expiresAt: Date.now() + 30_000, result };\n return result;\n } catch (e) {\n // Probe should never throw, but if it does we just bypass the cache.\n this.browserReadinessCache = null;\n log.warn({ err: e }, 'browserReadiness probe failed');\n return null;\n }\n }\n\n /** Invalidate the readiness cache (config hot-reload, settings-page save, etc.). */\n invalidateBrowserReadinessCache(): void {\n this.browserReadinessCache = null;\n }\n\n private browserSupervisorForTask(taskId: string): CdpSupervisor {\n let s = this.browserTaskSupervisors.get(taskId);\n if (!s) {\n const b = this.deps.getConfig?.()?.agents?.defaults?.browser;\n const dialogPolicy =\n b?.dialogPolicy === 'must_respond' || b?.dialogPolicy === 'auto_accept' || b?.dialogPolicy === 'auto_dismiss'\n ? b.dialogPolicy\n : 'auto_dismiss';\n const dialogTimeoutSeconds =\n typeof b?.dialogTimeoutSeconds === 'number' &&\n Number.isFinite(b.dialogTimeoutSeconds) &&\n b.dialogTimeoutSeconds >= 1\n ? Math.floor(b.dialogTimeoutSeconds)\n : 300;\n s = new CdpSupervisor({ dialogPolicy, dialogTimeoutSeconds });\n this.browserTaskSupervisors.set(taskId, s);\n }\n return s;\n }\n\n private async acquireBrowserPage(): Promise<Page> {\n const taskId = this.deps.getCurrentContext()?.sessionKey ?? 'default';\n const mgr = this.ensureBrowserManager();\n await mgr.ensureConnected();\n if (mgr.getExtensionProvider()) {\n return null as unknown as Page;\n }\n const page = await mgr.getPage(taskId);\n this.browserSupervisorForTask(taskId).attach(page);\n return page;\n }\n\n private ensureBrowserManager(): BrowserManager {\n if (!this.browserManager) {\n this.browserManager = new BrowserManager({\n getHeadless: () => this.deps.getConfig?.()?.agents?.defaults?.browser?.headless === true,\n getBackend: () => resolveBrowserBackendFromConfig(this.deps.getConfig?.()),\n });\n }\n return this.browserManager;\n }\n\n /** Close Playwright and all pages (gateway stop, agent manager dispose, or config hot-reload). */\n async shutdownBrowser(): Promise<void> {\n this.browserReadinessCache = null;\n if (!this.browserManager) {\n return;\n }\n await this.browserManager.shutdown();\n this.browserManager = null;\n this.browserTaskSupervisors.clear();\n }\n\n /** Drop the tab for a session when its agent instance is removed. */\n async closeBrowserPageForSession(sessionKey: string): Promise<void> {\n this.browserTaskSupervisors.delete(sessionKey);\n await this.browserManager?.closePage(sessionKey);\n }\n\n createCoreTools(options?: CreateCoreToolsOptions): AgentTool<any, any>[] {\n const workspace = options?.workspace ?? this.deps.workspace;\n const { bus } = this.deps;\n const getPrimary = options?.getPrimaryModel ?? this.deps.getPrimaryModel;\n const getBuiltin = options?.getBuiltinMemoryStore ?? this.deps.getBuiltinMemoryStore;\n const builtinStore = getBuiltin?.();\n const memoriesDir = builtinStore?.memoriesDir;\n const getMemMgr = options?.getMemoryManager ?? this.deps.getMemoryManager;\n const getSkillMgr = options?.getSkillManager;\n const disabled = options?.disabledTools;\n\n const primary = getPrimary?.();\n const modelHasVision = primary?.input?.includes('image') ?? false;\n const cfg = this.deps.getConfig?.();\n const imageTool = createImageTool({\n config: cfg,\n workspace,\n modelHasVision,\n });\n const imageGenerateTool = createImageGenerateTool({\n config: cfg,\n workspace,\n });\n\n const optionalTools = [imageTool, imageGenerateTool].filter((t) => t != null) as any[];\n\n const readTool = createReadFileTool(workspace, {\n profileMarkdownRoot: options?.profileMarkdownRoot,\n });\n const writeTool = createWriteFileTool(workspace);\n const editTool = createEditFileTool(workspace);\n const listDir = createListDirTool(workspace);\n const grep = createGrepTool(workspace);\n const find = createFindTool(workspace);\n\n const core: AgentTool<any, any>[] = [\n createSessionStatusTool(),\n createDreamingTool({\n getWorkspace: () => workspace,\n getConfig: () => this.deps.getConfig?.(),\n }),\n createClarifyTool({\n resolveAskUser: () => {\n const req = this.deps.gatewayClarify?.requestClarification;\n if (!req) return null;\n const ctx = this.deps.getCurrentContext();\n if (!ctx?.sessionKey) return null;\n const source = clarifyTransportSource(ctx.sessionKey);\n if (!source || !CLARIFY_SUPPORTED_CHANNELS.has(source)) return null;\n return (r) => req(ctx.sessionKey, r);\n },\n }),\n createTodoTool({\n getSessionKey: () => this.deps.getCurrentContext()?.sessionKey,\n }),\n ...(getSkillMgr\n ? [\n createSkillsListTool({\n getSkillManager: getSkillMgr,\n getSkillIndexingContext: this.deps.getSkillIndexingContext,\n }),\n createSkillViewTool({\n getSkillManager: getSkillMgr,\n getSkillIndexingContext: this.deps.getSkillIndexingContext,\n registerSkillEnvPassthrough: this.deps.registerSkillEnvPassthrough,\n }),\n createSkillManageTool({\n getSkillManager: getSkillMgr,\n getWorkspace: () => workspace,\n onSkillsFilesystemMutate: this.deps.onSkillsFilesystemMutate,\n }),\n ]\n : []),\n readTool,\n writeTool,\n editTool,\n listDir,\n grep,\n find,\n createShellTool(workspace, {\n getSkillPassthroughEnvVarNames: this.deps.getSkillPassthroughEnvVarNames,\n }),\n createWebSearchTool(() => this.deps.getConfig?.()),\n createWebFetchTool(() => this.deps.getConfig?.()),\n createWebExtractTool({ getConfig: () => this.deps.getConfig?.() }),\n // Note: TTS is NOT handled by send_message tool anymore\n // TTS is applied at the ChannelManager dispatch layer\n createMessageTool(bus, () => this.deps.getCurrentContext()),\n ...(mergeTtsConfigFromAppConfig(cfg?.messages?.tts).enabled\n ? [\n createTextToSpeechTool({\n bus,\n getContext: () => this.deps.getCurrentContext(),\n getConfig: () => this.deps.getConfig?.(),\n }),\n ]\n : []),\n createSendMediaTool(workspace, bus, () => this.deps.getCurrentContext()),\n ...(isShareToolAvailable(cfg)\n ? [\n createCreateShareTool({\n workspace,\n getConfig: () => this.deps.getConfig?.(),\n }),\n ]\n : []),\n createMemorySearchTool({ workspaceDir: workspace, memoriesDir }),\n createMemoryGetTool({ workspaceDir: workspace, memoriesDir }),\n ...(getBuiltin && shouldRegisterCuratedMemoryTool(this.deps.getConfig?.())\n ? [\n createCuratedMemoryTool(getBuiltin, {\n onMemoryWrite: (action, target, content) => {\n getMemMgr?.().onMemoryWrite(action, target, content);\n },\n }),\n ]\n : []),\n ...(getMemMgr?.().getAdditionalTools() ?? []),\n ...(this.deps.getSessionStore\n ? [\n createSessionSearchTool({\n getSessionStore: this.deps.getSessionStore,\n getConfig: this.deps.getConfig,\n getCurrentSessionKey: () => this.deps.getCurrentContext()?.sessionKey,\n }),\n ]\n : []),\n ...(this.deps.getCronService\n ? [\n createCronjobTool({\n getCronService: this.deps.getCronService,\n }),\n ]\n : []),\n ...(cfg?.agents?.defaults?.browser?.enabled !== false\n ? [\n createBrowserUseTool({\n getManager: () => this.ensureBrowserManager(),\n getPageForTask: () => this.acquireBrowserPage(),\n getTaskId: () => this.deps.getCurrentContext()?.sessionKey ?? 'default',\n getConfig: () => this.deps.getConfig?.(),\n getReadiness: () => this.checkBrowserReadinessCached(),\n getSupervisor: () =>\n this.browserSupervisorForTask(this.deps.getCurrentContext()?.sessionKey ?? 'default'),\n notifyBrowserPageClosed: (taskId) => {\n this.browserTaskSupervisors.delete(taskId);\n },\n }),\n ]\n : []),\n ...(cfg?.agents?.defaults?.workflow?.enabled !== false && primary\n ? [\n createWorkflowTool({\n workspace,\n bus: this.deps.bus,\n getConfig: () => this.deps.getConfig?.(),\n catalog: createWorkflowCatalog(),\n getCurrentSessionKey: () => this.deps.getCurrentContext()?.sessionKey,\n getSubagentModel: () => {\n const gp = options?.getPrimaryModel ?? this.deps.getPrimaryModel;\n const m = gp?.();\n if (!m) {\n throw new Error('No primary model configured for workflow');\n }\n return m;\n },\n toolExecutorConfig: this.deps.toolExecutorConfig,\n // Injected so `subagent-runner.ts` doesn't import `AgentToolsFactory`\n // (which would form the same cycle delegate-tool was carved out to avoid).\n buildChildTools: (childOpts) => {\n const childFactory = new AgentToolsFactory({\n workspace: childOpts.workspace,\n bus: childOpts.bus,\n getCurrentContext: () => null,\n getConfig: childOpts.getConfig,\n getPrimaryModel: () => childOpts.model,\n toolExecutorConfig: childOpts.toolExecutorConfig,\n });\n return childFactory.createAllTools({\n workspace: childOpts.workspace,\n getPrimaryModel: () => childOpts.model,\n disabledTools: new Set(['extensions']),\n });\n },\n }),\n ]\n : []),\n ...(cfg?.agents?.defaults?.delegate?.enabled === true && primary\n ? [\n createDelegateTool({\n workspace,\n getSubagentModel: () => {\n const gp = options?.getPrimaryModel ?? this.deps.getPrimaryModel;\n const m = gp?.();\n if (!m) {\n throw new Error('No primary model configured for delegate_task');\n }\n return m;\n },\n bus: this.deps.bus,\n getConfig: () => this.deps.getConfig?.(),\n getCurrentContext: () => this.deps.getCurrentContext?.() ?? null,\n hookRunner: this.deps.hookRunner,\n toolExecutorConfig: this.deps.toolExecutorConfig,\n // Injected so `child-agent-factory.ts` does not need to import\n // `AgentToolsFactory` directly (which would form a cycle).\n buildChildTools: (childOpts) => {\n const childFactory = new AgentToolsFactory({\n workspace: childOpts.workspace,\n bus: childOpts.bus,\n getCurrentContext: () => null,\n getConfig: childOpts.getConfig,\n getPrimaryModel: () => childOpts.model,\n toolExecutorConfig: childOpts.toolExecutorConfig,\n });\n return childFactory.createAllTools({\n workspace: childOpts.workspace,\n getPrimaryModel: () => childOpts.model,\n disabledTools: new Set(['extensions']),\n });\n },\n }),\n ]\n : []),\n ...optionalTools,\n ];\n\n return filterToolsByDisabledSet(core, disabled);\n }\n\n createAllTools(coreOptions?: CreateCoreToolsOptions): AgentTool<any, any>[] {\n const coreTools = this.createCoreTools(coreOptions);\n const disableExtensions = coreOptions?.disabledTools?.has('extensions');\n const cfg = this.deps.getConfig?.();\n\n let bundled: AgentTool<any, any>[];\n if (!this.deps.extensionRegistry || disableExtensions) {\n bundled = coreTools;\n } else {\n const extensionTools = this.deps.extensionRegistry.getAllTools();\n log.info({ count: extensionTools.length }, 'Loaded extension tools');\n bundled = [...coreTools, ...extensionTools];\n }\n\n const wrapped = wrapToolsWithProtection(bundled, this.deps.toolExecutorConfig);\n\n const executeEnabled =\n cfg?.agents?.defaults?.executeCode?.enabled === true &&\n !coreOptions?.disabledTools?.has('execute_code');\n\n if (executeEnabled) {\n const sandboxMap = buildSandboxToolMap(wrapped);\n const executeTool = createExecuteCodeTool({ getSandboxToolMap: () => sandboxMap });\n const wrappedExecute = wrapToolsWithProtection([executeTool as any], this.deps.toolExecutorConfig);\n return [...wrapped, ...wrappedExecute];\n }\n\n return wrapped;\n }\n}\n\nfunction filterToolsByDisabledSet(\n tools: any[],\n disabled: Set<string> | undefined,\n): any[] {\n if (!disabled || disabled.size === 0) {\n return tools;\n }\n return tools.filter((t) => !disabled.has(t.name));\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"factory.js","names":["parseRoutingSessionKey"],"sources":["../../../../src/agent/tools/factory.ts"],"sourcesContent":["/**\n * Agent Tools Factory - Creates and configures agent tools\n *\n * Centralizes tool creation logic to keep service.ts focused on orchestration.\n *\n * TTS: auto TTS is applied at the ChannelManager via maybeApplyTtsToPayload().\n * Optional \\`text_to_speech\\` tool sends explicit voice when TTS is enabled.\n */\n\nimport type { AgentTool } from '@earendil-works/pi-agent-core';\nimport type { Model, Api } from '@earendil-works/pi-ai';\nimport type { Page } from 'playwright-core';\nimport type { Config } from '../../config/schema.js';\nimport type { MessageBus } from '../../infra/bus/index.js';\nimport {\n createReadFileTool,\n createWriteFileTool,\n createEditFileTool,\n createListDirTool,\n createGrepTool,\n createFindTool,\n createShellTool,\n createWebSearchTool,\n createWebFetchTool,\n createWebExtractTool,\n createMessageTool,\n createSendMediaTool,\n createCreateShareTool,\n isShareToolAvailable,\n createMemorySearchTool,\n createMemoryGetTool,\n createTodoTool,\n createSessionStatusTool,\n createDreamingTool,\n createClarifyTool,\n} from './index.js';\nimport { createCuratedMemoryTool } from './curated-memory-tool.js';\nimport { createSessionSearchTool } from './session-search-tool.js';\nimport type { BuiltinMemoryStore } from '../memory/builtin-memory-store.js';\nimport type { MemoryManager } from '../memory/manager.js';\nimport { shouldRegisterCuratedMemoryTool } from '../memory/memory-config.js';\nimport type { SessionStore } from '../../session/store.js';\nimport { parseSessionKey as parseRoutingSessionKey } from '../../routing/session-key.js';\nimport type { GatewayClarifyRequestFn } from './clarify-tool.js';\nimport { createImageTool } from './image-tool.js';\nimport { createImageGenerateTool } from './image-generate-tool.js';\nimport {\n BrowserManager,\n BrowserNotReadyError,\n CdpSupervisor,\n checkBrowserReadiness,\n resolveBrowserBackendFromConfig,\n} from '../../browser/index.js';\nimport { createBrowserUseTool } from './browser/tool/browser-use-tool.js';\nimport { createDelegateTool } from './delegate-tool.js';\nimport { createWorkflowTool } from './workflow-tool.js';\nimport { createWorkflowCatalog } from '../workflow/catalog.js';\nimport { buildSandboxToolMap, createExecuteCodeTool } from './execute-code-tool.js';\nimport { createCronjobTool } from './cronjob-tool.js';\nimport type { CronService } from '../../cron/index.js';\nimport type { WorkflowRunServiceLike } from '../../workflows/service/workflow-run-service.types.js';\nimport { createLogger } from '../../utils/logger.js';\nimport type { SkillManager } from '../skills/skill-manager.js';\nimport { wrapToolsWithProtection, type ToolExecutorConfig } from './executor.js';\nimport { createSkillsListTool, createSkillViewTool } from './skills-tools.js';\nimport { createSkillManageTool } from './skill-manage-tool.js';\nimport { createTextToSpeechTool } from './tts-tool.js';\nimport { mergeTtsConfigFromAppConfig } from '../../voice/tts/merge-config.js';\n\nconst log = createLogger('AgentToolsFactory');\n\n/** Channels where `clarify` can block for a user answer (web UI, Telegram, CLI readline). */\nconst CLARIFY_SUPPORTED_CHANNELS = new Set(['webchat', 'telegram', 'cli']);\n\nfunction clarifyTransportSource(sessionKey: string): string | undefined {\n const parsed = parseRoutingSessionKey(sessionKey);\n if (parsed) return parsed.source;\n // Fallback for simple `<channel>:<chatId>` keys used by webchat and CLI.\n const first = sessionKey.split(':').filter(Boolean)[0] ?? '';\n if (first === 'cli' || first === 'webchat') return first;\n return undefined;\n}\n\nexport interface ToolFactoryDeps {\n workspace: string;\n extensionRegistry?: any;\n getCurrentContext: () => { channel: string; chatId: string; sessionKey: string } | null;\n hookRunner?: import('../../extensions/index.js').ExtensionHookRunner;\n bus: MessageBus;\n toolExecutorConfig?: Partial<ToolExecutorConfig>;\n /** Agent defaults (image tools, etc.); use getter so hot-reloaded config applies. */\n getConfig?: () => Config | undefined;\n /** Session / default chat model for vision tool description. */\n getPrimaryModel?: () => Model<Api>;\n /** Built-in curated memory store (agent home `memories/`). */\n getBuiltinMemoryStore?: () => BuiltinMemoryStore;\n /** Memory orchestration (prefetch/sync + external tools). */\n getMemoryManager?: () => MemoryManager;\n /** Session store for `session_search`. */\n getSessionStore?: () => SessionStore;\n /** When set (gateway webchat), enables the `clarify` tool. */\n gatewayClarify?: { requestClarification: GatewayClarifyRequestFn };\n /** Gateway: enables the `cronjob` tool. */\n getCronService?: () => CronService | undefined;\n /** Gateway: starts persisted workflow runs (dedicated chat session per run). */\n getWorkflowRunService?: () => WorkflowRunServiceLike | undefined;\n /** Current session skill indexing (tool gating + allowlist); used by skills_list / skill_view. */\n getSkillIndexingContext?: () =>\n | { registeredToolNames: string[]; skillAllowlist?: string[] }\n | undefined;\n /** After skill_manage mutates disk, reload skills + refresh agent prompts (optional). */\n onSkillsFilesystemMutate?: () => void;\n /** Names registered via skill_view for shell env passthrough. */\n getSkillPassthroughEnvVarNames?: () => string[];\n /** Add declared env names for the current session (no values stored). */\n registerSkillEnvPassthrough?: (names: string[]) => void;\n}\n\nexport interface CreateCoreToolsOptions {\n /** Workspace root for file/shell tools (defaults to factory workspace). */\n workspace?: string;\n /** Canonical `agents/<id>/profile/`: bare SOUL.md / IDENTITY.md resolve here after the workspace. */\n profileMarkdownRoot?: string;\n /** Tool `name` values to omit (e.g. `shell`, `extensions` for extension tools). */\n disabledTools?: Set<string>;\n /** Optional primary model for image tool heuristics. */\n getPrimaryModel?: () => Model<Api>;\n getBuiltinMemoryStore?: () => BuiltinMemoryStore;\n getMemoryManager?: () => MemoryManager;\n /** When set, registers `skills_list` and `skill_view` bound to this workspace\\'s skills. */\n getSkillManager?: () => SkillManager;\n}\n\nexport class AgentToolsFactory {\n private browserManager: BrowserManager | null = null;\n /** One dialog/console supervisor per chat session (browser tab). */\n private readonly browserTaskSupervisors = new Map<string, CdpSupervisor>();\n /** Cached readiness probe — keyed by backend mode + extension host:port. */\n private browserReadinessCache: {\n key: string;\n expiresAt: number;\n inflight?: Promise<BrowserNotReadyError | null>;\n result?: BrowserNotReadyError | null;\n } | null = null;\n\n constructor(private deps: ToolFactoryDeps) {}\n\n private browserReadinessKey(): string {\n const cfg = this.deps.getConfig?.();\n const backend = resolveBrowserBackendFromConfig(cfg);\n const ext = cfg?.agents?.defaults?.browser?.extension;\n const host = typeof ext?.host === 'string' && ext.host.trim() ? ext.host.trim() : '127.0.0.1';\n const port = typeof ext?.port === 'number' ? ext.port : 19820;\n const cdpUrl = backend.mode === 'cdp' ? backend.config.wsEndpoint : '';\n const cloudKind = backend.mode === 'cloud' ? backend.config.type : '';\n return `${backend.mode}@${host}:${port}|${cdpUrl}|${cloudKind}`;\n }\n\n private async checkBrowserReadinessCached(): Promise<BrowserNotReadyError | null> {\n const key = this.browserReadinessKey();\n const now = Date.now();\n const cached = this.browserReadinessCache;\n if (cached && cached.key === key && cached.expiresAt > now && cached.inflight === undefined) {\n return cached.result ?? null;\n }\n if (cached && cached.key === key && cached.inflight) {\n return cached.inflight;\n }\n const inflight = checkBrowserReadiness(this.deps.getConfig?.());\n this.browserReadinessCache = { key, expiresAt: now + 30_000, inflight };\n try {\n const result = await inflight;\n this.browserReadinessCache = { key, expiresAt: Date.now() + 30_000, result };\n return result;\n } catch (e) {\n // Probe should never throw, but if it does we just bypass the cache.\n this.browserReadinessCache = null;\n log.warn({ err: e }, 'browserReadiness probe failed');\n return null;\n }\n }\n\n /** Invalidate the readiness cache (config hot-reload, settings-page save, etc.). */\n invalidateBrowserReadinessCache(): void {\n this.browserReadinessCache = null;\n }\n\n private browserSupervisorForTask(taskId: string): CdpSupervisor {\n let s = this.browserTaskSupervisors.get(taskId);\n if (!s) {\n const b = this.deps.getConfig?.()?.agents?.defaults?.browser;\n const dialogPolicy =\n b?.dialogPolicy === 'must_respond' || b?.dialogPolicy === 'auto_accept' || b?.dialogPolicy === 'auto_dismiss'\n ? b.dialogPolicy\n : 'auto_dismiss';\n const dialogTimeoutSeconds =\n typeof b?.dialogTimeoutSeconds === 'number' &&\n Number.isFinite(b.dialogTimeoutSeconds) &&\n b.dialogTimeoutSeconds >= 1\n ? Math.floor(b.dialogTimeoutSeconds)\n : 300;\n s = new CdpSupervisor({ dialogPolicy, dialogTimeoutSeconds });\n this.browserTaskSupervisors.set(taskId, s);\n }\n return s;\n }\n\n private async acquireBrowserPage(): Promise<Page> {\n const taskId = this.deps.getCurrentContext()?.sessionKey ?? 'default';\n const mgr = this.ensureBrowserManager();\n await mgr.ensureConnected();\n if (mgr.getExtensionProvider()) {\n return null as unknown as Page;\n }\n const page = await mgr.getPage(taskId);\n this.browserSupervisorForTask(taskId).attach(page);\n return page;\n }\n\n private ensureBrowserManager(): BrowserManager {\n if (!this.browserManager) {\n this.browserManager = new BrowserManager({\n getHeadless: () => this.deps.getConfig?.()?.agents?.defaults?.browser?.headless === true,\n getBackend: () => resolveBrowserBackendFromConfig(this.deps.getConfig?.()),\n });\n }\n return this.browserManager;\n }\n\n /** Close Playwright and all pages (gateway stop, agent manager dispose, or config hot-reload). */\n async shutdownBrowser(): Promise<void> {\n this.browserReadinessCache = null;\n if (!this.browserManager) {\n return;\n }\n await this.browserManager.shutdown();\n this.browserManager = null;\n this.browserTaskSupervisors.clear();\n }\n\n /** Drop the tab for a session when its agent instance is removed. */\n async closeBrowserPageForSession(sessionKey: string): Promise<void> {\n this.browserTaskSupervisors.delete(sessionKey);\n await this.browserManager?.closePage(sessionKey);\n }\n\n createCoreTools(options?: CreateCoreToolsOptions): AgentTool<any, any>[] {\n const workspace = options?.workspace ?? this.deps.workspace;\n const { bus } = this.deps;\n const getPrimary = options?.getPrimaryModel ?? this.deps.getPrimaryModel;\n const getBuiltin = options?.getBuiltinMemoryStore ?? this.deps.getBuiltinMemoryStore;\n const builtinStore = getBuiltin?.();\n const memoriesDir = builtinStore?.memoriesDir;\n const getMemMgr = options?.getMemoryManager ?? this.deps.getMemoryManager;\n const getSkillMgr = options?.getSkillManager;\n const disabled = options?.disabledTools;\n\n const primary = getPrimary?.();\n const modelHasVision = primary?.input?.includes('image') ?? false;\n const cfg = this.deps.getConfig?.();\n const imageTool = createImageTool({\n config: cfg,\n workspace,\n modelHasVision,\n });\n const imageGenerateTool = createImageGenerateTool({\n config: cfg,\n workspace,\n });\n\n const optionalTools = [imageTool, imageGenerateTool].filter((t) => t != null) as any[];\n\n const readTool = createReadFileTool(workspace, {\n profileMarkdownRoot: options?.profileMarkdownRoot,\n });\n const writeTool = createWriteFileTool(workspace, {\n profileMarkdownRoot: options?.profileMarkdownRoot,\n });\n const editTool = createEditFileTool(workspace, {\n profileMarkdownRoot: options?.profileMarkdownRoot,\n });\n const listDir = createListDirTool(workspace);\n const grep = createGrepTool(workspace);\n const find = createFindTool(workspace);\n\n const core: AgentTool<any, any>[] = [\n createSessionStatusTool(),\n createDreamingTool({\n getWorkspace: () => workspace,\n getConfig: () => this.deps.getConfig?.(),\n }),\n createClarifyTool({\n resolveAskUser: () => {\n const req = this.deps.gatewayClarify?.requestClarification;\n if (!req) return null;\n const ctx = this.deps.getCurrentContext();\n if (!ctx?.sessionKey) return null;\n const source = clarifyTransportSource(ctx.sessionKey);\n if (!source || !CLARIFY_SUPPORTED_CHANNELS.has(source)) return null;\n return (r) => req(ctx.sessionKey, r);\n },\n }),\n createTodoTool({\n getSessionKey: () => this.deps.getCurrentContext()?.sessionKey,\n }),\n ...(getSkillMgr\n ? [\n createSkillsListTool({\n getSkillManager: getSkillMgr,\n getSkillIndexingContext: this.deps.getSkillIndexingContext,\n }),\n createSkillViewTool({\n getSkillManager: getSkillMgr,\n getSkillIndexingContext: this.deps.getSkillIndexingContext,\n registerSkillEnvPassthrough: this.deps.registerSkillEnvPassthrough,\n }),\n createSkillManageTool({\n getSkillManager: getSkillMgr,\n getWorkspace: () => workspace,\n onSkillsFilesystemMutate: this.deps.onSkillsFilesystemMutate,\n }),\n ]\n : []),\n readTool,\n writeTool,\n editTool,\n listDir,\n grep,\n find,\n createShellTool(workspace, {\n getSkillPassthroughEnvVarNames: this.deps.getSkillPassthroughEnvVarNames,\n }),\n createWebSearchTool(() => this.deps.getConfig?.()),\n createWebFetchTool(() => this.deps.getConfig?.()),\n createWebExtractTool({ getConfig: () => this.deps.getConfig?.() }),\n // Note: TTS is NOT handled by send_message tool anymore\n // TTS is applied at the ChannelManager dispatch layer\n createMessageTool(bus, () => this.deps.getCurrentContext()),\n ...(mergeTtsConfigFromAppConfig(cfg?.messages?.tts).enabled\n ? [\n createTextToSpeechTool({\n bus,\n getContext: () => this.deps.getCurrentContext(),\n getConfig: () => this.deps.getConfig?.(),\n }),\n ]\n : []),\n createSendMediaTool(workspace, bus, () => this.deps.getCurrentContext()),\n ...(isShareToolAvailable(cfg)\n ? [\n createCreateShareTool({\n workspace,\n getConfig: () => this.deps.getConfig?.(),\n }),\n ]\n : []),\n createMemorySearchTool({ workspaceDir: workspace, memoriesDir }),\n createMemoryGetTool({ workspaceDir: workspace, memoriesDir }),\n ...(getBuiltin && shouldRegisterCuratedMemoryTool(this.deps.getConfig?.())\n ? [\n createCuratedMemoryTool(getBuiltin, {\n onMemoryWrite: (action, target, content) => {\n getMemMgr?.().onMemoryWrite(action, target, content);\n },\n }),\n ]\n : []),\n ...(getMemMgr?.().getAdditionalTools() ?? []),\n ...(this.deps.getSessionStore\n ? [\n createSessionSearchTool({\n getSessionStore: this.deps.getSessionStore,\n getConfig: this.deps.getConfig,\n getCurrentSessionKey: () => this.deps.getCurrentContext()?.sessionKey,\n }),\n ]\n : []),\n ...(this.deps.getCronService\n ? [\n createCronjobTool({\n getCronService: this.deps.getCronService,\n }),\n ]\n : []),\n ...(cfg?.agents?.defaults?.browser?.enabled !== false\n ? [\n createBrowserUseTool({\n getManager: () => this.ensureBrowserManager(),\n getPageForTask: () => this.acquireBrowserPage(),\n getTaskId: () => this.deps.getCurrentContext()?.sessionKey ?? 'default',\n getConfig: () => this.deps.getConfig?.(),\n getReadiness: () => this.checkBrowserReadinessCached(),\n getSupervisor: () =>\n this.browserSupervisorForTask(this.deps.getCurrentContext()?.sessionKey ?? 'default'),\n notifyBrowserPageClosed: (taskId) => {\n this.browserTaskSupervisors.delete(taskId);\n },\n }),\n ]\n : []),\n ...(cfg?.agents?.defaults?.workflow?.enabled !== false && primary\n ? [\n createWorkflowTool({\n catalog: createWorkflowCatalog(),\n getCurrentSessionKey: () => this.deps.getCurrentContext()?.sessionKey,\n getConfig: () => this.deps.getConfig?.(),\n startWorkflowRun: this.deps.getWorkflowRunService\n ? (params) => this.deps.getWorkflowRunService!().startWorkflowRun(params)\n : undefined,\n }),\n ]\n : []),\n ...(cfg?.agents?.defaults?.delegate?.enabled === true && primary\n ? [\n createDelegateTool({\n workspace,\n getSubagentModel: () => {\n const gp = options?.getPrimaryModel ?? this.deps.getPrimaryModel;\n const m = gp?.();\n if (!m) {\n throw new Error('No primary model configured for delegate_task');\n }\n return m;\n },\n bus: this.deps.bus,\n getConfig: () => this.deps.getConfig?.(),\n getCurrentContext: () => this.deps.getCurrentContext?.() ?? null,\n hookRunner: this.deps.hookRunner,\n toolExecutorConfig: this.deps.toolExecutorConfig,\n // Injected so `child-agent-factory.ts` does not need to import\n // `AgentToolsFactory` directly (which would form a cycle).\n buildChildTools: (childOpts) => {\n const childFactory = new AgentToolsFactory({\n workspace: childOpts.workspace,\n bus: childOpts.bus,\n getCurrentContext: () => null,\n getConfig: childOpts.getConfig,\n getPrimaryModel: () => childOpts.model,\n toolExecutorConfig: childOpts.toolExecutorConfig,\n });\n return childFactory.createAllTools({\n workspace: childOpts.workspace,\n getPrimaryModel: () => childOpts.model,\n disabledTools: new Set(['extensions']),\n });\n },\n }),\n ]\n : []),\n ...optionalTools,\n ];\n\n return filterToolsByDisabledSet(core, disabled);\n }\n\n createAllTools(coreOptions?: CreateCoreToolsOptions): AgentTool<any, any>[] {\n const coreTools = this.createCoreTools(coreOptions);\n const disableExtensions = coreOptions?.disabledTools?.has('extensions');\n const cfg = this.deps.getConfig?.();\n\n let bundled: AgentTool<any, any>[];\n if (!this.deps.extensionRegistry || disableExtensions) {\n bundled = coreTools;\n } else {\n const extensionTools = this.deps.extensionRegistry.getAllTools();\n log.info({ count: extensionTools.length }, 'Loaded extension tools');\n bundled = [...coreTools, ...extensionTools];\n }\n\n const wrapped = wrapToolsWithProtection(bundled, this.deps.toolExecutorConfig);\n\n const executeEnabled =\n cfg?.agents?.defaults?.executeCode?.enabled === true &&\n !coreOptions?.disabledTools?.has('execute_code');\n\n if (executeEnabled) {\n const sandboxMap = buildSandboxToolMap(wrapped);\n const executeTool = createExecuteCodeTool({ getSandboxToolMap: () => sandboxMap });\n const wrappedExecute = wrapToolsWithProtection([executeTool as any], this.deps.toolExecutorConfig);\n return [...wrapped, ...wrappedExecute];\n }\n\n return wrapped;\n }\n}\n\nfunction filterToolsByDisabledSet(\n tools: any[],\n disabled: Set<string> | undefined,\n): any[] {\n if (!disabled || disabled.size === 0) {\n return tools;\n }\n return tools.filter((t) => !disabled.has(t.name));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA0CyF;aAmBpC;AAQrD,MAAM,MAAM,aAAa,oBAAoB;;AAG7C,MAAM,6BAA6B,IAAI,IAAI;CAAC;CAAW;CAAY;CAAM,CAAC;AAE1E,SAAS,uBAAuB,YAAwC;CACtE,MAAM,SAASA,gBAAuB,WAAW;AACjD,KAAI,OAAQ,QAAO,OAAO;CAE1B,MAAM,QAAQ,WAAW,MAAM,IAAI,CAAC,OAAO,QAAQ,CAAC,MAAM;AAC1D,KAAI,UAAU,SAAS,UAAU,UAAW,QAAO;;AAsDrD,IAAa,oBAAb,MAAa,kBAAkB;CAC7B,iBAAgD;;CAEhD,yCAA0C,IAAI,KAA4B;;CAE1E,wBAKW;CAEX,YAAY,MAA+B;AAAvB,OAAA,OAAA;;CAEpB,sBAAsC;EACpC,MAAM,MAAM,KAAK,KAAK,aAAa;EACnC,MAAM,UAAU,gCAAgC,IAAI;EACpD,MAAM,MAAM,KAAK,QAAQ,UAAU,SAAS;EAC5C,MAAM,OAAO,OAAO,KAAK,SAAS,YAAY,IAAI,KAAK,MAAM,GAAG,IAAI,KAAK,MAAM,GAAG;EAClF,MAAM,OAAO,OAAO,KAAK,SAAS,WAAW,IAAI,OAAO;EACxD,MAAM,SAAS,QAAQ,SAAS,QAAQ,QAAQ,OAAO,aAAa;EACpE,MAAM,YAAY,QAAQ,SAAS,UAAU,QAAQ,OAAO,OAAO;AACnE,SAAO,GAAG,QAAQ,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,OAAO,GAAG;;CAGtD,MAAc,8BAAoE;EAChF,MAAM,MAAM,KAAK,qBAAqB;EACtC,MAAM,MAAM,KAAK,KAAK;EACtB,MAAM,SAAS,KAAK;AACpB,MAAI,UAAU,OAAO,QAAQ,OAAO,OAAO,YAAY,OAAO,OAAO,aAAa,KAAA,EAChF,QAAO,OAAO,UAAU;AAE1B,MAAI,UAAU,OAAO,QAAQ,OAAO,OAAO,SACzC,QAAO,OAAO;EAEhB,MAAM,WAAW,sBAAsB,KAAK,KAAK,aAAa,CAAC;AAC/D,OAAK,wBAAwB;GAAE;GAAK,WAAW,MAAM;GAAQ;GAAU;AACvE,MAAI;GACF,MAAM,SAAS,MAAM;AACrB,QAAK,wBAAwB;IAAE;IAAK,WAAW,KAAK,KAAK,GAAG;IAAQ;IAAQ;AAC5E,UAAO;WACA,GAAG;AAEV,QAAK,wBAAwB;AAC7B,OAAI,KAAK,EAAE,KAAK,GAAG,EAAE,gCAAgC;AACrD,UAAO;;;;CAKX,kCAAwC;AACtC,OAAK,wBAAwB;;CAG/B,yBAAiC,QAA+B;EAC9D,IAAI,IAAI,KAAK,uBAAuB,IAAI,OAAO;AAC/C,MAAI,CAAC,GAAG;GACN,MAAM,IAAI,KAAK,KAAK,aAAa,EAAE,QAAQ,UAAU;AAWrD,OAAI,IAAI,cAAc;IAAE,cATtB,GAAG,iBAAiB,kBAAkB,GAAG,iBAAiB,iBAAiB,GAAG,iBAAiB,iBAC3F,EAAE,eACF;IAOgC,sBALpC,OAAO,GAAG,yBAAyB,YACnC,OAAO,SAAS,EAAE,qBAAqB,IACvC,EAAE,wBAAwB,IACtB,KAAK,MAAM,EAAE,qBAAqB,GAClC;IACsD,CAAC;AAC7D,QAAK,uBAAuB,IAAI,QAAQ,EAAE;;AAE5C,SAAO;;CAGT,MAAc,qBAAoC;EAChD,MAAM,SAAS,KAAK,KAAK,mBAAmB,EAAE,cAAc;EAC5D,MAAM,MAAM,KAAK,sBAAsB;AACvC,QAAM,IAAI,iBAAiB;AAC3B,MAAI,IAAI,sBAAsB,CAC5B,QAAO;EAET,MAAM,OAAO,MAAM,IAAI,QAAQ,OAAO;AACtC,OAAK,yBAAyB,OAAO,CAAC,OAAO,KAAK;AAClD,SAAO;;CAGT,uBAA+C;AAC7C,MAAI,CAAC,KAAK,eACR,MAAK,iBAAiB,IAAI,eAAe;GACvC,mBAAmB,KAAK,KAAK,aAAa,EAAE,QAAQ,UAAU,SAAS,aAAa;GACpF,kBAAkB,gCAAgC,KAAK,KAAK,aAAa,CAAC;GAC3E,CAAC;AAEJ,SAAO,KAAK;;;CAId,MAAM,kBAAiC;AACrC,OAAK,wBAAwB;AAC7B,MAAI,CAAC,KAAK,eACR;AAEF,QAAM,KAAK,eAAe,UAAU;AACpC,OAAK,iBAAiB;AACtB,OAAK,uBAAuB,OAAO;;;CAIrC,MAAM,2BAA2B,YAAmC;AAClE,OAAK,uBAAuB,OAAO,WAAW;AAC9C,QAAM,KAAK,gBAAgB,UAAU,WAAW;;CAGlD,gBAAgB,SAAyD;EACvE,MAAM,YAAY,SAAS,aAAa,KAAK,KAAK;EAClD,MAAM,EAAE,QAAQ,KAAK;EACrB,MAAM,aAAa,SAAS,mBAAmB,KAAK,KAAK;EACzD,MAAM,aAAa,SAAS,yBAAyB,KAAK,KAAK;EAE/D,MAAM,eADe,cAAc,GACD;EAClC,MAAM,YAAY,SAAS,oBAAoB,KAAK,KAAK;EACzD,MAAM,cAAc,SAAS;EAC7B,MAAM,WAAW,SAAS;EAE1B,MAAM,UAAU,cAAc;EAC9B,MAAM,iBAAiB,SAAS,OAAO,SAAS,QAAQ,IAAI;EAC5D,MAAM,MAAM,KAAK,KAAK,aAAa;EAWnC,MAAM,gBAAgB,CAVJ,gBAAgB;GAChC,QAAQ;GACR;GACA;GACD,CAM+B,EALN,wBAAwB;GAChD,QAAQ;GACR;GACD,CAEkD,CAAC,CAAC,QAAQ,MAAM,KAAK,KAAK;EAE7E,MAAM,WAAW,mBAAmB,WAAW,EAC7C,qBAAqB,SAAS,qBAC/B,CAAC;EACF,MAAM,YAAY,oBAAoB,WAAW,EAC/C,qBAAqB,SAAS,qBAC/B,CAAC;EACF,MAAM,WAAW,mBAAmB,WAAW,EAC7C,qBAAqB,SAAS,qBAC/B,CAAC;EACF,MAAM,UAAU,kBAAkB,UAAU;EAC5C,MAAM,OAAO,eAAe,UAAU;EACtC,MAAM,OAAO,eAAe,UAAU;AAyKtC,SAAO,yBAAyB;GAtK9B,yBAAyB;GACzB,mBAAmB;IACjB,oBAAoB;IACpB,iBAAiB,KAAK,KAAK,aAAa;IACzC,CAAC;GACF,kBAAkB,EAChB,sBAAsB;IACpB,MAAM,MAAM,KAAK,KAAK,gBAAgB;AACtC,QAAI,CAAC,IAAK,QAAO;IACjB,MAAM,MAAM,KAAK,KAAK,mBAAmB;AACzC,QAAI,CAAC,KAAK,WAAY,QAAO;IAC7B,MAAM,SAAS,uBAAuB,IAAI,WAAW;AACrD,QAAI,CAAC,UAAU,CAAC,2BAA2B,IAAI,OAAO,CAAE,QAAO;AAC/D,YAAQ,MAAM,IAAI,IAAI,YAAY,EAAE;MAEvC,CAAC;GACF,eAAe,EACb,qBAAqB,KAAK,KAAK,mBAAmB,EAAE,YACrD,CAAC;GACF,GAAI,cACA;IACE,qBAAqB;KACnB,iBAAiB;KACjB,yBAAyB,KAAK,KAAK;KACpC,CAAC;IACF,oBAAoB;KAClB,iBAAiB;KACjB,yBAAyB,KAAK,KAAK;KACnC,6BAA6B,KAAK,KAAK;KACxC,CAAC;IACF,sBAAsB;KACpB,iBAAiB;KACjB,oBAAoB;KACpB,0BAA0B,KAAK,KAAK;KACrC,CAAC;IACH,GACD,EAAE;GACN;GACA;GACA;GACA;GACA;GACA;GACA,gBAAgB,WAAW,EACzB,gCAAgC,KAAK,KAAK,gCAC3C,CAAC;GACF,0BAA0B,KAAK,KAAK,aAAa,CAAC;GAClD,yBAAyB,KAAK,KAAK,aAAa,CAAC;GACjD,qBAAqB,EAAE,iBAAiB,KAAK,KAAK,aAAa,EAAE,CAAC;GAGlE,kBAAkB,WAAW,KAAK,KAAK,mBAAmB,CAAC;GAC3D,GAAI,4BAA4B,KAAK,UAAU,IAAI,CAAC,UAChD,CACE,uBAAuB;IACrB;IACA,kBAAkB,KAAK,KAAK,mBAAmB;IAC/C,iBAAiB,KAAK,KAAK,aAAa;IACzC,CAAC,CACH,GACD,EAAE;GACN,oBAAoB,WAAW,WAAW,KAAK,KAAK,mBAAmB,CAAC;GACxE,GAAI,qBAAqB,IAAI,GACzB,CACE,sBAAsB;IACpB;IACA,iBAAiB,KAAK,KAAK,aAAa;IACzC,CAAC,CACH,GACD,EAAE;GACN,uBAAuB;IAAE,cAAc;IAAW;IAAa,CAAC;GAChE,oBAAoB;IAAE,cAAc;IAAW;IAAa,CAAC;GAC7D,GAAI,cAAc,gCAAgC,KAAK,KAAK,aAAa,CAAC,GACtE,CACE,wBAAwB,YAAY,EAClC,gBAAgB,QAAQ,QAAQ,YAAY;AAC1C,iBAAa,CAAC,cAAc,QAAQ,QAAQ,QAAQ;MAEvD,CAAC,CACH,GACD,EAAE;GACN,GAAI,aAAa,CAAC,oBAAoB,IAAI,EAAE;GAC5C,GAAI,KAAK,KAAK,kBACV,CACE,wBAAwB;IACtB,iBAAiB,KAAK,KAAK;IAC3B,WAAW,KAAK,KAAK;IACrB,4BAA4B,KAAK,KAAK,mBAAmB,EAAE;IAC5D,CAAC,CACH,GACD,EAAE;GACN,GAAI,KAAK,KAAK,iBACV,CACE,kBAAkB,EAChB,gBAAgB,KAAK,KAAK,gBAC3B,CAAC,CACH,GACD,EAAE;GACN,GAAI,KAAK,QAAQ,UAAU,SAAS,YAAY,QAC5C,CACE,qBAAqB;IACnB,kBAAkB,KAAK,sBAAsB;IAC7C,sBAAsB,KAAK,oBAAoB;IAC/C,iBAAiB,KAAK,KAAK,mBAAmB,EAAE,cAAc;IAC9D,iBAAiB,KAAK,KAAK,aAAa;IACxC,oBAAoB,KAAK,6BAA6B;IACtD,qBACE,KAAK,yBAAyB,KAAK,KAAK,mBAAmB,EAAE,cAAc,UAAU;IACvF,0BAA0B,WAAW;AACnC,UAAK,uBAAuB,OAAO,OAAO;;IAE7C,CAAC,CACH,GACD,EAAE;GACN,GAAI,KAAK,QAAQ,UAAU,UAAU,YAAY,SAAS,UACtD,CACE,mBAAmB;IACjB,SAAS,uBAAuB;IAChC,4BAA4B,KAAK,KAAK,mBAAmB,EAAE;IAC3D,iBAAiB,KAAK,KAAK,aAAa;IACxC,kBAAkB,KAAK,KAAK,yBACvB,WAAW,KAAK,KAAK,uBAAwB,CAAC,iBAAiB,OAAO,GACvE,KAAA;IACL,CAAC,CACH,GACD,EAAE;GACN,GAAI,KAAK,QAAQ,UAAU,UAAU,YAAY,QAAQ,UACrD,CACE,mBAAmB;IACjB;IACA,wBAAwB;KAEtB,MAAM,KADK,SAAS,mBAAmB,KAAK,KAAK,oBACjC;AAChB,SAAI,CAAC,EACH,OAAM,IAAI,MAAM,gDAAgD;AAElE,YAAO;;IAET,KAAK,KAAK,KAAK;IACf,iBAAiB,KAAK,KAAK,aAAa;IACxC,yBAAyB,KAAK,KAAK,qBAAqB,IAAI;IAC5D,YAAY,KAAK,KAAK;IACtB,oBAAoB,KAAK,KAAK;IAG9B,kBAAkB,cAAc;AAS9B,YAAO,IARkB,kBAAkB;MACzC,WAAW,UAAU;MACrB,KAAK,UAAU;MACf,yBAAyB;MACzB,WAAW,UAAU;MACrB,uBAAuB,UAAU;MACjC,oBAAoB,UAAU;MAC/B,CACkB,CAAC,eAAe;MACjC,WAAW,UAAU;MACrB,uBAAuB,UAAU;MACjC,eAAe,IAAI,IAAI,CAAC,aAAa,CAAC;MACvC,CAAC;;IAEL,CAAC,CACH,GACD,EAAE;GACN,GAAG;GAG+B,EAAE,SAAS;;CAGjD,eAAe,aAA6D;EAC1E,MAAM,YAAY,KAAK,gBAAgB,YAAY;EACnD,MAAM,oBAAoB,aAAa,eAAe,IAAI,aAAa;EACvE,MAAM,MAAM,KAAK,KAAK,aAAa;EAEnC,IAAI;AACJ,MAAI,CAAC,KAAK,KAAK,qBAAqB,kBAClC,WAAU;OACL;GACL,MAAM,iBAAiB,KAAK,KAAK,kBAAkB,aAAa;AAChE,OAAI,KAAK,EAAE,OAAO,eAAe,QAAQ,EAAE,yBAAyB;AACpE,aAAU,CAAC,GAAG,WAAW,GAAG,eAAe;;EAG7C,MAAM,UAAU,wBAAwB,SAAS,KAAK,KAAK,mBAAmB;AAM9E,MAHE,KAAK,QAAQ,UAAU,aAAa,YAAY,QAChD,CAAC,aAAa,eAAe,IAAI,eAAe,EAE9B;GAClB,MAAM,aAAa,oBAAoB,QAAQ;GAE/C,MAAM,iBAAiB,wBAAwB,CAD3B,sBAAsB,EAAE,yBAAyB,YAAY,CACtB,CAAQ,EAAE,KAAK,KAAK,mBAAmB;AAClG,UAAO,CAAC,GAAG,SAAS,GAAG,eAAe;;AAGxC,SAAO;;;AAIX,SAAS,yBACP,OACA,UACO;AACP,KAAI,CAAC,YAAY,SAAS,SAAS,EACjC,QAAO;AAET,QAAO,MAAM,QAAQ,MAAM,CAAC,SAAS,IAAI,EAAE,KAAK,CAAC"}
@@ -1,41 +1,19 @@
1
1
  /**
2
- * `workflow` — the AgentTool the parent model calls to spawn a fan-out run.
3
- *
4
- * Shape mirrors `delegate-tool`: factory builds a closure over deps; `execute`
5
- * parses the script, instantiates the {@link DelegateSubagentRunner}, drives the
6
- * {@link runWorkflow} runtime, and pushes a live text snapshot through
7
- * `onUpdate` for streaming UIs (TUI, gateway console).
8
- *
9
- * Why this lives in `src/agent/tools/` (not under `src/agent/workflow/`):
10
- * the runtime is reusable infrastructure; the AgentTool wrapping is a
11
- * presentation concern that depends on the AgentToolsFactory wiring. Keeping
12
- * the wrapper here matches how `delegate-tool` and `execute-code-tool` are
13
- * organised today.
2
+ * `workflow` — starts a persisted workflow run in a dedicated chat session.
14
3
  */
15
4
  import type { AgentTool } from '@earendil-works/pi-agent-core';
16
- import type { Api, Model } from '@earendil-works/pi-ai';
17
- import type { Config } from '../../config/schema.js';
18
- import type { MessageBus } from '../../infra/bus/index.js';
19
- import type { BuildChildToolsOptions } from '../child-agent-factory.js';
20
- import { type WorkflowCatalog } from '../workflow/index.js';
21
- import type { ToolExecutorConfig } from './executor.js';
5
+ import type { WorkflowCatalog } from '../workflow/catalog.js';
6
+ import type { StartWorkflowRunServiceParams, WorkflowRunServiceResult } from '../../workflows/service/workflow-run-service.types.js';
22
7
  export type WorkflowToolInput = {
23
8
  name?: string;
24
9
  script?: string;
25
10
  args?: unknown;
11
+ goal?: string;
26
12
  };
27
13
  export interface WorkflowToolDeps {
28
- workspace: string;
29
- bus: MessageBus;
30
- /** Returns the parent agent's primary model — subagents default to this. */
31
- getSubagentModel: () => Model<Api>;
32
- getConfig: () => Config | undefined;
33
- /** Same injection point delegate-tool uses; supplied by AgentToolsFactory. */
34
- buildChildTools: (opts: BuildChildToolsOptions) => AgentTool<any, any>[];
35
- toolExecutorConfig?: Partial<ToolExecutorConfig>;
36
- /** Catalog for `name` lookups (built-in + ~/.xopc/workflows/). */
37
14
  catalog: WorkflowCatalog;
38
- /** Per-call sessionKey lookup — used to record "last successful workflow" for /workflow save. */
39
15
  getCurrentSessionKey?: () => string | undefined;
16
+ getConfig: () => import('../../config/schema.js').Config | undefined;
17
+ startWorkflowRun?: (params: StartWorkflowRunServiceParams) => Promise<WorkflowRunServiceResult>;
40
18
  }
41
19
  export declare function createWorkflowTool(deps: WorkflowToolDeps): AgentTool;