@xopcai/xopc 0.0.89 → 0.0.91

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 (267) hide show
  1. package/README.md +36 -12
  2. package/README.zh-CN.md +36 -12
  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/Combination-HAlzriaz.js +41 -0
  6. package/dist/gateway/static/root/assets/agents-bVWUlrlD.js +222 -0
  7. package/dist/gateway/static/root/assets/apps-page-CIC8bmvZ.js +1 -0
  8. package/dist/gateway/static/root/assets/{attachment-preview-renderer-CpyoFbs4.js → attachment-preview-renderer-DBAxQXb-.js} +2 -2
  9. package/dist/gateway/static/root/assets/{attachment-process-heavy-CqVriadb.js → attachment-process-heavy-Csq3TrrP.js} +4 -4
  10. package/dist/gateway/static/root/assets/channels-settings-C8G8RAAP.js +1 -0
  11. package/dist/gateway/static/root/assets/{channels-status-swr-DaHGkRF1.js → channels-status-swr-CYWL5DLD.js} +1 -1
  12. package/dist/gateway/static/root/assets/circle-check-C23XjkUj.js +1 -0
  13. package/dist/gateway/static/root/assets/copy-Dv6d4Dvw.js +1 -0
  14. package/dist/gateway/static/root/assets/cron-api-TVqLlGAC.js +1 -0
  15. package/dist/gateway/static/root/assets/cron-dreaming-jobs-Ip703-qM.js +2 -0
  16. package/dist/gateway/static/root/assets/cron-page-BtcFYlvv.js +1 -0
  17. package/dist/gateway/static/root/assets/dist-CUV1uY5f.js +1 -0
  18. package/dist/gateway/static/root/assets/{extension-debug-page-CtuKJ9tE.js → extension-debug-page-mTLHRDp1.js} +1 -1
  19. package/dist/gateway/static/root/assets/{extension-page-ykzjOkR5.js → extension-page-iI8BI7WK.js} +1 -1
  20. package/dist/gateway/static/root/assets/{extension-settings-page-Ce2qrdpO.js → extension-settings-page-ByXcdubM.js} +1 -1
  21. package/dist/gateway/static/root/assets/{fetch-C9FFJjuH.js → fetch-BWtQq_Ys.js} +1 -1
  22. package/dist/gateway/static/root/assets/{field-primitives-BFcrNeTU.js → field-primitives-BsZ-4VT5.js} +1 -1
  23. package/dist/gateway/static/root/assets/{heartbeat-config-api-CEg4Vr9R.js → heartbeat-config-api-WjTsRLCU.js} +1 -1
  24. package/dist/gateway/static/root/assets/{index-CZfy9oxs.js → index-CKkR-v9U.js} +101 -97
  25. package/dist/gateway/static/root/assets/index-VlELBY99.css +1 -0
  26. package/dist/gateway/static/root/assets/logs-page-ClnIpxfd.js +1 -0
  27. package/dist/gateway/static/root/assets/note-detail-page-B91pLkEI.css +1 -0
  28. package/dist/gateway/static/root/assets/note-detail-page-DJ2Mb4x7.js +179 -0
  29. package/dist/gateway/static/root/assets/note-time-JLBPSLzK.js +1 -0
  30. package/dist/gateway/static/root/assets/notes-page-BE-75qz9.js +1 -0
  31. package/dist/gateway/static/root/assets/{pdf-BnEvgIXZ.js → pdf-epILhEOn.js} +1 -1
  32. package/dist/gateway/static/root/assets/preload-helper-zJ_50EbN.js +1 -0
  33. package/dist/gateway/static/root/assets/sessions-page-bJJkWtTl.js +1 -0
  34. package/dist/gateway/static/root/assets/{settings-form-section-BqdzA28u.js → settings-form-section-DSYCknxM.js} +1 -1
  35. package/dist/gateway/static/root/assets/settings-page-WcMXLq2U.js +3 -0
  36. package/dist/gateway/static/root/assets/share-preview-page-awRqs4hV.js +2 -0
  37. package/dist/gateway/static/root/assets/skills-page-Lu-i1JG7.js +2 -0
  38. package/dist/gateway/static/root/assets/{theme-store-CNqbmTNV.js → theme-store-BC-42BoZ.js} +1 -1
  39. package/dist/gateway/static/root/assets/toast-z0toXu32.js +1 -0
  40. package/dist/gateway/static/root/assets/url-CY1RQKTU.js +3 -0
  41. package/dist/gateway/static/root/assets/{utils-BWm2tG2w.js → utils-DX3TQuap.js} +1 -1
  42. package/dist/gateway/static/root/assets/vendor-codemirror-DYoKfS8f.js +45 -0
  43. package/dist/gateway/static/root/assets/voice-api-key-field-B5uKlDqA.js +1 -0
  44. package/dist/gateway/static/root/assets/workflow-page.utils-ClC37yEp.js +1 -0
  45. package/dist/gateway/static/root/assets/workflows-page-C7VhIXtR.js +27 -0
  46. package/dist/gateway/static/root/index.html +11 -7
  47. package/dist/package.js +1 -1
  48. package/dist/src/agent/skills/marketplace/adapters/skillhub/adapter.js +20 -18
  49. package/dist/src/agent/skills/marketplace/adapters/skillhub/adapter.js.map +1 -1
  50. package/dist/src/agent/tools/cronjob-tool.d.ts +6 -0
  51. package/dist/src/agent/tools/cronjob-tool.js +74 -9
  52. package/dist/src/agent/tools/cronjob-tool.js.map +1 -1
  53. package/dist/src/agent/tools/edit.d.ts +5 -1
  54. package/dist/src/agent/tools/edit.js +7 -5
  55. package/dist/src/agent/tools/edit.js.map +1 -1
  56. package/dist/src/agent/tools/factory.js +2 -2
  57. package/dist/src/agent/tools/factory.js.map +1 -1
  58. package/dist/src/agent/tools/write.d.ts +5 -1
  59. package/dist/src/agent/tools/write.js +7 -5
  60. package/dist/src/agent/tools/write.js.map +1 -1
  61. package/dist/src/agent/workflow/agent-progress.js +2 -0
  62. package/dist/src/agent/workflow/agent-progress.js.map +1 -1
  63. package/dist/src/agent/workflow/builtins/client-proposal.d.ts +12 -0
  64. package/dist/src/agent/workflow/builtins/client-proposal.js +155 -0
  65. package/dist/src/agent/workflow/builtins/client-proposal.js.map +1 -0
  66. package/dist/src/agent/workflow/builtins/competitor-scan.d.ts +12 -0
  67. package/dist/src/agent/workflow/builtins/competitor-scan.js +150 -0
  68. package/dist/src/agent/workflow/builtins/competitor-scan.js.map +1 -0
  69. package/dist/src/agent/workflow/builtins/content-draft.d.ts +13 -0
  70. package/dist/src/agent/workflow/builtins/content-draft.js +146 -0
  71. package/dist/src/agent/workflow/builtins/content-draft.js.map +1 -0
  72. package/dist/src/agent/workflow/builtins/content-repurpose.d.ts +11 -0
  73. package/dist/src/agent/workflow/builtins/content-repurpose.js +137 -0
  74. package/dist/src/agent/workflow/builtins/content-repurpose.js.map +1 -0
  75. package/dist/src/agent/workflow/builtins/decision-compare.d.ts +13 -0
  76. package/dist/src/agent/workflow/builtins/decision-compare.js +173 -0
  77. package/dist/src/agent/workflow/builtins/decision-compare.js.map +1 -0
  78. package/dist/src/agent/workflow/builtins/inbox-triage.d.ts +11 -0
  79. package/dist/src/agent/workflow/builtins/inbox-triage.js +148 -0
  80. package/dist/src/agent/workflow/builtins/inbox-triage.js.map +1 -0
  81. package/dist/src/agent/workflow/builtins/index.d.ts +10 -1
  82. package/dist/src/agent/workflow/builtins/index.js +46 -1
  83. package/dist/src/agent/workflow/builtins/index.js.map +1 -1
  84. package/dist/src/agent/workflow/builtins/meeting-prep.d.ts +12 -0
  85. package/dist/src/agent/workflow/builtins/meeting-prep.js +144 -0
  86. package/dist/src/agent/workflow/builtins/meeting-prep.js.map +1 -0
  87. package/dist/src/agent/workflow/builtins/offer-design.d.ts +12 -0
  88. package/dist/src/agent/workflow/builtins/offer-design.js +161 -0
  89. package/dist/src/agent/workflow/builtins/offer-design.js.map +1 -0
  90. package/dist/src/agent/workflow/builtins/weekly-review.d.ts +12 -0
  91. package/dist/src/agent/workflow/builtins/weekly-review.js +131 -0
  92. package/dist/src/agent/workflow/builtins/weekly-review.js.map +1 -0
  93. package/dist/src/agent/workflow/step-labels.js +2 -2
  94. package/dist/src/agent/workflow/step-labels.js.map +1 -1
  95. package/dist/src/agent/workflow/subagent-runner.js +3 -1
  96. package/dist/src/agent/workflow/subagent-runner.js.map +1 -1
  97. package/dist/src/agent/workflow/types.d.ts +4 -0
  98. package/dist/src/chat-commands/agent-edit.d.ts +4 -0
  99. package/dist/src/chat-commands/agent-edit.js +136 -0
  100. package/dist/src/chat-commands/agent-edit.js.map +1 -0
  101. package/dist/src/chat-commands/index.d.ts +1 -0
  102. package/dist/src/chat-commands/index.js +3 -1
  103. package/dist/src/chat-commands/index.js.map +1 -1
  104. package/dist/src/cli/bin.js +2 -0
  105. package/dist/src/cli/bin.js.map +1 -1
  106. package/dist/src/cli/commands/cron.js +42 -3
  107. package/dist/src/cli/commands/cron.js.map +1 -1
  108. package/dist/src/cli/commands/doctor/checks/session-integrity.js +79 -56
  109. package/dist/src/cli/commands/doctor/checks/session-integrity.js.map +1 -1
  110. package/dist/src/cli/commands/gateway/lifecycle.js +1 -1
  111. package/dist/src/cli/commands/update.js +86 -79
  112. package/dist/src/cli/commands/update.js.map +1 -1
  113. package/dist/src/commands/agents.config.d.ts +3 -2
  114. package/dist/src/commands/agents.config.js +5 -2
  115. package/dist/src/commands/agents.config.js.map +1 -1
  116. package/dist/src/config/agent-typed-models.d.ts +2 -7
  117. package/dist/src/config/agent-typed-models.js +3 -14
  118. package/dist/src/config/agent-typed-models.js.map +1 -1
  119. package/dist/src/config/localized-text.d.ts +6 -0
  120. package/dist/src/config/localized-text.js +42 -0
  121. package/dist/src/config/localized-text.js.map +1 -0
  122. package/dist/src/config/models-json.d.ts +6 -6
  123. package/dist/src/config/schema.d.ts +6 -21
  124. package/dist/src/config/schema.js +4 -4
  125. package/dist/src/config/schema.js.map +1 -1
  126. package/dist/src/cron/executor.d.ts +2 -0
  127. package/dist/src/cron/executor.js +111 -1
  128. package/dist/src/cron/executor.js.map +1 -1
  129. package/dist/src/cron/types.d.ts +8 -1
  130. package/dist/src/cron/validation.d.ts +4 -0
  131. package/dist/src/cron/validation.js +4 -3
  132. package/dist/src/cron/validation.js.map +1 -1
  133. package/dist/src/cron/workflow-run-completion.d.ts +23 -0
  134. package/dist/src/cron/workflow-run-completion.js +72 -0
  135. package/dist/src/cron/workflow-run-completion.js.map +1 -0
  136. package/dist/src/extensions/update.d.ts +51 -0
  137. package/dist/src/extensions/update.js +260 -0
  138. package/dist/src/extensions/update.js.map +1 -0
  139. package/dist/src/gateway/agents-admin.d.ts +15 -8
  140. package/dist/src/gateway/agents-admin.js +77 -28
  141. package/dist/src/gateway/agents-admin.js.map +1 -1
  142. package/dist/src/gateway/heartbeat/service.js +1 -1
  143. package/dist/src/gateway/hono/lib/config-payload.d.ts +6 -0
  144. package/dist/src/gateway/hono/lib/config-payload.js +3 -1
  145. package/dist/src/gateway/hono/lib/config-payload.js.map +1 -1
  146. package/dist/src/gateway/hono/middleware/auth.d.ts +2 -0
  147. package/dist/src/gateway/hono/middleware/auth.js +11 -7
  148. package/dist/src/gateway/hono/middleware/auth.js.map +1 -1
  149. package/dist/src/gateway/hono/routes/agents.js +55 -12
  150. package/dist/src/gateway/hono/routes/agents.js.map +1 -1
  151. package/dist/src/gateway/hono/routes/config-patch/agents.js +1 -1
  152. package/dist/src/gateway/hono/routes/config-patch/gateway.d.ts +2 -2
  153. package/dist/src/gateway/hono/routes/config-patch/gateway.js +12 -0
  154. package/dist/src/gateway/hono/routes/config-patch/gateway.js.map +1 -1
  155. package/dist/src/gateway/hono/routes/lazy-bundles.js +8 -0
  156. package/dist/src/gateway/hono/routes/lazy-bundles.js.map +1 -1
  157. package/dist/src/gateway/hono/routes/notes.d.ts +3 -0
  158. package/dist/src/gateway/hono/routes/notes.js +274 -0
  159. package/dist/src/gateway/hono/routes/notes.js.map +1 -0
  160. package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
  161. package/dist/src/gateway/hono/routes/update.js +55 -107
  162. package/dist/src/gateway/hono/routes/update.js.map +1 -1
  163. package/dist/src/gateway/hono/routes/workflows.js +3 -1
  164. package/dist/src/gateway/hono/routes/workflows.js.map +1 -1
  165. package/dist/src/gateway/server.js +2 -0
  166. package/dist/src/gateway/server.js.map +1 -1
  167. package/dist/src/gateway/service.d.ts +3 -0
  168. package/dist/src/gateway/service.js +12 -1
  169. package/dist/src/gateway/service.js.map +1 -1
  170. package/dist/src/gateway/workspace-ripgrep.d.ts +6 -0
  171. package/dist/src/gateway/workspace-ripgrep.js +62 -11
  172. package/dist/src/gateway/workspace-ripgrep.js.map +1 -1
  173. package/dist/src/heartbeat/index.js +1 -1
  174. package/dist/src/infra/brew.d.ts +4 -0
  175. package/dist/src/infra/brew.js +20 -0
  176. package/dist/src/infra/brew.js.map +1 -0
  177. package/dist/src/infra/package-json.d.ts +2 -0
  178. package/dist/src/infra/package-json.js +23 -0
  179. package/dist/src/infra/package-json.js.map +1 -0
  180. package/dist/src/infra/package-update-steps.d.ts +35 -0
  181. package/dist/src/infra/package-update-steps.js +304 -0
  182. package/dist/src/infra/package-update-steps.js.map +1 -0
  183. package/dist/src/infra/path-env.d.ts +11 -0
  184. package/dist/src/infra/path-env.js +90 -0
  185. package/dist/src/infra/path-env.js.map +1 -0
  186. package/dist/src/infra/path-prepend.d.ts +7 -0
  187. package/dist/src/infra/path-prepend.js +44 -0
  188. package/dist/src/infra/path-prepend.js.map +1 -0
  189. package/dist/src/infra/stable-node-path.d.ts +2 -0
  190. package/dist/src/infra/stable-node-path.js +28 -0
  191. package/dist/src/infra/stable-node-path.js.map +1 -0
  192. package/dist/src/infra/update-global.d.ts +30 -23
  193. package/dist/src/infra/update-global.js +113 -64
  194. package/dist/src/infra/update-global.js.map +1 -1
  195. package/dist/src/infra/update-log.d.ts +1 -0
  196. package/dist/src/infra/update-log.js +12 -0
  197. package/dist/src/infra/update-log.js.map +1 -0
  198. package/dist/src/infra/update-restart.d.ts +20 -0
  199. package/dist/src/infra/update-restart.js +165 -0
  200. package/dist/src/infra/update-restart.js.map +1 -0
  201. package/dist/src/infra/update-runner.d.ts +89 -1
  202. package/dist/src/infra/update-runner.js +604 -173
  203. package/dist/src/infra/update-runner.js.map +1 -1
  204. package/dist/src/infra/update-startup.d.ts +3 -0
  205. package/dist/src/infra/update-startup.js +8 -4
  206. package/dist/src/infra/update-startup.js.map +1 -1
  207. package/dist/src/notes/attachment-ref.d.ts +9 -0
  208. package/dist/src/notes/attachment-ref.js +27 -0
  209. package/dist/src/notes/attachment-ref.js.map +1 -0
  210. package/dist/src/notes/index.d.ts +4 -0
  211. package/dist/src/notes/index.js +4 -0
  212. package/dist/src/notes/note-attachment-sync.d.ts +7 -0
  213. package/dist/src/notes/note-attachment-sync.js +46 -0
  214. package/dist/src/notes/note-attachment-sync.js.map +1 -0
  215. package/dist/src/notes/note-index-meta.d.ts +14 -0
  216. package/dist/src/notes/note-index-meta.js +87 -0
  217. package/dist/src/notes/note-index-meta.js.map +1 -0
  218. package/dist/src/notes/paths.d.ts +5 -0
  219. package/dist/src/notes/paths.js +23 -0
  220. package/dist/src/notes/paths.js.map +1 -0
  221. package/dist/src/notes/service.d.ts +42 -0
  222. package/dist/src/notes/service.js +331 -0
  223. package/dist/src/notes/service.js.map +1 -0
  224. package/dist/src/notes/store.d.ts +33 -0
  225. package/dist/src/notes/store.js +317 -0
  226. package/dist/src/notes/store.js.map +1 -0
  227. package/dist/src/notes/types.d.ts +162 -0
  228. package/dist/src/notes/types.js +1 -0
  229. package/dist/src/routing/resolve-route.d.ts +3 -1
  230. package/dist/src/routing/resolve-route.js.map +1 -1
  231. package/dist/src/session/store.d.ts +5 -3
  232. package/dist/src/session/store.js +66 -20
  233. package/dist/src/session/store.js.map +1 -1
  234. package/dist/src/utils/logger/stats.d.ts +1 -1
  235. package/dist/src/workflows/domain/event.d.ts +3 -0
  236. package/dist/src/workflows/domain/run.d.ts +3 -0
  237. package/dist/src/workflows/domain/run.js.map +1 -1
  238. package/dist/src/workflows/engine/projector.js +17 -0
  239. package/dist/src/workflows/engine/projector.js.map +1 -1
  240. package/dist/src/workflows/engine/workflow-engine.js +127 -0
  241. package/dist/src/workflows/engine/workflow-engine.js.map +1 -1
  242. package/dist/src/workflows/index.js +1 -1
  243. package/dist/src/workflows/service/run-view-to-snapshot.js +3 -1
  244. package/dist/src/workflows/service/run-view-to-snapshot.js.map +1 -1
  245. package/dist/src/workflows/service/workflow-run-service.d.ts +1 -0
  246. package/dist/src/workflows/service/workflow-run-service.js +4 -1
  247. package/dist/src/workflows/service/workflow-run-service.js.map +1 -1
  248. package/dist/src/workflows/service/workflow-session-bridge.js +1 -1
  249. package/package.json +1 -1
  250. package/dist/gateway/static/root/assets/agents-B6PJB07W.js +0 -222
  251. package/dist/gateway/static/root/assets/apps-page-BOr0B1wv.js +0 -1
  252. package/dist/gateway/static/root/assets/channels-settings-BelUKggl.js +0 -1
  253. package/dist/gateway/static/root/assets/cron-api-CjOg-BIj.js +0 -1
  254. package/dist/gateway/static/root/assets/cron-dreaming-jobs-DueM3rBz.js +0 -2
  255. package/dist/gateway/static/root/assets/cron-page-DhoZmZXb.js +0 -1
  256. package/dist/gateway/static/root/assets/dist-6LecgDx5.js +0 -1
  257. package/dist/gateway/static/root/assets/dist-BTWC-BTN.js +0 -45
  258. package/dist/gateway/static/root/assets/index-CiN1cQiQ.css +0 -1
  259. package/dist/gateway/static/root/assets/logs-page-BwWLfqvd.js +0 -1
  260. package/dist/gateway/static/root/assets/sessions-page-DV5WN8uk.js +0 -1
  261. package/dist/gateway/static/root/assets/settings-page-CfOBRbPX.js +0 -3
  262. package/dist/gateway/static/root/assets/share-preview-page-Di5Bzh4g.js +0 -2
  263. package/dist/gateway/static/root/assets/skills-page-D0H5Kaxg.js +0 -2
  264. package/dist/gateway/static/root/assets/url-aYn-Rj1C.js +0 -7
  265. package/dist/gateway/static/root/assets/vendor-codemirror-D0yxdRpg.js +0 -58
  266. package/dist/gateway/static/root/assets/voice-api-key-field-X2UfnHeq.js +0 -1
  267. package/dist/gateway/static/root/assets/workflows-page-BOPpO3NG.js +0 -27
