@xopcai/xopc 0.0.123 → 0.0.125

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 (436) hide show
  1. package/README.md +28 -11
  2. package/README.zh-CN.md +28 -11
  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-BzaXOLd9.js +41 -0
  6. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-Bicr3ZBg.js → agent-avatar-dicebear-BKDKICZV.js} +1 -1
  7. package/dist/gateway/static/root/assets/agents-BnTB7-c5.js +2 -0
  8. package/dist/gateway/static/root/assets/app-management-settings-panel-DI4BqHKW.js +1 -0
  9. package/dist/gateway/static/root/assets/appearance-settings-6_ie3NQ4.js +1 -0
  10. package/dist/gateway/static/root/assets/apps-page-B6bG65SW.js +1 -0
  11. package/dist/gateway/static/root/assets/{archive-plugin-Bpy64GjU.js → archive-plugin-kUHEJ1HK.js} +1 -1
  12. package/dist/gateway/static/root/assets/{attachment-load-DMkp4xSb.js → attachment-load-Dm-9prpU.js} +1 -1
  13. package/dist/gateway/static/root/assets/automation-suggestion-card-OTxQTPnC.js +1 -0
  14. package/dist/gateway/static/root/assets/automations-page-1lVbo0dS.js +1 -0
  15. package/dist/gateway/static/root/assets/{binary-plugins-DA9C5mCw.js → binary-plugins-DllA_jQ0.js} +1 -1
  16. package/dist/gateway/static/root/assets/{block-editor-CiSXk_YC.js → block-editor-Cm6CWNj1.js} +5 -5
  17. package/dist/gateway/static/root/assets/browser-settings-page-CzDBlOK0.js +8 -0
  18. package/dist/gateway/static/root/assets/capability-presets-api-VJ89b6I2.js +1 -0
  19. package/dist/gateway/static/root/assets/capability-presets-settings-panel-BaPj8wC2.js +1 -0
  20. package/dist/gateway/static/root/assets/{channel-recipient-api-CDhrhfhN.js → channel-recipient-api-B5PwBEUx.js} +1 -1
  21. package/dist/gateway/static/root/assets/channels-settings-Df4m7p1k.js +1 -0
  22. package/dist/gateway/static/root/assets/code-editor-gbRgS14t.js +1 -0
  23. package/dist/gateway/static/root/assets/connectors-page-BMTphmHS.js +2 -0
  24. package/dist/gateway/static/root/assets/{directory-picker-path-field-7jKfbH0J.js → directory-picker-path-field-DySMzOF6.js} +1 -1
  25. package/dist/gateway/static/root/assets/dist-By6Rh2ee.js +1 -0
  26. package/dist/gateway/static/root/assets/dist-CkobR7mk.js +1 -0
  27. package/dist/gateway/static/root/assets/dist-DokQNVMQ.js +1 -0
  28. package/dist/gateway/static/root/assets/dreaming-settings-D_AjSnV_.js +3 -0
  29. package/dist/gateway/static/root/assets/extension-debug-page-BaM0FpW7.js +1 -0
  30. package/dist/gateway/static/root/assets/extension-page-DuZWVOGA.js +1 -0
  31. package/dist/gateway/static/root/assets/extension-settings-page-DarVtbmJ.js +1 -0
  32. package/dist/gateway/static/root/assets/{fetch-Dtpbbe0s.js → fetch-NFKfkqrd.js} +1 -1
  33. package/dist/gateway/static/root/assets/{gateway-config-api-BN6pyX2c.js → gateway-config-api-CFEm9rgy.js} +1 -1
  34. package/dist/gateway/static/root/assets/gateway-settings-BMk2S5td.js +1 -0
  35. package/dist/gateway/static/root/assets/{gateway-startup-retry-BIET2x3y.js → gateway-startup-retry-Dtwh_yGg.js} +1 -1
  36. package/dist/gateway/static/root/assets/goal-create-dialog-BKP7ylcG.js +1 -0
  37. package/dist/gateway/static/root/assets/goal-detail-page-C2cTxf71.js +1 -0
  38. package/dist/gateway/static/root/assets/{goals-config-api-CQm2r1Ty.js → goals-config-api-DTWZNA_Y.js} +1 -1
  39. package/dist/gateway/static/root/assets/goals-page-CTmA3hWK.js +2 -0
  40. package/dist/gateway/static/root/assets/goals-settings-Bhct0ZT-.js +1 -0
  41. package/dist/gateway/static/root/assets/heartbeat-settings-C-UxSU4a.js +1 -0
  42. package/dist/gateway/static/root/assets/{html-workspace-editor-CQLWDiwE.js → html-workspace-editor-DV90ddkE.js} +1 -1
  43. package/dist/gateway/static/root/assets/index-BNHDd2j1.css +1 -0
  44. package/dist/gateway/static/root/assets/index-BPXLPMuY.js +193 -0
  45. package/dist/gateway/static/root/assets/keyboard-shortcuts-settings-DCjy2y4d.js +1 -0
  46. package/dist/gateway/static/root/assets/logs-page-CGc3_CQU.js +2 -0
  47. package/dist/gateway/static/root/assets/{markdown-editor-AssmZFx4.js → markdown-editor-BFOjBBla.js} +2 -2
  48. package/dist/gateway/static/root/assets/markdown-split-BaqxP-GQ.js +1 -0
  49. package/dist/gateway/static/root/assets/{media-plugins-DS44pJzr.js → media-plugins-DQ_EXloK.js} +1 -1
  50. package/dist/gateway/static/root/assets/messages-CjuIJcZl.js +3 -0
  51. package/dist/gateway/static/root/assets/models-hub-panel-Drz4UQMb.js +2 -0
  52. package/dist/gateway/static/root/assets/notes-page-CY1ylng1.js +1 -0
  53. package/dist/gateway/static/root/assets/notes-workbench-X7IaD--t.js +2 -0
  54. package/dist/gateway/static/root/assets/page-tabs-EIZhLppK.js +1 -0
  55. package/dist/gateway/static/root/assets/{preview-open-alternatives-Dej6zHnr.js → preview-open-alternatives-r_uB4Epr.js} +1 -1
  56. package/dist/gateway/static/root/assets/project-detail-page-DE5adRBE.js +1 -0
  57. package/dist/gateway/static/root/assets/projects-page-CuOb1k6F.js +1 -0
  58. package/dist/gateway/static/root/assets/remote-access-hub-BOxNrUVA.js +1 -0
  59. package/dist/gateway/static/root/assets/{save-bar-controls-Zu1FzH6z.js → save-bar-controls-B132Uf-2.js} +1 -1
  60. package/dist/gateway/static/root/assets/{schedule-field-BW78mKc0.js → schedule-field-B5qshALm.js} +1 -1
  61. package/dist/gateway/static/root/assets/{schema-form-Cc-IOydE.js → schema-form-CvLmkdS9.js} +1 -1
  62. package/dist/gateway/static/root/assets/sessions-page-CY0IQcGW.js +1 -0
  63. package/dist/gateway/static/root/assets/settings-advanced-gate-D4NBNVCB.js +1 -0
  64. package/dist/gateway/static/root/assets/{settings-collapsible-section-DIpIYht_.js → settings-collapsible-section-pRlvqsAf.js} +1 -1
  65. package/dist/gateway/static/root/assets/{settings-form-section-BFxBNxOG.js → settings-form-section-BBYUDLVd.js} +1 -1
  66. package/dist/gateway/static/root/assets/{settings-page-C4fivemT.js → settings-page-DaO7zYZM.js} +1 -1
  67. package/dist/gateway/static/root/assets/{settings-page-layout-CLx4o2PN.js → settings-page-layout-D8g9PAek.js} +1 -1
  68. package/dist/gateway/static/root/assets/setup-status-panel-Bks0CLLT.js +1 -0
  69. package/dist/gateway/static/root/assets/{share-preview-page-ZswWCrDu.js → share-preview-page-v6-IOp8t.js} +1 -1
  70. package/dist/gateway/static/root/assets/shares-settings-CEZsKm9r.js +1 -0
  71. package/dist/gateway/static/root/assets/skill-reload-api-oTkHbb9W.js +1 -0
  72. package/dist/gateway/static/root/assets/skills-page-DigsbZ6K.js +2 -0
  73. package/dist/gateway/static/root/assets/{system-settings-panel-3ZNcGRhS.js → system-settings-panel-HUU2FFjM.js} +1 -1
  74. package/dist/gateway/static/root/assets/{typed-models-lib-bpzZjiVF.js → typed-models-lib-AH160QHh.js} +1 -1
  75. package/dist/gateway/static/root/assets/{use-channel-catalog-BCjZLZ3O.js → use-channel-catalog-BXzDmjCQ.js} +1 -1
  76. package/dist/gateway/static/root/assets/{user-profile-settings-panel-CIXafITi.js → user-profile-settings-panel-DxwnQvQ3.js} +1 -1
  77. package/dist/gateway/static/root/assets/{utils-DXMtz5j_.js → utils-i5POEtqd.js} +1 -1
  78. package/dist/gateway/static/root/assets/{vendor-codemirror-CNP1wPe7.js → vendor-codemirror-BvNgyE1Q.js} +1 -1
  79. package/dist/gateway/static/root/assets/voice-api-key-field-CEJklEgb.js +1 -0
  80. package/dist/gateway/static/root/assets/work-item-detail-page-CD6FL_Rs.js +1 -0
  81. package/dist/gateway/static/root/assets/workflow-run-setup-panel-ClVVMgN-.js +3 -0
  82. package/dist/gateway/static/root/assets/{workflow-start-dialog-B4sZpTDQ.js → workflow-start-dialog-Dv4ZA1T0.js} +1 -1
  83. package/dist/gateway/static/root/assets/workflows-page-OLQiUpoW.js +18 -0
  84. package/dist/gateway/static/root/index.html +8 -7
  85. package/dist/package.js +1 -1
  86. package/dist/src/agent/agent-manager.d.ts +6 -1
  87. package/dist/src/agent/agent-manager.js +34 -14
  88. package/dist/src/agent/agent-manager.js.map +1 -1
  89. package/dist/src/agent/child-agent-factory.d.ts +2 -0
  90. package/dist/src/agent/child-agent-factory.js +2 -0
  91. package/dist/src/agent/child-agent-factory.js.map +1 -1
  92. package/dist/src/agent/coding/index.d.ts +1 -0
  93. package/dist/src/agent/coding/index.js +2 -0
  94. package/dist/src/agent/coding/turn-diff-tracker.d.ts +67 -0
  95. package/dist/src/agent/coding/turn-diff-tracker.js +233 -0
  96. package/dist/src/agent/coding/turn-diff-tracker.js.map +1 -0
  97. package/dist/src/agent/context/workspace-templates/AGENTS.md +1 -1
  98. package/dist/src/agent/embedded/xopc-tools-bridge.js +11 -7
  99. package/dist/src/agent/embedded/xopc-tools-bridge.js.map +1 -1
  100. package/dist/src/agent/lifecycle/progress.js +3 -1
  101. package/dist/src/agent/lifecycle/progress.js.map +1 -1
  102. package/dist/src/agent/lifecycle/timeout-wrapper.d.ts +2 -2
  103. package/dist/src/agent/lifecycle/timeout-wrapper.js +4 -4
  104. package/dist/src/agent/lifecycle/timeout-wrapper.js.map +1 -1
  105. package/dist/src/agent/memory/dreaming/config.d.ts +2 -2
  106. package/dist/src/agent/memory/dreaming/config.js +3 -3
  107. package/dist/src/agent/memory/dreaming/config.js.map +1 -1
  108. package/dist/src/agent/memory/dreaming/constants.d.ts +1 -1
  109. package/dist/src/agent/memory/dreaming/constants.js +1 -1
  110. package/dist/src/agent/memory/dreaming/constants.js.map +1 -1
  111. package/dist/src/agent/memory/dreaming/deep-promotion.d.ts +1 -0
  112. package/dist/src/agent/memory/dreaming/deep-promotion.js +11 -20
  113. package/dist/src/agent/memory/dreaming/deep-promotion.js.map +1 -1
  114. package/dist/src/agent/memory/dreaming/events.d.ts +3 -3
  115. package/dist/src/agent/memory/dreaming/events.js +6 -6
  116. package/dist/src/agent/memory/dreaming/events.js.map +1 -1
  117. package/dist/src/agent/memory/dreaming/last-run.d.ts +2 -2
  118. package/dist/src/agent/memory/dreaming/last-run.js +1 -1
  119. package/dist/src/agent/memory/dreaming/last-run.js.map +1 -1
  120. package/dist/src/agent/memory/dreaming/light-sweep.d.ts +1 -1
  121. package/dist/src/agent/memory/dreaming/light-sweep.js +3 -3
  122. package/dist/src/agent/memory/dreaming/light-sweep.js.map +1 -1
  123. package/dist/src/agent/memory/dreaming/preview.d.ts +1 -0
  124. package/dist/src/agent/memory/dreaming/preview.js +2 -2
  125. package/dist/src/agent/memory/dreaming/preview.js.map +1 -1
  126. package/dist/src/agent/memory/dreaming/rem-patterns.d.ts +1 -1
  127. package/dist/src/agent/memory/dreaming/rem-patterns.js +13 -13
  128. package/dist/src/agent/memory/dreaming/rem-patterns.js.map +1 -1
  129. package/dist/src/agent/memory/dreaming/scope.d.ts +14 -0
  130. package/dist/src/agent/memory/dreaming/scope.js +27 -0
  131. package/dist/src/agent/memory/dreaming/scope.js.map +1 -0
  132. package/dist/src/agent/memory/dreaming/short-term-store.d.ts +4 -4
  133. package/dist/src/agent/memory/dreaming/short-term-store.js +17 -17
  134. package/dist/src/agent/memory/dreaming/short-term-store.js.map +1 -1
  135. package/dist/src/agent/memory/summary-generator.d.ts +1 -1
  136. package/dist/src/agent/memory/summary-generator.js +1 -2
  137. package/dist/src/agent/memory/summary-generator.js.map +1 -1
  138. package/dist/src/agent/middleware/index.d.ts +1 -1
  139. package/dist/src/agent/middleware/self-verify.d.ts +44 -14
  140. package/dist/src/agent/middleware/self-verify.js +214 -44
  141. package/dist/src/agent/middleware/self-verify.js.map +1 -1
  142. package/dist/src/agent/orchestration/agent-event-handler.d.ts +2 -0
  143. package/dist/src/agent/orchestration/agent-event-handler.js +69 -14
  144. package/dist/src/agent/orchestration/agent-event-handler.js.map +1 -1
  145. package/dist/src/agent/orchestration/agent-orchestrator.js +22 -12
  146. package/dist/src/agent/orchestration/agent-orchestrator.js.map +1 -1
  147. package/dist/src/agent/prompt/sections/tooling.js +18 -13
  148. package/dist/src/agent/prompt/sections/tooling.js.map +1 -1
  149. package/dist/src/agent/prompt/system-reminder.js +2 -2
  150. package/dist/src/agent/prompt/system-reminder.js.map +1 -1
  151. package/dist/src/agent/sandbox/command-validator.js +12 -0
  152. package/dist/src/agent/sandbox/command-validator.js.map +1 -1
  153. package/dist/src/agent/sandbox/sanitize-env-vars.d.ts +2 -2
  154. package/dist/src/agent/sandbox/sanitize-env-vars.js +2 -2
  155. package/dist/src/agent/sandbox/sanitize-env-vars.js.map +1 -1
  156. package/dist/src/agent/sandbox/types.d.ts +2 -2
  157. package/dist/src/agent/sandbox/types.js.map +1 -1
  158. package/dist/src/agent/service/btw-query.js +1 -1
  159. package/dist/src/agent/service/btw-query.js.map +1 -1
  160. package/dist/src/agent/service/process-direct-one-shot.js +6 -2
  161. package/dist/src/agent/service/process-direct-one-shot.js.map +1 -1
  162. package/dist/src/agent/service/process-direct-streaming.js +12 -7
  163. package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
  164. package/dist/src/agent/service.d.ts +2 -1
  165. package/dist/src/agent/service.js +19 -6
  166. package/dist/src/agent/service.js.map +1 -1
  167. package/dist/src/agent/session/session-inspector.d.ts +5 -3
  168. package/dist/src/agent/session/session-inspector.js +16 -7
  169. package/dist/src/agent/session/session-inspector.js.map +1 -1
  170. package/dist/src/agent/skills/skill-tool-gating.js +1 -1
  171. package/dist/src/agent/skills/skill-tool-gating.js.map +1 -1
  172. package/dist/src/agent/source-context/types.d.ts +1 -1
  173. package/dist/src/agent/source-context/types.js +1 -1
  174. package/dist/src/agent/source-context/types.js.map +1 -1
  175. package/dist/src/agent/starter-agents.js +9 -9
  176. package/dist/src/agent/starter-agents.js.map +1 -1
  177. package/dist/src/agent/tools/apply-patch.d.ts +25 -0
  178. package/dist/src/agent/tools/apply-patch.js +310 -0
  179. package/dist/src/agent/tools/apply-patch.js.map +1 -0
  180. package/dist/src/agent/tools/concurrency.d.ts +8 -0
  181. package/dist/src/agent/tools/concurrency.js +31 -0
  182. package/dist/src/agent/tools/concurrency.js.map +1 -0
  183. package/dist/src/agent/tools/delegate-tool.d.ts +1 -1
  184. package/dist/src/agent/tools/delegate-tool.js +3 -3
  185. package/dist/src/agent/tools/delegate-tool.js.map +1 -1
  186. package/dist/src/agent/tools/dreaming-tool.d.ts +2 -0
  187. package/dist/src/agent/tools/dreaming-tool.js +13 -10
  188. package/dist/src/agent/tools/dreaming-tool.js.map +1 -1
  189. package/dist/src/agent/tools/exec-command.d.ts +30 -0
  190. package/dist/src/agent/tools/exec-command.js +228 -0
  191. package/dist/src/agent/tools/exec-command.js.map +1 -0
  192. package/dist/src/agent/tools/execute-code-tool.js +7 -4
  193. package/dist/src/agent/tools/execute-code-tool.js.map +1 -1
  194. package/dist/src/agent/tools/executor.d.ts +6 -4
  195. package/dist/src/agent/tools/executor.js +7 -4
  196. package/dist/src/agent/tools/executor.js.map +1 -1
  197. package/dist/src/agent/tools/factory.d.ts +6 -3
  198. package/dist/src/agent/tools/factory.js +14 -9
  199. package/dist/src/agent/tools/factory.js.map +1 -1
  200. package/dist/src/agent/tools/find.js +2 -0
  201. package/dist/src/agent/tools/find.js.map +1 -1
  202. package/dist/src/agent/tools/grep.js +2 -0
  203. package/dist/src/agent/tools/grep.js.map +1 -1
  204. package/dist/src/agent/tools/index.d.ts +5 -2
  205. package/dist/src/agent/tools/index.js +7 -4
  206. package/dist/src/agent/tools/list-dir.js +2 -0
  207. package/dist/src/agent/tools/list-dir.js.map +1 -1
  208. package/dist/src/agent/tools/memory-tool.d.ts +1 -0
  209. package/dist/src/agent/tools/memory-tool.js +2 -2
  210. package/dist/src/agent/tools/memory-tool.js.map +1 -1
  211. package/dist/src/agent/tools/metadata.d.ts +15 -0
  212. package/dist/src/agent/tools/metadata.js +26 -0
  213. package/dist/src/agent/tools/metadata.js.map +1 -0
  214. package/dist/src/agent/tools/read.js +2 -0
  215. package/dist/src/agent/tools/read.js.map +1 -1
  216. package/dist/src/agent/tools/skills-tools.d.ts +1 -1
  217. package/dist/src/agent/tools/skills-tools.js.map +1 -1
  218. package/dist/src/agent/tools/update-plan-tool.d.ts +11 -0
  219. package/dist/src/agent/tools/update-plan-tool.js +72 -0
  220. package/dist/src/agent/tools/update-plan-tool.js.map +1 -0
  221. package/dist/src/agent/tools/write.js +6 -0
  222. package/dist/src/agent/tools/write.js.map +1 -1
  223. package/dist/src/agent/workflow/builtins/debug-incident.d.ts +1 -1
  224. package/dist/src/agent/workflow/builtins/debug-incident.js +1 -1
  225. package/dist/src/agent/workflow/builtins/debug-incident.js.map +1 -1
  226. package/dist/src/agent/workflow/builtins/pr-review.d.ts +1 -1
  227. package/dist/src/agent/workflow/builtins/pr-review.js +1 -1
  228. package/dist/src/agent/workflow/builtins/pr-review.js.map +1 -1
  229. package/dist/src/agent/workflow/builtins/release-check.d.ts +1 -1
  230. package/dist/src/agent/workflow/builtins/release-check.js +1 -1
  231. package/dist/src/agent/workflow/builtins/release-check.js.map +1 -1
  232. package/dist/src/agent/workflow/step-labels.js +7 -3
  233. package/dist/src/agent/workflow/step-labels.js.map +1 -1
  234. package/dist/src/agent/workflow/subagent-runner.d.ts +1 -0
  235. package/dist/src/agent/workflow/subagent-runner.js +5 -1
  236. package/dist/src/agent/workflow/subagent-runner.js.map +1 -1
  237. package/dist/src/agent/workflow/workflow-child-tools.js +5 -0
  238. package/dist/src/agent/workflow/workflow-child-tools.js.map +1 -1
  239. package/dist/src/chat-commands/agent-edit.js +1 -1
  240. package/dist/src/chat-commands/agent-edit.js.map +1 -1
  241. package/dist/src/chat-commands/builtins/review.d.ts +1 -0
  242. package/dist/src/chat-commands/builtins/review.js +167 -0
  243. package/dist/src/chat-commands/builtins/review.js.map +1 -0
  244. package/dist/src/chat-commands/index.d.ts +1 -0
  245. package/dist/src/chat-commands/index.js +3 -1
  246. package/dist/src/chat-commands/index.js.map +1 -1
  247. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  248. package/dist/src/cli/commands/gateway/lifecycle.js +1 -1
  249. package/dist/src/connectors/health.d.ts +2 -1
  250. package/dist/src/connectors/health.js +59 -1
  251. package/dist/src/connectors/health.js.map +1 -1
  252. package/dist/src/connectors/install.d.ts +1 -0
  253. package/dist/src/connectors/install.js +28 -1
  254. package/dist/src/connectors/install.js.map +1 -1
  255. package/dist/src/connectors/instances.js +7 -0
  256. package/dist/src/connectors/instances.js.map +1 -1
  257. package/dist/src/connectors/materialize.js +2 -1
  258. package/dist/src/connectors/materialize.js.map +1 -1
  259. package/dist/src/connectors/registries/modelscope.js +38 -13
  260. package/dist/src/connectors/registries/modelscope.js.map +1 -1
  261. package/dist/src/connectors/types.d.ts +2 -0
  262. package/dist/src/gateway/agent-builtin-tools.d.ts +1 -1
  263. package/dist/src/gateway/agent-builtin-tools.js +3 -2
  264. package/dist/src/gateway/agent-builtin-tools.js.map +1 -1
  265. package/dist/src/gateway/chat-stream/mapper.d.ts +6 -0
  266. package/dist/src/gateway/chat-stream/mapper.js +149 -6
  267. package/dist/src/gateway/chat-stream/mapper.js.map +1 -1
  268. package/dist/src/gateway/chat-stream/protocol.d.ts +51 -1
  269. package/dist/src/gateway/dreaming-automation-reconciler.d.ts +1 -0
  270. package/dist/src/gateway/dreaming-automation-reconciler.js +62 -42
  271. package/dist/src/gateway/dreaming-automation-reconciler.js.map +1 -1
  272. package/dist/src/gateway/hono/app.js +7 -1
  273. package/dist/src/gateway/hono/app.js.map +1 -1
  274. package/dist/src/gateway/hono/routes/commands-skills.js +21 -0
  275. package/dist/src/gateway/hono/routes/commands-skills.js.map +1 -1
  276. package/dist/src/gateway/hono/routes/connectors.js +46 -2
  277. package/dist/src/gateway/hono/routes/connectors.js.map +1 -1
  278. package/dist/src/gateway/hono/routes/dreaming.js +111 -66
  279. package/dist/src/gateway/hono/routes/dreaming.js.map +1 -1
  280. package/dist/src/gateway/hono/routes/index.js +2 -0
  281. package/dist/src/gateway/hono/routes/index.js.map +1 -1
  282. package/dist/src/gateway/hono/routes/lazy-bundles.js +1 -0
  283. package/dist/src/gateway/hono/routes/lazy-bundles.js.map +1 -1
  284. package/dist/src/gateway/hono/routes/projects.js +886 -45
  285. package/dist/src/gateway/hono/routes/projects.js.map +1 -1
  286. package/dist/src/gateway/hono/routes/search.d.ts +3 -0
  287. package/dist/src/gateway/hono/routes/search.js +47 -0
  288. package/dist/src/gateway/hono/routes/search.js.map +1 -0
  289. package/dist/src/gateway/hono/routes/sessions.js +1 -0
  290. package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
  291. package/dist/src/gateway/security/dangerous-tools.js +1 -1
  292. package/dist/src/gateway/security/dangerous-tools.js.map +1 -1
  293. package/dist/src/gateway/service.js +17 -8
  294. package/dist/src/gateway/service.js.map +1 -1
  295. package/dist/src/infra/update-restart.js +1 -1
  296. package/dist/src/mcp/channel-bridge.js +1 -1
  297. package/dist/src/media/store.js +1 -1
  298. package/dist/src/media/store.js.map +1 -1
  299. package/dist/src/media/types.d.ts +1 -1
  300. package/dist/src/media/uri.js +2 -1
  301. package/dist/src/media/uri.js.map +1 -1
  302. package/dist/src/projects/index.d.ts +2 -0
  303. package/dist/src/projects/index.js +3 -1
  304. package/dist/src/projects/project-agent-suggestion.d.ts +14 -0
  305. package/dist/src/projects/project-agent-suggestion.js +19 -0
  306. package/dist/src/projects/project-agent-suggestion.js.map +1 -0
  307. package/dist/src/projects/project-kind.d.ts +13 -0
  308. package/dist/src/projects/project-kind.js +147 -0
  309. package/dist/src/projects/project-kind.js.map +1 -0
  310. package/dist/src/projects/project-service.d.ts +1 -0
  311. package/dist/src/projects/project-service.js +1 -1
  312. package/dist/src/projects/project-service.js.map +1 -1
  313. package/dist/src/projects/project-store.js +32 -8
  314. package/dist/src/projects/project-store.js.map +1 -1
  315. package/dist/src/projects/types.d.ts +1 -0
  316. package/dist/src/review/review-git.d.ts +48 -0
  317. package/dist/src/review/review-git.js +333 -0
  318. package/dist/src/review/review-git.js.map +1 -0
  319. package/dist/src/review/review-types.d.ts +22 -0
  320. package/dist/src/review/review-types.js +29 -0
  321. package/dist/src/review/review-types.js.map +1 -0
  322. package/dist/src/session/session-context-for-llm.d.ts +2 -0
  323. package/dist/src/session/session-context-for-llm.js +165 -1
  324. package/dist/src/session/session-context-for-llm.js.map +1 -1
  325. package/dist/src/session/store.js +2 -2
  326. package/dist/src/session/store.js.map +1 -1
  327. package/dist/src/storage/sqlite/migrations/021_projects_fts.sql +21 -0
  328. package/dist/src/storage/sqlite/migrations/022_work_items.sql +61 -0
  329. package/dist/src/storage/sqlite/migrations/023_work_item_attachments.sql +15 -0
  330. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  331. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  332. package/dist/src/storage/sqlite/migrations/runner.js.map +1 -1
  333. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  334. package/dist/src/storage/sqlite/schema.js +1 -1
  335. package/dist/src/storage/sqlite/schema.sql +78 -0
  336. package/dist/src/tui/backends/embedded-backend.d.ts +1 -0
  337. package/dist/src/tui/backends/embedded-backend.js +20 -0
  338. package/dist/src/tui/backends/embedded-backend.js.map +1 -1
  339. package/dist/src/tui/backends/gateway-sse-backend.d.ts +2 -0
  340. package/dist/src/tui/backends/gateway-sse-backend.js +7 -0
  341. package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -1
  342. package/dist/src/tui/components/assistant-message.js +47 -2
  343. package/dist/src/tui/components/assistant-message.js.map +1 -1
  344. package/dist/src/tui/components/tool-execution.d.ts +8 -0
  345. package/dist/src/tui/components/tool-execution.js +87 -77
  346. package/dist/src/tui/components/tool-execution.js.map +1 -1
  347. package/dist/src/tui/components/visual-truncate.d.ts +1 -0
  348. package/dist/src/tui/components/visual-truncate.js +28 -1
  349. package/dist/src/tui/components/visual-truncate.js.map +1 -1
  350. package/dist/src/tui/tool-summary.d.ts +22 -0
  351. package/dist/src/tui/tool-summary.js +212 -0
  352. package/dist/src/tui/tool-summary.js.map +1 -0
  353. package/dist/src/tui/tui-agent-events.js +25 -0
  354. package/dist/src/tui/tui-agent-events.js.map +1 -1
  355. package/dist/src/tui/tui-backend.d.ts +4 -0
  356. package/dist/src/tui/tui-commands.d.ts +1 -0
  357. package/dist/src/tui/tui-commands.js +110 -4
  358. package/dist/src/tui/tui-commands.js.map +1 -1
  359. package/dist/src/tui/tui-picker-overlay.d.ts +2 -0
  360. package/dist/src/tui/tui-picker-overlay.js +153 -1
  361. package/dist/src/tui/tui-picker-overlay.js.map +1 -1
  362. package/dist/src/tui/tui-tool-diff.d.ts +15 -0
  363. package/dist/src/tui/tui-tool-diff.js +178 -7
  364. package/dist/src/tui/tui-tool-diff.js.map +1 -1
  365. package/dist/src/tui/tui.js +16 -3
  366. package/dist/src/tui/tui.js.map +1 -1
  367. package/dist/src/work-items/agent-context.d.ts +3 -0
  368. package/dist/src/work-items/agent-context.js +39 -0
  369. package/dist/src/work-items/agent-context.js.map +1 -0
  370. package/dist/src/work-items/index.d.ts +4 -0
  371. package/dist/src/work-items/index.js +4 -0
  372. package/dist/src/work-items/types.d.ts +112 -0
  373. package/dist/src/work-items/types.js +1 -0
  374. package/dist/src/work-items/work-item-service.d.ts +33 -0
  375. package/dist/src/work-items/work-item-service.js +180 -0
  376. package/dist/src/work-items/work-item-service.js.map +1 -0
  377. package/dist/src/work-items/work-item-store.d.ts +21 -0
  378. package/dist/src/work-items/work-item-store.js +302 -0
  379. package/dist/src/work-items/work-item-store.js.map +1 -0
  380. package/dist/src/workflows/service/workflow-run-service.js +4 -3
  381. package/dist/src/workflows/service/workflow-run-service.js.map +1 -1
  382. package/dist/src/workflows/service/workflow-session-bridge.js +3 -0
  383. package/dist/src/workflows/service/workflow-session-bridge.js.map +1 -1
  384. package/package.json +1 -1
  385. package/dist/gateway/static/root/assets/Combination-HAlzriaz.js +0 -41
  386. package/dist/gateway/static/root/assets/agents-CxDSFHGx.js +0 -2
  387. package/dist/gateway/static/root/assets/app-management-settings-panel-CbEy9sR1.js +0 -1
  388. package/dist/gateway/static/root/assets/appearance-settings-CTHja6n9.js +0 -1
  389. package/dist/gateway/static/root/assets/apps-page-CyZdgqvm.js +0 -1
  390. package/dist/gateway/static/root/assets/automation-suggestion-card-KnYaP7NT.js +0 -1
  391. package/dist/gateway/static/root/assets/automations-page-8VLKM4au.js +0 -1
  392. package/dist/gateway/static/root/assets/browser-settings-page-5tPg0DcP.js +0 -8
  393. package/dist/gateway/static/root/assets/capability-presets-api-CEJ44WCU.js +0 -1
  394. package/dist/gateway/static/root/assets/capability-presets-settings-panel-BJW3aVST.js +0 -1
  395. package/dist/gateway/static/root/assets/channels-settings-CorUCTrs.js +0 -1
  396. package/dist/gateway/static/root/assets/code-editor-dZTY2dRW.js +0 -1
  397. package/dist/gateway/static/root/assets/connectors-page-VKstC3sP.js +0 -2
  398. package/dist/gateway/static/root/assets/dist-Mys7eFJ5.js +0 -1
  399. package/dist/gateway/static/root/assets/dist-wO9a2_ID.js +0 -1
  400. package/dist/gateway/static/root/assets/dreaming-settings-m1-wmz4o.js +0 -1
  401. package/dist/gateway/static/root/assets/extension-debug-page-C9qQQrBS.js +0 -1
  402. package/dist/gateway/static/root/assets/extension-page-DcBBMEGl.js +0 -1
  403. package/dist/gateway/static/root/assets/extension-settings-page-BQ-jMMP9.js +0 -1
  404. package/dist/gateway/static/root/assets/gateway-settings-hQtgVmSR.js +0 -1
  405. package/dist/gateway/static/root/assets/goal-create-dialog-BuIMDx6C.js +0 -1
  406. package/dist/gateway/static/root/assets/goal-detail-page-FEypCq74.js +0 -1
  407. package/dist/gateway/static/root/assets/goals-page-C4-2_MLG.js +0 -2
  408. package/dist/gateway/static/root/assets/goals-settings-DDrdRMxB.js +0 -1
  409. package/dist/gateway/static/root/assets/heartbeat-settings-BJVok3Sl.js +0 -1
  410. package/dist/gateway/static/root/assets/index-CBDr5xjB.css +0 -1
  411. package/dist/gateway/static/root/assets/index-T0SaNd5a.js +0 -191
  412. package/dist/gateway/static/root/assets/keyboard-shortcuts-settings-C2TuQppb.js +0 -1
  413. package/dist/gateway/static/root/assets/logs-page-DwkwPgid.js +0 -2
  414. package/dist/gateway/static/root/assets/markdown-split-DhONQ81k.js +0 -1
  415. package/dist/gateway/static/root/assets/messages-avfvyZuQ.js +0 -3
  416. package/dist/gateway/static/root/assets/models-hub-panel-huNY4A1G.js +0 -2
  417. package/dist/gateway/static/root/assets/notes-page-DdfGuEZ1.js +0 -1
  418. package/dist/gateway/static/root/assets/notes-workbench-B0bT9Hqa.js +0 -2
  419. package/dist/gateway/static/root/assets/project-detail-page-CjxhPorG.js +0 -1
  420. package/dist/gateway/static/root/assets/projects-page-C4Z5OJMF.js +0 -1
  421. package/dist/gateway/static/root/assets/remote-access-hub-CVCOPUVd.js +0 -1
  422. package/dist/gateway/static/root/assets/sessions-page-B4qExXHC.js +0 -1
  423. package/dist/gateway/static/root/assets/settings-advanced-gate-Ce8yLTBi.js +0 -1
  424. package/dist/gateway/static/root/assets/setup-status-panel-Kyc4eUf0.js +0 -1
  425. package/dist/gateway/static/root/assets/shares-settings-Bu76CMiC.js +0 -1
  426. package/dist/gateway/static/root/assets/skill-reload-api-D-CFg86j.js +0 -1
  427. package/dist/gateway/static/root/assets/skills-page-CUU4f6Ss.js +0 -2
  428. package/dist/gateway/static/root/assets/voice-api-key-field-C1MrQD8R.js +0 -1
  429. package/dist/gateway/static/root/assets/workflow-run-setup-panel-DqizIcY5.js +0 -3
  430. package/dist/gateway/static/root/assets/workflows-page-B3sVNPFk.js +0 -18
  431. package/dist/src/agent/tools/edit.d.ts +0 -14
  432. package/dist/src/agent/tools/edit.js +0 -132
  433. package/dist/src/agent/tools/edit.js.map +0 -1
  434. package/dist/src/agent/tools/shell.d.ts +0 -15
  435. package/dist/src/agent/tools/shell.js +0 -158
  436. package/dist/src/agent/tools/shell.js.map +0 -1