@@ -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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"}
@@ -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);
@@ -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 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 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 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":"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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":"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"}
@@ -1,3 +1,7 @@
1
1
  import type { AgentTool } from '@earendil-works/pi-agent-core';
2
- export declare function createWriteFileTool(workspace: string): AgentTool;
2
+ export interface CreateWriteFileToolOptions {
3
+ /** When set and the path is a bare profile filename (e.g. SOUL.md), write to this root. */
4
+ profileMarkdownRoot?: string;
5
+ }
6
+ export declare function createWriteFileTool(workspace: string, options?: CreateWriteFileToolOptions): AgentTool;
3
7
  export declare const writeFileTool: AgentTool;
@@ -1,5 +1,5 @@
1
1
  import { checkFileSafety } from "../prompt/safety.js";
2
- import { resolvePathUnderWorkspace } from "./tool-paths.js";
2
+ import { isBareProfileMarkdownFileName, resolvePathUnderWorkspace, resolveProfileMarkdownPathIfBareName } from "./tool-paths.js";
3
3
  import { evaluateFilePolicy } from "../sandbox/exec-policy.js";
4
4
  import { dirname } from "path";
5
5
  import { mkdir, writeFile } from "fs/promises";
@@ -10,10 +10,10 @@ const WriteFileSchema = Type.Object({
10
10
  path: Type.String({ description: "File path to write" }),
11
11
  content: Type.String({ description: "Content to write" })
12
12
  });
13
- function createWriteFileTool(workspace) {
13
+ function createWriteFileTool(workspace, options) {
14
14
  return {
15
15
  name: "write_file",
16
- description: "Create or overwrite a file. Relative paths are under the current agent workspace.",
16
+ description: "Create or overwrite a file. Relative paths are under the current agent workspace; profile Markdown (SOUL.md, etc.) is written automatically when given by filename.",
17
17
  parameters: WriteFileSchema,
18
18
  label: "📝 Write",
19
19
  async execute(_toolCallId, params, _signal) {
@@ -27,10 +27,12 @@ function createWriteFileTool(workspace) {
27
27
  }],
28
28
  details: {}
29
29
  };
30
+ const writesProfileFile = Boolean(options?.profileMarkdownRoot && isBareProfileMarkdownFileName(p.path));
31
+ const workspaceRoot = writesProfileFile ? options.profileMarkdownRoot : workspace;
30
32
  const pathPolicy = evaluateFilePolicy({
31
33
  operation: "write",
32
34
  path: p.path,
33
- workspaceRoot: workspace
35
+ workspaceRoot
34
36
  });
35
37
  if (!pathPolicy.allowed) return {
36
38
  content: [{
@@ -47,7 +49,7 @@ function createWriteFileTool(workspace) {
47
49
  }],
48
50
  details: {}
49
51
  };
50
- const target = resolvePathUnderWorkspace(p.path, workspace);
52
+ const target = writesProfileFile ? resolveProfileMarkdownPathIfBareName(p.path, options.profileMarkdownRoot) : resolvePathUnderWorkspace(p.path, workspace);
51
53
  await mkdir(dirname(target), { recursive: true });
52
54
  await writeFile(target, p.content, "utf-8");
53
55
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"write.js","names":[],"sources":["../../../../src/agent/tools/write.ts"],"sourcesContent":["// Write file tool\nimport { Type } from '@sinclair/typebox';\nimport type { AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core';\nimport { writeFile, mkdir } from 'fs/promises';\nimport { dirname } from 'path';\nimport { checkFileSafety } from '../prompt/safety.js';\nimport { resolvePathUnderWorkspace } from './tool-paths.js';\nimport { evaluateFilePolicy } from '../sandbox/exec-policy.js';\n\nconst MAX_FILE_SIZE = 10 * 1024 * 1024;\n\nconst WriteFileSchema = Type.Object({\n path: Type.String({ description: 'File path to write' }),\n content: Type.String({ description: 'Content to write' }),\n});\n\ntype WriteFileParams = { path: string; content: string };\n\nexport function createWriteFileTool(workspace: string): AgentTool {\n return {\n name: 'write_file',\n description: 'Create or overwrite a file. Relative paths are under the current agent workspace.',\n parameters: WriteFileSchema,\n label: '📝 Write',\n\n async execute(\n _toolCallId: string,\n params: any,\n _signal?: AbortSignal,\n ): Promise<AgentToolResult<{}>> {\n try {\n const p = params as WriteFileParams;\n const safety = checkFileSafety('write', p.path);\n if (!safety.allowed) {\n return { content: [{ type: 'text', text: `🚫 ${safety.message}` }], details: {} };\n }\n\n // Sandbox path-policy check (blocked dirs, symlink escape, config protection)\n const pathPolicy = evaluateFilePolicy({\n operation: 'write',\n path: p.path,\n workspaceRoot: workspace,\n });\n if (!pathPolicy.allowed) {\n return { content: [{ type: 'text', text: `🚫 Sandbox: ${pathPolicy.reason}` }], details: {} };\n }\n\n const contentBytes = Buffer.byteLength(p.content, 'utf-8');\n if (contentBytes > MAX_FILE_SIZE) {\n return { content: [{ type: 'text', text: `🚫 File too large: ${contentBytes} bytes` }], details: {} };\n }\n\n const target = resolvePathUnderWorkspace(p.path, workspace);\n await mkdir(dirname(target), { recursive: true });\n await writeFile(target, p.content, 'utf-8');\n return { content: [{ type: 'text', text: `File written: ${target}` }], details: { size: contentBytes } };\n } catch (error) {\n return { content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }], details: {} };\n }\n },\n } as any;\n}\n\nexport const writeFileTool: AgentTool = createWriteFileTool(process.cwd());\n"],"mappings":";;;;;;;AASA,MAAM,gBAAgB,KAAK,OAAO;AAElC,MAAM,kBAAkB,KAAK,OAAO;CAClC,MAAM,KAAK,OAAO,EAAE,aAAa,sBAAsB,CAAC;CACxD,SAAS,KAAK,OAAO,EAAE,aAAa,oBAAoB,CAAC;CAC1D,CAAC;AAIF,SAAgB,oBAAoB,WAA8B;AAChE,QAAO;EACL,MAAM;EACN,aAAa;EACb,YAAY;EACZ,OAAO;EAEP,MAAM,QACJ,aACA,QACA,SAC8B;AAC9B,OAAI;IACF,MAAM,IAAI;IACV,MAAM,SAAS,gBAAgB,SAAS,EAAE,KAAK;AAC/C,QAAI,CAAC,OAAO,QACV,QAAO;KAAE,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,MAAM,OAAO;MAAW,CAAC;KAAE,SAAS,EAAE;KAAE;IAInF,MAAM,aAAa,mBAAmB;KACpC,WAAW;KACX,MAAM,EAAE;KACR,eAAe;KAChB,CAAC;AACF,QAAI,CAAC,WAAW,QACd,QAAO;KAAE,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,eAAe,WAAW;MAAU,CAAC;KAAE,SAAS,EAAE;KAAE;IAG/F,MAAM,eAAe,OAAO,WAAW,EAAE,SAAS,QAAQ;AAC1D,QAAI,eAAe,cACjB,QAAO;KAAE,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,sBAAsB,aAAa;MAAS,CAAC;KAAE,SAAS,EAAE;KAAE;IAGvG,MAAM,SAAS,0BAA0B,EAAE,MAAM,UAAU;AAC3D,UAAM,MAAM,QAAQ,OAAO,EAAE,EAAE,WAAW,MAAM,CAAC;AACjD,UAAM,UAAU,QAAQ,EAAE,SAAS,QAAQ;AAC3C,WAAO;KAAE,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,iBAAiB;MAAU,CAAC;KAAE,SAAS,EAAE,MAAM,cAAc;KAAE;YACjG,OAAO;AACd,WAAO;KAAE,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;MAAI,CAAC;KAAE,SAAS,EAAE;KAAE;;;EAGlI;;AAGH,MAAa,gBAA2B,oBAAoB,QAAQ,KAAK,CAAC"}
1
+ {"version":3,"file":"write.js","names":[],"sources":["../../../../src/agent/tools/write.ts"],"sourcesContent":["// Write file tool\nimport { Type } from '@sinclair/typebox';\nimport type { AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core';\nimport { writeFile, mkdir } from 'fs/promises';\nimport { dirname } from 'path';\nimport { checkFileSafety } from '../prompt/safety.js';\nimport {\n isBareProfileMarkdownFileName,\n resolveProfileMarkdownPathIfBareName,\n resolvePathUnderWorkspace,\n} from './tool-paths.js';\nimport { evaluateFilePolicy } from '../sandbox/exec-policy.js';\n\nconst MAX_FILE_SIZE = 10 * 1024 * 1024;\n\nconst WriteFileSchema = Type.Object({\n path: Type.String({ description: 'File path to write' }),\n content: Type.String({ description: 'Content to write' }),\n});\n\ntype WriteFileParams = { path: string; content: string };\n\nexport interface CreateWriteFileToolOptions {\n /** When set and the path is a bare profile filename (e.g. SOUL.md), write to this root. */\n profileMarkdownRoot?: string;\n}\n\nexport function createWriteFileTool(\n workspace: string,\n options?: CreateWriteFileToolOptions,\n): AgentTool {\n return {\n name: 'write_file',\n description:\n 'Create or overwrite a file. Relative paths are under the current agent workspace; profile Markdown (SOUL.md, etc.) is written automatically when given by filename.',\n parameters: WriteFileSchema,\n label: '📝 Write',\n\n async execute(\n _toolCallId: string,\n params: any,\n _signal?: AbortSignal,\n ): Promise<AgentToolResult<{}>> {\n try {\n const p = params as WriteFileParams;\n const safety = checkFileSafety('write', p.path);\n if (!safety.allowed) {\n return { content: [{ type: 'text', text: `🚫 ${safety.message}` }], details: {} };\n }\n\n const writesProfileFile = Boolean(\n options?.profileMarkdownRoot && isBareProfileMarkdownFileName(p.path),\n );\n const workspaceRoot = writesProfileFile ? options!.profileMarkdownRoot! : workspace;\n\n // Sandbox path-policy check (blocked dirs, symlink escape, config protection)\n const pathPolicy = evaluateFilePolicy({\n operation: 'write',\n path: p.path,\n workspaceRoot,\n });\n if (!pathPolicy.allowed) {\n return { content: [{ type: 'text', text: `🚫 Sandbox: ${pathPolicy.reason}` }], details: {} };\n }\n\n const contentBytes = Buffer.byteLength(p.content, 'utf-8');\n if (contentBytes > MAX_FILE_SIZE) {\n return { content: [{ type: 'text', text: `🚫 File too large: ${contentBytes} bytes` }], details: {} };\n }\n\n const target = writesProfileFile\n ? resolveProfileMarkdownPathIfBareName(p.path, options!.profileMarkdownRoot!)\n : resolvePathUnderWorkspace(p.path, workspace);\n await mkdir(dirname(target), { recursive: true });\n await writeFile(target, p.content, 'utf-8');\n return { content: [{ type: 'text', text: `File written: ${target}` }], details: { size: contentBytes } };\n } catch (error) {\n return { content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }], details: {} };\n }\n },\n } as any;\n}\n\nexport const writeFileTool: AgentTool = createWriteFileTool(process.cwd());\n"],"mappings":";;;;;;;AAaA,MAAM,gBAAgB,KAAK,OAAO;AAElC,MAAM,kBAAkB,KAAK,OAAO;CAClC,MAAM,KAAK,OAAO,EAAE,aAAa,sBAAsB,CAAC;CACxD,SAAS,KAAK,OAAO,EAAE,aAAa,oBAAoB,CAAC;CAC1D,CAAC;AASF,SAAgB,oBACd,WACA,SACW;AACX,QAAO;EACL,MAAM;EACN,aACE;EACF,YAAY;EACZ,OAAO;EAEP,MAAM,QACJ,aACA,QACA,SAC8B;AAC9B,OAAI;IACF,MAAM,IAAI;IACV,MAAM,SAAS,gBAAgB,SAAS,EAAE,KAAK;AAC/C,QAAI,CAAC,OAAO,QACV,QAAO;KAAE,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,MAAM,OAAO;MAAW,CAAC;KAAE,SAAS,EAAE;KAAE;IAGnF,MAAM,oBAAoB,QACxB,SAAS,uBAAuB,8BAA8B,EAAE,KAAK,CACtE;IACD,MAAM,gBAAgB,oBAAoB,QAAS,sBAAuB;IAG1E,MAAM,aAAa,mBAAmB;KACpC,WAAW;KACX,MAAM,EAAE;KACR;KACD,CAAC;AACF,QAAI,CAAC,WAAW,QACd,QAAO;KAAE,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,eAAe,WAAW;MAAU,CAAC;KAAE,SAAS,EAAE;KAAE;IAG/F,MAAM,eAAe,OAAO,WAAW,EAAE,SAAS,QAAQ;AAC1D,QAAI,eAAe,cACjB,QAAO;KAAE,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,sBAAsB,aAAa;MAAS,CAAC;KAAE,SAAS,EAAE;KAAE;IAGvG,MAAM,SAAS,oBACX,qCAAqC,EAAE,MAAM,QAAS,oBAAqB,GAC3E,0BAA0B,EAAE,MAAM,UAAU;AAChD,UAAM,MAAM,QAAQ,OAAO,EAAE,EAAE,WAAW,MAAM,CAAC;AACjD,UAAM,UAAU,QAAQ,EAAE,SAAS,QAAQ;AAC3C,WAAO;KAAE,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,iBAAiB;MAAU,CAAC;KAAE,SAAS,EAAE,MAAM,cAAc;KAAE;YACjG,OAAO;AACd,WAAO;KAAE,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;MAAI,CAAC;KAAE,SAAS,EAAE;KAAE;;;EAGlI;;AAGH,MAAa,gBAA2B,oBAAoB,QAAQ,KAAK,CAAC"}
@@ -25,6 +25,8 @@ function applySubagentProgress(agent, event) {
25
25
  const step = findStep(agent, event.toolCallId);
26
26
  if (!step) return false;
27
27
  step.status = event.isError ? "error" : "done";
28
+ step.resultPreview = event.resultPreview;
29
+ step.error = event.error;
28
30
  if (step.startedAtMs != null) step.durationMs = Date.now() - step.startedAtMs;
29
31
  if (agent.currentStep === formatCurrentStep(step)) agent.currentStep = void 0;
30
32
  return true;
@@ -1 +1 @@
1
- {"version":3,"file":"agent-progress.js","names":[],"sources":["../../../../src/agent/workflow/agent-progress.ts"],"sourcesContent":["/**\n * Apply live subagent progress events onto a {@link WorkflowAgentSnapshot}.\n */\n\nimport type {\n SubagentProgressEvent,\n WorkflowAgentSnapshot,\n WorkflowAgentStep,\n} from './types.js';\nimport { workflowStepLabel } from './step-labels.js';\n\nconst MAX_STEPS = 50;\nconst MAX_STREAM_TEXT = 32_000;\n\nexport function applySubagentProgress(\n agent: WorkflowAgentSnapshot,\n event: SubagentProgressEvent,\n): boolean {\n switch (event.type) {\n case 'tool_start': {\n if (!agent.steps) agent.steps = [];\n const { label, detail } = workflowStepLabel(event.toolName, event.args);\n const step: WorkflowAgentStep = {\n id: event.toolCallId,\n kind: 'tool',\n toolName: event.toolName,\n label,\n detail,\n status: 'running',\n startedAtMs: Date.now(),\n };\n agent.steps.push(step);\n trimSteps(agent);\n agent.currentStep = formatCurrentStep(step);\n return true;\n }\n case 'tool_end': {\n const step = findStep(agent, event.toolCallId);\n if (!step) return false;\n step.status = event.isError ? 'error' : 'done';\n if (step.startedAtMs != null) step.durationMs = Date.now() - step.startedAtMs;\n if (agent.currentStep === formatCurrentStep(step)) {\n agent.currentStep = undefined;\n }\n return true;\n }\n case 'iteration': {\n agent.iteration = event.count;\n agent.maxIterations = event.max;\n return true;\n }\n case 'text_delta': {\n if (!event.delta) return false;\n agent.streamText = appendStreamText(agent.streamText, event.delta);\n return true;\n }\n case 'thinking_delta': {\n if (!event.delta) return false;\n agent.streamText = appendStreamText(agent.streamText, event.delta);\n return true;\n }\n default:\n return false;\n }\n}\n\nfunction findStep(agent: WorkflowAgentSnapshot, id: string): WorkflowAgentStep | undefined {\n return agent.steps?.find((s) => s.id === id);\n}\n\nfunction formatCurrentStep(step: WorkflowAgentStep): string {\n return step.detail ? `${step.label}: ${step.detail}` : step.label;\n}\n\nfunction trimSteps(agent: WorkflowAgentSnapshot): void {\n if (!agent.steps || agent.steps.length <= MAX_STEPS) return;\n agent.steps = agent.steps.slice(-MAX_STEPS);\n}\n\nfunction appendStreamText(existing: string | undefined, delta: string): string {\n const next = (existing ?? '') + delta;\n if (next.length <= MAX_STREAM_TEXT) return next;\n return next.slice(-MAX_STREAM_TEXT);\n}\n"],"mappings":";;AAWA,MAAM,YAAY;AAClB,MAAM,kBAAkB;AAExB,SAAgB,sBACd,OACA,OACS;AACT,SAAQ,MAAM,MAAd;EACE,KAAK,cAAc;AACjB,OAAI,CAAC,MAAM,MAAO,OAAM,QAAQ,EAAE;GAClC,MAAM,EAAE,OAAO,WAAW,kBAAkB,MAAM,UAAU,MAAM,KAAK;GACvE,MAAM,OAA0B;IAC9B,IAAI,MAAM;IACV,MAAM;IACN,UAAU,MAAM;IAChB;IACA;IACA,QAAQ;IACR,aAAa,KAAK,KAAK;IACxB;AACD,SAAM,MAAM,KAAK,KAAK;AACtB,aAAU,MAAM;AAChB,SAAM,cAAc,kBAAkB,KAAK;AAC3C,UAAO;;EAET,KAAK,YAAY;GACf,MAAM,OAAO,SAAS,OAAO,MAAM,WAAW;AAC9C,OAAI,CAAC,KAAM,QAAO;AAClB,QAAK,SAAS,MAAM,UAAU,UAAU;AACxC,OAAI,KAAK,eAAe,KAAM,MAAK,aAAa,KAAK,KAAK,GAAG,KAAK;AAClE,OAAI,MAAM,gBAAgB,kBAAkB,KAAK,CAC/C,OAAM,cAAc,KAAA;AAEtB,UAAO;;EAET,KAAK;AACH,SAAM,YAAY,MAAM;AACxB,SAAM,gBAAgB,MAAM;AAC5B,UAAO;EAET,KAAK;AACH,OAAI,CAAC,MAAM,MAAO,QAAO;AACzB,SAAM,aAAa,iBAAiB,MAAM,YAAY,MAAM,MAAM;AAClE,UAAO;EAET,KAAK;AACH,OAAI,CAAC,MAAM,MAAO,QAAO;AACzB,SAAM,aAAa,iBAAiB,MAAM,YAAY,MAAM,MAAM;AAClE,UAAO;EAET,QACE,QAAO;;;AAIb,SAAS,SAAS,OAA8B,IAA2C;AACzF,QAAO,MAAM,OAAO,MAAM,MAAM,EAAE,OAAO,GAAG;;AAG9C,SAAS,kBAAkB,MAAiC;AAC1D,QAAO,KAAK,SAAS,GAAG,KAAK,MAAM,IAAI,KAAK,WAAW,KAAK;;AAG9D,SAAS,UAAU,OAAoC;AACrD,KAAI,CAAC,MAAM,SAAS,MAAM,MAAM,UAAU,UAAW;AACrD,OAAM,QAAQ,MAAM,MAAM,MAAM,CAAC,UAAU;;AAG7C,SAAS,iBAAiB,UAA8B,OAAuB;CAC7E,MAAM,QAAQ,YAAY,MAAM;AAChC,KAAI,KAAK,UAAU,gBAAiB,QAAO;AAC3C,QAAO,KAAK,MAAM,CAAC,gBAAgB"}
1
+ {"version":3,"file":"agent-progress.js","names":[],"sources":["../../../../src/agent/workflow/agent-progress.ts"],"sourcesContent":["/**\n * Apply live subagent progress events onto a {@link WorkflowAgentSnapshot}.\n */\n\nimport type {\n SubagentProgressEvent,\n WorkflowAgentSnapshot,\n WorkflowAgentStep,\n} from './types.js';\nimport { workflowStepLabel } from './step-labels.js';\n\nconst MAX_STEPS = 50;\nconst MAX_STREAM_TEXT = 32_000;\n\nexport function applySubagentProgress(\n agent: WorkflowAgentSnapshot,\n event: SubagentProgressEvent,\n): boolean {\n switch (event.type) {\n case 'tool_start': {\n if (!agent.steps) agent.steps = [];\n const { label, detail } = workflowStepLabel(event.toolName, event.args);\n const step: WorkflowAgentStep = {\n id: event.toolCallId,\n kind: 'tool',\n toolName: event.toolName,\n label,\n detail,\n status: 'running',\n startedAtMs: Date.now(),\n };\n agent.steps.push(step);\n trimSteps(agent);\n agent.currentStep = formatCurrentStep(step);\n return true;\n }\n case 'tool_end': {\n const step = findStep(agent, event.toolCallId);\n if (!step) return false;\n step.status = event.isError ? 'error' : 'done';\n step.resultPreview = event.resultPreview;\n step.error = event.error;\n if (step.startedAtMs != null) step.durationMs = Date.now() - step.startedAtMs;\n if (agent.currentStep === formatCurrentStep(step)) {\n agent.currentStep = undefined;\n }\n return true;\n }\n case 'iteration': {\n agent.iteration = event.count;\n agent.maxIterations = event.max;\n return true;\n }\n case 'text_delta': {\n if (!event.delta) return false;\n agent.streamText = appendStreamText(agent.streamText, event.delta);\n return true;\n }\n case 'thinking_delta': {\n if (!event.delta) return false;\n agent.streamText = appendStreamText(agent.streamText, event.delta);\n return true;\n }\n default:\n return false;\n }\n}\n\nfunction findStep(agent: WorkflowAgentSnapshot, id: string): WorkflowAgentStep | undefined {\n return agent.steps?.find((s) => s.id === id);\n}\n\nfunction formatCurrentStep(step: WorkflowAgentStep): string {\n return step.detail ? `${step.label}: ${step.detail}` : step.label;\n}\n\nfunction trimSteps(agent: WorkflowAgentSnapshot): void {\n if (!agent.steps || agent.steps.length <= MAX_STEPS) return;\n agent.steps = agent.steps.slice(-MAX_STEPS);\n}\n\nfunction appendStreamText(existing: string | undefined, delta: string): string {\n const next = (existing ?? '') + delta;\n if (next.length <= MAX_STREAM_TEXT) return next;\n return next.slice(-MAX_STREAM_TEXT);\n}\n"],"mappings":";;AAWA,MAAM,YAAY;AAClB,MAAM,kBAAkB;AAExB,SAAgB,sBACd,OACA,OACS;AACT,SAAQ,MAAM,MAAd;EACE,KAAK,cAAc;AACjB,OAAI,CAAC,MAAM,MAAO,OAAM,QAAQ,EAAE;GAClC,MAAM,EAAE,OAAO,WAAW,kBAAkB,MAAM,UAAU,MAAM,KAAK;GACvE,MAAM,OAA0B;IAC9B,IAAI,MAAM;IACV,MAAM;IACN,UAAU,MAAM;IAChB;IACA;IACA,QAAQ;IACR,aAAa,KAAK,KAAK;IACxB;AACD,SAAM,MAAM,KAAK,KAAK;AACtB,aAAU,MAAM;AAChB,SAAM,cAAc,kBAAkB,KAAK;AAC3C,UAAO;;EAET,KAAK,YAAY;GACf,MAAM,OAAO,SAAS,OAAO,MAAM,WAAW;AAC9C,OAAI,CAAC,KAAM,QAAO;AAClB,QAAK,SAAS,MAAM,UAAU,UAAU;AACxC,QAAK,gBAAgB,MAAM;AAC3B,QAAK,QAAQ,MAAM;AACnB,OAAI,KAAK,eAAe,KAAM,MAAK,aAAa,KAAK,KAAK,GAAG,KAAK;AAClE,OAAI,MAAM,gBAAgB,kBAAkB,KAAK,CAC/C,OAAM,cAAc,KAAA;AAEtB,UAAO;;EAET,KAAK;AACH,SAAM,YAAY,MAAM;AACxB,SAAM,gBAAgB,MAAM;AAC5B,UAAO;EAET,KAAK;AACH,OAAI,CAAC,MAAM,MAAO,QAAO;AACzB,SAAM,aAAa,iBAAiB,MAAM,YAAY,MAAM,MAAM;AAClE,UAAO;EAET,KAAK;AACH,OAAI,CAAC,MAAM,MAAO,QAAO;AACzB,SAAM,aAAa,iBAAiB,MAAM,YAAY,MAAM,MAAM;AAClE,UAAO;EAET,QACE,QAAO;;;AAIb,SAAS,SAAS,OAA8B,IAA2C;AACzF,QAAO,MAAM,OAAO,MAAM,MAAM,EAAE,OAAO,GAAG;;AAG9C,SAAS,kBAAkB,MAAiC;AAC1D,QAAO,KAAK,SAAS,GAAG,KAAK,MAAM,IAAI,KAAK,WAAW,KAAK;;AAG9D,SAAS,UAAU,OAAoC;AACrD,KAAI,CAAC,MAAM,SAAS,MAAM,MAAM,UAAU,UAAW;AACrD,OAAM,QAAQ,MAAM,MAAM,MAAM,CAAC,UAAU;;AAG7C,SAAS,iBAAiB,UAA8B,OAAuB;CAC7E,MAAM,QAAQ,YAAY,MAAM;AAChC,KAAI,KAAK,UAAU,gBAAiB,QAAO;AAC3C,QAAO,KAAK,MAAM,CAAC,gBAAgB"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Built-in workflow: `client_proposal`
3
+ *
4
+ * Draft a client-facing proposal from a brief — scope, timeline, pricing logic,
5
+ * risks, and next steps. For freelancers, consultants, and solo service providers.
6
+ *
7
+ * Args:
8
+ * - client_brief: what the client wants
9
+ * - offer: what you provide
10
+ * - budget_hint: budget range or constraints (optional)
11
+ */
12
+ export declare const CLIENT_PROPOSAL_SCRIPT = "export const meta = {\n name: 'client_proposal',\n description: 'Turn a client brief into a structured proposal with scope, timeline, pricing logic, and risks.',\n whenToUse: 'Freelancer or solo consultant needs a client-ready proposal or SOW draft.',\n examplePrompts: [\n { field: 'client_brief', text: 'SaaS startup wants 3-month growth consulting, budget 50\u201380k CNY' },\n { field: 'offer', text: 'Audit funnel, weekly strategy calls, async Slack support' },\n ],\n i18n: {\n zh: {\n description: '\u5C06\u5BA2\u6237\u9700\u6C42\u8F6C\u5316\u4E3A\u542B\u8303\u56F4\u3001\u65F6\u95F4\u7EBF\u3001\u62A5\u4EF7\u903B\u8F91\u4E0E\u98CE\u9669\u8BF4\u660E\u7684\u5BA2\u6237\u65B9\u6848\u3002',\n whenToUse: '\u81EA\u7531\u804C\u4E1A\u8005\u6216\u72EC\u7ACB\u987E\u95EE\u9700\u8981\u8D77\u8349\u5BA2\u6237\u65B9\u6848 / SOW \u65F6\u3002',\n examplePrompts: [\n { field: 'client_brief', text: 'SaaS \u521B\u4E1A\u516C\u53F8\u8981 3 \u4E2A\u6708\u589E\u957F\u54A8\u8BE2\uFF0C\u9884\u7B97 5\u20138 \u4E07' },\n { field: 'offer', text: '\u6F0F\u6597\u5BA1\u8BA1\u3001\u6BCF\u5468\u7B56\u7565\u4F1A\u3001Slack \u5F02\u6B65\u652F\u6301' },\n ],\n },\n },\n tags: ['writing', 'content', 'document'],\n estimatedAgents: { min: 4, max: 5 },\n phases: [\n { title: 'Understand' },\n { title: 'Structure' },\n { title: 'Draft' },\n { title: 'Polish' },\n ],\n}\n\nconst clientBrief = args && typeof args === 'object' && args.client_brief\n ? String(args.client_brief)\n : 'Infer the client brief from the most recent user turn.'\n\nconst offer = args && typeof args === 'object' && args.offer\n ? String(args.offer)\n : 'Infer your offer from context.'\n\nconst budgetHint = args && typeof args === 'object' && args.budget_hint\n ? String(args.budget_hint)\n : ''\n\nphase('Understand')\nconst understanding = await agent(\n 'Extract client needs, implicit constraints, success criteria, red flags, and what is out of scope.\\n\\n' +\n 'CLIENT BRIEF:\\n' + clientBrief + '\\nYOUR OFFER:\\n' + offer +\n (budgetHint ? '\\nBUDGET HINT:\\n' + budgetHint : ''),\n {\n label: 'understand',\n schema: {\n type: 'object',\n properties: {\n clientGoals: { type: 'array', items: { type: 'string' } },\n constraints: { type: 'array', items: { type: 'string' } },\n successCriteria: { type: 'array', items: { type: 'string' } },\n redFlags: { type: 'array', items: { type: 'string' } },\n outOfScope: { type: 'array', items: { type: 'string' } },\n },\n required: ['clientGoals', 'successCriteria'],\n },\n },\n)\n\nphase('Structure')\nconst structure = await agent(\n 'Design proposal structure: deliverables, milestones, timeline, pricing tiers or logic, assumptions, and exclusions. Fit a solo operator capacity.\\n\\n' +\n JSON.stringify({ clientBrief, offer, budgetHint, understanding }, null, 2),\n {\n label: 'structure',\n schema: {\n type: 'object',\n properties: {\n deliverables: { type: 'array', items: { type: 'string' } },\n milestones: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n name: { type: 'string' },\n duration: { type: 'string' },\n output: { type: 'string' },\n },\n required: ['name', 'output'],\n },\n },\n pricingApproach: { type: 'string' },\n assumptions: { type: 'array', items: { type: 'string' } },\n },\n required: ['deliverables', 'milestones', 'pricingApproach'],\n },\n },\n)\n\nphase('Draft')\nconst draft = await agent(\n 'Write a client-ready proposal draft in professional but warm tone. Include executive summary, scope, timeline, pricing section (with rationale), risks, and next steps.\\n\\n' +\n JSON.stringify({ understanding, structure }, null, 2),\n {\n label: 'draft',\n schema: {\n type: 'object',\n properties: {\n title: { type: 'string' },\n executiveSummary: { type: 'string' },\n scope: { type: 'string' },\n timeline: { type: 'string' },\n pricing: { type: 'string' },\n risks: { type: 'array', items: { type: 'string' } },\n nextSteps: { type: 'array', items: { type: 'string' } },\n },\n required: ['executiveSummary', 'scope', 'pricing', 'nextSteps'],\n },\n },\n)\n\nphase('Polish')\nconst polished = await agent(\n 'Polish the proposal: tighten language, add a one-line value prop, flag anything that could scare the client, and suggest 2 negotiation flex points.\\n\\n' +\n JSON.stringify(draft, null, 2),\n {\n label: 'polish',\n schema: {\n type: 'object',\n properties: {\n valueProp: { type: 'string' },\n fullProposal: { type: 'string' },\n clientConcerns: { type: 'array', items: { type: 'string' } },\n flexPoints: { type: 'array', items: { type: 'string' } },\n },\n required: ['valueProp', 'fullProposal'],\n },\n },\n)\n\nreturn {\n ok: true,\n understanding,\n structure,\n ...(polished ?? { valueProp: '', fullProposal: 'draft failed' }),\n}\n";
@@ -0,0 +1,155 @@
1
+ //#region src/agent/workflow/builtins/client-proposal.ts
2
+ /**
3
+ * Built-in workflow: `client_proposal`
4
+ *
5
+ * Draft a client-facing proposal from a brief — scope, timeline, pricing logic,
6
+ * risks, and next steps. For freelancers, consultants, and solo service providers.
7
+ *
8
+ * Args:
9
+ * - client_brief: what the client wants
10
+ * - offer: what you provide
11
+ * - budget_hint: budget range or constraints (optional)
12
+ */
13
+ const CLIENT_PROPOSAL_SCRIPT = `export const meta = {
14
+ name: 'client_proposal',
15
+ description: 'Turn a client brief into a structured proposal with scope, timeline, pricing logic, and risks.',
16
+ whenToUse: 'Freelancer or solo consultant needs a client-ready proposal or SOW draft.',
17
+ examplePrompts: [
18
+ { field: 'client_brief', text: 'SaaS startup wants 3-month growth consulting, budget 50–80k CNY' },
19
+ { field: 'offer', text: 'Audit funnel, weekly strategy calls, async Slack support' },
20
+ ],
21
+ i18n: {
22
+ zh: {
23
+ description: '将客户需求转化为含范围、时间线、报价逻辑与风险说明的客户方案。',
24
+ whenToUse: '自由职业者或独立顾问需要起草客户方案 / SOW 时。',
25
+ examplePrompts: [
26
+ { field: 'client_brief', text: 'SaaS 创业公司要 3 个月增长咨询,预算 5–8 万' },
27
+ { field: 'offer', text: '漏斗审计、每周策略会、Slack 异步支持' },
28
+ ],
29
+ },
30
+ },
31
+ tags: ['writing', 'content', 'document'],
32
+ estimatedAgents: { min: 4, max: 5 },
33
+ phases: [
34
+ { title: 'Understand' },
35
+ { title: 'Structure' },
36
+ { title: 'Draft' },
37
+ { title: 'Polish' },
38
+ ],
39
+ }
40
+
41
+ const clientBrief = args && typeof args === 'object' && args.client_brief
42
+ ? String(args.client_brief)
43
+ : 'Infer the client brief from the most recent user turn.'
44
+
45
+ const offer = args && typeof args === 'object' && args.offer
46
+ ? String(args.offer)
47
+ : 'Infer your offer from context.'
48
+
49
+ const budgetHint = args && typeof args === 'object' && args.budget_hint
50
+ ? String(args.budget_hint)
51
+ : ''
52
+
53
+ phase('Understand')
54
+ const understanding = await agent(
55
+ 'Extract client needs, implicit constraints, success criteria, red flags, and what is out of scope.\\n\\n' +
56
+ 'CLIENT BRIEF:\\n' + clientBrief + '\\nYOUR OFFER:\\n' + offer +
57
+ (budgetHint ? '\\nBUDGET HINT:\\n' + budgetHint : ''),
58
+ {
59
+ label: 'understand',
60
+ schema: {
61
+ type: 'object',
62
+ properties: {
63
+ clientGoals: { type: 'array', items: { type: 'string' } },
64
+ constraints: { type: 'array', items: { type: 'string' } },
65
+ successCriteria: { type: 'array', items: { type: 'string' } },
66
+ redFlags: { type: 'array', items: { type: 'string' } },
67
+ outOfScope: { type: 'array', items: { type: 'string' } },
68
+ },
69
+ required: ['clientGoals', 'successCriteria'],
70
+ },
71
+ },
72
+ )
73
+
74
+ phase('Structure')
75
+ const structure = await agent(
76
+ 'Design proposal structure: deliverables, milestones, timeline, pricing tiers or logic, assumptions, and exclusions. Fit a solo operator capacity.\\n\\n' +
77
+ JSON.stringify({ clientBrief, offer, budgetHint, understanding }, null, 2),
78
+ {
79
+ label: 'structure',
80
+ schema: {
81
+ type: 'object',
82
+ properties: {
83
+ deliverables: { type: 'array', items: { type: 'string' } },
84
+ milestones: {
85
+ type: 'array',
86
+ items: {
87
+ type: 'object',
88
+ properties: {
89
+ name: { type: 'string' },
90
+ duration: { type: 'string' },
91
+ output: { type: 'string' },
92
+ },
93
+ required: ['name', 'output'],
94
+ },
95
+ },
96
+ pricingApproach: { type: 'string' },
97
+ assumptions: { type: 'array', items: { type: 'string' } },
98
+ },
99
+ required: ['deliverables', 'milestones', 'pricingApproach'],
100
+ },
101
+ },
102
+ )
103
+
104
+ phase('Draft')
105
+ const draft = await agent(
106
+ 'Write a client-ready proposal draft in professional but warm tone. Include executive summary, scope, timeline, pricing section (with rationale), risks, and next steps.\\n\\n' +
107
+ JSON.stringify({ understanding, structure }, null, 2),
108
+ {
109
+ label: 'draft',
110
+ schema: {
111
+ type: 'object',
112
+ properties: {
113
+ title: { type: 'string' },
114
+ executiveSummary: { type: 'string' },
115
+ scope: { type: 'string' },
116
+ timeline: { type: 'string' },
117
+ pricing: { type: 'string' },
118
+ risks: { type: 'array', items: { type: 'string' } },
119
+ nextSteps: { type: 'array', items: { type: 'string' } },
120
+ },
121
+ required: ['executiveSummary', 'scope', 'pricing', 'nextSteps'],
122
+ },
123
+ },
124
+ )
125
+
126
+ phase('Polish')
127
+ const polished = await agent(
128
+ 'Polish the proposal: tighten language, add a one-line value prop, flag anything that could scare the client, and suggest 2 negotiation flex points.\\n\\n' +
129
+ JSON.stringify(draft, null, 2),
130
+ {
131
+ label: 'polish',
132
+ schema: {
133
+ type: 'object',
134
+ properties: {
135
+ valueProp: { type: 'string' },
136
+ fullProposal: { type: 'string' },
137
+ clientConcerns: { type: 'array', items: { type: 'string' } },
138
+ flexPoints: { type: 'array', items: { type: 'string' } },
139
+ },
140
+ required: ['valueProp', 'fullProposal'],
141
+ },
142
+ },
143
+ )
144
+
145
+ return {
146
+ ok: true,
147
+ understanding,
148
+ structure,
149
+ ...(polished ?? { valueProp: '', fullProposal: 'draft failed' }),
150
+ }
151
+ `;
152
+ //#endregion
153
+ export { CLIENT_PROPOSAL_SCRIPT };
154
+
155
+ //# sourceMappingURL=client-proposal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-proposal.js","names":[],"sources":["../../../../../src/agent/workflow/builtins/client-proposal.ts"],"sourcesContent":["/**\n * Built-in workflow: `client_proposal`\n *\n * Draft a client-facing proposal from a brief — scope, timeline, pricing logic,\n * risks, and next steps. For freelancers, consultants, and solo service providers.\n *\n * Args:\n * - client_brief: what the client wants\n * - offer: what you provide\n * - budget_hint: budget range or constraints (optional)\n */\n\nexport const CLIENT_PROPOSAL_SCRIPT = `export const meta = {\n name: 'client_proposal',\n description: 'Turn a client brief into a structured proposal with scope, timeline, pricing logic, and risks.',\n whenToUse: 'Freelancer or solo consultant needs a client-ready proposal or SOW draft.',\n examplePrompts: [\n { field: 'client_brief', text: 'SaaS startup wants 3-month growth consulting, budget 50–80k CNY' },\n { field: 'offer', text: 'Audit funnel, weekly strategy calls, async Slack support' },\n ],\n i18n: {\n zh: {\n description: '将客户需求转化为含范围、时间线、报价逻辑与风险说明的客户方案。',\n whenToUse: '自由职业者或独立顾问需要起草客户方案 / SOW 时。',\n examplePrompts: [\n { field: 'client_brief', text: 'SaaS 创业公司要 3 个月增长咨询,预算 5–8 万' },\n { field: 'offer', text: '漏斗审计、每周策略会、Slack 异步支持' },\n ],\n },\n },\n tags: ['writing', 'content', 'document'],\n estimatedAgents: { min: 4, max: 5 },\n phases: [\n { title: 'Understand' },\n { title: 'Structure' },\n { title: 'Draft' },\n { title: 'Polish' },\n ],\n}\n\nconst clientBrief = args && typeof args === 'object' && args.client_brief\n ? String(args.client_brief)\n : 'Infer the client brief from the most recent user turn.'\n\nconst offer = args && typeof args === 'object' && args.offer\n ? String(args.offer)\n : 'Infer your offer from context.'\n\nconst budgetHint = args && typeof args === 'object' && args.budget_hint\n ? String(args.budget_hint)\n : ''\n\nphase('Understand')\nconst understanding = await agent(\n 'Extract client needs, implicit constraints, success criteria, red flags, and what is out of scope.\\\\n\\\\n' +\n 'CLIENT BRIEF:\\\\n' + clientBrief + '\\\\nYOUR OFFER:\\\\n' + offer +\n (budgetHint ? '\\\\nBUDGET HINT:\\\\n' + budgetHint : ''),\n {\n label: 'understand',\n schema: {\n type: 'object',\n properties: {\n clientGoals: { type: 'array', items: { type: 'string' } },\n constraints: { type: 'array', items: { type: 'string' } },\n successCriteria: { type: 'array', items: { type: 'string' } },\n redFlags: { type: 'array', items: { type: 'string' } },\n outOfScope: { type: 'array', items: { type: 'string' } },\n },\n required: ['clientGoals', 'successCriteria'],\n },\n },\n)\n\nphase('Structure')\nconst structure = await agent(\n 'Design proposal structure: deliverables, milestones, timeline, pricing tiers or logic, assumptions, and exclusions. Fit a solo operator capacity.\\\\n\\\\n' +\n JSON.stringify({ clientBrief, offer, budgetHint, understanding }, null, 2),\n {\n label: 'structure',\n schema: {\n type: 'object',\n properties: {\n deliverables: { type: 'array', items: { type: 'string' } },\n milestones: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n name: { type: 'string' },\n duration: { type: 'string' },\n output: { type: 'string' },\n },\n required: ['name', 'output'],\n },\n },\n pricingApproach: { type: 'string' },\n assumptions: { type: 'array', items: { type: 'string' } },\n },\n required: ['deliverables', 'milestones', 'pricingApproach'],\n },\n },\n)\n\nphase('Draft')\nconst draft = await agent(\n 'Write a client-ready proposal draft in professional but warm tone. Include executive summary, scope, timeline, pricing section (with rationale), risks, and next steps.\\\\n\\\\n' +\n JSON.stringify({ understanding, structure }, null, 2),\n {\n label: 'draft',\n schema: {\n type: 'object',\n properties: {\n title: { type: 'string' },\n executiveSummary: { type: 'string' },\n scope: { type: 'string' },\n timeline: { type: 'string' },\n pricing: { type: 'string' },\n risks: { type: 'array', items: { type: 'string' } },\n nextSteps: { type: 'array', items: { type: 'string' } },\n },\n required: ['executiveSummary', 'scope', 'pricing', 'nextSteps'],\n },\n },\n)\n\nphase('Polish')\nconst polished = await agent(\n 'Polish the proposal: tighten language, add a one-line value prop, flag anything that could scare the client, and suggest 2 negotiation flex points.\\\\n\\\\n' +\n JSON.stringify(draft, null, 2),\n {\n label: 'polish',\n schema: {\n type: 'object',\n properties: {\n valueProp: { type: 'string' },\n fullProposal: { type: 'string' },\n clientConcerns: { type: 'array', items: { type: 'string' } },\n flexPoints: { type: 'array', items: { type: 'string' } },\n },\n required: ['valueProp', 'fullProposal'],\n },\n },\n)\n\nreturn {\n ok: true,\n understanding,\n structure,\n ...(polished ?? { valueProp: '', fullProposal: 'draft failed' }),\n}\n`\n"],"mappings":";;;;;;;;;;;;AAYA,MAAa,yBAAyB"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Built-in workflow: `competitor_scan`
3
+ *
4
+ * Parallel competitor scan for solopreneurs — positioning, pricing, strengths,
5
+ * weaknesses, and differentiation opportunities.
6
+ *
7
+ * Args:
8
+ * - market: what you are building or selling
9
+ * - competitors: competitor names (optional)
10
+ * - focus: what you care most about (optional)
11
+ */
12
+ export declare const COMPETITOR_SCAN_SCRIPT = "export const meta = {\n name: 'competitor_scan',\n description: 'Scan competitors in parallel and synthesize positioning, pricing, and differentiation opportunities.',\n whenToUse: 'Solo founder comparing alternatives before pricing, positioning, or go-to-market decisions.',\n examplePrompts: [\n { field: 'market', text: 'AI writing assistant for solo creators' },\n { field: 'competitors', text: 'Notion AI, Jasper, Cursor' },\n ],\n i18n: {\n zh: {\n description: '\u5E76\u884C\u626B\u63CF\u7ADE\u54C1\uFF0C\u7EFC\u5408\u5B9A\u4F4D\u3001\u5B9A\u4EF7\u4E0E\u5DEE\u5F02\u5316\u673A\u4F1A\u3002',\n whenToUse: '\u4E00\u4EBA\u516C\u53F8\u5728\u5B9A\u4EF7\u3001\u5B9A\u4F4D\u6216\u8FDB\u5165\u5E02\u573A\u524D\u9700\u8981\u7ADE\u54C1\u5BF9\u6BD4\u65F6\u3002',\n examplePrompts: [\n { field: 'market', text: '\u9762\u5411\u72EC\u7ACB\u521B\u4F5C\u8005\u7684 AI \u5199\u4F5C\u52A9\u624B' },\n { field: 'competitors', text: 'Notion AI\u3001Jasper\u3001Cursor' },\n ],\n },\n },\n tags: ['research', 'investigation'],\n estimatedAgents: { min: 4, max: 7 },\n phases: [\n { title: 'Frame' },\n { title: 'Scan' },\n { title: 'Synthesize' },\n ],\n}\n\nconst RESEARCH_TOOLS = ['web_search', 'web_fetch']\n\nconst market = args && typeof args === 'object' && args.market\n ? String(args.market)\n : 'Infer the market from the most recent user turn.'\n\nconst competitorsRaw = args && typeof args === 'object' && args.competitors\n ? String(args.competitors)\n : ''\n\nconst focus = args && typeof args === 'object' && args.focus\n ? String(args.focus)\n : 'positioning and pricing'\n\nphase('Frame')\nconst frame = await agent(\n 'Frame this competitor scan. If competitors are not listed, propose 3\u20134 realistic competitors. Return scan criteria and your assumed buyer persona.\\n\\n' +\n 'MARKET:\\n' + market + '\\n' +\n (competitorsRaw ? 'COMPETITORS:\\n' + competitorsRaw + '\\n' : '') +\n 'FOCUS:\\n' + focus,\n {\n label: 'frame',\n toolset: RESEARCH_TOOLS,\n schema: {\n type: 'object',\n properties: {\n competitors: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n name: { type: 'string' },\n oneLiner: { type: 'string' },\n },\n required: ['name'],\n },\n },\n criteria: { type: 'array', items: { type: 'string' } },\n buyerPersona: { type: 'string' },\n },\n required: ['competitors', 'criteria'],\n },\n },\n)\n\nif (!frame || !frame.competitors?.length) {\n return { ok: false, reason: 'framing failed', market }\n}\n\nconst competitors = frame.competitors.slice(0, 4)\n\nphase('Scan')\nconst scans = await parallel(\n competitors.map((c) => () =>\n agent(\n 'Research this competitor for a solo founder. Use web search. Return positioning, pricing model, strengths, weaknesses, and target customer \u2014 cite sources where possible.\\n\\n' +\n 'COMPETITOR: ' + c.name + '\\nMARKET: ' + market + '\\nFOCUS: ' + focus,\n {\n label: c.name,\n toolset: RESEARCH_TOOLS,\n maxIterations: 25,\n schema: {\n type: 'object',\n properties: {\n name: { type: 'string' },\n positioning: { type: 'string' },\n pricing: { type: 'string' },\n strengths: { type: 'array', items: { type: 'string' } },\n weaknesses: { type: 'array', items: { type: 'string' } },\n targetCustomer: { type: 'string' },\n sources: { type: 'array', items: { type: 'string' } },\n },\n required: ['name', 'positioning', 'strengths', 'weaknesses'],\n },\n },\n ),\n ),\n)\n\nphase('Synthesize')\nconst synthesis = await agent(\n 'Synthesize a competitor matrix and differentiation playbook for a one-person company. Include whitespace opportunities, pricing band recommendation, and 3 positioning angles to test.\\n\\n' +\n JSON.stringify({ market, focus, frame, scans: scans.filter(Boolean) }, null, 2),\n {\n label: 'synthesis',\n schema: {\n type: 'object',\n properties: {\n matrixSummary: { type: 'string' },\n whitespace: { type: 'array', items: { type: 'string' } },\n pricingBand: { type: 'string' },\n positioningAngles: { type: 'array', items: { type: 'string' } },\n avoidCompetingOn: { type: 'array', items: { type: 'string' } },\n },\n required: ['matrixSummary', 'whitespace', 'positioningAngles'],\n },\n },\n)\n\nreturn {\n ok: true,\n market,\n competitorCount: competitors.length,\n scans: scans.filter(Boolean),\n ...(synthesis ?? { matrixSummary: 'synthesis failed', whitespace: [], positioningAngles: [] }),\n}\n";