@@ -35,16 +35,18 @@ function createDreamingTool(deps) {
35
35
  return {
36
36
  name: "dreaming",
37
37
  label: "💤 Dreaming",
38
- description: "Inspect and maintain the dreaming promotion state.\n\nActions:\n- status: show config gates and short-term store stats\n- reset_store: clear short-term recall store (memory/.dreams/short-term-recall.json)\n- clear_lock: remove a stale promotion lock file (memory/.dreams/short-term-promotion.lock)",
38
+ description: "Inspect and maintain the dreaming promotion state.\n\nActions:\n- status: show config gates and short-term store stats\n- reset_store: clear this agent short-term recall store (.dreams/short-term-recall.json)\n- clear_lock: remove this agent stale promotion lock file (.dreams/short-term-promotion.lock)",
39
39
  parameters: DreamingSchema,
40
40
  async execute(_toolCallId, params) {
41
41
  const action = params.action;
42
- const workspaceDir = deps.getWorkspace();
43
- const resolved = resolveDreamingConfig(deps.getConfig());
44
- const storePath = path.join(workspaceDir, SHORT_TERM_RECALL_STORE_RELATIVE);
45
- const lockPath = path.join(workspaceDir, SHORT_TERM_PROMOTION_LOCK_RELATIVE);
42
+ const dreamingRoot = deps.getDreamingRoot();
43
+ const cfg = deps.getConfig();
44
+ const agentId = deps.getAgentId?.();
45
+ const resolved = resolveDreamingConfig(cfg, agentId);
46
+ const storePath = path.join(dreamingRoot, SHORT_TERM_RECALL_STORE_RELATIVE);
47
+ const lockPath = path.join(dreamingRoot, SHORT_TERM_PROMOTION_LOCK_RELATIVE);
46
48
  if (action === "status") {
47
- const { store } = await loadDreamingStore({ workspaceDir });
49
+ const { store } = await loadDreamingStore({ dreamingRoot });
48
50
  const entries = Object.values(store.entries ?? {});
49
51
  const total = entries.length;
50
52
  const promoted = entries.filter((e) => Boolean(e.promotedAt)).length;
@@ -64,7 +66,8 @@ function createDreamingTool(deps) {
64
66
  `deep.minRecallCount: ${resolved.deep.minRecallCount}`,
65
67
  `deep.limit: ${resolved.deep.limit}`,
66
68
  "",
67
- `storePath: ${SHORT_TERM_RECALL_STORE_RELATIVE}`,
69
+ `agentId: ${agentId ?? "(unknown)"}`,
70
+ `storePath: ${storePath}`,
68
71
  `entryCount: ${total}`,
69
72
  `promotedCount: ${promoted}`,
70
73
  `promotedToday: ${promotedToday}`,
@@ -76,17 +79,17 @@ function createDreamingTool(deps) {
76
79
  ].join("\n"));
77
80
  }
78
81
  if (action === "reset_store") {
79
- const { store } = await loadDreamingStore({ workspaceDir });
82
+ const { store } = await loadDreamingStore({ dreamingRoot });
80
83
  const before = Object.keys(store.entries ?? {}).length;
81
84
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
82
85
  store.entries = {};
83
86
  store.updatedAt = nowIso;
84
87
  await saveDreamingStore({
85
- workspaceDir,
88
+ dreamingRoot,
86
89
  store
87
90
  });
88
91
  log.info({
89
- workspaceDir,
92
+ dreamingRoot,
90
93
  before
91
94
  }, "Dreaming store reset");
92
95
  return textResult(`Reset short-term store. Removed ${before} entr${before === 1 ? "y" : "ies"}. Path: ${storePath}`);
@@ -1 +1 @@
1
- {"version":3,"file":"dreaming-tool.js","names":[],"sources":["../../../../src/agent/tools/dreaming-tool.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { Type } from '@sinclair/typebox';\nimport type { AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core';\n\nimport type { Config } from '../../config/schema.js';\nimport { createLogger } from '../../utils/logger.js';\nimport { resolveDreamingConfig } from '../memory/dreaming/config.js';\nimport {\n SHORT_TERM_PROMOTION_LOCK_RELATIVE,\n SHORT_TERM_RECALL_STORE_RELATIVE,\n} from '../memory/dreaming/constants.js';\nimport { loadDreamingStore, saveDreamingStore } from '../memory/dreaming/short-term-store.js';\n\nconst log = createLogger('DreamingTool');\n\nconst DreamingSchema = Type.Object({\n action: Type.Union([Type.Literal('status'), Type.Literal('reset_store'), Type.Literal('clear_lock')]),\n});\n\ntype DreamingParams = {\n action: 'status' | 'reset_store' | 'clear_lock';\n};\n\nexport interface DreamingToolDeps {\n getWorkspace: () => string;\n getConfig: () => Config | undefined;\n}\n\nfunction textResult(text: string): AgentToolResult<{}> {\n return { content: [{ type: 'text', text }], details: {} };\n}\n\nfunction safeStatMs(ms: number | undefined): number | null {\n if (typeof ms !== 'number' || !Number.isFinite(ms) || ms <= 0) return null;\n return Math.floor(ms);\n}\n\nfunction isoDay(isoLike: string | undefined): string | null {\n if (!isoLike) return null;\n const m = isoLike.match(/^(\\d{4}-\\d{2}-\\d{2})/);\n return m?.[1] ?? null;\n}\n\nexport function createDreamingTool(deps: DreamingToolDeps): AgentTool {\n return {\n name: 'dreaming',\n label: '💤 Dreaming',\n description:\n 'Inspect and maintain the dreaming promotion state.\\n\\n' +\n 'Actions:\\n' +\n '- status: show config gates and short-term store stats\\n' +\n '- reset_store: clear short-term recall store (memory/.dreams/short-term-recall.json)\\n' +\n '- clear_lock: remove a stale promotion lock file (memory/.dreams/short-term-promotion.lock)',\n parameters: DreamingSchema,\n async execute(_toolCallId, params: any): Promise<AgentToolResult<{}>> {\n const action = (params as DreamingParams).action;\n const workspaceDir = deps.getWorkspace();\n const cfg = deps.getConfig();\n const resolved = resolveDreamingConfig(cfg);\n\n const storePath = path.join(workspaceDir, SHORT_TERM_RECALL_STORE_RELATIVE);\n const lockPath = path.join(workspaceDir, SHORT_TERM_PROMOTION_LOCK_RELATIVE);\n\n if (action === 'status') {\n const { store } = await loadDreamingStore({ workspaceDir });\n const entries = Object.values(store.entries ?? {});\n const total = entries.length;\n const promoted = entries.filter((e) => Boolean(e.promotedAt)).length;\n const today = new Date().toISOString().slice(0, 10);\n const promotedToday = entries.filter((e) => isoDay(e.promotedAt) === today).length;\n const lastPromotedAt = entries\n .map((e) => e.promotedAt)\n .filter((x): x is string => typeof x === 'string' && x.length > 0)\n .sort()\n .at(-1);\n\n const lockStat = await fs.stat(lockPath).catch(() => null);\n const lockAgeMs =\n lockStat && safeStatMs(lockStat.mtimeMs) ? Date.now() - Math.floor(lockStat.mtimeMs) : null;\n\n const lines = [\n 'Dreaming status',\n '',\n `enabled: ${resolved.enabled ? 'true' : 'false'}`,\n `deep.enabled: ${resolved.deep.enabled ? 'true' : 'false'}`,\n `cron.frequency: ${resolved.frequency}`,\n `cron.timezone: ${resolved.timezone ?? '(default)'}`,\n `deep.minScore: ${resolved.deep.minScore}`,\n `deep.minRecallCount: ${resolved.deep.minRecallCount}`,\n `deep.limit: ${resolved.deep.limit}`,\n '',\n `storePath: ${SHORT_TERM_RECALL_STORE_RELATIVE}`,\n `entryCount: ${total}`,\n `promotedCount: ${promoted}`,\n `promotedToday: ${promotedToday}`,\n `lastPromotedAt: ${lastPromotedAt ?? '(none)'}`,\n '',\n `lockPath: ${SHORT_TERM_PROMOTION_LOCK_RELATIVE}`,\n `lockPresent: ${lockStat ? 'true' : 'false'}`,\n `lockAgeMs: ${lockAgeMs ?? '(n/a)'}`,\n ];\n return textResult(lines.join('\\n'));\n }\n\n if (action === 'reset_store') {\n const { store } = await loadDreamingStore({ workspaceDir });\n const before = Object.keys(store.entries ?? {}).length;\n const nowIso = new Date().toISOString();\n store.entries = {};\n store.updatedAt = nowIso;\n await saveDreamingStore({ workspaceDir, store });\n log.info({ workspaceDir, before }, 'Dreaming store reset');\n return textResult(\n `Reset short-term store. Removed ${before} entr${before === 1 ? 'y' : 'ies'}. Path: ${storePath}`,\n );\n }\n\n await fs.unlink(lockPath).catch(() => {});\n return textResult(`Cleared lock (if present). Path: ${lockPath}`);\n },\n } as any;\n}\n\n"],"mappings":"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"}
1
+ {"version":3,"file":"dreaming-tool.js","names":[],"sources":["../../../../src/agent/tools/dreaming-tool.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { Type } from '@sinclair/typebox';\nimport type { AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core';\n\nimport type { Config } from '../../config/schema.js';\nimport { createLogger } from '../../utils/logger.js';\nimport { resolveDreamingConfig } from '../memory/dreaming/config.js';\nimport {\n SHORT_TERM_PROMOTION_LOCK_RELATIVE,\n SHORT_TERM_RECALL_STORE_RELATIVE,\n} from '../memory/dreaming/constants.js';\nimport { loadDreamingStore, saveDreamingStore } from '../memory/dreaming/short-term-store.js';\n\nconst log = createLogger('DreamingTool');\n\nconst DreamingSchema = Type.Object({\n action: Type.Union([Type.Literal('status'), Type.Literal('reset_store'), Type.Literal('clear_lock')]),\n});\n\ntype DreamingParams = {\n action: 'status' | 'reset_store' | 'clear_lock';\n};\n\nexport interface DreamingToolDeps {\n getWorkspace: () => string;\n getDreamingRoot: () => string;\n getConfig: () => Config | undefined;\n getAgentId?: () => string | undefined;\n}\n\nfunction textResult(text: string): AgentToolResult<{}> {\n return { content: [{ type: 'text', text }], details: {} };\n}\n\nfunction safeStatMs(ms: number | undefined): number | null {\n if (typeof ms !== 'number' || !Number.isFinite(ms) || ms <= 0) return null;\n return Math.floor(ms);\n}\n\nfunction isoDay(isoLike: string | undefined): string | null {\n if (!isoLike) return null;\n const m = isoLike.match(/^(\\d{4}-\\d{2}-\\d{2})/);\n return m?.[1] ?? null;\n}\n\nexport function createDreamingTool(deps: DreamingToolDeps): AgentTool {\n return {\n name: 'dreaming',\n label: '💤 Dreaming',\n description:\n 'Inspect and maintain the dreaming promotion state.\\n\\n' +\n 'Actions:\\n' +\n '- status: show config gates and short-term store stats\\n' +\n '- reset_store: clear this agent short-term recall store (.dreams/short-term-recall.json)\\n' +\n '- clear_lock: remove this agent stale promotion lock file (.dreams/short-term-promotion.lock)',\n parameters: DreamingSchema,\n async execute(_toolCallId, params: any): Promise<AgentToolResult<{}>> {\n const action = (params as DreamingParams).action;\n const dreamingRoot = deps.getDreamingRoot();\n const cfg = deps.getConfig();\n const agentId = deps.getAgentId?.();\n const resolved = resolveDreamingConfig(cfg, agentId);\n\n const storePath = path.join(dreamingRoot, SHORT_TERM_RECALL_STORE_RELATIVE);\n const lockPath = path.join(dreamingRoot, SHORT_TERM_PROMOTION_LOCK_RELATIVE);\n\n if (action === 'status') {\n const { store } = await loadDreamingStore({ dreamingRoot });\n const entries = Object.values(store.entries ?? {});\n const total = entries.length;\n const promoted = entries.filter((e) => Boolean(e.promotedAt)).length;\n const today = new Date().toISOString().slice(0, 10);\n const promotedToday = entries.filter((e) => isoDay(e.promotedAt) === today).length;\n const lastPromotedAt = entries\n .map((e) => e.promotedAt)\n .filter((x): x is string => typeof x === 'string' && x.length > 0)\n .sort()\n .at(-1);\n\n const lockStat = await fs.stat(lockPath).catch(() => null);\n const lockAgeMs =\n lockStat && safeStatMs(lockStat.mtimeMs) ? Date.now() - Math.floor(lockStat.mtimeMs) : null;\n\n const lines = [\n 'Dreaming status',\n '',\n `enabled: ${resolved.enabled ? 'true' : 'false'}`,\n `deep.enabled: ${resolved.deep.enabled ? 'true' : 'false'}`,\n `cron.frequency: ${resolved.frequency}`,\n `cron.timezone: ${resolved.timezone ?? '(default)'}`,\n `deep.minScore: ${resolved.deep.minScore}`,\n `deep.minRecallCount: ${resolved.deep.minRecallCount}`,\n `deep.limit: ${resolved.deep.limit}`,\n '',\n `agentId: ${agentId ?? '(unknown)'}`,\n `storePath: ${storePath}`,\n `entryCount: ${total}`,\n `promotedCount: ${promoted}`,\n `promotedToday: ${promotedToday}`,\n `lastPromotedAt: ${lastPromotedAt ?? '(none)'}`,\n '',\n `lockPath: ${SHORT_TERM_PROMOTION_LOCK_RELATIVE}`,\n `lockPresent: ${lockStat ? 'true' : 'false'}`,\n `lockAgeMs: ${lockAgeMs ?? '(n/a)'}`,\n ];\n return textResult(lines.join('\\n'));\n }\n\n if (action === 'reset_store') {\n const { store } = await loadDreamingStore({ dreamingRoot });\n const before = Object.keys(store.entries ?? {}).length;\n const nowIso = new Date().toISOString();\n store.entries = {};\n store.updatedAt = nowIso;\n await saveDreamingStore({ dreamingRoot, store });\n log.info({ dreamingRoot, before }, 'Dreaming store reset');\n return textResult(\n `Reset short-term store. Removed ${before} entr${before === 1 ? 'y' : 'ies'}. Path: ${storePath}`,\n );\n }\n\n await fs.unlink(lockPath).catch(() => {});\n return textResult(`Cleared lock (if present). Path: ${lockPath}`);\n },\n } as any;\n}\n"],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import type { AgentTool } from '@earendil-works/pi-agent-core';
2
+ import type { GoalEvidenceRecordInput } from './goal-evidence-recorder.js';
3
+ export interface ExecCommandDetails {
4
+ command: string;
5
+ cwd: string;
6
+ status: 'success' | 'failed' | 'timed_out' | 'blocked';
7
+ exitCode: number | null;
8
+ durationMs: number;
9
+ timedOut: boolean;
10
+ truncated: boolean;
11
+ truncatedBy?: 'lines' | 'bytes' | null;
12
+ outputBytes: number;
13
+ stdout: string;
14
+ stderr: string;
15
+ aggregatedOutput: string;
16
+ failureHint?: string;
17
+ }
18
+ export interface ExecCommandUpdateDetails {
19
+ kind: 'command_output_delta';
20
+ command: string;
21
+ cwd: string;
22
+ stream: 'stdout' | 'stderr';
23
+ delta: string;
24
+ }
25
+ export interface CreateExecCommandToolOptions {
26
+ /** Env var names allowed through prepareSafeToolEnv even if they match secret heuristics. */
27
+ getSkillPassthroughEnvVarNames?: () => string[];
28
+ recordGoalEvidence?: (input: GoalEvidenceRecordInput) => Promise<void> | void;
29
+ }
30
+ export declare function createExecCommandTool(workspaceCwd: string, options?: CreateExecCommandToolOptions): AgentTool;
@@ -0,0 +1,228 @@
1
+ import { formatSize, truncateTail } from "./truncate.js";
2
+ import { evaluateExecPolicy } from "../sandbox/exec-policy.js";
3
+ import { resolve } from "node:path";
4
+ import { spawn } from "node:child_process";
5
+ import { Type } from "@sinclair/typebox";
6
+ //#region src/agent/tools/exec-command.ts
7
+ const MAX_COMMAND_TIMEOUT_MS = 3e5;
8
+ const DEFAULT_TIMEOUT_MS = 12e4;
9
+ const DEFAULT_MAX_OUTPUT_CHARS = 5e4;
10
+ const STREAM_DELTA_MAX_CHARS = 16e3;
11
+ const ExecCommandSchema = Type.Object({
12
+ cmd: Type.String({ description: "Shell command to execute." }),
13
+ cwd: Type.Optional(Type.String({ description: "Working directory. Relative paths are resolved under the agent workspace." })),
14
+ timeoutMs: Type.Optional(Type.Number({ description: "Command timeout in milliseconds. Defaults to 120000 and is capped at 300000." })),
15
+ maxOutputChars: Type.Optional(Type.Number({ description: "Maximum characters returned to the model. UI streaming still receives output deltas." }))
16
+ });
17
+ function clampTimeoutMs(raw) {
18
+ return Math.min(MAX_COMMAND_TIMEOUT_MS, Math.max(1e3, Math.floor(typeof raw === "number" && Number.isFinite(raw) ? raw : DEFAULT_TIMEOUT_MS)));
19
+ }
20
+ function clampMaxOutputChars(raw) {
21
+ return Math.max(1e3, Math.min(5e5, Math.floor(typeof raw === "number" && Number.isFinite(raw) ? raw : DEFAULT_MAX_OUTPUT_CHARS)));
22
+ }
23
+ function isLikelyTestCommand(command) {
24
+ return /\b(test|vitest|jest|pytest|go test|cargo test|npm test|pnpm test|yarn test|typecheck|type-check|lint)\b/i.test(command);
25
+ }
26
+ function commandStatus(exitCode, timedOut) {
27
+ if (timedOut) return "timed_out";
28
+ if (exitCode === 0) return "success";
29
+ return "failed";
30
+ }
31
+ function commandFailureHint(details) {
32
+ if (details.status === "success") return void 0;
33
+ if (details.status === "timed_out") return "The command timed out. Re-run a narrower command, increase timeoutMs if the long run is expected, or inspect partial output before retrying.";
34
+ if (details.exitCode !== 0 && details.stderr.trim()) return "The command failed. Inspect stderr first, fix the underlying issue, then re-run the narrowest relevant verification command.";
35
+ if (details.exitCode !== 0 && details.stdout.trim()) return "The command failed. Inspect stdout for the failure location, then re-run the narrowest relevant verification command.";
36
+ return "The command failed without output. Verify the command, cwd, and required dependencies before retrying.";
37
+ }
38
+ function formatOutputForModel(details, maxOutputChars) {
39
+ let prefix = "";
40
+ if (details.timedOut) prefix += `Command timed out after ${Math.round(details.durationMs / 1e3)}s\n`;
41
+ if (details.exitCode !== 0 && details.exitCode != null) prefix += `Command exited with code ${details.exitCode}\n`;
42
+ const truncation = truncateTail(details.aggregatedOutput, { maxBytes: Math.min(maxOutputChars, DEFAULT_MAX_OUTPUT_CHARS) });
43
+ let text = `${prefix}${truncation.content}`.trimEnd();
44
+ if (!text) text = "(no output)";
45
+ if (truncation.truncated) text += `\n\n[Output truncated: ${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)}]`;
46
+ if (details.failureHint) text += `\n\nNext action: ${details.failureHint}`;
47
+ return {
48
+ text,
49
+ truncated: truncation.truncated,
50
+ truncatedBy: truncation.truncatedBy,
51
+ outputBytes: truncation.outputBytes
52
+ };
53
+ }
54
+ function createExecCommandTool(workspaceCwd, options) {
55
+ return {
56
+ name: "exec_command",
57
+ description: [
58
+ "Run shell commands for code inspection, builds, tests, type checks, and verification.",
59
+ "Use apply_patch for file edits; do not edit files by shell redirection unless explicitly necessary.",
60
+ "Relative cwd values resolve under the current agent workspace."
61
+ ].join(" "),
62
+ parameters: ExecCommandSchema,
63
+ label: "Run Command",
64
+ mutationScope: "unknown",
65
+ supportsParallel: false,
66
+ idempotent: false,
67
+ finalGuardRelevant: true,
68
+ async execute(_toolCallId, params, signal, onUpdate) {
69
+ const command = (params.cmd ?? params.command ?? "").trim();
70
+ if (!command) return {
71
+ content: [{
72
+ type: "text",
73
+ text: "Error: cmd is required"
74
+ }],
75
+ details: {
76
+ command,
77
+ cwd: workspaceCwd,
78
+ status: "failed",
79
+ exitCode: null,
80
+ durationMs: 0,
81
+ timedOut: false,
82
+ truncated: false,
83
+ outputBytes: 0,
84
+ stdout: "",
85
+ stderr: "cmd is required",
86
+ aggregatedOutput: "cmd is required",
87
+ failureHint: "Provide a non-empty cmd value."
88
+ }
89
+ };
90
+ const requestedCwd = params.cwd?.trim() ? resolve(workspaceCwd, params.cwd.trim()) : workspaceCwd;
91
+ const policy = evaluateExecPolicy({
92
+ command,
93
+ cwd: requestedCwd,
94
+ allowedEnvVars: options?.getSkillPassthroughEnvVarNames?.() ?? []
95
+ });
96
+ if (!policy.allowed) {
97
+ const text = `Sandbox blocked command: ${policy.reason}`;
98
+ return {
99
+ content: [{
100
+ type: "text",
101
+ text
102
+ }],
103
+ details: {
104
+ command,
105
+ cwd: requestedCwd,
106
+ status: "blocked",
107
+ exitCode: null,
108
+ durationMs: 0,
109
+ timedOut: false,
110
+ truncated: false,
111
+ outputBytes: Buffer.byteLength(text),
112
+ stdout: "",
113
+ stderr: text,
114
+ aggregatedOutput: text,
115
+ failureHint: "Use a narrower workspace-safe command or inspect files with read/search tools instead."
116
+ }
117
+ };
118
+ }
119
+ const timeoutMs = Math.min(clampTimeoutMs(params.timeoutMs), policy.timeoutMs);
120
+ const maxOutputChars = clampMaxOutputChars(params.maxOutputChars);
121
+ const startTime = Date.now();
122
+ let stdout = "";
123
+ let stderr = "";
124
+ let aggregatedOutput = "";
125
+ let timedOut = false;
126
+ let settled = false;
127
+ return new Promise((resolveTool) => {
128
+ const proc = spawn(command, [], {
129
+ shell: true,
130
+ cwd: policy.effectiveCwd,
131
+ env: {
132
+ ...policy.sanitizedEnv,
133
+ COLUMNS: "200"
134
+ }
135
+ });
136
+ const publishDelta = (stream, raw) => {
137
+ const text = raw.toString();
138
+ if (!text) return;
139
+ if (stream === "stdout") stdout += text;
140
+ else stderr += text;
141
+ aggregatedOutput += text;
142
+ const delta = text.length > STREAM_DELTA_MAX_CHARS ? `${text.slice(0, STREAM_DELTA_MAX_CHARS)}\n[stream delta truncated]` : text;
143
+ onUpdate?.({
144
+ content: [],
145
+ details: {
146
+ kind: "command_output_delta",
147
+ command,
148
+ cwd: policy.effectiveCwd,
149
+ stream,
150
+ delta
151
+ }
152
+ });
153
+ };
154
+ const finish = (exitCode, errorText) => {
155
+ if (settled) return;
156
+ settled = true;
157
+ clearTimeout(timeout);
158
+ signal?.removeEventListener("abort", abort);
159
+ if (errorText) {
160
+ stderr += errorText;
161
+ aggregatedOutput += errorText;
162
+ }
163
+ const durationMs = Date.now() - startTime;
164
+ const baseDetails = {
165
+ command,
166
+ cwd: policy.effectiveCwd,
167
+ status: commandStatus(exitCode, timedOut),
168
+ exitCode,
169
+ durationMs,
170
+ timedOut,
171
+ truncated: false,
172
+ outputBytes: Buffer.byteLength(aggregatedOutput),
173
+ stdout,
174
+ stderr,
175
+ aggregatedOutput
176
+ };
177
+ baseDetails.failureHint = commandFailureHint(baseDetails);
178
+ const formatted = formatOutputForModel(baseDetails, maxOutputChars);
179
+ const details = {
180
+ ...baseDetails,
181
+ truncated: formatted.truncated,
182
+ truncatedBy: formatted.truncatedBy,
183
+ outputBytes: formatted.outputBytes
184
+ };
185
+ const result = {
186
+ content: [{
187
+ type: "text",
188
+ text: formatted.text
189
+ }],
190
+ details
191
+ };
192
+ Promise.resolve(options?.recordGoalEvidence?.({
193
+ kind: timedOut || exitCode !== 0 ? "command" : isLikelyTestCommand(command) ? "test" : "command",
194
+ title: `Command: ${command.slice(0, 120)}`,
195
+ summary: formatted.text.slice(0, 2e3),
196
+ data: {
197
+ command,
198
+ cwd: policy.effectiveCwd,
199
+ exitCode,
200
+ durationMs,
201
+ timedOut,
202
+ truncated: details.truncated,
203
+ outputBytes: details.outputBytes
204
+ }
205
+ })).finally(() => resolveTool(result));
206
+ };
207
+ const abort = () => {
208
+ timedOut = true;
209
+ proc.kill("SIGKILL");
210
+ };
211
+ const timeout = setTimeout(() => {
212
+ timedOut = true;
213
+ proc.kill("SIGKILL");
214
+ }, timeoutMs);
215
+ if (signal?.aborted) abort();
216
+ else signal?.addEventListener("abort", abort, { once: true });
217
+ proc.stdout?.on("data", (data) => publishDelta("stdout", data));
218
+ proc.stderr?.on("data", (data) => publishDelta("stderr", data));
219
+ proc.on("error", (err) => finish(null, `Error: ${err.message}`));
220
+ proc.on("close", (code) => finish(code));
221
+ });
222
+ }
223
+ };
224
+ }
225
+ //#endregion
226
+ export { createExecCommandTool };
227
+
228
+ //# sourceMappingURL=exec-command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exec-command.js","names":[],"sources":["../../../../src/agent/tools/exec-command.ts"],"sourcesContent":["import { Type } from '@sinclair/typebox';\nimport type {\n AgentTool,\n AgentToolResult,\n AgentToolUpdateCallback,\n} from '@earendil-works/pi-agent-core';\nimport { spawn } from 'node:child_process';\nimport { resolve } from 'node:path';\n\nimport { evaluateExecPolicy } from '../sandbox/exec-policy.js';\nimport { formatSize, truncateTail } from './truncate.js';\nimport type { GoalEvidenceRecordInput } from './goal-evidence-recorder.js';\n\nconst MAX_COMMAND_TIMEOUT_MS = 300_000;\nconst DEFAULT_TIMEOUT_MS = 120_000;\nconst DEFAULT_MAX_OUTPUT_CHARS = 50_000;\nconst STREAM_DELTA_MAX_CHARS = 16_000;\n\nconst ExecCommandSchema = Type.Object({\n cmd: Type.String({ description: 'Shell command to execute.' }),\n cwd: Type.Optional(\n Type.String({\n description: 'Working directory. Relative paths are resolved under the agent workspace.',\n }),\n ),\n timeoutMs: Type.Optional(\n Type.Number({\n description: 'Command timeout in milliseconds. Defaults to 120000 and is capped at 300000.',\n }),\n ),\n maxOutputChars: Type.Optional(\n Type.Number({\n description: 'Maximum characters returned to the model. UI streaming still receives output deltas.',\n }),\n ),\n});\n\nexport interface ExecCommandDetails {\n command: string;\n cwd: string;\n status: 'success' | 'failed' | 'timed_out' | 'blocked';\n exitCode: number | null;\n durationMs: number;\n timedOut: boolean;\n truncated: boolean;\n truncatedBy?: 'lines' | 'bytes' | null;\n outputBytes: number;\n stdout: string;\n stderr: string;\n aggregatedOutput: string;\n failureHint?: string;\n}\n\nexport interface ExecCommandUpdateDetails {\n kind: 'command_output_delta';\n command: string;\n cwd: string;\n stream: 'stdout' | 'stderr';\n delta: string;\n}\n\nexport interface CreateExecCommandToolOptions {\n /** Env var names allowed through prepareSafeToolEnv even if they match secret heuristics. */\n getSkillPassthroughEnvVarNames?: () => string[];\n recordGoalEvidence?: (input: GoalEvidenceRecordInput) => Promise<void> | void;\n}\n\ntype ExecCommandParams = {\n cmd?: string;\n command?: string;\n cwd?: string;\n timeoutMs?: number;\n maxOutputChars?: number;\n};\n\nfunction clampTimeoutMs(raw: unknown): number {\n const n = typeof raw === 'number' && Number.isFinite(raw) ? raw : DEFAULT_TIMEOUT_MS;\n return Math.min(MAX_COMMAND_TIMEOUT_MS, Math.max(1_000, Math.floor(n)));\n}\n\nfunction clampMaxOutputChars(raw: unknown): number {\n const n = typeof raw === 'number' && Number.isFinite(raw) ? raw : DEFAULT_MAX_OUTPUT_CHARS;\n return Math.max(1_000, Math.min(500_000, Math.floor(n)));\n}\n\nfunction isLikelyTestCommand(command: string): boolean {\n return /\\b(test|vitest|jest|pytest|go test|cargo test|npm test|pnpm test|yarn test|typecheck|type-check|lint)\\b/i.test(command);\n}\n\nfunction commandStatus(exitCode: number | null, timedOut: boolean): ExecCommandDetails['status'] {\n if (timedOut) return 'timed_out';\n if (exitCode === 0) return 'success';\n return 'failed';\n}\n\nfunction commandFailureHint(details: Pick<ExecCommandDetails, 'status' | 'exitCode' | 'timedOut' | 'stderr' | 'stdout'>): string | undefined {\n if (details.status === 'success') return undefined;\n if (details.status === 'timed_out') {\n return 'The command timed out. Re-run a narrower command, increase timeoutMs if the long run is expected, or inspect partial output before retrying.';\n }\n if (details.exitCode !== 0 && details.stderr.trim()) {\n return 'The command failed. Inspect stderr first, fix the underlying issue, then re-run the narrowest relevant verification command.';\n }\n if (details.exitCode !== 0 && details.stdout.trim()) {\n return 'The command failed. Inspect stdout for the failure location, then re-run the narrowest relevant verification command.';\n }\n return 'The command failed without output. Verify the command, cwd, and required dependencies before retrying.';\n}\n\nfunction formatOutputForModel(\n details: ExecCommandDetails,\n maxOutputChars: number,\n): { text: string; truncated: boolean; truncatedBy?: 'lines' | 'bytes' | null; outputBytes: number } {\n let prefix = '';\n if (details.timedOut) {\n prefix += `Command timed out after ${Math.round(details.durationMs / 1000)}s\\n`;\n }\n if (details.exitCode !== 0 && details.exitCode != null) {\n prefix += `Command exited with code ${details.exitCode}\\n`;\n }\n\n const truncation = truncateTail(details.aggregatedOutput, {\n maxBytes: Math.min(maxOutputChars, DEFAULT_MAX_OUTPUT_CHARS),\n });\n let text = `${prefix}${truncation.content}`.trimEnd();\n if (!text) text = '(no output)';\n if (truncation.truncated) {\n text += `\\n\\n[Output truncated: ${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)}]`;\n }\n if (details.failureHint) {\n text += `\\n\\nNext action: ${details.failureHint}`;\n }\n return {\n text,\n truncated: truncation.truncated,\n truncatedBy: truncation.truncatedBy,\n outputBytes: truncation.outputBytes,\n };\n}\n\nexport function createExecCommandTool(\n workspaceCwd: string,\n options?: CreateExecCommandToolOptions,\n): AgentTool {\n return {\n name: 'exec_command',\n description: [\n 'Run shell commands for code inspection, builds, tests, type checks, and verification.',\n 'Use apply_patch for file edits; do not edit files by shell redirection unless explicitly necessary.',\n 'Relative cwd values resolve under the current agent workspace.',\n ].join(' '),\n parameters: ExecCommandSchema,\n label: 'Run Command',\n mutationScope: 'unknown',\n supportsParallel: false,\n idempotent: false,\n finalGuardRelevant: true,\n\n async execute(\n _toolCallId: string,\n params: ExecCommandParams,\n signal?: AbortSignal,\n onUpdate?: AgentToolUpdateCallback<ExecCommandUpdateDetails>,\n ): Promise<AgentToolResult<ExecCommandDetails>> {\n const command = (params.cmd ?? params.command ?? '').trim();\n if (!command) {\n return {\n content: [{ type: 'text', text: 'Error: cmd is required' }],\n details: {\n command,\n cwd: workspaceCwd,\n status: 'failed',\n exitCode: null,\n durationMs: 0,\n timedOut: false,\n truncated: false,\n outputBytes: 0,\n stdout: '',\n stderr: 'cmd is required',\n aggregatedOutput: 'cmd is required',\n failureHint: 'Provide a non-empty cmd value.',\n },\n };\n }\n\n const requestedCwd = params.cwd?.trim()\n ? resolve(workspaceCwd, params.cwd.trim())\n : workspaceCwd;\n const passthroughNames = options?.getSkillPassthroughEnvVarNames?.() ?? [];\n const policy = evaluateExecPolicy({\n command,\n cwd: requestedCwd,\n allowedEnvVars: passthroughNames,\n });\n if (!policy.allowed) {\n const text = `Sandbox blocked command: ${policy.reason}`;\n return {\n content: [{ type: 'text', text }],\n details: {\n command,\n cwd: requestedCwd,\n status: 'blocked',\n exitCode: null,\n durationMs: 0,\n timedOut: false,\n truncated: false,\n outputBytes: Buffer.byteLength(text),\n stdout: '',\n stderr: text,\n aggregatedOutput: text,\n failureHint: 'Use a narrower workspace-safe command or inspect files with read/search tools instead.',\n },\n };\n }\n\n const timeoutMs = Math.min(clampTimeoutMs(params.timeoutMs), policy.timeoutMs);\n const maxOutputChars = clampMaxOutputChars(params.maxOutputChars);\n const startTime = Date.now();\n let stdout = '';\n let stderr = '';\n let aggregatedOutput = '';\n let timedOut = false;\n let settled = false;\n\n return new Promise((resolveTool) => {\n const proc = spawn(command, [], {\n shell: true,\n cwd: policy.effectiveCwd,\n env: {\n ...policy.sanitizedEnv,\n COLUMNS: '200',\n },\n });\n\n const publishDelta = (stream: 'stdout' | 'stderr', raw: Buffer | string) => {\n const text = raw.toString();\n if (!text) return;\n if (stream === 'stdout') stdout += text;\n else stderr += text;\n aggregatedOutput += text;\n\n const delta = text.length > STREAM_DELTA_MAX_CHARS\n ? `${text.slice(0, STREAM_DELTA_MAX_CHARS)}\\n[stream delta truncated]`\n : text;\n onUpdate?.({\n content: [],\n details: {\n kind: 'command_output_delta',\n command,\n cwd: policy.effectiveCwd,\n stream,\n delta,\n },\n });\n };\n\n const finish = (\n exitCode: number | null,\n errorText?: string,\n ) => {\n if (settled) return;\n settled = true;\n clearTimeout(timeout);\n signal?.removeEventListener('abort', abort);\n if (errorText) {\n stderr += errorText;\n aggregatedOutput += errorText;\n }\n\n const durationMs = Date.now() - startTime;\n const baseDetails: ExecCommandDetails = {\n command,\n cwd: policy.effectiveCwd,\n status: commandStatus(exitCode, timedOut),\n exitCode,\n durationMs,\n timedOut,\n truncated: false,\n outputBytes: Buffer.byteLength(aggregatedOutput),\n stdout,\n stderr,\n aggregatedOutput,\n };\n baseDetails.failureHint = commandFailureHint(baseDetails);\n const formatted = formatOutputForModel(baseDetails, maxOutputChars);\n const details: ExecCommandDetails = {\n ...baseDetails,\n truncated: formatted.truncated,\n truncatedBy: formatted.truncatedBy,\n outputBytes: formatted.outputBytes,\n };\n\n const result: AgentToolResult<ExecCommandDetails> = {\n content: [{ type: 'text', text: formatted.text }],\n details,\n };\n\n void Promise.resolve(options?.recordGoalEvidence?.({\n kind: timedOut || exitCode !== 0 ? 'command' : isLikelyTestCommand(command) ? 'test' : 'command',\n title: `Command: ${command.slice(0, 120)}`,\n summary: formatted.text.slice(0, 2000),\n data: {\n command,\n cwd: policy.effectiveCwd,\n exitCode,\n durationMs,\n timedOut,\n truncated: details.truncated,\n outputBytes: details.outputBytes,\n },\n })).finally(() => resolveTool(result));\n };\n\n const abort = () => {\n timedOut = true;\n proc.kill('SIGKILL');\n };\n\n const timeout = setTimeout(() => {\n timedOut = true;\n proc.kill('SIGKILL');\n }, timeoutMs);\n\n if (signal?.aborted) {\n abort();\n } else {\n signal?.addEventListener('abort', abort, { once: true });\n }\n\n proc.stdout?.on('data', (data) => publishDelta('stdout', data));\n proc.stderr?.on('data', (data) => publishDelta('stderr', data));\n proc.on('error', (err) => finish(null, `Error: ${err.message}`));\n proc.on('close', (code) => finish(code));\n });\n },\n } as any;\n}\n"],"mappings":"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"}
@@ -10,7 +10,7 @@ const DEFAULT_TIMEOUT_SEC = 1800;
10
10
  /** Hard cap for `timeout` parameter (4 hours). */
11
11
  const MAX_TIMEOUT_SEC = 14400;
12
12
  const ExecuteCodeSchema = Type.Object({
13
- code: Type.String({ description: "JavaScript to run. Exposed as `tools` (async functions) and `console`:\n await tools.web_search(query, count?)\n await tools.web_fetch(url, maxChars?)\n await tools.read_file(path, limit?)\n await tools.write_file(path, content)\n await tools.grep(pattern, { path?, glob?, ignoreCase?, literal?, context?, limit? })\n await tools.find(pattern, { path?, limit? })\n await tools.shell(command)\n\nUse console.log() for output; combined stdout/stderr is returned." }),
13
+ code: Type.String({ description: "JavaScript to run. Exposed as `tools` (async functions) and `console`:\n await tools.web_search(query, count?)\n await tools.web_fetch(url, maxChars?)\n await tools.read_file(path, limit?)\n await tools.write_file(path, content)\n await tools.grep(pattern, { path?, glob?, ignoreCase?, literal?, context?, limit? })\n await tools.find(pattern, { path?, limit? })\n await tools.exec_command(cmd, cwd?)\n\nUse console.log() for output; combined stdout/stderr is returned." }),
14
14
  timeout: Type.Optional(Type.Number({
15
15
  description: "Execution timeout in seconds (default: 3600 = 60m, max: 14400 = 4h)",
16
16
  default: DEFAULT_TIMEOUT_SEC
@@ -23,7 +23,7 @@ const SANDBOX_ALLOWED_TOOLS = new Set([
23
23
  "write_file",
24
24
  "grep",
25
25
  "find",
26
- "shell",
26
+ "exec_command",
27
27
  "skills_list",
28
28
  "skill_view"
29
29
  ]);
@@ -99,7 +99,10 @@ function createToolsApi(getMap, signal) {
99
99
  ...typeof o.limit === "number" && Number.isFinite(o.limit) ? { limit: o.limit } : {}
100
100
  });
101
101
  },
102
- shell: async (command) => run("shell", { command: String(command ?? "") })
102
+ exec_command: async (cmd, cwd) => run("exec_command", {
103
+ cmd: String(cmd ?? ""),
104
+ ...typeof cwd === "string" && cwd.trim() ? { cwd } : {}
105
+ })
103
106
  };
104
107
  }
105
108
  async function runSandboxedScript(code, sandbox, timeoutMs, signal) {
@@ -128,7 +131,7 @@ function createExecuteCodeTool(deps) {
128
131
  return {
129
132
  name: "execute_code",
130
133
  label: "⚡ Execute Code",
131
- description: "Run sandboxed JavaScript that calls a subset of tools via `tools.*` (batch work in one step).\n\nOnly stdout/stderr from `console` plus tool return text (as strings) are visible — not full tool JSON.\n\nWHEN TO USE: loops over files/URLs, simple branching between tool calls.\nWHEN NOT TO USE: single tool calls; tasks needing full tool schemas or disallowed tools.\n\nAPI: `await tools.web_search(q, count?)`, `web_fetch(url, maxChars?)`, `read_file(path, limit?)`, `write_file(path, content)`, `grep(pattern, opts?)`, `find(glob, opts?)`, `shell(command)`. Use `console.log` for output.",
134
+ description: "Run sandboxed JavaScript that calls a subset of tools via `tools.*` (batch work in one step).\n\nOnly stdout/stderr from `console` plus tool return text (as strings) are visible — not full tool JSON.\n\nWHEN TO USE: loops over files/URLs, simple branching between tool calls.\nWHEN NOT TO USE: single tool calls; tasks needing full tool schemas or disallowed tools.\n\nAPI: `await tools.web_search(q, count?)`, `web_fetch(url, maxChars?)`, `read_file(path, limit?)`, `write_file(path, content)`, `grep(pattern, opts?)`, `find(glob, opts?)`, `exec_command(cmd, cwd?)`. Use `console.log` for output.",
132
135
  parameters: ExecuteCodeSchema,
133
136
  async execute(_toolCallId, params, signal) {
134
137
  const p = params;
@@ -1 +1 @@
1
- {"version":3,"file":"execute-code-tool.js","names":[],"sources":["../../../../src/agent/tools/execute-code-tool.ts"],"sourcesContent":["/**\n * Programmatic tool calling (PTC): run sandboxed JS that invokes a subset of agent tools.\n * vm is not a strong security boundary; only trusted models should use this tool.\n */\n\nimport { Script, createContext } from 'node:vm';\n\nimport { Type } from '@sinclair/typebox';\nimport type { AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core';\n\n/** Default script wall time when `timeout` omitted (30 minutes). */\nconst DEFAULT_TIMEOUT_SEC = 30 * 60;\n/** Hard cap for `timeout` parameter (4 hours). */\nconst MAX_TIMEOUT_SEC = 4 * 60 * 60;\n\nconst ExecuteCodeSchema = Type.Object({\n code: Type.String({\n description:\n 'JavaScript to run. Exposed as `tools` (async functions) and `console`:\\n' +\n ' await tools.web_search(query, count?)\\n' +\n ' await tools.web_fetch(url, maxChars?)\\n' +\n ' await tools.read_file(path, limit?)\\n' +\n ' await tools.write_file(path, content)\\n' +\n ' await tools.grep(pattern, { path?, glob?, ignoreCase?, literal?, context?, limit? })\\n' +\n ' await tools.find(pattern, { path?, limit? })\\n' +\n ' await tools.shell(command)\\n\\n' +\n 'Use console.log() for output; combined stdout/stderr is returned.',\n }),\n timeout: Type.Optional(\n Type.Number({\n description: 'Execution timeout in seconds (default: 3600 = 60m, max: 14400 = 4h)',\n default: DEFAULT_TIMEOUT_SEC,\n }),\n ),\n});\n\nexport const SANDBOX_ALLOWED_TOOLS = new Set([\n 'web_search',\n 'web_fetch',\n 'read_file',\n 'write_file',\n 'grep',\n 'find',\n 'shell',\n 'skills_list',\n 'skill_view',\n]);\n\nconst MAX_TIMEOUT_MS = MAX_TIMEOUT_SEC * 1000;\nconst MAX_TOOL_CALLS = 50;\nconst MAX_STDOUT_CHARS = 50_000;\nconst MAX_STDERR_CHARS = 10_000;\n\nfunction stringifyArg(a: unknown): string {\n if (typeof a === 'string') {\n return a;\n }\n try {\n return JSON.stringify(a);\n } catch {\n return String(a);\n }\n}\n\nfunction extractTextFromResult(result: AgentToolResult<unknown>): string {\n return result.content\n .filter((c): c is { type: 'text'; text: string } => c.type === 'text')\n .map((c) => c.text)\n .join('');\n}\n\nexport function buildSandboxToolMap(tools: AgentTool<any, any>[]): Map<string, AgentTool<any, any>> {\n const m = new Map<string, AgentTool<any, any>>();\n for (const t of tools) {\n if (SANDBOX_ALLOWED_TOOLS.has(t.name)) {\n m.set(t.name, t);\n }\n }\n return m;\n}\n\nfunction createToolsApi(\n getMap: () => Map<string, AgentTool<any, any>>,\n signal: AbortSignal | undefined,\n): Record<string, (...args: unknown[]) => Promise<string>> {\n let seq = 0;\n let calls = 0;\n\n const bump = (): void => {\n calls += 1;\n if (calls > MAX_TOOL_CALLS) {\n throw new Error(`Exceeded max sandbox tool calls (${MAX_TOOL_CALLS})`);\n }\n };\n\n const run = async (name: string, params: Record<string, unknown>): Promise<string> => {\n if (signal?.aborted) {\n throw new Error('aborted');\n }\n bump();\n const tool = getMap().get(name);\n if (!tool) {\n throw new Error(`Tool not available in sandbox: ${name}`);\n }\n const id = `ptc-${Date.now()}-${seq++}`;\n const result = await (tool as any).execute(id, params, signal);\n return extractTextFromResult(result);\n };\n\n return {\n web_search: async (query: unknown, count?: unknown) =>\n run('web_search', {\n query: String(query ?? ''),\n ...(typeof count === 'number' && Number.isFinite(count) ? { count } : {}),\n }),\n\n web_fetch: async (url: unknown, maxChars?: unknown) =>\n run('web_fetch', {\n url: String(url ?? ''),\n ...(typeof maxChars === 'number' && Number.isFinite(maxChars) ? { maxChars } : {}),\n }),\n\n read_file: async (path: unknown, limit?: unknown) =>\n run('read_file', {\n path: String(path ?? ''),\n ...(typeof limit === 'number' && Number.isFinite(limit) ? { limit } : {}),\n }),\n\n write_file: async (path: unknown, content: unknown) =>\n run('write_file', {\n path: String(path ?? ''),\n content: String(content ?? ''),\n }),\n\n grep: async (pattern: unknown, opts?: unknown) => {\n const o = opts && typeof opts === 'object' && opts !== null ? (opts as Record<string, unknown>) : {};\n return run('grep', {\n pattern: String(pattern ?? ''),\n ...(typeof o.path === 'string' ? { path: o.path } : {}),\n ...(typeof o.glob === 'string' ? { glob: o.glob } : {}),\n ...(typeof o.ignoreCase === 'boolean' ? { ignoreCase: o.ignoreCase } : {}),\n ...(typeof o.literal === 'boolean' ? { literal: o.literal } : {}),\n ...(typeof o.context === 'number' && Number.isFinite(o.context) ? { context: o.context } : {}),\n ...(typeof o.limit === 'number' && Number.isFinite(o.limit) ? { limit: o.limit } : {}),\n });\n },\n\n find: async (pattern: unknown, opts?: unknown) => {\n const o = opts && typeof opts === 'object' && opts !== null ? (opts as Record<string, unknown>) : {};\n return run('find', {\n pattern: String(pattern ?? ''),\n ...(typeof o.path === 'string' ? { path: o.path } : {}),\n ...(typeof o.limit === 'number' && Number.isFinite(o.limit) ? { limit: o.limit } : {}),\n });\n },\n\n shell: async (command: unknown) => run('shell', { command: String(command ?? '') }),\n };\n}\n\nasync function runSandboxedScript(\n code: string,\n sandbox: Record<string, unknown>,\n timeoutMs: number,\n signal?: AbortSignal,\n): Promise<void> {\n const context = createContext(sandbox, { name: 'execute_code' });\n const wrapped = `(async () => {\\n${code}\\n})()`;\n const script = new Script(wrapped, { filename: 'execute_code.vm' });\n const runPromise = script.runInContext(context) as Promise<unknown>;\n\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n const timeoutPromise = new Promise<never>((_, rej) => {\n timeoutId = setTimeout(\n () => rej(new Error(`Script timed out after ${Math.round(timeoutMs / 1000)}s`)),\n timeoutMs,\n );\n });\n\n const abortPromise =\n signal &&\n new Promise<never>((_, rej) => {\n if (signal.aborted) {\n rej(new Error('aborted'));\n return;\n }\n signal.addEventListener('abort', () => rej(new Error('aborted')), { once: true });\n });\n\n const racers: Promise<unknown>[] = [runPromise, timeoutPromise];\n if (abortPromise) {\n racers.push(abortPromise);\n }\n\n try {\n await Promise.race(racers);\n } finally {\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n }\n }\n}\n\nexport interface ExecuteCodeToolDeps {\n getSandboxToolMap: () => Map<string, AgentTool<any, any>>;\n}\n\ntype ExecuteCodeParams = { code: string; timeout?: number };\n\nexport function createExecuteCodeTool(deps: ExecuteCodeToolDeps): AgentTool {\n return {\n name: 'execute_code',\n label: '⚡ Execute Code',\n description:\n 'Run sandboxed JavaScript that calls a subset of tools via `tools.*` (batch work in one step).\\n\\n' +\n 'Only stdout/stderr from `console` plus tool return text (as strings) are visible — not full tool JSON.\\n\\n' +\n 'WHEN TO USE: loops over files/URLs, simple branching between tool calls.\\n' +\n 'WHEN NOT TO USE: single tool calls; tasks needing full tool schemas or disallowed tools.\\n\\n' +\n 'API: `await tools.web_search(q, count?)`, `web_fetch(url, maxChars?)`, `read_file(path, limit?)`, ' +\n '`write_file(path, content)`, `grep(pattern, opts?)`, `find(glob, opts?)`, `shell(command)`. ' +\n 'Use `console.log` for output.',\n parameters: ExecuteCodeSchema,\n\n async execute(\n _toolCallId: string,\n params: any,\n signal?: AbortSignal,\n ): Promise<AgentToolResult<{ exitCode: number }>> {\n const p = params as ExecuteCodeParams;\n const sec = p.timeout ?? DEFAULT_TIMEOUT_SEC;\n const timeoutMs = Math.min(\n Math.max(1, Number.isFinite(sec) ? sec : DEFAULT_TIMEOUT_SEC) * 1000,\n MAX_TIMEOUT_MS,\n );\n\n const stdout: string[] = [];\n const stderr: string[] = [];\n\n const toolsApi = createToolsApi(deps.getSandboxToolMap, signal);\n\n const sandbox: Record<string, unknown> = {\n tools: toolsApi,\n Promise,\n JSON,\n Math,\n Date,\n Array,\n Object,\n String,\n Number,\n Boolean,\n RegExp,\n Map,\n Set,\n Error,\n TypeError,\n RangeError,\n parseInt,\n parseFloat,\n isNaN,\n isFinite,\n console: {\n log: (...args: unknown[]) => {\n stdout.push(args.map(stringifyArg).join(' '));\n },\n error: (...args: unknown[]) => {\n stderr.push(args.map(stringifyArg).join(' '));\n },\n warn: (...args: unknown[]) => {\n stderr.push(args.map(stringifyArg).join(' '));\n },\n },\n setTimeout,\n clearTimeout,\n };\n\n try {\n await runSandboxedScript(p.code, sandbox, timeoutMs, signal);\n\n let out = stdout.join('\\n');\n if (out.length > MAX_STDOUT_CHARS) {\n out = `${out.slice(0, MAX_STDOUT_CHARS)}\\n...(truncated)`;\n }\n\n const parts: string[] = [];\n if (out.length > 0) {\n parts.push(out);\n }\n if (stderr.length > 0) {\n let err = stderr.join('\\n');\n if (err.length > MAX_STDERR_CHARS) {\n err = `${err.slice(0, MAX_STDERR_CHARS)}\\n...(truncated)`;\n }\n parts.push(`\\nSTDERR:\\n${err}`);\n }\n\n return {\n content: [{ type: 'text', text: parts.join('') || '(no output)' }],\n details: { exitCode: 0 },\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text', text: `Execution failed: ${message}` }],\n details: { exitCode: 1 },\n };\n }\n },\n } as any;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"execute-code-tool.js","names":[],"sources":["../../../../src/agent/tools/execute-code-tool.ts"],"sourcesContent":["/**\n * Programmatic tool calling (PTC): run sandboxed JS that invokes a subset of agent tools.\n * vm is not a strong security boundary; only trusted models should use this tool.\n */\n\nimport { Script, createContext } from 'node:vm';\n\nimport { Type } from '@sinclair/typebox';\nimport type { AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core';\n\n/** Default script wall time when `timeout` omitted (30 minutes). */\nconst DEFAULT_TIMEOUT_SEC = 30 * 60;\n/** Hard cap for `timeout` parameter (4 hours). */\nconst MAX_TIMEOUT_SEC = 4 * 60 * 60;\n\nconst ExecuteCodeSchema = Type.Object({\n code: Type.String({\n description:\n 'JavaScript to run. Exposed as `tools` (async functions) and `console`:\\n' +\n ' await tools.web_search(query, count?)\\n' +\n ' await tools.web_fetch(url, maxChars?)\\n' +\n ' await tools.read_file(path, limit?)\\n' +\n ' await tools.write_file(path, content)\\n' +\n ' await tools.grep(pattern, { path?, glob?, ignoreCase?, literal?, context?, limit? })\\n' +\n ' await tools.find(pattern, { path?, limit? })\\n' +\n ' await tools.exec_command(cmd, cwd?)\\n\\n' +\n 'Use console.log() for output; combined stdout/stderr is returned.',\n }),\n timeout: Type.Optional(\n Type.Number({\n description: 'Execution timeout in seconds (default: 3600 = 60m, max: 14400 = 4h)',\n default: DEFAULT_TIMEOUT_SEC,\n }),\n ),\n});\n\nexport const SANDBOX_ALLOWED_TOOLS = new Set([\n 'web_search',\n 'web_fetch',\n 'read_file',\n 'write_file',\n 'grep',\n 'find',\n 'exec_command',\n 'skills_list',\n 'skill_view',\n]);\n\nconst MAX_TIMEOUT_MS = MAX_TIMEOUT_SEC * 1000;\nconst MAX_TOOL_CALLS = 50;\nconst MAX_STDOUT_CHARS = 50_000;\nconst MAX_STDERR_CHARS = 10_000;\n\nfunction stringifyArg(a: unknown): string {\n if (typeof a === 'string') {\n return a;\n }\n try {\n return JSON.stringify(a);\n } catch {\n return String(a);\n }\n}\n\nfunction extractTextFromResult(result: AgentToolResult<unknown>): string {\n return result.content\n .filter((c): c is { type: 'text'; text: string } => c.type === 'text')\n .map((c) => c.text)\n .join('');\n}\n\nexport function buildSandboxToolMap(tools: AgentTool<any, any>[]): Map<string, AgentTool<any, any>> {\n const m = new Map<string, AgentTool<any, any>>();\n for (const t of tools) {\n if (SANDBOX_ALLOWED_TOOLS.has(t.name)) {\n m.set(t.name, t);\n }\n }\n return m;\n}\n\nfunction createToolsApi(\n getMap: () => Map<string, AgentTool<any, any>>,\n signal: AbortSignal | undefined,\n): Record<string, (...args: unknown[]) => Promise<string>> {\n let seq = 0;\n let calls = 0;\n\n const bump = (): void => {\n calls += 1;\n if (calls > MAX_TOOL_CALLS) {\n throw new Error(`Exceeded max sandbox tool calls (${MAX_TOOL_CALLS})`);\n }\n };\n\n const run = async (name: string, params: Record<string, unknown>): Promise<string> => {\n if (signal?.aborted) {\n throw new Error('aborted');\n }\n bump();\n const tool = getMap().get(name);\n if (!tool) {\n throw new Error(`Tool not available in sandbox: ${name}`);\n }\n const id = `ptc-${Date.now()}-${seq++}`;\n const result = await (tool as any).execute(id, params, signal);\n return extractTextFromResult(result);\n };\n\n return {\n web_search: async (query: unknown, count?: unknown) =>\n run('web_search', {\n query: String(query ?? ''),\n ...(typeof count === 'number' && Number.isFinite(count) ? { count } : {}),\n }),\n\n web_fetch: async (url: unknown, maxChars?: unknown) =>\n run('web_fetch', {\n url: String(url ?? ''),\n ...(typeof maxChars === 'number' && Number.isFinite(maxChars) ? { maxChars } : {}),\n }),\n\n read_file: async (path: unknown, limit?: unknown) =>\n run('read_file', {\n path: String(path ?? ''),\n ...(typeof limit === 'number' && Number.isFinite(limit) ? { limit } : {}),\n }),\n\n write_file: async (path: unknown, content: unknown) =>\n run('write_file', {\n path: String(path ?? ''),\n content: String(content ?? ''),\n }),\n\n grep: async (pattern: unknown, opts?: unknown) => {\n const o = opts && typeof opts === 'object' && opts !== null ? (opts as Record<string, unknown>) : {};\n return run('grep', {\n pattern: String(pattern ?? ''),\n ...(typeof o.path === 'string' ? { path: o.path } : {}),\n ...(typeof o.glob === 'string' ? { glob: o.glob } : {}),\n ...(typeof o.ignoreCase === 'boolean' ? { ignoreCase: o.ignoreCase } : {}),\n ...(typeof o.literal === 'boolean' ? { literal: o.literal } : {}),\n ...(typeof o.context === 'number' && Number.isFinite(o.context) ? { context: o.context } : {}),\n ...(typeof o.limit === 'number' && Number.isFinite(o.limit) ? { limit: o.limit } : {}),\n });\n },\n\n find: async (pattern: unknown, opts?: unknown) => {\n const o = opts && typeof opts === 'object' && opts !== null ? (opts as Record<string, unknown>) : {};\n return run('find', {\n pattern: String(pattern ?? ''),\n ...(typeof o.path === 'string' ? { path: o.path } : {}),\n ...(typeof o.limit === 'number' && Number.isFinite(o.limit) ? { limit: o.limit } : {}),\n });\n },\n\n exec_command: async (cmd: unknown, cwd?: unknown) =>\n run('exec_command', {\n cmd: String(cmd ?? ''),\n ...(typeof cwd === 'string' && cwd.trim() ? { cwd } : {}),\n }),\n };\n}\n\nasync function runSandboxedScript(\n code: string,\n sandbox: Record<string, unknown>,\n timeoutMs: number,\n signal?: AbortSignal,\n): Promise<void> {\n const context = createContext(sandbox, { name: 'execute_code' });\n const wrapped = `(async () => {\\n${code}\\n})()`;\n const script = new Script(wrapped, { filename: 'execute_code.vm' });\n const runPromise = script.runInContext(context) as Promise<unknown>;\n\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n const timeoutPromise = new Promise<never>((_, rej) => {\n timeoutId = setTimeout(\n () => rej(new Error(`Script timed out after ${Math.round(timeoutMs / 1000)}s`)),\n timeoutMs,\n );\n });\n\n const abortPromise =\n signal &&\n new Promise<never>((_, rej) => {\n if (signal.aborted) {\n rej(new Error('aborted'));\n return;\n }\n signal.addEventListener('abort', () => rej(new Error('aborted')), { once: true });\n });\n\n const racers: Promise<unknown>[] = [runPromise, timeoutPromise];\n if (abortPromise) {\n racers.push(abortPromise);\n }\n\n try {\n await Promise.race(racers);\n } finally {\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n }\n }\n}\n\nexport interface ExecuteCodeToolDeps {\n getSandboxToolMap: () => Map<string, AgentTool<any, any>>;\n}\n\ntype ExecuteCodeParams = { code: string; timeout?: number };\n\nexport function createExecuteCodeTool(deps: ExecuteCodeToolDeps): AgentTool {\n return {\n name: 'execute_code',\n label: '⚡ Execute Code',\n description:\n 'Run sandboxed JavaScript that calls a subset of tools via `tools.*` (batch work in one step).\\n\\n' +\n 'Only stdout/stderr from `console` plus tool return text (as strings) are visible — not full tool JSON.\\n\\n' +\n 'WHEN TO USE: loops over files/URLs, simple branching between tool calls.\\n' +\n 'WHEN NOT TO USE: single tool calls; tasks needing full tool schemas or disallowed tools.\\n\\n' +\n 'API: `await tools.web_search(q, count?)`, `web_fetch(url, maxChars?)`, `read_file(path, limit?)`, ' +\n '`write_file(path, content)`, `grep(pattern, opts?)`, `find(glob, opts?)`, `exec_command(cmd, cwd?)`. ' +\n 'Use `console.log` for output.',\n parameters: ExecuteCodeSchema,\n\n async execute(\n _toolCallId: string,\n params: any,\n signal?: AbortSignal,\n ): Promise<AgentToolResult<{ exitCode: number }>> {\n const p = params as ExecuteCodeParams;\n const sec = p.timeout ?? DEFAULT_TIMEOUT_SEC;\n const timeoutMs = Math.min(\n Math.max(1, Number.isFinite(sec) ? sec : DEFAULT_TIMEOUT_SEC) * 1000,\n MAX_TIMEOUT_MS,\n );\n\n const stdout: string[] = [];\n const stderr: string[] = [];\n\n const toolsApi = createToolsApi(deps.getSandboxToolMap, signal);\n\n const sandbox: Record<string, unknown> = {\n tools: toolsApi,\n Promise,\n JSON,\n Math,\n Date,\n Array,\n Object,\n String,\n Number,\n Boolean,\n RegExp,\n Map,\n Set,\n Error,\n TypeError,\n RangeError,\n parseInt,\n parseFloat,\n isNaN,\n isFinite,\n console: {\n log: (...args: unknown[]) => {\n stdout.push(args.map(stringifyArg).join(' '));\n },\n error: (...args: unknown[]) => {\n stderr.push(args.map(stringifyArg).join(' '));\n },\n warn: (...args: unknown[]) => {\n stderr.push(args.map(stringifyArg).join(' '));\n },\n },\n setTimeout,\n clearTimeout,\n };\n\n try {\n await runSandboxedScript(p.code, sandbox, timeoutMs, signal);\n\n let out = stdout.join('\\n');\n if (out.length > MAX_STDOUT_CHARS) {\n out = `${out.slice(0, MAX_STDOUT_CHARS)}\\n...(truncated)`;\n }\n\n const parts: string[] = [];\n if (out.length > 0) {\n parts.push(out);\n }\n if (stderr.length > 0) {\n let err = stderr.join('\\n');\n if (err.length > MAX_STDERR_CHARS) {\n err = `${err.slice(0, MAX_STDERR_CHARS)}\\n...(truncated)`;\n }\n parts.push(`\\nSTDERR:\\n${err}`);\n }\n\n return {\n content: [{ type: 'text', text: parts.join('') || '(no output)' }],\n details: { exitCode: 0 },\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text', text: `Execution failed: ${message}` }],\n details: { exitCode: 1 },\n };\n }\n },\n } as any;\n}\n"],"mappings":"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"}
@@ -12,6 +12,9 @@
12
12
  * instead of synthesising fake-success results.
13
13
  */
14
14
  import type { AgentTool, AgentToolResult, AgentToolUpdateCallback } from '@earendil-works/pi-agent-core';
15
+ import type { XopcToolMetadata } from './metadata.js';
16
+ import { ToolConcurrencyController } from './concurrency.js';
17
+ import type { ToolConcurrencyController as ToolConcurrencyControllerType } from './concurrency.js';
15
18
  export interface ToolExecutorConfig {
16
19
  /** Default per-tool timeout when the tool does not declare its own `timeoutMs`. */
17
20
  defaultTimeoutMs: number;
@@ -32,9 +35,8 @@ declare const DEFAULT_CONFIG: ToolExecutorConfig;
32
35
  * - `idempotent`: marks a tool as safe to retry. The wrapper retries only
33
36
  * tools that opt in — write/edit-like tools must leave this `false`.
34
37
  */
35
- export interface XopcToolHints {
38
+ export interface XopcToolHints extends XopcToolMetadata {
36
39
  timeoutMs?: number;
37
- idempotent?: boolean;
38
40
  }
39
41
  /**
40
42
  * Execute tool with timeout (always) and retry (only for idempotent tools).
@@ -45,9 +47,9 @@ export declare function executeToolWithProtection<TDetails>(tool: AgentTool<any,
45
47
  * Wrap a single tool with the protection pipeline. Preserves the original
46
48
  * `execute` signature so streaming `signal` / `onUpdate` reach the tool.
47
49
  */
48
- export declare function wrapToolWithProtection<TDetails>(tool: AgentTool<any, TDetails>, config?: Partial<ToolExecutorConfig>): AgentTool<any, TDetails>;
50
+ export declare function wrapToolWithProtection<TDetails>(tool: AgentTool<any, TDetails>, config?: Partial<ToolExecutorConfig>, concurrency?: ToolConcurrencyControllerType): AgentTool<any, TDetails>;
49
51
  /**
50
52
  * Wrap a batch of tools with protection.
51
53
  */
52
- export declare function wrapToolsWithProtection(tools: AgentTool<any, any>[], config?: Partial<ToolExecutorConfig>): AgentTool<any, any>[];
54
+ export declare function wrapToolsWithProtection(tools: AgentTool<any, any>[], config?: Partial<ToolExecutorConfig>, concurrency?: ToolConcurrencyController): AgentTool<any, any>[];
53
55
  export { DEFAULT_CONFIG as DEFAULT_TOOL_EXECUTOR_CONFIG };
@@ -1,5 +1,6 @@
1
1
  import { createLogger } from "../../utils/logger/index.js";
2
2
  import { init_logger } from "../../utils/logger.js";
3
+ import { ToolConcurrencyController, resolveToolLockMode } from "./concurrency.js";
3
4
  import { TimeoutError, executeWithTimeout } from "../lifecycle/timeout-wrapper.js";
4
5
  import { withRetry } from "../../infra/retry.js";
5
6
  //#region src/agent/tools/executor.ts
@@ -91,19 +92,21 @@ async function executeToolWithProtection(tool, toolCallId, params, signal, onUpd
91
92
  * Wrap a single tool with the protection pipeline. Preserves the original
92
93
  * `execute` signature so streaming `signal` / `onUpdate` reach the tool.
93
94
  */
94
- function wrapToolWithProtection(tool, config) {
95
+ function wrapToolWithProtection(tool, config, concurrency) {
96
+ const lockMode = resolveToolLockMode(tool);
95
97
  return {
96
98
  ...tool,
97
99
  async execute(toolCallId, params, signal, onUpdate) {
98
- return executeToolWithProtection(tool, toolCallId, params, signal, onUpdate, config);
100
+ const run = () => executeToolWithProtection(tool, toolCallId, params, signal, onUpdate, config);
101
+ return concurrency ? concurrency.run(lockMode, run) : run();
99
102
  }
100
103
  };
101
104
  }
102
105
  /**
103
106
  * Wrap a batch of tools with protection.
104
107
  */
105
- function wrapToolsWithProtection(tools, config) {
106
- return tools.map((tool) => wrapToolWithProtection(tool, config));
108
+ function wrapToolsWithProtection(tools, config, concurrency = new ToolConcurrencyController()) {
109
+ return tools.map((tool) => wrapToolWithProtection(tool, config, concurrency));
107
110
  }
108
111
  //#endregion
109
112
  export { DEFAULT_CONFIG as DEFAULT_TOOL_EXECUTOR_CONFIG, executeToolWithProtection, wrapToolWithProtection, wrapToolsWithProtection };
@@ -1 +1 @@
1
- {"version":3,"file":"executor.js","names":[],"sources":["../../../../src/agent/tools/executor.ts"],"sourcesContent":["/**\n * Tool Executor - Unified tool execution wrapper.\n *\n * Adds:\n * - Timeout protection (tools that hang would otherwise stall the turn).\n * - Optional retry for tools explicitly marked `idempotent`.\n *\n * Pi-agent contract (from `AgentTool.execute` docstring): \"Throw on failure\n * instead of encoding errors in `content`.\" Pi-agent turns thrown errors into\n * `tool_execution_end` events with `isError=true`, which feeds the loop guard,\n * error tracker, and error-pattern matcher. This wrapper therefore re-throws\n * instead of synthesising fake-success results.\n */\n\nimport type {\n AgentTool,\n AgentToolResult,\n AgentToolUpdateCallback,\n} from '@earendil-works/pi-agent-core';\nimport { createLogger } from '../../utils/logger.js';\nimport { executeWithTimeout, TimeoutError } from '../lifecycle/timeout-wrapper.js';\nimport { withRetry } from '../../infra/retry.js';\n\nconst log = createLogger('ToolExecutor');\n\nexport interface ToolExecutorConfig {\n /** Default per-tool timeout when the tool does not declare its own `timeoutMs`. */\n defaultTimeoutMs: number;\n\n /** Max retry attempts for tools opted into retry via `idempotent: true`. */\n maxRetries: number;\n /** Initial backoff between retries (passed straight to `withRetry`). */\n retryDelayMs: number;\n\n /** Master switches; default both on so the wrapper is still effective. */\n enableTimeout: boolean;\n enableRetry: boolean;\n}\n\nconst DEFAULT_CONFIG: ToolExecutorConfig = {\n defaultTimeoutMs: 5 * 60 * 1000, // 5 minutes\n maxRetries: 2,\n retryDelayMs: 1000,\n enableTimeout: true,\n enableRetry: true,\n};\n\n/**\n * Optional xopc-side hints that any tool may attach. They are not part of the\n * pi-agent `AgentTool` contract; the wrapper reads them via structural typing.\n *\n * - `timeoutMs`: per-tool override of the default execution timeout.\n * - `idempotent`: marks a tool as safe to retry. The wrapper retries only\n * tools that opt in — write/edit-like tools must leave this `false`.\n */\nexport interface XopcToolHints {\n timeoutMs?: number;\n idempotent?: boolean;\n}\n\nfunction readToolHints(tool: AgentTool<any, any>): XopcToolHints {\n const t = tool as AgentTool<any, any> & XopcToolHints;\n return {\n timeoutMs: typeof t.timeoutMs === 'number' && t.timeoutMs > 0 ? t.timeoutMs : undefined,\n idempotent: t.idempotent === true,\n };\n}\n\nfunction resolveTimeoutMs(tool: AgentTool<any, any>, config: ToolExecutorConfig): number {\n const hints = readToolHints(tool);\n return hints.timeoutMs ?? config.defaultTimeoutMs;\n}\n\n/**\n * Execute tool with timeout (always) and retry (only for idempotent tools).\n * Re-throws on failure so pi-agent records `isError=true`.\n */\nexport async function executeToolWithProtection<TDetails>(\n tool: AgentTool<any, TDetails>,\n toolCallId: string,\n params: any,\n signal?: AbortSignal,\n onUpdate?: AgentToolUpdateCallback<TDetails>,\n config: Partial<ToolExecutorConfig> = {},\n): Promise<AgentToolResult<TDetails>> {\n const fullConfig = { ...DEFAULT_CONFIG, ...config };\n const toolName = tool.name;\n const hints = readToolHints(tool);\n\n const runOnce = (): Promise<AgentToolResult<TDetails>> =>\n tool.execute(toolCallId, params, signal, onUpdate);\n\n let operation: () => Promise<AgentToolResult<TDetails>> = runOnce;\n\n if (fullConfig.enableTimeout) {\n const timeoutMs = resolveTimeoutMs(tool, fullConfig);\n const inner = operation;\n operation = () =>\n executeWithTimeout(inner, {\n toolName,\n timeoutMs,\n description: `Executing ${toolName}`,\n });\n }\n\n const shouldRetry = fullConfig.enableRetry && fullConfig.maxRetries > 0 && hints.idempotent;\n if (shouldRetry) {\n const inner = operation;\n operation = () =>\n withRetry(inner, {\n attempts: fullConfig.maxRetries + 1,\n minDelayMs: fullConfig.retryDelayMs,\n onRetry: (info) => {\n log.warn(\n { tool: toolName, attempt: info.attempt, delayMs: info.delayMs, error: info.error },\n 'Tool execution failed, retrying (idempotent)',\n );\n },\n });\n }\n\n const startTime = Date.now();\n try {\n const result = await operation();\n log.debug(\n { tool: toolName, durationMs: Date.now() - startTime, success: true },\n 'Tool execution completed',\n );\n return result;\n } catch (error) {\n const durationMs = Date.now() - startTime;\n\n if (error instanceof TimeoutError) {\n log.error(\n { tool: toolName, timeoutMs: error.timeoutMs, durationMs },\n 'Tool execution timed out',\n );\n throw new Error(`Tool '${toolName}' timed out after ${error.timeoutMs}ms`, { cause: error });\n }\n\n const errorMessage = error instanceof Error ? error.message : String(error);\n log.error(\n { tool: toolName, error: errorMessage, durationMs },\n 'Tool execution failed',\n );\n throw error instanceof Error ? error : new Error(errorMessage);\n }\n}\n\n/**\n * Wrap a single tool with the protection pipeline. Preserves the original\n * `execute` signature so streaming `signal` / `onUpdate` reach the tool.\n */\nexport function wrapToolWithProtection<TDetails>(\n tool: AgentTool<any, TDetails>,\n config?: Partial<ToolExecutorConfig>,\n): AgentTool<any, TDetails> {\n return {\n ...tool,\n async execute(\n toolCallId: string,\n params: any,\n signal?: AbortSignal,\n onUpdate?: AgentToolUpdateCallback<TDetails>,\n ): Promise<AgentToolResult<TDetails>> {\n return executeToolWithProtection(tool, toolCallId, params, signal, onUpdate, config);\n },\n } as AgentTool<any, TDetails>;\n}\n\n/**\n * Wrap a batch of tools with protection.\n */\nexport function wrapToolsWithProtection(\n tools: AgentTool<any, any>[],\n config?: Partial<ToolExecutorConfig>,\n): AgentTool<any, any>[] {\n return tools.map((tool) => wrapToolWithProtection(tool, config));\n}\n\n// Export configuration\nexport { DEFAULT_CONFIG as DEFAULT_TOOL_EXECUTOR_CONFIG };\n"],"mappings":";;;;;aAmBqD;AAIrD,MAAM,MAAM,aAAa,eAAe;AAgBxC,MAAM,iBAAqC;CACzC,kBAAkB,MAAS;CAC3B,YAAY;CACZ,cAAc;CACd,eAAe;CACf,aAAa;CACd;AAeD,SAAS,cAAc,MAA0C;CAC/D,MAAM,IAAI;AACV,QAAO;EACL,WAAW,OAAO,EAAE,cAAc,YAAY,EAAE,YAAY,IAAI,EAAE,YAAY,KAAA;EAC9E,YAAY,EAAE,eAAe;EAC9B;;AAGH,SAAS,iBAAiB,MAA2B,QAAoC;AAEvF,QADc,cAAc,KAChB,CAAC,aAAa,OAAO;;;;;;AAOnC,eAAsB,0BACpB,MACA,YACA,QACA,QACA,UACA,SAAsC,EAAE,EACJ;CACpC,MAAM,aAAa;EAAE,GAAG;EAAgB,GAAG;EAAQ;CACnD,MAAM,WAAW,KAAK;CACtB,MAAM,QAAQ,cAAc,KAAK;CAEjC,MAAM,gBACJ,KAAK,QAAQ,YAAY,QAAQ,QAAQ,SAAS;CAEpD,IAAI,YAAsD;AAE1D,KAAI,WAAW,eAAe;EAC5B,MAAM,YAAY,iBAAiB,MAAM,WAAW;EACpD,MAAM,QAAQ;AACd,oBACE,mBAAmB,OAAO;GACxB;GACA;GACA,aAAa,aAAa;GAC3B,CAAC;;AAIN,KADoB,WAAW,eAAe,WAAW,aAAa,KAAK,MAAM,YAChE;EACf,MAAM,QAAQ;AACd,oBACE,UAAU,OAAO;GACf,UAAU,WAAW,aAAa;GAClC,YAAY,WAAW;GACvB,UAAU,SAAS;AACjB,QAAI,KACF;KAAE,MAAM;KAAU,SAAS,KAAK;KAAS,SAAS,KAAK;KAAS,OAAO,KAAK;KAAO,EACnF,+CACD;;GAEJ,CAAC;;CAGN,MAAM,YAAY,KAAK,KAAK;AAC5B,KAAI;EACF,MAAM,SAAS,MAAM,WAAW;AAChC,MAAI,MACF;GAAE,MAAM;GAAU,YAAY,KAAK,KAAK,GAAG;GAAW,SAAS;GAAM,EACrE,2BACD;AACD,SAAO;UACA,OAAO;EACd,MAAM,aAAa,KAAK,KAAK,GAAG;AAEhC,MAAI,iBAAiB,cAAc;AACjC,OAAI,MACF;IAAE,MAAM;IAAU,WAAW,MAAM;IAAW;IAAY,EAC1D,2BACD;AACD,SAAM,IAAI,MAAM,SAAS,SAAS,oBAAoB,MAAM,UAAU,KAAK,EAAE,OAAO,OAAO,CAAC;;EAG9F,MAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AAC3E,MAAI,MACF;GAAE,MAAM;GAAU,OAAO;GAAc;GAAY,EACnD,wBACD;AACD,QAAM,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,aAAa;;;;;;;AAQlE,SAAgB,uBACd,MACA,QAC0B;AAC1B,QAAO;EACL,GAAG;EACH,MAAM,QACJ,YACA,QACA,QACA,UACoC;AACpC,UAAO,0BAA0B,MAAM,YAAY,QAAQ,QAAQ,UAAU,OAAO;;EAEvF;;;;;AAMH,SAAgB,wBACd,OACA,QACuB;AACvB,QAAO,MAAM,KAAK,SAAS,uBAAuB,MAAM,OAAO,CAAC"}
1
+ {"version":3,"file":"executor.js","names":[],"sources":["../../../../src/agent/tools/executor.ts"],"sourcesContent":["/**\n * Tool Executor - Unified tool execution wrapper.\n *\n * Adds:\n * - Timeout protection (tools that hang would otherwise stall the turn).\n * - Optional retry for tools explicitly marked `idempotent`.\n *\n * Pi-agent contract (from `AgentTool.execute` docstring): \"Throw on failure\n * instead of encoding errors in `content`.\" Pi-agent turns thrown errors into\n * `tool_execution_end` events with `isError=true`, which feeds the loop guard,\n * error tracker, and error-pattern matcher. This wrapper therefore re-throws\n * instead of synthesising fake-success results.\n */\n\nimport type {\n AgentTool,\n AgentToolResult,\n AgentToolUpdateCallback,\n} from '@earendil-works/pi-agent-core';\nimport { createLogger } from '../../utils/logger.js';\nimport { executeWithTimeout, TimeoutError } from '../lifecycle/timeout-wrapper.js';\nimport { withRetry } from '../../infra/retry.js';\nimport type { XopcToolMetadata } from './metadata.js';\nimport { ToolConcurrencyController, resolveToolLockMode } from './concurrency.js';\nimport type { ToolConcurrencyController as ToolConcurrencyControllerType } from './concurrency.js';\n\nconst log = createLogger('ToolExecutor');\n\nexport interface ToolExecutorConfig {\n /** Default per-tool timeout when the tool does not declare its own `timeoutMs`. */\n defaultTimeoutMs: number;\n\n /** Max retry attempts for tools opted into retry via `idempotent: true`. */\n maxRetries: number;\n /** Initial backoff between retries (passed straight to `withRetry`). */\n retryDelayMs: number;\n\n /** Master switches; default both on so the wrapper is still effective. */\n enableTimeout: boolean;\n enableRetry: boolean;\n}\n\nconst DEFAULT_CONFIG: ToolExecutorConfig = {\n defaultTimeoutMs: 5 * 60 * 1000, // 5 minutes\n maxRetries: 2,\n retryDelayMs: 1000,\n enableTimeout: true,\n enableRetry: true,\n};\n\n/**\n * Optional xopc-side hints that any tool may attach. They are not part of the\n * pi-agent `AgentTool` contract; the wrapper reads them via structural typing.\n *\n * - `timeoutMs`: per-tool override of the default execution timeout.\n * - `idempotent`: marks a tool as safe to retry. The wrapper retries only\n * tools that opt in — write/edit-like tools must leave this `false`.\n */\nexport interface XopcToolHints extends XopcToolMetadata {\n timeoutMs?: number;\n}\n\nfunction readToolHints(tool: AgentTool<any, any>): XopcToolHints {\n const t = tool as AgentTool<any, any> & XopcToolHints;\n return {\n timeoutMs: typeof t.timeoutMs === 'number' && t.timeoutMs > 0 ? t.timeoutMs : undefined,\n idempotent: t.idempotent === true,\n };\n}\n\nfunction resolveTimeoutMs(tool: AgentTool<any, any>, config: ToolExecutorConfig): number {\n const hints = readToolHints(tool);\n return hints.timeoutMs ?? config.defaultTimeoutMs;\n}\n\n/**\n * Execute tool with timeout (always) and retry (only for idempotent tools).\n * Re-throws on failure so pi-agent records `isError=true`.\n */\nexport async function executeToolWithProtection<TDetails>(\n tool: AgentTool<any, TDetails>,\n toolCallId: string,\n params: any,\n signal?: AbortSignal,\n onUpdate?: AgentToolUpdateCallback<TDetails>,\n config: Partial<ToolExecutorConfig> = {},\n): Promise<AgentToolResult<TDetails>> {\n const fullConfig = { ...DEFAULT_CONFIG, ...config };\n const toolName = tool.name;\n const hints = readToolHints(tool);\n\n const runOnce = (): Promise<AgentToolResult<TDetails>> =>\n tool.execute(toolCallId, params, signal, onUpdate);\n\n let operation: () => Promise<AgentToolResult<TDetails>> = runOnce;\n\n if (fullConfig.enableTimeout) {\n const timeoutMs = resolveTimeoutMs(tool, fullConfig);\n const inner = operation;\n operation = () =>\n executeWithTimeout(inner, {\n toolName,\n timeoutMs,\n description: `Executing ${toolName}`,\n });\n }\n\n const shouldRetry = fullConfig.enableRetry && fullConfig.maxRetries > 0 && hints.idempotent;\n if (shouldRetry) {\n const inner = operation;\n operation = () =>\n withRetry(inner, {\n attempts: fullConfig.maxRetries + 1,\n minDelayMs: fullConfig.retryDelayMs,\n onRetry: (info) => {\n log.warn(\n { tool: toolName, attempt: info.attempt, delayMs: info.delayMs, error: info.error },\n 'Tool execution failed, retrying (idempotent)',\n );\n },\n });\n }\n\n const startTime = Date.now();\n try {\n const result = await operation();\n log.debug(\n { tool: toolName, durationMs: Date.now() - startTime, success: true },\n 'Tool execution completed',\n );\n return result;\n } catch (error) {\n const durationMs = Date.now() - startTime;\n\n if (error instanceof TimeoutError) {\n log.error(\n { tool: toolName, timeoutMs: error.timeoutMs, durationMs },\n 'Tool execution timed out',\n );\n throw new Error(`Tool '${toolName}' timed out after ${error.timeoutMs}ms`, { cause: error });\n }\n\n const errorMessage = error instanceof Error ? error.message : String(error);\n log.error(\n { tool: toolName, error: errorMessage, durationMs },\n 'Tool execution failed',\n );\n throw error instanceof Error ? error : new Error(errorMessage);\n }\n}\n\n/**\n * Wrap a single tool with the protection pipeline. Preserves the original\n * `execute` signature so streaming `signal` / `onUpdate` reach the tool.\n */\nexport function wrapToolWithProtection<TDetails>(\n tool: AgentTool<any, TDetails>,\n config?: Partial<ToolExecutorConfig>,\n concurrency?: ToolConcurrencyControllerType,\n): AgentTool<any, TDetails> {\n const lockMode = resolveToolLockMode(tool);\n return {\n ...tool,\n async execute(\n toolCallId: string,\n params: any,\n signal?: AbortSignal,\n onUpdate?: AgentToolUpdateCallback<TDetails>,\n ): Promise<AgentToolResult<TDetails>> {\n const run = () => executeToolWithProtection(tool, toolCallId, params, signal, onUpdate, config);\n return concurrency ? concurrency.run(lockMode, run) : run();\n },\n } as AgentTool<any, TDetails>;\n}\n\n/**\n * Wrap a batch of tools with protection.\n */\nexport function wrapToolsWithProtection(\n tools: AgentTool<any, any>[],\n config?: Partial<ToolExecutorConfig>,\n concurrency = new ToolConcurrencyController(),\n): AgentTool<any, any>[] {\n return tools.map((tool) => wrapToolWithProtection(tool, config, concurrency));\n}\n\n// Export configuration\nexport { DEFAULT_CONFIG as DEFAULT_TOOL_EXECUTOR_CONFIG };\n"],"mappings":";;;;;;aAmBqD;AAOrD,MAAM,MAAM,aAAa,eAAe;AAgBxC,MAAM,iBAAqC;CACzC,kBAAkB,MAAS;CAC3B,YAAY;CACZ,cAAc;CACd,eAAe;CACf,aAAa;CACd;AAcD,SAAS,cAAc,MAA0C;CAC/D,MAAM,IAAI;AACV,QAAO;EACL,WAAW,OAAO,EAAE,cAAc,YAAY,EAAE,YAAY,IAAI,EAAE,YAAY,KAAA;EAC9E,YAAY,EAAE,eAAe;EAC9B;;AAGH,SAAS,iBAAiB,MAA2B,QAAoC;AAEvF,QADc,cAAc,KAChB,CAAC,aAAa,OAAO;;;;;;AAOnC,eAAsB,0BACpB,MACA,YACA,QACA,QACA,UACA,SAAsC,EAAE,EACJ;CACpC,MAAM,aAAa;EAAE,GAAG;EAAgB,GAAG;EAAQ;CACnD,MAAM,WAAW,KAAK;CACtB,MAAM,QAAQ,cAAc,KAAK;CAEjC,MAAM,gBACJ,KAAK,QAAQ,YAAY,QAAQ,QAAQ,SAAS;CAEpD,IAAI,YAAsD;AAE1D,KAAI,WAAW,eAAe;EAC5B,MAAM,YAAY,iBAAiB,MAAM,WAAW;EACpD,MAAM,QAAQ;AACd,oBACE,mBAAmB,OAAO;GACxB;GACA;GACA,aAAa,aAAa;GAC3B,CAAC;;AAIN,KADoB,WAAW,eAAe,WAAW,aAAa,KAAK,MAAM,YAChE;EACf,MAAM,QAAQ;AACd,oBACE,UAAU,OAAO;GACf,UAAU,WAAW,aAAa;GAClC,YAAY,WAAW;GACvB,UAAU,SAAS;AACjB,QAAI,KACF;KAAE,MAAM;KAAU,SAAS,KAAK;KAAS,SAAS,KAAK;KAAS,OAAO,KAAK;KAAO,EACnF,+CACD;;GAEJ,CAAC;;CAGN,MAAM,YAAY,KAAK,KAAK;AAC5B,KAAI;EACF,MAAM,SAAS,MAAM,WAAW;AAChC,MAAI,MACF;GAAE,MAAM;GAAU,YAAY,KAAK,KAAK,GAAG;GAAW,SAAS;GAAM,EACrE,2BACD;AACD,SAAO;UACA,OAAO;EACd,MAAM,aAAa,KAAK,KAAK,GAAG;AAEhC,MAAI,iBAAiB,cAAc;AACjC,OAAI,MACF;IAAE,MAAM;IAAU,WAAW,MAAM;IAAW;IAAY,EAC1D,2BACD;AACD,SAAM,IAAI,MAAM,SAAS,SAAS,oBAAoB,MAAM,UAAU,KAAK,EAAE,OAAO,OAAO,CAAC;;EAG9F,MAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AAC3E,MAAI,MACF;GAAE,MAAM;GAAU,OAAO;GAAc;GAAY,EACnD,wBACD;AACD,QAAM,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,aAAa;;;;;;;AAQlE,SAAgB,uBACd,MACA,QACA,aAC0B;CAC1B,MAAM,WAAW,oBAAoB,KAAK;AAC1C,QAAO;EACL,GAAG;EACH,MAAM,QACJ,YACA,QACA,QACA,UACoC;GACpC,MAAM,YAAY,0BAA0B,MAAM,YAAY,QAAQ,QAAQ,UAAU,OAAO;AAC/F,UAAO,cAAc,YAAY,IAAI,UAAU,IAAI,GAAG,KAAK;;EAE9D;;;;;AAMH,SAAgB,wBACd,OACA,QACA,cAAc,IAAI,2BAA2B,EACtB;AACvB,QAAO,MAAM,KAAK,SAAS,uBAAuB,MAAM,QAAQ,YAAY,CAAC"}