@xopcai/xopc 0.0.120 → 0.0.121

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 (239) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/dashscope/src/image-generation-provider.d.ts +6 -5
  3. package/dist/extensions/dashscope/src/image-generation-provider.js +40 -64
  4. package/dist/extensions/dashscope/src/image-generation-provider.js.map +1 -1
  5. package/dist/extensions/dashscope/src/index.d.ts +1 -1
  6. package/dist/extensions/dashscope/src/index.js +2 -2
  7. package/dist/extensions/dashscope/xopc.extension.json +3 -2
  8. package/dist/extensions/minimax/src/image-generation-provider.d.ts +7 -5
  9. package/dist/extensions/minimax/src/image-generation-provider.js +43 -37
  10. package/dist/extensions/minimax/src/image-generation-provider.js.map +1 -1
  11. package/dist/extensions/minimax/src/index.d.ts +1 -1
  12. package/dist/extensions/minimax/src/index.js +2 -2
  13. package/dist/extensions/minimax/xopc.extension.json +1 -1
  14. package/dist/extensions/telegram/xopc.extension.json +1 -1
  15. package/dist/gateway/static/root/assets/Combination-HAlzriaz.js +41 -0
  16. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-APTlX9Sq.js → agent-avatar-dicebear-4AW4JgSU.js} +1 -1
  17. package/dist/gateway/static/root/assets/agents-2SVUUPlp.js +2 -0
  18. package/dist/gateway/static/root/assets/{app-management-settings-panel-DTKc5HHT.js → app-management-settings-panel-CE6GkGU2.js} +1 -1
  19. package/dist/gateway/static/root/assets/{appearance-settings-BTciFrE9.js → appearance-settings-BmaUjghw.js} +1 -1
  20. package/dist/gateway/static/root/assets/{apps-page-DT52OrbM.js → apps-page-BWeQ1I49.js} +1 -1
  21. package/dist/gateway/static/root/assets/{archive-plugin-CiHaUMII.js → archive-plugin-CX4_KWut.js} +1 -1
  22. package/dist/gateway/static/root/assets/{attachment-load-t4gSrNjL.js → attachment-load-jf-PIIUU.js} +1 -1
  23. package/dist/gateway/static/root/assets/{automation-suggestion-card-CtysjSoQ.js → automation-suggestion-card-BHk5rtJz.js} +1 -1
  24. package/dist/gateway/static/root/assets/automations-page-QHotl53D.js +1 -0
  25. package/dist/gateway/static/root/assets/{binary-plugins-BAbSldW5.js → binary-plugins-DG9XQikd.js} +1 -1
  26. package/dist/gateway/static/root/assets/{block-editor-BNr_82xl.js → block-editor-Up_VIxLJ.js} +6 -6
  27. package/dist/gateway/static/root/assets/browser-settings-page-Cmwi6IIQ.js +8 -0
  28. package/dist/gateway/static/root/assets/{capability-presets-api-tzD702Gg.js → capability-presets-api-CGM4OyMw.js} +1 -1
  29. package/dist/gateway/static/root/assets/{capability-presets-settings-panel-AGhTueAc.js → capability-presets-settings-panel-CzWt0LBb.js} +1 -1
  30. package/dist/gateway/static/root/assets/{channel-recipient-api-DL_KzwGE.js → channel-recipient-api-BEByL4_o.js} +1 -1
  31. package/dist/gateway/static/root/assets/channels-settings-BOMCUe2J.js +1 -0
  32. package/dist/gateway/static/root/assets/connectors-page-C-Dexk_F.js +2 -0
  33. package/dist/gateway/static/root/assets/dist-Mys7eFJ5.js +1 -0
  34. package/dist/gateway/static/root/assets/dist-wO9a2_ID.js +1 -0
  35. package/dist/gateway/static/root/assets/{dreaming-settings-BEwWA1yf.js → dreaming-settings-WPEj8pir.js} +1 -1
  36. package/dist/gateway/static/root/assets/{extension-debug-page-Dl2kO0Sf.js → extension-debug-page-DZxa_j7-.js} +1 -1
  37. package/dist/gateway/static/root/assets/{extension-page-DmbLPsx_.js → extension-page-CseziW53.js} +1 -1
  38. package/dist/gateway/static/root/assets/extension-settings-page-B_9J-xyo.js +1 -0
  39. package/dist/gateway/static/root/assets/{fetch-B3ME5Eri.js → fetch-Cs4ck8zA.js} +1 -1
  40. package/dist/gateway/static/root/assets/{gateway-config-api-DI9_BBTo.js → gateway-config-api-Dl223a7r.js} +1 -1
  41. package/dist/gateway/static/root/assets/gateway-settings-DdzbtW9R.js +1 -0
  42. package/dist/gateway/static/root/assets/{gateway-startup-retry-CSZQTz6K.js → gateway-startup-retry-IvePwjFE.js} +1 -1
  43. package/dist/gateway/static/root/assets/goal-detail-page-Dxldogms.js +1 -0
  44. package/dist/gateway/static/root/assets/{goals-config-api-Bhzqrp2w.js → goals-config-api-CUCP6yCl.js} +1 -1
  45. package/dist/gateway/static/root/assets/goals-page-DU5Nd6Xp.js +2 -0
  46. package/dist/gateway/static/root/assets/goals-settings-Dj87qPxv.js +1 -0
  47. package/dist/gateway/static/root/assets/heartbeat-settings-Cgm5JmwH.js +1 -0
  48. package/dist/gateway/static/root/assets/index-BU1pvxmS.js +191 -0
  49. package/dist/gateway/static/root/assets/index-l54WfSqe.css +1 -0
  50. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-kMp08SlX.js → keyboard-shortcuts-settings-DJgYw0nB.js} +1 -1
  51. package/dist/gateway/static/root/assets/logs-page-C10aS4XO.js +2 -0
  52. package/dist/gateway/static/root/assets/{markdown-split-Bc0w40vz.js → markdown-split-gnQ_EiGo.js} +1 -1
  53. package/dist/gateway/static/root/assets/{media-plugins-DJ_HvgIy.js → media-plugins-CQV-mgJn.js} +1 -1
  54. package/dist/gateway/static/root/assets/messages-C6Kc4Jhf.js +3 -0
  55. package/dist/gateway/static/root/assets/models-hub-panel-B35CAfg6.js +2 -0
  56. package/dist/gateway/static/root/assets/note-detail-page-Brlwse8x.js +2 -0
  57. package/dist/gateway/static/root/assets/{note-image-lightbox-4zntqpZq.js → note-image-lightbox-BthwKjLA.js} +1 -1
  58. package/dist/gateway/static/root/assets/{notes-page-VocWOjoK.js → notes-page-CoAwRc7L.js} +1 -1
  59. package/dist/gateway/static/root/assets/{preview-open-alternatives-BbA7sRd_.js → preview-open-alternatives-BPMXC-2u.js} +1 -1
  60. package/dist/gateway/static/root/assets/remote-access-hub-B36RQ05h.js +1 -0
  61. package/dist/gateway/static/root/assets/{save-bar-controls-Dividh61.js → save-bar-controls-BEHqwZjz.js} +1 -1
  62. package/dist/gateway/static/root/assets/schedule-field-CMY25m97.js +1 -0
  63. package/dist/gateway/static/root/assets/schema-form-CxjxsFfg.js +1 -0
  64. package/dist/gateway/static/root/assets/sessions-page-73THChnj.js +1 -0
  65. package/dist/gateway/static/root/assets/settings-advanced-gate-CR9VsY-R.js +1 -0
  66. package/dist/gateway/static/root/assets/{settings-collapsible-section-BMCOfuE_.js → settings-collapsible-section-s-kiLAxP.js} +1 -1
  67. package/dist/gateway/static/root/assets/{settings-form-section-DBkGwpIN.js → settings-form-section-CIxIEYxU.js} +1 -1
  68. package/dist/gateway/static/root/assets/settings-page-BJ1AhfKI.js +1 -0
  69. package/dist/gateway/static/root/assets/{settings-page-layout-NxoW5Y57.js → settings-page-layout--6qT7ZJa.js} +1 -1
  70. package/dist/gateway/static/root/assets/setup-status-panel-C1TtS1vJ.js +1 -0
  71. package/dist/gateway/static/root/assets/{share-preview-page-B0suFCfj.js → share-preview-page-BZADSbS1.js} +1 -1
  72. package/dist/gateway/static/root/assets/shares-settings-gqC8Rmhy.js +1 -0
  73. package/dist/gateway/static/root/assets/skill-reload-api-C9P-WYvS.js +1 -0
  74. package/dist/gateway/static/root/assets/skills-page-CEpSvOhF.js +2 -0
  75. package/dist/gateway/static/root/assets/{system-settings-panel-CrmEnyX4.js → system-settings-panel-Czjf0kLJ.js} +1 -1
  76. package/dist/gateway/static/root/assets/{typed-models-lib-BAsGn25g.js → typed-models-lib-Cmt8SYdI.js} +1 -1
  77. package/dist/gateway/static/root/assets/{use-channel-catalog-C7vcAoGb.js → use-channel-catalog-DyM-ckEQ.js} +1 -1
  78. package/dist/gateway/static/root/assets/user-profile-settings-panel-BygLv8vm.js +1 -0
  79. package/dist/gateway/static/root/assets/{utils-Dl9efgnV.js → utils-CloW8coB.js} +1 -1
  80. package/dist/gateway/static/root/assets/voice-api-key-field-BsoqJba6.js +1 -0
  81. package/dist/gateway/static/root/assets/{voice-player-store-CMC5NOJp.js → voice-player-store-DduWlJYV.js} +1 -1
  82. package/dist/gateway/static/root/assets/workflow-run-setup-panel-CcQeX6l5.js +3 -0
  83. package/dist/gateway/static/root/assets/workflows-page-CeA8A9Ef.js +18 -0
  84. package/dist/gateway/static/root/index.html +7 -8
  85. package/dist/package.js +1 -1
  86. package/dist/src/agent/embedded/xopc-auth-storage.d.ts +1 -5
  87. package/dist/src/agent/embedded/xopc-auth-storage.js +2 -8
  88. package/dist/src/agent/embedded/xopc-auth-storage.js.map +1 -1
  89. package/dist/src/agent/embedded/xopc-tools-bridge.js +23 -0
  90. package/dist/src/agent/embedded/xopc-tools-bridge.js.map +1 -1
  91. package/dist/src/agent/goals/checklist-judge.js +1 -1
  92. package/dist/src/agent/goals/checklist-judge.js.map +1 -1
  93. package/dist/src/agent/goals/judge.js +1 -1
  94. package/dist/src/agent/goals/judge.js.map +1 -1
  95. package/dist/src/agent/image/tool-model-config.d.ts +2 -0
  96. package/dist/src/agent/image/tool-model-config.js +2 -2
  97. package/dist/src/agent/image/tool-model-config.js.map +1 -1
  98. package/dist/src/agent/image/understanding/pi-ai-provider.js +1 -1
  99. package/dist/src/agent/image/understanding/pi-ai-provider.js.map +1 -1
  100. package/dist/src/agent/memory/compaction.d.ts +1 -1
  101. package/dist/src/agent/memory/compaction.js +1 -1
  102. package/dist/src/agent/memory/compaction.js.map +1 -1
  103. package/dist/src/agent/middleware/self-verify.d.ts +3 -0
  104. package/dist/src/agent/middleware/self-verify.js +18 -0
  105. package/dist/src/agent/middleware/self-verify.js.map +1 -1
  106. package/dist/src/agent/orchestration/agent-event-handler.js +28 -3
  107. package/dist/src/agent/orchestration/agent-event-handler.js.map +1 -1
  108. package/dist/src/agent/prompt/sections/tooling.js +21 -5
  109. package/dist/src/agent/prompt/sections/tooling.js.map +1 -1
  110. package/dist/src/agent/prompt/service-prompt-builder.js +1 -0
  111. package/dist/src/agent/prompt/service-prompt-builder.js.map +1 -1
  112. package/dist/src/agent/prompt/system-prompt.d.ts +1 -0
  113. package/dist/src/agent/prompt/system-prompt.js +13 -1
  114. package/dist/src/agent/prompt/system-prompt.js.map +1 -1
  115. package/dist/src/agent/sandbox/path-policy.js +30 -6
  116. package/dist/src/agent/sandbox/path-policy.js.map +1 -1
  117. package/dist/src/agent/service/btw-query.js +1 -1
  118. package/dist/src/agent/service/btw-query.js.map +1 -1
  119. package/dist/src/agent/starter-agents.js +16 -16
  120. package/dist/src/agent/starter-agents.js.map +1 -1
  121. package/dist/src/agent/tools/image-generate-tool.js +12 -1
  122. package/dist/src/agent/tools/image-generate-tool.js.map +1 -1
  123. package/dist/src/agent/tools/session-search-tool.js +1 -1
  124. package/dist/src/agent/tools/session-search-tool.js.map +1 -1
  125. package/dist/src/agent/tools/web-extract.js +1 -1
  126. package/dist/src/agent/tools/web-extract.js.map +1 -1
  127. package/dist/src/agent-manifest/index.js +2 -2
  128. package/dist/src/agent-manifest/schema.d.ts +66 -0
  129. package/dist/src/agent-manifest/schema.js +28 -2
  130. package/dist/src/agent-manifest/schema.js.map +1 -1
  131. package/dist/src/automations/draft/automation-draft-service.js +1 -1
  132. package/dist/src/automations/draft/automation-draft-service.js.map +1 -1
  133. package/dist/src/cli/commands/agent.js +2 -2
  134. package/dist/src/cli/commands/gateway/lifecycle.js +1 -1
  135. package/dist/src/cli/commands/models.js +1 -1
  136. package/dist/src/cli/commands/onboard/model.js +161 -11
  137. package/dist/src/cli/commands/onboard/model.js.map +1 -1
  138. package/dist/src/cli/commands/onboard.js +1 -1
  139. package/dist/src/cli/commands/profile.js +1 -1
  140. package/dist/src/cli/commands/tunnel.js +1 -1
  141. package/dist/src/cli/commands/update.js +1 -1
  142. package/dist/src/cli/extension-cli-register.js +1 -1
  143. package/dist/src/config/agent-profile.js +1 -1
  144. package/dist/src/config/default-model.d.ts +11 -0
  145. package/dist/src/config/default-model.js +19 -0
  146. package/dist/src/config/default-model.js.map +1 -0
  147. package/dist/src/config/index.js +3 -3
  148. package/dist/src/config/schema.d.ts +71 -11
  149. package/dist/src/config/schema.js +57 -22
  150. package/dist/src/config/schema.js.map +1 -1
  151. package/dist/src/connectors/registries/smithery.js +10 -9
  152. package/dist/src/connectors/registries/smithery.js.map +1 -1
  153. package/dist/src/daemon/install-plan.js +2 -1
  154. package/dist/src/daemon/install-plan.js.map +1 -1
  155. package/dist/src/gateway/agents-admin.js +1 -1
  156. package/dist/src/gateway/global-defaults-admin.js +6 -16
  157. package/dist/src/gateway/global-defaults-admin.js.map +1 -1
  158. package/dist/src/gateway/hono/lib/config-payload.d.ts +24 -0
  159. package/dist/src/gateway/hono/routes/agents.js +1 -79
  160. package/dist/src/gateway/hono/routes/agents.js.map +1 -1
  161. package/dist/src/gateway/hono/routes/models.js +132 -14
  162. package/dist/src/gateway/hono/routes/models.js.map +1 -1
  163. package/dist/src/gateway/hono/routes/voice.js +1 -1
  164. package/dist/src/gateway/hono/routes/voice.js.map +1 -1
  165. package/dist/src/gateway/image-capabilities.d.ts +11 -1
  166. package/dist/src/gateway/image-capabilities.js +32 -43
  167. package/dist/src/gateway/image-capabilities.js.map +1 -1
  168. package/dist/src/gateway/run-loop.js +1 -1
  169. package/dist/src/gateway/workspace-editor-path.js +9 -1
  170. package/dist/src/gateway/workspace-editor-path.js.map +1 -1
  171. package/dist/src/generated/bundled-image-generation-providers.d.ts +4 -2
  172. package/dist/src/generated/bundled-image-generation-providers.js +6 -4
  173. package/dist/src/generated/bundled-image-generation-providers.js.map +1 -1
  174. package/dist/src/mcp/channel-bridge.js +1 -1
  175. package/dist/src/media/paths.js +3 -4
  176. package/dist/src/media/paths.js.map +1 -1
  177. package/dist/src/notes/service.js +1 -1
  178. package/dist/src/notes/service.js.map +1 -1
  179. package/dist/src/providers/domestic-presets.d.ts +615 -0
  180. package/dist/src/providers/domestic-presets.js +834 -0
  181. package/dist/src/providers/domestic-presets.js.map +1 -0
  182. package/dist/src/providers/env-keys.js +29 -5
  183. package/dist/src/providers/env-keys.js.map +1 -1
  184. package/dist/src/providers/extension-stream-bridge.js +1 -1
  185. package/dist/src/providers/extension-stream-bridge.js.map +1 -1
  186. package/dist/src/providers/index.d.ts +5 -5
  187. package/dist/src/providers/index.js +101 -21
  188. package/dist/src/providers/index.js.map +1 -1
  189. package/dist/src/providers/model-discovery.d.ts +17 -0
  190. package/dist/src/providers/model-discovery.js +61 -0
  191. package/dist/src/providers/model-discovery.js.map +1 -0
  192. package/dist/src/providers/model-registry.d.ts +1 -1
  193. package/dist/src/providers/model-registry.js +1 -1
  194. package/dist/src/providers/model-registry.js.map +1 -1
  195. package/dist/src/providers/presentation.js +185 -14
  196. package/dist/src/providers/presentation.js.map +1 -1
  197. package/dist/src/session/session-title.js +1 -1
  198. package/dist/src/session/session-title.js.map +1 -1
  199. package/dist/src/tui/xopc-tui-keybindings.d.ts +1 -1
  200. package/dist/src/voice/tts/summarize.js +1 -1
  201. package/dist/src/voice/tts/summarize.js.map +1 -1
  202. package/dist/src/workflows/draft/workflow-draft-service.js +1 -1
  203. package/dist/src/workflows/draft/workflow-draft-service.js.map +1 -1
  204. package/package.json +6 -6
  205. package/dist/gateway/static/root/assets/Combination-BzaXOLd9.js +0 -41
  206. package/dist/gateway/static/root/assets/agents-CFW_FS_V.js +0 -2
  207. package/dist/gateway/static/root/assets/automations-page-B2YvT3ES.js +0 -1
  208. package/dist/gateway/static/root/assets/browser-settings-page-BQEXPhDU.js +0 -8
  209. package/dist/gateway/static/root/assets/channels-settings-DRTutLlN.js +0 -1
  210. package/dist/gateway/static/root/assets/connectors-page-DneJVuge.js +0 -2
  211. package/dist/gateway/static/root/assets/dist-By6Rh2ee.js +0 -1
  212. package/dist/gateway/static/root/assets/dist-CkobR7mk.js +0 -1
  213. package/dist/gateway/static/root/assets/dist-DokQNVMQ.js +0 -1
  214. package/dist/gateway/static/root/assets/extension-settings-page-BS2rx40e.js +0 -1
  215. package/dist/gateway/static/root/assets/gateway-settings-DAFI_T0W.js +0 -1
  216. package/dist/gateway/static/root/assets/goal-detail-page-DLuz4jC9.js +0 -1
  217. package/dist/gateway/static/root/assets/goals-page-DTAfv9Ft.js +0 -2
  218. package/dist/gateway/static/root/assets/goals-settings-CWN2iirQ.js +0 -1
  219. package/dist/gateway/static/root/assets/heartbeat-settings-CkkkHvOi.js +0 -1
  220. package/dist/gateway/static/root/assets/index-CnjzMw5i.js +0 -191
  221. package/dist/gateway/static/root/assets/index-E1QinYKp.css +0 -1
  222. package/dist/gateway/static/root/assets/logs-page-CDXDlS_W.js +0 -2
  223. package/dist/gateway/static/root/assets/messages-CISb1HLv.js +0 -3
  224. package/dist/gateway/static/root/assets/models-hub-panel-BTwxMIgZ.js +0 -2
  225. package/dist/gateway/static/root/assets/note-detail-page-BBfOeB0m.js +0 -2
  226. package/dist/gateway/static/root/assets/remote-access-hub-CXwjuD_Q.js +0 -1
  227. package/dist/gateway/static/root/assets/schedule-field-pRheF2CO.js +0 -1
  228. package/dist/gateway/static/root/assets/schema-form-D6PiPxfr.js +0 -1
  229. package/dist/gateway/static/root/assets/sessions-page-CpcVySQK.js +0 -1
  230. package/dist/gateway/static/root/assets/settings-advanced-gate-BVy_TawT.js +0 -1
  231. package/dist/gateway/static/root/assets/settings-page-CZpNyCWn.js +0 -1
  232. package/dist/gateway/static/root/assets/setup-status-panel-MVHLLu_a.js +0 -1
  233. package/dist/gateway/static/root/assets/shares-settings-C6DBbQGo.js +0 -1
  234. package/dist/gateway/static/root/assets/skill-reload-api-6hJmhwSn.js +0 -1
  235. package/dist/gateway/static/root/assets/skills-page-Im1MA0Iw.js +0 -2
  236. package/dist/gateway/static/root/assets/user-profile-settings-panel-QAQWGelF.js +0 -1
  237. package/dist/gateway/static/root/assets/voice-api-key-field-D-6XueTv.js +0 -1
  238. package/dist/gateway/static/root/assets/workflow-run-setup-panel-DVPllR4k.js +0 -3
  239. package/dist/gateway/static/root/assets/workflows-page-B94WISTW.js +0 -18
@@ -2,6 +2,8 @@ import type { AgentModelConfig, Config } from '../../config/schema.js';
2
2
  export type ToolModelConfig = {
3
3
  primary?: string;
4
4
  fallbacks?: string[];
5
+ timeoutMs?: number;
6
+ autoProviderFallback?: boolean;
5
7
  };
6
8
  export declare function hasToolModelConfig(model: ToolModelConfig | undefined): boolean;
7
9
  export declare function resolveDefaultModelRef(cfg?: Config): {
@@ -1,4 +1,4 @@
1
- import { getAgentDefaultModelRef, init_schema, parseModelRef } from "../../config/schema.js";
1
+ import { getAgentDefaultImageModelConfig, getAgentDefaultModelRef, init_schema, parseModelRef } from "../../config/schema.js";
2
2
  import { getDefaultModelSync, getModelsByProvider, init_providers, isProviderConfiguredSync } from "../../providers/index.js";
3
3
  import { resolveAgentModelFallbackValues, resolveAgentModelPrimaryValue } from "../../config/model-input.js";
4
4
  //#region src/agent/image/tool-model-config.ts
@@ -52,7 +52,7 @@ function firstVisionModelRef(provider) {
52
52
  * Effective image understanding model inferred from configured providers.
53
53
  */
54
54
  function resolveImageModelConfigForTool(params) {
55
- const explicit = coerceToolModelConfig(void 0);
55
+ const explicit = coerceToolModelConfig(params.cfg ? getAgentDefaultImageModelConfig(params.cfg) : void 0);
56
56
  if (hasToolModelConfig(explicit)) return explicit;
57
57
  const primary = resolveDefaultModelRef(params.cfg);
58
58
  const primaryCandidates = [];
@@ -1 +1 @@
1
- {"version":3,"file":"tool-model-config.js","names":[],"sources":["../../../../src/agent/image/tool-model-config.ts"],"sourcesContent":["import type { AgentModelConfig, Config } from '../../config/schema.js';\nimport { getAgentDefaultModelRef, parseModelRef } from '../../config/schema.js';\nimport {\n resolveAgentModelFallbackValues,\n resolveAgentModelPrimaryValue,\n} from '../../config/model-input.js';\nimport { getDefaultModelSync, getModelsByProvider, isProviderConfiguredSync } from '../../providers/index.js';\n\nexport type ToolModelConfig = { primary?: string; fallbacks?: string[] };\n\nexport function hasToolModelConfig(model: ToolModelConfig | undefined): boolean {\n return Boolean(\n model?.primary?.trim() || (model?.fallbacks ?? []).some((entry) => entry.trim().length > 0),\n );\n}\n\nexport function resolveDefaultModelRef(cfg?: Config): { provider: string; model: string } {\n const ref = cfg ? getAgentDefaultModelRef(cfg) : undefined;\n if (ref) {\n const p = parseModelRef(ref);\n if (p) {\n return p;\n }\n }\n const fallback = getDefaultModelSync(cfg);\n const p2 = parseModelRef(fallback);\n if (p2) {\n return p2;\n }\n return { provider: 'deepseek', model: 'deepseek-v4-flash' };\n}\n\nexport function coerceToolModelConfig(model?: AgentModelConfig): ToolModelConfig {\n const primary = resolveAgentModelPrimaryValue(model);\n const fallbacks = resolveAgentModelFallbackValues(model);\n return {\n ...(primary?.trim() ? { primary: primary.trim() } : {}),\n ...(fallbacks.length > 0 ? { fallbacks } : {}),\n };\n}\n\nexport function buildToolModelConfigFromCandidates(params: {\n explicit: ToolModelConfig;\n candidates: Array<string | null | undefined>;\n}): ToolModelConfig | null {\n if (hasToolModelConfig(params.explicit)) {\n return params.explicit;\n }\n\n const deduped: string[] = [];\n for (const candidate of params.candidates) {\n const trimmed = candidate?.trim();\n if (!trimmed || !trimmed.includes('/')) {\n continue;\n }\n const provider = trimmed.slice(0, trimmed.indexOf('/')).trim();\n if (!provider || !isProviderConfiguredSync(provider)) {\n continue;\n }\n if (!deduped.includes(trimmed)) {\n deduped.push(trimmed);\n }\n }\n\n if (deduped.length === 0) {\n return null;\n }\n\n return {\n primary: deduped[0],\n ...(deduped.length > 1 ? { fallbacks: deduped.slice(1) } : {}),\n };\n}\n\nfunction firstVisionModelRef(provider: string): string | undefined {\n const m = getModelsByProvider(provider).find((x) => x.input?.includes('image'));\n return m ? `${provider}/${m.id}` : undefined;\n}\n\n/**\n * Effective image understanding model inferred from configured providers.\n */\nexport function resolveImageModelConfigForTool(params: { cfg?: Config }): ToolModelConfig | null {\n const explicit = coerceToolModelConfig(undefined);\n if (hasToolModelConfig(explicit)) {\n return explicit;\n }\n\n const primary = resolveDefaultModelRef(params.cfg);\n const primaryCandidates: string[] = [];\n const vision = firstVisionModelRef(primary.provider);\n if (vision) {\n primaryCandidates.push(vision);\n }\n if (primary.provider === 'openai') {\n primaryCandidates.push('openai/gpt-4o-mini');\n }\n if (primary.provider === 'anthropic') {\n primaryCandidates.push('anthropic/claude-sonnet-4-5');\n }\n if (primary.provider === 'google') {\n primaryCandidates.push('google/gemini-2.0-flash');\n }\n\n return buildToolModelConfigFromCandidates({\n explicit,\n candidates: [\n ...primaryCandidates,\n firstVisionModelRef('openai') ?? 'openai/gpt-4o-mini',\n firstVisionModelRef('anthropic') ?? 'anthropic/claude-sonnet-4-5',\n ],\n });\n}\n"],"mappings":";;;;aACgF;gBAK8B;AAI9G,SAAgB,mBAAmB,OAA6C;AAC9E,QAAO,QACL,OAAO,SAAS,MAAM,KAAK,OAAO,aAAa,EAAE,EAAE,MAAM,UAAU,MAAM,MAAM,CAAC,SAAS,EAAE,CAC5F;;AAGH,SAAgB,uBAAuB,KAAmD;CACxF,MAAM,MAAM,MAAM,wBAAwB,IAAI,GAAG,KAAA;AACjD,KAAI,KAAK;EACP,MAAM,IAAI,cAAc,IAAI;AAC5B,MAAI,EACF,QAAO;;CAIX,MAAM,KAAK,cADM,oBAAoB,IACJ,CAAC;AAClC,KAAI,GACF,QAAO;AAET,QAAO;EAAE,UAAU;EAAY,OAAO;EAAqB;;AAG7D,SAAgB,sBAAsB,OAA2C;CAC/E,MAAM,UAAU,8BAA8B,MAAM;CACpD,MAAM,YAAY,gCAAgC,MAAM;AACxD,QAAO;EACL,GAAI,SAAS,MAAM,GAAG,EAAE,SAAS,QAAQ,MAAM,EAAE,GAAG,EAAE;EACtD,GAAI,UAAU,SAAS,IAAI,EAAE,WAAW,GAAG,EAAE;EAC9C;;AAGH,SAAgB,mCAAmC,QAGxB;AACzB,KAAI,mBAAmB,OAAO,SAAS,CACrC,QAAO,OAAO;CAGhB,MAAM,UAAoB,EAAE;AAC5B,MAAK,MAAM,aAAa,OAAO,YAAY;EACzC,MAAM,UAAU,WAAW,MAAM;AACjC,MAAI,CAAC,WAAW,CAAC,QAAQ,SAAS,IAAI,CACpC;EAEF,MAAM,WAAW,QAAQ,MAAM,GAAG,QAAQ,QAAQ,IAAI,CAAC,CAAC,MAAM;AAC9D,MAAI,CAAC,YAAY,CAAC,yBAAyB,SAAS,CAClD;AAEF,MAAI,CAAC,QAAQ,SAAS,QAAQ,CAC5B,SAAQ,KAAK,QAAQ;;AAIzB,KAAI,QAAQ,WAAW,EACrB,QAAO;AAGT,QAAO;EACL,SAAS,QAAQ;EACjB,GAAI,QAAQ,SAAS,IAAI,EAAE,WAAW,QAAQ,MAAM,EAAE,EAAE,GAAG,EAAE;EAC9D;;AAGH,SAAS,oBAAoB,UAAsC;CACjE,MAAM,IAAI,oBAAoB,SAAS,CAAC,MAAM,MAAM,EAAE,OAAO,SAAS,QAAQ,CAAC;AAC/E,QAAO,IAAI,GAAG,SAAS,GAAG,EAAE,OAAO,KAAA;;;;;AAMrC,SAAgB,+BAA+B,QAAkD;CAC/F,MAAM,WAAW,sBAAsB,KAAA,EAAU;AACjD,KAAI,mBAAmB,SAAS,CAC9B,QAAO;CAGT,MAAM,UAAU,uBAAuB,OAAO,IAAI;CAClD,MAAM,oBAA8B,EAAE;CACtC,MAAM,SAAS,oBAAoB,QAAQ,SAAS;AACpD,KAAI,OACF,mBAAkB,KAAK,OAAO;AAEhC,KAAI,QAAQ,aAAa,SACvB,mBAAkB,KAAK,qBAAqB;AAE9C,KAAI,QAAQ,aAAa,YACvB,mBAAkB,KAAK,8BAA8B;AAEvD,KAAI,QAAQ,aAAa,SACvB,mBAAkB,KAAK,0BAA0B;AAGnD,QAAO,mCAAmC;EACxC;EACA,YAAY;GACV,GAAG;GACH,oBAAoB,SAAS,IAAI;GACjC,oBAAoB,YAAY,IAAI;GACrC;EACF,CAAC"}
1
+ {"version":3,"file":"tool-model-config.js","names":[],"sources":["../../../../src/agent/image/tool-model-config.ts"],"sourcesContent":["import type { AgentModelConfig, Config } from '../../config/schema.js';\nimport {\n getAgentDefaultImageModelConfig,\n getAgentDefaultModelRef,\n parseModelRef,\n} from '../../config/schema.js';\nimport {\n resolveAgentModelFallbackValues,\n resolveAgentModelPrimaryValue,\n} from '../../config/model-input.js';\nimport { getDefaultModelSync, getModelsByProvider, isProviderConfiguredSync } from '../../providers/index.js';\n\nexport type ToolModelConfig = {\n primary?: string;\n fallbacks?: string[];\n timeoutMs?: number;\n autoProviderFallback?: boolean;\n};\n\nexport function hasToolModelConfig(model: ToolModelConfig | undefined): boolean {\n return Boolean(\n model?.primary?.trim() || (model?.fallbacks ?? []).some((entry) => entry.trim().length > 0),\n );\n}\n\nexport function resolveDefaultModelRef(cfg?: Config): { provider: string; model: string } {\n const ref = cfg ? getAgentDefaultModelRef(cfg) : undefined;\n if (ref) {\n const p = parseModelRef(ref);\n if (p) {\n return p;\n }\n }\n const fallback = getDefaultModelSync(cfg);\n const p2 = parseModelRef(fallback);\n if (p2) {\n return p2;\n }\n return { provider: 'deepseek', model: 'deepseek-v4-flash' };\n}\n\nexport function coerceToolModelConfig(model?: AgentModelConfig): ToolModelConfig {\n const primary = resolveAgentModelPrimaryValue(model);\n const fallbacks = resolveAgentModelFallbackValues(model);\n return {\n ...(primary?.trim() ? { primary: primary.trim() } : {}),\n ...(fallbacks.length > 0 ? { fallbacks } : {}),\n };\n}\n\nexport function buildToolModelConfigFromCandidates(params: {\n explicit: ToolModelConfig;\n candidates: Array<string | null | undefined>;\n}): ToolModelConfig | null {\n if (hasToolModelConfig(params.explicit)) {\n return params.explicit;\n }\n\n const deduped: string[] = [];\n for (const candidate of params.candidates) {\n const trimmed = candidate?.trim();\n if (!trimmed || !trimmed.includes('/')) {\n continue;\n }\n const provider = trimmed.slice(0, trimmed.indexOf('/')).trim();\n if (!provider || !isProviderConfiguredSync(provider)) {\n continue;\n }\n if (!deduped.includes(trimmed)) {\n deduped.push(trimmed);\n }\n }\n\n if (deduped.length === 0) {\n return null;\n }\n\n return {\n primary: deduped[0],\n ...(deduped.length > 1 ? { fallbacks: deduped.slice(1) } : {}),\n };\n}\n\nfunction firstVisionModelRef(provider: string): string | undefined {\n const m = getModelsByProvider(provider).find((x) => x.input?.includes('image'));\n return m ? `${provider}/${m.id}` : undefined;\n}\n\n/**\n * Effective image understanding model inferred from configured providers.\n */\nexport function resolveImageModelConfigForTool(params: { cfg?: Config }): ToolModelConfig | null {\n const explicit = coerceToolModelConfig(\n params.cfg ? getAgentDefaultImageModelConfig(params.cfg) : undefined,\n );\n if (hasToolModelConfig(explicit)) {\n return explicit;\n }\n\n const primary = resolveDefaultModelRef(params.cfg);\n const primaryCandidates: string[] = [];\n const vision = firstVisionModelRef(primary.provider);\n if (vision) {\n primaryCandidates.push(vision);\n }\n if (primary.provider === 'openai') {\n primaryCandidates.push('openai/gpt-4o-mini');\n }\n if (primary.provider === 'anthropic') {\n primaryCandidates.push('anthropic/claude-sonnet-4-5');\n }\n if (primary.provider === 'google') {\n primaryCandidates.push('google/gemini-2.0-flash');\n }\n\n return buildToolModelConfigFromCandidates({\n explicit,\n candidates: [\n ...primaryCandidates,\n firstVisionModelRef('openai') ?? 'openai/gpt-4o-mini',\n firstVisionModelRef('anthropic') ?? 'anthropic/claude-sonnet-4-5',\n ],\n });\n}\n"],"mappings":";;;;aAKgC;gBAK8E;AAS9G,SAAgB,mBAAmB,OAA6C;AAC9E,QAAO,QACL,OAAO,SAAS,MAAM,KAAK,OAAO,aAAa,EAAE,EAAE,MAAM,UAAU,MAAM,MAAM,CAAC,SAAS,EAAE,CAC5F;;AAGH,SAAgB,uBAAuB,KAAmD;CACxF,MAAM,MAAM,MAAM,wBAAwB,IAAI,GAAG,KAAA;AACjD,KAAI,KAAK;EACP,MAAM,IAAI,cAAc,IAAI;AAC5B,MAAI,EACF,QAAO;;CAIX,MAAM,KAAK,cADM,oBAAoB,IACJ,CAAC;AAClC,KAAI,GACF,QAAO;AAET,QAAO;EAAE,UAAU;EAAY,OAAO;EAAqB;;AAG7D,SAAgB,sBAAsB,OAA2C;CAC/E,MAAM,UAAU,8BAA8B,MAAM;CACpD,MAAM,YAAY,gCAAgC,MAAM;AACxD,QAAO;EACL,GAAI,SAAS,MAAM,GAAG,EAAE,SAAS,QAAQ,MAAM,EAAE,GAAG,EAAE;EACtD,GAAI,UAAU,SAAS,IAAI,EAAE,WAAW,GAAG,EAAE;EAC9C;;AAGH,SAAgB,mCAAmC,QAGxB;AACzB,KAAI,mBAAmB,OAAO,SAAS,CACrC,QAAO,OAAO;CAGhB,MAAM,UAAoB,EAAE;AAC5B,MAAK,MAAM,aAAa,OAAO,YAAY;EACzC,MAAM,UAAU,WAAW,MAAM;AACjC,MAAI,CAAC,WAAW,CAAC,QAAQ,SAAS,IAAI,CACpC;EAEF,MAAM,WAAW,QAAQ,MAAM,GAAG,QAAQ,QAAQ,IAAI,CAAC,CAAC,MAAM;AAC9D,MAAI,CAAC,YAAY,CAAC,yBAAyB,SAAS,CAClD;AAEF,MAAI,CAAC,QAAQ,SAAS,QAAQ,CAC5B,SAAQ,KAAK,QAAQ;;AAIzB,KAAI,QAAQ,WAAW,EACrB,QAAO;AAGT,QAAO;EACL,SAAS,QAAQ;EACjB,GAAI,QAAQ,SAAS,IAAI,EAAE,WAAW,QAAQ,MAAM,EAAE,EAAE,GAAG,EAAE;EAC9D;;AAGH,SAAS,oBAAoB,UAAsC;CACjE,MAAM,IAAI,oBAAoB,SAAS,CAAC,MAAM,MAAM,EAAE,OAAO,SAAS,QAAQ,CAAC;AAC/E,QAAO,IAAI,GAAG,SAAS,GAAG,EAAE,OAAO,KAAA;;;;;AAMrC,SAAgB,+BAA+B,QAAkD;CAC/F,MAAM,WAAW,sBACf,OAAO,MAAM,gCAAgC,OAAO,IAAI,GAAG,KAAA,EAC5D;AACD,KAAI,mBAAmB,SAAS,CAC9B,QAAO;CAGT,MAAM,UAAU,uBAAuB,OAAO,IAAI;CAClD,MAAM,oBAA8B,EAAE;CACtC,MAAM,SAAS,oBAAoB,QAAQ,SAAS;AACpD,KAAI,OACF,mBAAkB,KAAK,OAAO;AAEhC,KAAI,QAAQ,aAAa,SACvB,mBAAkB,KAAK,qBAAqB;AAE9C,KAAI,QAAQ,aAAa,YACvB,mBAAkB,KAAK,8BAA8B;AAEvD,KAAI,QAAQ,aAAa,SACvB,mBAAkB,KAAK,0BAA0B;AAGnD,QAAO,mCAAmC;EACxC;EACA,YAAY;GACV,GAAG;GACH,oBAAoB,SAAS,IAAI;GACjC,oBAAoB,YAAY,IAAI;GACrC;EACF,CAAC"}
@@ -1,7 +1,7 @@
1
1
  import { getApiKey, getModelsByProvider, init_providers, resolveModel } from "../../../providers/index.js";
2
2
  import { registerImageUnderstandingProvider, registerImageUnderstandingProviderFactory } from "./provider-registry.js";
3
3
  import { coerceImageAssistantText } from "../image-helpers.js";
4
- import { complete } from "@earendil-works/pi-ai";
4
+ import { complete } from "@earendil-works/pi-ai/compat";
5
5
  //#region src/agent/image/understanding/pi-ai-provider.ts
6
6
  init_providers();
7
7
  function resolveMaxTokens(modelMaxTokens, requestedMaxTokens = 4096) {
@@ -1 +1 @@
1
- {"version":3,"file":"pi-ai-provider.js","names":[],"sources":["../../../../../src/agent/image/understanding/pi-ai-provider.ts"],"sourcesContent":["import { complete, type Api, type Context, type Model } from '@earendil-works/pi-ai';\nimport { resolveModel, getApiKey, getModelsByProvider } from '../../../providers/index.js';\nimport { coerceImageAssistantText } from '../image-helpers.js';\nimport {\n registerImageUnderstandingProvider,\n registerImageUnderstandingProviderFactory,\n} from './provider-registry.js';\nimport type { ImageUnderstandingProvider } from './types.js';\n\nfunction resolveMaxTokens(modelMaxTokens: number | undefined, requestedMaxTokens = 4096): number {\n if (\n typeof modelMaxTokens !== 'number' ||\n !Number.isFinite(modelMaxTokens) ||\n modelMaxTokens <= 0\n ) {\n return requestedMaxTokens;\n }\n return Math.min(requestedMaxTokens, modelMaxTokens);\n}\n\nexport function buildPiAiImageUnderstandingProvider(providerId: string): ImageUnderstandingProvider {\n return {\n id: providerId,\n label: `pi-ai (${providerId})`,\n async isConfigured() {\n const apiKey = await getApiKey(providerId);\n return Boolean(apiKey);\n },\n async describeImages(modelId, request) {\n const modelRef = `${providerId}/${modelId}`;\n const model = resolveModel(modelRef) as Model<Api>;\n if (!model.input?.includes('image')) {\n throw new Error(`Model does not support images: ${modelRef}`);\n }\n const apiKey = await getApiKey(providerId);\n if (!apiKey) {\n throw new Error(`No API key configured for provider: ${providerId}`);\n }\n\n const context: Context = {\n messages: [\n {\n role: 'user',\n content: [\n { type: 'text', text: request.prompt },\n ...request.images.map((img) => ({\n type: 'image' as const,\n data: img.buffer.toString('base64'),\n mimeType: img.mimeType || 'image/jpeg',\n })),\n ],\n timestamp: Date.now(),\n },\n ],\n };\n\n const maxTokens = resolveMaxTokens(model.maxTokens, request.maxTokens ?? 512);\n const timeoutMs = request.timeoutMs ?? 60_000;\n const timeoutSignal =\n typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function'\n ? AbortSignal.timeout(timeoutMs)\n : undefined;\n const signal = (() => {\n if (timeoutSignal && request.signal && typeof AbortSignal.any === 'function') {\n return AbortSignal.any([request.signal, timeoutSignal]);\n }\n if (request.signal) {\n return request.signal;\n }\n if (timeoutSignal) {\n return timeoutSignal;\n }\n const controller = new AbortController();\n setTimeout(() => controller.abort(), timeoutMs);\n return controller.signal;\n })();\n\n const message = await complete(model, context, {\n apiKey,\n maxTokens,\n signal,\n });\n\n const text = coerceImageAssistantText({\n message,\n provider: providerId,\n model: modelId,\n });\n return { text, provider: providerId, model: modelId };\n },\n };\n}\n\nfor (const providerId of ['openai', 'anthropic', 'google']) {\n registerImageUnderstandingProvider(buildPiAiImageUnderstandingProvider(providerId));\n}\n\nregisterImageUnderstandingProviderFactory((providerId) => {\n const hasVisionModel = getModelsByProvider(providerId).some((model) =>\n model.input?.includes('image'),\n );\n return hasVisionModel ? buildPiAiImageUnderstandingProvider(providerId) : undefined;\n});\n"],"mappings":";;;;;gBAC2F;AAQ3F,SAAS,iBAAiB,gBAAoC,qBAAqB,MAAc;AAC/F,KACE,OAAO,mBAAmB,YAC1B,CAAC,OAAO,SAAS,eAAe,IAChC,kBAAkB,EAElB,QAAO;AAET,QAAO,KAAK,IAAI,oBAAoB,eAAe;;AAGrD,SAAgB,oCAAoC,YAAgD;AAClG,QAAO;EACL,IAAI;EACJ,OAAO,UAAU,WAAW;EAC5B,MAAM,eAAe;GACnB,MAAM,SAAS,MAAM,UAAU,WAAW;AAC1C,UAAO,QAAQ,OAAO;;EAExB,MAAM,eAAe,SAAS,SAAS;GACrC,MAAM,WAAW,GAAG,WAAW,GAAG;GAClC,MAAM,QAAQ,aAAa,SAAS;AACpC,OAAI,CAAC,MAAM,OAAO,SAAS,QAAQ,CACjC,OAAM,IAAI,MAAM,kCAAkC,WAAW;GAE/D,MAAM,SAAS,MAAM,UAAU,WAAW;AAC1C,OAAI,CAAC,OACH,OAAM,IAAI,MAAM,uCAAuC,aAAa;GAGtE,MAAM,UAAmB,EACvB,UAAU,CACR;IACE,MAAM;IACN,SAAS,CACP;KAAE,MAAM;KAAQ,MAAM,QAAQ;KAAQ,EACtC,GAAG,QAAQ,OAAO,KAAK,SAAS;KAC9B,MAAM;KACN,MAAM,IAAI,OAAO,SAAS,SAAS;KACnC,UAAU,IAAI,YAAY;KAC3B,EAAE,CACJ;IACD,WAAW,KAAK,KAAK;IACtB,CACF,EACF;GAED,MAAM,YAAY,iBAAiB,MAAM,WAAW,QAAQ,aAAa,IAAI;GAC7E,MAAM,YAAY,QAAQ,aAAa;GACvC,MAAM,gBACJ,OAAO,gBAAgB,eAAe,OAAO,YAAY,YAAY,aACjE,YAAY,QAAQ,UAAU,GAC9B,KAAA;AA2BN,UAAO;IAAE,MALI,yBAAyB;KACpC,SAAA,MAPoB,SAAS,OAAO,SAAS;MAC7C;MACA;MACA,eAlBoB;AACpB,WAAI,iBAAiB,QAAQ,UAAU,OAAO,YAAY,QAAQ,WAChE,QAAO,YAAY,IAAI,CAAC,QAAQ,QAAQ,cAAc,CAAC;AAEzD,WAAI,QAAQ,OACV,QAAO,QAAQ;AAEjB,WAAI,cACF,QAAO;OAET,MAAM,aAAa,IAAI,iBAAiB;AACxC,wBAAiB,WAAW,OAAO,EAAE,UAAU;AAC/C,cAAO,WAAW;UAMZ;MACP,CAAC;KAIA,UAAU;KACV,OAAO;KACR,CACY;IAAE,UAAU;IAAY,OAAO;IAAS;;EAExD;;AAGH,KAAK,MAAM,cAAc;CAAC;CAAU;CAAa;CAAS,CACxD,oCAAmC,oCAAoC,WAAW,CAAC;AAGrF,2CAA2C,eAAe;AAIxD,QAHuB,oBAAoB,WAAW,CAAC,MAAM,UAC3D,MAAM,OAAO,SAAS,QAAQ,CAEX,GAAG,oCAAoC,WAAW,GAAG,KAAA;EAC1E"}
1
+ {"version":3,"file":"pi-ai-provider.js","names":[],"sources":["../../../../../src/agent/image/understanding/pi-ai-provider.ts"],"sourcesContent":["import { complete, type Api, type Context, type Model } from '@earendil-works/pi-ai/compat';\nimport { resolveModel, getApiKey, getModelsByProvider } from '../../../providers/index.js';\nimport { coerceImageAssistantText } from '../image-helpers.js';\nimport {\n registerImageUnderstandingProvider,\n registerImageUnderstandingProviderFactory,\n} from './provider-registry.js';\nimport type { ImageUnderstandingProvider } from './types.js';\n\nfunction resolveMaxTokens(modelMaxTokens: number | undefined, requestedMaxTokens = 4096): number {\n if (\n typeof modelMaxTokens !== 'number' ||\n !Number.isFinite(modelMaxTokens) ||\n modelMaxTokens <= 0\n ) {\n return requestedMaxTokens;\n }\n return Math.min(requestedMaxTokens, modelMaxTokens);\n}\n\nexport function buildPiAiImageUnderstandingProvider(providerId: string): ImageUnderstandingProvider {\n return {\n id: providerId,\n label: `pi-ai (${providerId})`,\n async isConfigured() {\n const apiKey = await getApiKey(providerId);\n return Boolean(apiKey);\n },\n async describeImages(modelId, request) {\n const modelRef = `${providerId}/${modelId}`;\n const model = resolveModel(modelRef) as Model<Api>;\n if (!model.input?.includes('image')) {\n throw new Error(`Model does not support images: ${modelRef}`);\n }\n const apiKey = await getApiKey(providerId);\n if (!apiKey) {\n throw new Error(`No API key configured for provider: ${providerId}`);\n }\n\n const context: Context = {\n messages: [\n {\n role: 'user',\n content: [\n { type: 'text', text: request.prompt },\n ...request.images.map((img) => ({\n type: 'image' as const,\n data: img.buffer.toString('base64'),\n mimeType: img.mimeType || 'image/jpeg',\n })),\n ],\n timestamp: Date.now(),\n },\n ],\n };\n\n const maxTokens = resolveMaxTokens(model.maxTokens, request.maxTokens ?? 512);\n const timeoutMs = request.timeoutMs ?? 60_000;\n const timeoutSignal =\n typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function'\n ? AbortSignal.timeout(timeoutMs)\n : undefined;\n const signal = (() => {\n if (timeoutSignal && request.signal && typeof AbortSignal.any === 'function') {\n return AbortSignal.any([request.signal, timeoutSignal]);\n }\n if (request.signal) {\n return request.signal;\n }\n if (timeoutSignal) {\n return timeoutSignal;\n }\n const controller = new AbortController();\n setTimeout(() => controller.abort(), timeoutMs);\n return controller.signal;\n })();\n\n const message = await complete(model, context, {\n apiKey,\n maxTokens,\n signal,\n });\n\n const text = coerceImageAssistantText({\n message,\n provider: providerId,\n model: modelId,\n });\n return { text, provider: providerId, model: modelId };\n },\n };\n}\n\nfor (const providerId of ['openai', 'anthropic', 'google']) {\n registerImageUnderstandingProvider(buildPiAiImageUnderstandingProvider(providerId));\n}\n\nregisterImageUnderstandingProviderFactory((providerId) => {\n const hasVisionModel = getModelsByProvider(providerId).some((model) =>\n model.input?.includes('image'),\n );\n return hasVisionModel ? buildPiAiImageUnderstandingProvider(providerId) : undefined;\n});\n"],"mappings":";;;;;gBAC2F;AAQ3F,SAAS,iBAAiB,gBAAoC,qBAAqB,MAAc;AAC/F,KACE,OAAO,mBAAmB,YAC1B,CAAC,OAAO,SAAS,eAAe,IAChC,kBAAkB,EAElB,QAAO;AAET,QAAO,KAAK,IAAI,oBAAoB,eAAe;;AAGrD,SAAgB,oCAAoC,YAAgD;AAClG,QAAO;EACL,IAAI;EACJ,OAAO,UAAU,WAAW;EAC5B,MAAM,eAAe;GACnB,MAAM,SAAS,MAAM,UAAU,WAAW;AAC1C,UAAO,QAAQ,OAAO;;EAExB,MAAM,eAAe,SAAS,SAAS;GACrC,MAAM,WAAW,GAAG,WAAW,GAAG;GAClC,MAAM,QAAQ,aAAa,SAAS;AACpC,OAAI,CAAC,MAAM,OAAO,SAAS,QAAQ,CACjC,OAAM,IAAI,MAAM,kCAAkC,WAAW;GAE/D,MAAM,SAAS,MAAM,UAAU,WAAW;AAC1C,OAAI,CAAC,OACH,OAAM,IAAI,MAAM,uCAAuC,aAAa;GAGtE,MAAM,UAAmB,EACvB,UAAU,CACR;IACE,MAAM;IACN,SAAS,CACP;KAAE,MAAM;KAAQ,MAAM,QAAQ;KAAQ,EACtC,GAAG,QAAQ,OAAO,KAAK,SAAS;KAC9B,MAAM;KACN,MAAM,IAAI,OAAO,SAAS,SAAS;KACnC,UAAU,IAAI,YAAY;KAC3B,EAAE,CACJ;IACD,WAAW,KAAK,KAAK;IACtB,CACF,EACF;GAED,MAAM,YAAY,iBAAiB,MAAM,WAAW,QAAQ,aAAa,IAAI;GAC7E,MAAM,YAAY,QAAQ,aAAa;GACvC,MAAM,gBACJ,OAAO,gBAAgB,eAAe,OAAO,YAAY,YAAY,aACjE,YAAY,QAAQ,UAAU,GAC9B,KAAA;AA2BN,UAAO;IAAE,MALI,yBAAyB;KACpC,SAAA,MAPoB,SAAS,OAAO,SAAS;MAC7C;MACA;MACA,eAlBoB;AACpB,WAAI,iBAAiB,QAAQ,UAAU,OAAO,YAAY,QAAQ,WAChE,QAAO,YAAY,IAAI,CAAC,QAAQ,QAAQ,cAAc,CAAC;AAEzD,WAAI,QAAQ,OACV,QAAO,QAAQ;AAEjB,WAAI,cACF,QAAO;OAET,MAAM,aAAa,IAAI,iBAAiB;AACxC,wBAAiB,WAAW,OAAO,EAAE,UAAU;AAC/C,cAAO,WAAW;UAMZ;MACP,CAAC;KAIA,UAAU;KACV,OAAO;KACR,CACY;IAAE,UAAU;IAAY,OAAO;IAAS;;EAExD;;AAGH,KAAK,MAAM,cAAc;CAAC;CAAU;CAAa;CAAS,CACxD,oCAAmC,oCAAoC,WAAW,CAAC;AAGrF,2CAA2C,eAAe;AAIxD,QAHuB,oBAAoB,WAAW,CAAC,MAAM,UAC3D,MAAM,OAAO,SAAS,QAAQ,CAEX,GAAG,oCAAoC,WAAW,GAAG,KAAA;EAC1E"}
@@ -1,5 +1,5 @@
1
1
  import type { AgentMessage } from '@earendil-works/pi-agent-core';
2
- import { type Model, type Api } from '@earendil-works/pi-ai';
2
+ import { type Model, type Api } from '@earendil-works/pi-ai/compat';
3
3
  import { type ConversationSummary } from './summary-generator.js';
4
4
  export interface CompactionResult {
5
5
  summary: string;
@@ -1,6 +1,6 @@
1
1
  import { readAgentMessageContent } from "./agent-message-access.js";
2
2
  import { formatSummaryAsText, generateStructuredSummary } from "./summary-generator.js";
3
- import { complete } from "@earendil-works/pi-ai";
3
+ import { complete } from "@earendil-works/pi-ai/compat";
4
4
  //#region src/agent/memory/compaction.ts
5
5
  const DEFAULT_CONFIG = {
6
6
  enabled: true,
@@ -1 +1 @@
1
- {"version":3,"file":"compaction.js","names":[],"sources":["../../../../src/agent/memory/compaction.ts"],"sourcesContent":["import type { AgentMessage } from '@earendil-works/pi-agent-core';\nimport { complete, type Model, type Api, type UserMessage } from '@earendil-works/pi-ai';\nimport { readAgentMessageContent } from './agent-message-access.js';\nimport { generateStructuredSummary, formatSummaryAsText, type ConversationSummary } from './summary-generator.js';\n\nexport interface CompactionResult {\n summary: string;\n firstKeptIndex: number;\n tokensBefore: number;\n tokensAfter: number;\n compacted: boolean;\n structuredSummary?: ConversationSummary;\n compactedUsage?: {\n input: number;\n output: number;\n total: number;\n cost?: number;\n };\n}\n\nexport interface CompactionConfig {\n enabled: boolean;\n mode: 'extractive' | 'abstractive' | 'structured';\n reserveTokens: number;\n triggerThreshold: number;\n minMessagesBeforeCompact: number;\n keepRecentMessages: number;\n summaryMaxTokens: number;\n evictionWindow: number;\n retentionWindow: number;\n preserveReasoning: boolean;\n accumulateUsage: boolean;\n}\n\nconst DEFAULT_CONFIG: CompactionConfig = {\n enabled: true,\n mode: 'abstractive',\n reserveTokens: 8000,\n triggerThreshold: 0.8,\n minMessagesBeforeCompact: 10,\n keepRecentMessages: 10,\n summaryMaxTokens: 500,\n evictionWindow: 0.2,\n retentionWindow: 6,\n preserveReasoning: true,\n accumulateUsage: true,\n};\n\nfunction estimateTokens(text: string): number {\n return Math.ceil(text.length / 4);\n}\n\nfunction estimateMessageTokens(msg: AgentMessage): number {\n let text = '';\n const raw = readAgentMessageContent(msg);\n\n if (typeof raw === 'string') {\n text = raw;\n } else if (Array.isArray(raw)) {\n text = raw\n .filter(c => c.type === 'text')\n .map(c => (c as { text?: string }).text || '')\n .join('\\n');\n }\n \n return estimateTokens(text) + 10;\n}\n\n// Internal: Reasoning extraction utilities\ninterface ReasoningDetails {\n thinking?: string;\n signature?: string;\n}\n\nfunction extractLastReasoning(messages: AgentMessage[]): ReasoningDetails | null {\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i];\n if (msg.role === 'assistant') {\n const rd = (msg as unknown as { reasoning_details?: ReasoningDetails }).reasoning_details;\n if (rd && (rd.thinking || rd.signature)) {\n return rd;\n }\n }\n }\n return null;\n}\n\n// Internal: Inject reasoning into first assistant message\nfunction injectReasoningIntoFirstAssistant(\n messages: AgentMessage[],\n reasoning: ReasoningDetails\n): void {\n for (const msg of messages) {\n if (msg.role === 'assistant') {\n const existing = (msg as unknown as { reasoning_details?: ReasoningDetails }).reasoning_details;\n if (!existing || (!existing.thinking && !existing.signature)) {\n (msg as unknown as { reasoning_details?: ReasoningDetails }).reasoning_details = reasoning;\n }\n break;\n }\n }\n}\n\n// Internal: Message usage tracking\ninterface MessageUsage {\n input: number;\n output: number;\n total: number;\n cost?: number;\n}\n\nexport function accumulateUsage(messages: AgentMessage[]): MessageUsage | undefined {\n let totalInput = 0;\n let totalOutput = 0;\n let totalCost = 0;\n let hasUsage = false;\n\n for (const msg of messages) {\n const usage = (msg as unknown as { usage?: MessageUsage }).usage;\n if (usage) {\n hasUsage = true;\n totalInput += usage.input || 0;\n totalOutput += usage.output || 0;\n totalCost += usage.cost || 0;\n }\n }\n\n if (!hasUsage) return undefined;\n\n return {\n input: totalInput,\n output: totalOutput,\n total: totalInput + totalOutput,\n cost: totalCost > 0 ? totalCost : undefined,\n };\n}\n\n// Internal: Droppable message filtering\ntype DroppableMessage = AgentMessage & {\n droppable?: boolean;\n};\n\nfunction filterDroppableMessages(messages: AgentMessage[]): AgentMessage[] {\n return messages.filter(msg => !(msg as DroppableMessage).droppable);\n}\n\nfunction findNthTurnFromEnd(messages: AgentMessage[], n: number): number {\n if (n <= 0) return messages.length;\n \n let turnsFound = 0;\n let lastRole: string | null = null;\n \n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i];\n \n if (msg.role === 'user' && lastRole !== 'user') {\n turnsFound++;\n if (turnsFound === n) {\n return i;\n }\n }\n lastRole = msg.role;\n }\n \n return 0;\n}\n\n// Internal: Calculate compaction range\nfunction calculateCompactionRange(\n messages: AgentMessage[],\n config: CompactionConfig\n): { start: number; end: number } | null {\n const totalMessages = messages.length;\n \n if (totalMessages < config.minMessagesBeforeCompact) {\n return null;\n }\n \n const evictionEnd = Math.floor(totalMessages * config.evictionWindow);\n const retentionStart = findNthTurnFromEnd(messages, config.retentionWindow);\n const retentionEnd = retentionStart > 0 ? retentionStart - 1 : 0;\n const compactionEnd = Math.min(evictionEnd, retentionEnd);\n \n if (compactionEnd <= 1) {\n return null;\n }\n \n return { start: 0, end: compactionEnd };\n}\n\nexport class SessionCompactor {\n private config: CompactionConfig;\n \n constructor(\n config?: Partial<CompactionConfig>,\n private model?: Model<Api>\n ) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n }\n\n needsCompaction(\n messages: AgentMessage[],\n contextWindow: number\n ): { needed: boolean; reason: string; usagePercent?: number } {\n if (!this.config.enabled) {\n return { needed: false, reason: 'disabled' };\n }\n\n if (messages.length < this.config.minMessagesBeforeCompact) {\n return { needed: false, reason: 'not_enough_messages' };\n }\n\n const totalTokens = this.estimateTotalTokens(messages);\n const usagePercent = totalTokens / contextWindow;\n\n if (usagePercent > this.config.triggerThreshold) {\n return { \n needed: true, \n reason: 'threshold_exceeded',\n usagePercent \n };\n }\n\n return { needed: false, reason: 'within_threshold' };\n }\n\n async compact(\n messages: AgentMessage[],\n instructions?: string,\n force?: boolean,\n ): Promise<CompactionResult> {\n const effectiveMessages = filterDroppableMessages(messages);\n\n const minRequired = this.config.minMessagesBeforeCompact;\n if (!force && effectiveMessages.length < minRequired) {\n return {\n summary: '',\n firstKeptIndex: 0,\n tokensBefore: this.estimateTotalTokens(effectiveMessages),\n tokensAfter: this.estimateTotalTokens(effectiveMessages),\n compacted: false,\n };\n }\n\n if (force && effectiveMessages.length < 2) {\n return {\n summary: '',\n firstKeptIndex: 0,\n tokensBefore: this.estimateTotalTokens(effectiveMessages),\n tokensAfter: this.estimateTotalTokens(effectiveMessages),\n compacted: false,\n };\n }\n\n const range = calculateCompactionRange(effectiveMessages, this.config);\n let messagesToSummarize: AgentMessage[];\n let keptMessages: AgentMessage[];\n\n if (range) {\n messagesToSummarize = effectiveMessages.slice(0, range.end);\n keptMessages = effectiveMessages.slice(range.end);\n } else if (force) {\n const keep = Math.min(\n this.config.keepRecentMessages,\n Math.max(1, effectiveMessages.length - 1),\n );\n messagesToSummarize = effectiveMessages.slice(0, effectiveMessages.length - keep);\n keptMessages = effectiveMessages.slice(-keep);\n } else {\n const keepRecent = this.config.keepRecentMessages;\n messagesToSummarize = effectiveMessages.slice(0, -keepRecent);\n keptMessages = effectiveMessages.slice(-keepRecent);\n }\n\n if (messagesToSummarize.length === 0) {\n return {\n summary: '',\n firstKeptIndex: 0,\n tokensBefore: this.estimateTotalTokens(effectiveMessages),\n tokensAfter: this.estimateTotalTokens(effectiveMessages),\n compacted: false,\n };\n }\n\n let preservedReasoning: ReasoningDetails | null = null;\n if (this.config.preserveReasoning) {\n preservedReasoning = extractLastReasoning(messagesToSummarize);\n if (preservedReasoning) {\n injectReasoningIntoFirstAssistant(keptMessages, preservedReasoning);\n }\n }\n\n const summary = await this.generateSummary(messagesToSummarize, instructions);\n const structuredSummary = this.config.mode === 'structured' \n ? generateStructuredSummary(messagesToSummarize)\n : undefined;\n\n const tokensBefore = this.estimateTotalTokens(effectiveMessages);\n const summaryTokens = estimateTokens(summary) + 20;\n const keptTokens = this.estimateTotalTokens(keptMessages);\n const tokensAfter = summaryTokens + keptTokens;\n\n let compactedUsage: MessageUsage | undefined;\n if (this.config.accumulateUsage) {\n compactedUsage = accumulateUsage(messagesToSummarize);\n }\n\n return {\n summary,\n firstKeptIndex: keptMessages.length > 0 ? effectiveMessages.length - keptMessages.length : 0,\n tokensBefore,\n tokensAfter,\n compacted: true,\n structuredSummary,\n compactedUsage,\n };\n }\n\n private async generateSummary(messages: AgentMessage[], instructions?: string): Promise<string> {\n if (this.model && (this.config.mode === 'abstractive' || this.config.mode === 'structured')) {\n try {\n if (this.config.mode === 'structured') {\n const structured = generateStructuredSummary(messages);\n return formatSummaryAsText(structured, true);\n } else {\n return await this.llmAbstractiveSummary(messages, instructions);\n }\n } catch (err) {\n console.warn('[Compactor] LLM summarization failed, falling back to extractive', err);\n }\n }\n\n return this.extractiveSummary(messages);\n }\n\n private async llmAbstractiveSummary(messages: AgentMessage[], instructions?: string): Promise<string> {\n if (!this.model) {\n throw new Error('Model not available');\n }\n\n const conversation = this.formatMessages(messages);\n const extra = instructions?.trim()\n ? `\\nAdditional focus from the user:\\n${instructions.trim()}\\n`\n : '';\n const prompt = `Summarize the following conversation in 2-3 concise sentences. Focus on:\n1. What the user was trying to accomplish\n2. Key decisions, outcomes, or solutions\n3. Any important context that should be preserved\n${extra}\nConversation:\n${conversation}\n\nSummary:`;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 30000);\n\n try {\n const summaryMessage: UserMessage = { role: 'user', content: prompt, timestamp: Date.now() };\n const result = await complete(this.model, { \n messages: [summaryMessage]\n }, {\n maxTokens: this.config.summaryMaxTokens,\n temperature: 0.3,\n signal: controller.signal as any,\n });\n\n const text = Array.isArray(result.content)\n ? result.content.filter((c: any) => c.type === 'text').map((c: any) => c.text).join('')\n : '';\n\n return text.trim();\n } catch (err) {\n if (err instanceof Error && err.name === 'AbortError') {\n throw new Error('LLM summarization timed out after 30 seconds');\n }\n throw err;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n private formatMessages(messages: AgentMessage[]): string {\n return messages\n .map(m => {\n const role = m.role;\n const raw = readAgentMessageContent(m);\n const content = typeof raw === 'string'\n ? raw\n : (raw as Array<{ type: string; text?: string }>).filter(c => c.type === 'text').map(c => c.text || '').join('\\n');\n return `[${role}]: ${content}`;\n })\n .join('\\n\\n');\n }\n\n private extractiveSummary(messages: AgentMessage[]): string {\n const userMessages = messages\n .filter(m => m.role === 'user')\n .slice(-5)\n .map(m => {\n const raw = readAgentMessageContent(m);\n if (typeof raw === 'string') return raw;\n if (Array.isArray(raw)) {\n return raw\n .filter(c => c.type === 'text')\n .map(c => (c as { text?: string }).text || '')\n .join('\\n');\n }\n return '';\n })\n .filter(t => t.length > 0)\n .join('; ');\n\n return `Previous conversation covered: ${userMessages.slice(0, 300)}...`;\n }\n\n estimateTotalTokens(messages: AgentMessage[]): number {\n return messages.reduce((sum, msg) => sum + estimateMessageTokens(msg), 0);\n }\n\n applyCompaction(\n messages: AgentMessage[],\n result: CompactionResult\n ): AgentMessage[] {\n if (!result.compacted || !result.summary) {\n return messages;\n }\n\n const summaryMessage: AgentMessage & { usage?: MessageUsage } = {\n role: 'user',\n content: [{ type: 'text', text: `[Previous conversation summary]: ${result.summary}` }],\n timestamp: Date.now(),\n };\n\n if (result.compactedUsage) {\n summaryMessage.usage = result.compactedUsage;\n }\n\n const keptMessages = messages.slice(result.firstKeptIndex);\n return [summaryMessage, ...keptMessages];\n }\n}\n"],"mappings":";;;;AAkCA,MAAM,iBAAmC;CACvC,SAAS;CACT,MAAM;CACN,eAAe;CACf,kBAAkB;CAClB,0BAA0B;CAC1B,oBAAoB;CACpB,kBAAkB;CAClB,gBAAgB;CAChB,iBAAiB;CACjB,mBAAmB;CACnB,iBAAiB;CAClB;AAED,SAAS,eAAe,MAAsB;AAC5C,QAAO,KAAK,KAAK,KAAK,SAAS,EAAE;;AAGnC,SAAS,sBAAsB,KAA2B;CACxD,IAAI,OAAO;CACX,MAAM,MAAM,wBAAwB,IAAI;AAExC,KAAI,OAAO,QAAQ,SACjB,QAAO;UACE,MAAM,QAAQ,IAAI,CAC3B,QAAO,IACJ,QAAO,MAAK,EAAE,SAAS,OAAO,CAC9B,KAAI,MAAM,EAAwB,QAAQ,GAAG,CAC7C,KAAK,KAAK;AAGf,QAAO,eAAe,KAAK,GAAG;;AAShC,SAAS,qBAAqB,UAAmD;AAC/E,MAAK,IAAI,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;EAC7C,MAAM,MAAM,SAAS;AACrB,MAAI,IAAI,SAAS,aAAa;GAC5B,MAAM,KAAM,IAA4D;AACxE,OAAI,OAAO,GAAG,YAAY,GAAG,WAC3B,QAAO;;;AAIb,QAAO;;AAIT,SAAS,kCACP,UACA,WACM;AACN,MAAK,MAAM,OAAO,SAChB,KAAI,IAAI,SAAS,aAAa;EAC5B,MAAM,WAAY,IAA4D;AAC9E,MAAI,CAAC,YAAa,CAAC,SAAS,YAAY,CAAC,SAAS,UAC/C,KAA4D,oBAAoB;AAEnF;;;AAaN,SAAgB,gBAAgB,UAAoD;CAClF,IAAI,aAAa;CACjB,IAAI,cAAc;CAClB,IAAI,YAAY;CAChB,IAAI,WAAW;AAEf,MAAK,MAAM,OAAO,UAAU;EAC1B,MAAM,QAAS,IAA4C;AAC3D,MAAI,OAAO;AACT,cAAW;AACX,iBAAc,MAAM,SAAS;AAC7B,kBAAe,MAAM,UAAU;AAC/B,gBAAa,MAAM,QAAQ;;;AAI/B,KAAI,CAAC,SAAU,QAAO,KAAA;AAEtB,QAAO;EACL,OAAO;EACP,QAAQ;EACR,OAAO,aAAa;EACpB,MAAM,YAAY,IAAI,YAAY,KAAA;EACnC;;AAQH,SAAS,wBAAwB,UAA0C;AACzE,QAAO,SAAS,QAAO,QAAO,CAAE,IAAyB,UAAU;;AAGrE,SAAS,mBAAmB,UAA0B,GAAmB;AACvE,KAAI,KAAK,EAAG,QAAO,SAAS;CAE5B,IAAI,aAAa;CACjB,IAAI,WAA0B;AAE9B,MAAK,IAAI,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;EAC7C,MAAM,MAAM,SAAS;AAErB,MAAI,IAAI,SAAS,UAAU,aAAa,QAAQ;AAC9C;AACA,OAAI,eAAe,EACjB,QAAO;;AAGX,aAAW,IAAI;;AAGjB,QAAO;;AAIT,SAAS,yBACP,UACA,QACuC;CACvC,MAAM,gBAAgB,SAAS;AAE/B,KAAI,gBAAgB,OAAO,yBACzB,QAAO;CAGT,MAAM,cAAc,KAAK,MAAM,gBAAgB,OAAO,eAAe;CACrE,MAAM,iBAAiB,mBAAmB,UAAU,OAAO,gBAAgB;CAC3E,MAAM,eAAe,iBAAiB,IAAI,iBAAiB,IAAI;CAC/D,MAAM,gBAAgB,KAAK,IAAI,aAAa,aAAa;AAEzD,KAAI,iBAAiB,EACnB,QAAO;AAGT,QAAO;EAAE,OAAO;EAAG,KAAK;EAAe;;AAGzC,IAAa,mBAAb,MAA8B;CAC5B;CAEA,YACE,QACA,OACA;AADQ,OAAA,QAAA;AAER,OAAK,SAAS;GAAE,GAAG;GAAgB,GAAG;GAAQ;;CAGhD,gBACE,UACA,eAC4D;AAC5D,MAAI,CAAC,KAAK,OAAO,QACf,QAAO;GAAE,QAAQ;GAAO,QAAQ;GAAY;AAG9C,MAAI,SAAS,SAAS,KAAK,OAAO,yBAChC,QAAO;GAAE,QAAQ;GAAO,QAAQ;GAAuB;EAIzD,MAAM,eADc,KAAK,oBAAoB,SACb,GAAG;AAEnC,MAAI,eAAe,KAAK,OAAO,iBAC7B,QAAO;GACL,QAAQ;GACR,QAAQ;GACR;GACD;AAGH,SAAO;GAAE,QAAQ;GAAO,QAAQ;GAAoB;;CAGtD,MAAM,QACJ,UACA,cACA,OAC2B;EAC3B,MAAM,oBAAoB,wBAAwB,SAAS;EAE3D,MAAM,cAAc,KAAK,OAAO;AAChC,MAAI,CAAC,SAAS,kBAAkB,SAAS,YACvC,QAAO;GACL,SAAS;GACT,gBAAgB;GAChB,cAAc,KAAK,oBAAoB,kBAAkB;GACzD,aAAa,KAAK,oBAAoB,kBAAkB;GACxD,WAAW;GACZ;AAGH,MAAI,SAAS,kBAAkB,SAAS,EACtC,QAAO;GACL,SAAS;GACT,gBAAgB;GAChB,cAAc,KAAK,oBAAoB,kBAAkB;GACzD,aAAa,KAAK,oBAAoB,kBAAkB;GACxD,WAAW;GACZ;EAGH,MAAM,QAAQ,yBAAyB,mBAAmB,KAAK,OAAO;EACtE,IAAI;EACJ,IAAI;AAEJ,MAAI,OAAO;AACT,yBAAsB,kBAAkB,MAAM,GAAG,MAAM,IAAI;AAC3D,kBAAe,kBAAkB,MAAM,MAAM,IAAI;aACxC,OAAO;GAChB,MAAM,OAAO,KAAK,IAChB,KAAK,OAAO,oBACZ,KAAK,IAAI,GAAG,kBAAkB,SAAS,EAAE,CAC1C;AACD,yBAAsB,kBAAkB,MAAM,GAAG,kBAAkB,SAAS,KAAK;AACjF,kBAAe,kBAAkB,MAAM,CAAC,KAAK;SACxC;GACL,MAAM,aAAa,KAAK,OAAO;AAC/B,yBAAsB,kBAAkB,MAAM,GAAG,CAAC,WAAW;AAC7D,kBAAe,kBAAkB,MAAM,CAAC,WAAW;;AAGrD,MAAI,oBAAoB,WAAW,EACjC,QAAO;GACL,SAAS;GACT,gBAAgB;GAChB,cAAc,KAAK,oBAAoB,kBAAkB;GACzD,aAAa,KAAK,oBAAoB,kBAAkB;GACxD,WAAW;GACZ;EAGH,IAAI,qBAA8C;AAClD,MAAI,KAAK,OAAO,mBAAmB;AACjC,wBAAqB,qBAAqB,oBAAoB;AAC9D,OAAI,mBACF,mCAAkC,cAAc,mBAAmB;;EAIvE,MAAM,UAAU,MAAM,KAAK,gBAAgB,qBAAqB,aAAa;EAC7E,MAAM,oBAAoB,KAAK,OAAO,SAAS,eAC3C,0BAA0B,oBAAoB,GAC9C,KAAA;EAEJ,MAAM,eAAe,KAAK,oBAAoB,kBAAkB;EAGhE,MAAM,cAFgB,eAAe,QAAQ,GAAG,KAC7B,KAAK,oBAAoB,aACE;EAE9C,IAAI;AACJ,MAAI,KAAK,OAAO,gBACd,kBAAiB,gBAAgB,oBAAoB;AAGvD,SAAO;GACL;GACA,gBAAgB,aAAa,SAAS,IAAI,kBAAkB,SAAS,aAAa,SAAS;GAC3F;GACA;GACA,WAAW;GACX;GACA;GACD;;CAGH,MAAc,gBAAgB,UAA0B,cAAwC;AAC9F,MAAI,KAAK,UAAU,KAAK,OAAO,SAAS,iBAAiB,KAAK,OAAO,SAAS,cAC5E,KAAI;AACF,OAAI,KAAK,OAAO,SAAS,aAEvB,QAAO,oBADY,0BAA0B,SACR,EAAE,KAAK;OAE5C,QAAO,MAAM,KAAK,sBAAsB,UAAU,aAAa;WAE1D,KAAK;AACZ,WAAQ,KAAK,oEAAoE,IAAI;;AAIzF,SAAO,KAAK,kBAAkB,SAAS;;CAGzC,MAAc,sBAAsB,UAA0B,cAAwC;AACpG,MAAI,CAAC,KAAK,MACR,OAAM,IAAI,MAAM,sBAAsB;EAGxC,MAAM,eAAe,KAAK,eAAe,SAAS;EAIlD,MAAM,SAAS;;;;EAHD,cAAc,MAAM,GAC9B,sCAAsC,aAAa,MAAM,CAAC,MAC1D,GAKA;;EAEN,aAAa;;;EAIX,MAAM,aAAa,IAAI,iBAAiB;EACxC,MAAM,YAAY,iBAAiB,WAAW,OAAO,EAAE,IAAM;AAE7D,MAAI;GACF,MAAM,iBAA8B;IAAE,MAAM;IAAQ,SAAS;IAAQ,WAAW,KAAK,KAAK;IAAE;GAC5F,MAAM,SAAS,MAAM,SAAS,KAAK,OAAO,EACxC,UAAU,CAAC,eAAe,EAC3B,EAAE;IACD,WAAW,KAAK,OAAO;IACvB,aAAa;IACb,QAAQ,WAAW;IACpB,CAAC;AAMF,WAJa,MAAM,QAAQ,OAAO,QAAQ,GACtC,OAAO,QAAQ,QAAQ,MAAW,EAAE,SAAS,OAAO,CAAC,KAAK,MAAW,EAAE,KAAK,CAAC,KAAK,GAAG,GACrF,IAEQ,MAAM;WACX,KAAK;AACZ,OAAI,eAAe,SAAS,IAAI,SAAS,aACvC,OAAM,IAAI,MAAM,+CAA+C;AAEjE,SAAM;YACE;AACR,gBAAa,UAAU;;;CAI3B,eAAuB,UAAkC;AACvD,SAAO,SACJ,KAAI,MAAK;GACR,MAAM,OAAO,EAAE;GACf,MAAM,MAAM,wBAAwB,EAAE;AAItC,UAAO,IAAI,KAAK,KAHA,OAAO,QAAQ,WAC3B,MACC,IAA+C,QAAO,MAAK,EAAE,SAAS,OAAO,CAAC,KAAI,MAAK,EAAE,QAAQ,GAAG,CAAC,KAAK,KAAK;IAEpH,CACD,KAAK,OAAO;;CAGjB,kBAA0B,UAAkC;AAkB1D,SAAO,kCAjBc,SAClB,QAAO,MAAK,EAAE,SAAS,OAAO,CAC9B,MAAM,GAAG,CACT,KAAI,MAAK;GACR,MAAM,MAAM,wBAAwB,EAAE;AACtC,OAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,OAAI,MAAM,QAAQ,IAAI,CACpB,QAAO,IACJ,QAAO,MAAK,EAAE,SAAS,OAAO,CAC9B,KAAI,MAAM,EAAwB,QAAQ,GAAG,CAC7C,KAAK,KAAK;AAEf,UAAO;IACP,CACD,QAAO,MAAK,EAAE,SAAS,EAAE,CACzB,KAAK,KAE6C,CAAC,MAAM,GAAG,IAAI,CAAC;;CAGtE,oBAAoB,UAAkC;AACpD,SAAO,SAAS,QAAQ,KAAK,QAAQ,MAAM,sBAAsB,IAAI,EAAE,EAAE;;CAG3E,gBACE,UACA,QACgB;AAChB,MAAI,CAAC,OAAO,aAAa,CAAC,OAAO,QAC/B,QAAO;EAGT,MAAM,iBAA0D;GAC9D,MAAM;GACN,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,oCAAoC,OAAO;IAAW,CAAC;GACvF,WAAW,KAAK,KAAK;GACtB;AAED,MAAI,OAAO,eACT,gBAAe,QAAQ,OAAO;AAIhC,SAAO,CAAC,gBAAgB,GADH,SAAS,MAAM,OAAO,eACJ,CAAC"}
1
+ {"version":3,"file":"compaction.js","names":[],"sources":["../../../../src/agent/memory/compaction.ts"],"sourcesContent":["import type { AgentMessage } from '@earendil-works/pi-agent-core';\nimport { complete, type Model, type Api, type UserMessage } from '@earendil-works/pi-ai/compat';\nimport { readAgentMessageContent } from './agent-message-access.js';\nimport { generateStructuredSummary, formatSummaryAsText, type ConversationSummary } from './summary-generator.js';\n\nexport interface CompactionResult {\n summary: string;\n firstKeptIndex: number;\n tokensBefore: number;\n tokensAfter: number;\n compacted: boolean;\n structuredSummary?: ConversationSummary;\n compactedUsage?: {\n input: number;\n output: number;\n total: number;\n cost?: number;\n };\n}\n\nexport interface CompactionConfig {\n enabled: boolean;\n mode: 'extractive' | 'abstractive' | 'structured';\n reserveTokens: number;\n triggerThreshold: number;\n minMessagesBeforeCompact: number;\n keepRecentMessages: number;\n summaryMaxTokens: number;\n evictionWindow: number;\n retentionWindow: number;\n preserveReasoning: boolean;\n accumulateUsage: boolean;\n}\n\nconst DEFAULT_CONFIG: CompactionConfig = {\n enabled: true,\n mode: 'abstractive',\n reserveTokens: 8000,\n triggerThreshold: 0.8,\n minMessagesBeforeCompact: 10,\n keepRecentMessages: 10,\n summaryMaxTokens: 500,\n evictionWindow: 0.2,\n retentionWindow: 6,\n preserveReasoning: true,\n accumulateUsage: true,\n};\n\nfunction estimateTokens(text: string): number {\n return Math.ceil(text.length / 4);\n}\n\nfunction estimateMessageTokens(msg: AgentMessage): number {\n let text = '';\n const raw = readAgentMessageContent(msg);\n\n if (typeof raw === 'string') {\n text = raw;\n } else if (Array.isArray(raw)) {\n text = raw\n .filter(c => c.type === 'text')\n .map(c => (c as { text?: string }).text || '')\n .join('\\n');\n }\n \n return estimateTokens(text) + 10;\n}\n\n// Internal: Reasoning extraction utilities\ninterface ReasoningDetails {\n thinking?: string;\n signature?: string;\n}\n\nfunction extractLastReasoning(messages: AgentMessage[]): ReasoningDetails | null {\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i];\n if (msg.role === 'assistant') {\n const rd = (msg as unknown as { reasoning_details?: ReasoningDetails }).reasoning_details;\n if (rd && (rd.thinking || rd.signature)) {\n return rd;\n }\n }\n }\n return null;\n}\n\n// Internal: Inject reasoning into first assistant message\nfunction injectReasoningIntoFirstAssistant(\n messages: AgentMessage[],\n reasoning: ReasoningDetails\n): void {\n for (const msg of messages) {\n if (msg.role === 'assistant') {\n const existing = (msg as unknown as { reasoning_details?: ReasoningDetails }).reasoning_details;\n if (!existing || (!existing.thinking && !existing.signature)) {\n (msg as unknown as { reasoning_details?: ReasoningDetails }).reasoning_details = reasoning;\n }\n break;\n }\n }\n}\n\n// Internal: Message usage tracking\ninterface MessageUsage {\n input: number;\n output: number;\n total: number;\n cost?: number;\n}\n\nexport function accumulateUsage(messages: AgentMessage[]): MessageUsage | undefined {\n let totalInput = 0;\n let totalOutput = 0;\n let totalCost = 0;\n let hasUsage = false;\n\n for (const msg of messages) {\n const usage = (msg as unknown as { usage?: MessageUsage }).usage;\n if (usage) {\n hasUsage = true;\n totalInput += usage.input || 0;\n totalOutput += usage.output || 0;\n totalCost += usage.cost || 0;\n }\n }\n\n if (!hasUsage) return undefined;\n\n return {\n input: totalInput,\n output: totalOutput,\n total: totalInput + totalOutput,\n cost: totalCost > 0 ? totalCost : undefined,\n };\n}\n\n// Internal: Droppable message filtering\ntype DroppableMessage = AgentMessage & {\n droppable?: boolean;\n};\n\nfunction filterDroppableMessages(messages: AgentMessage[]): AgentMessage[] {\n return messages.filter(msg => !(msg as DroppableMessage).droppable);\n}\n\nfunction findNthTurnFromEnd(messages: AgentMessage[], n: number): number {\n if (n <= 0) return messages.length;\n \n let turnsFound = 0;\n let lastRole: string | null = null;\n \n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i];\n \n if (msg.role === 'user' && lastRole !== 'user') {\n turnsFound++;\n if (turnsFound === n) {\n return i;\n }\n }\n lastRole = msg.role;\n }\n \n return 0;\n}\n\n// Internal: Calculate compaction range\nfunction calculateCompactionRange(\n messages: AgentMessage[],\n config: CompactionConfig\n): { start: number; end: number } | null {\n const totalMessages = messages.length;\n \n if (totalMessages < config.minMessagesBeforeCompact) {\n return null;\n }\n \n const evictionEnd = Math.floor(totalMessages * config.evictionWindow);\n const retentionStart = findNthTurnFromEnd(messages, config.retentionWindow);\n const retentionEnd = retentionStart > 0 ? retentionStart - 1 : 0;\n const compactionEnd = Math.min(evictionEnd, retentionEnd);\n \n if (compactionEnd <= 1) {\n return null;\n }\n \n return { start: 0, end: compactionEnd };\n}\n\nexport class SessionCompactor {\n private config: CompactionConfig;\n \n constructor(\n config?: Partial<CompactionConfig>,\n private model?: Model<Api>\n ) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n }\n\n needsCompaction(\n messages: AgentMessage[],\n contextWindow: number\n ): { needed: boolean; reason: string; usagePercent?: number } {\n if (!this.config.enabled) {\n return { needed: false, reason: 'disabled' };\n }\n\n if (messages.length < this.config.minMessagesBeforeCompact) {\n return { needed: false, reason: 'not_enough_messages' };\n }\n\n const totalTokens = this.estimateTotalTokens(messages);\n const usagePercent = totalTokens / contextWindow;\n\n if (usagePercent > this.config.triggerThreshold) {\n return { \n needed: true, \n reason: 'threshold_exceeded',\n usagePercent \n };\n }\n\n return { needed: false, reason: 'within_threshold' };\n }\n\n async compact(\n messages: AgentMessage[],\n instructions?: string,\n force?: boolean,\n ): Promise<CompactionResult> {\n const effectiveMessages = filterDroppableMessages(messages);\n\n const minRequired = this.config.minMessagesBeforeCompact;\n if (!force && effectiveMessages.length < minRequired) {\n return {\n summary: '',\n firstKeptIndex: 0,\n tokensBefore: this.estimateTotalTokens(effectiveMessages),\n tokensAfter: this.estimateTotalTokens(effectiveMessages),\n compacted: false,\n };\n }\n\n if (force && effectiveMessages.length < 2) {\n return {\n summary: '',\n firstKeptIndex: 0,\n tokensBefore: this.estimateTotalTokens(effectiveMessages),\n tokensAfter: this.estimateTotalTokens(effectiveMessages),\n compacted: false,\n };\n }\n\n const range = calculateCompactionRange(effectiveMessages, this.config);\n let messagesToSummarize: AgentMessage[];\n let keptMessages: AgentMessage[];\n\n if (range) {\n messagesToSummarize = effectiveMessages.slice(0, range.end);\n keptMessages = effectiveMessages.slice(range.end);\n } else if (force) {\n const keep = Math.min(\n this.config.keepRecentMessages,\n Math.max(1, effectiveMessages.length - 1),\n );\n messagesToSummarize = effectiveMessages.slice(0, effectiveMessages.length - keep);\n keptMessages = effectiveMessages.slice(-keep);\n } else {\n const keepRecent = this.config.keepRecentMessages;\n messagesToSummarize = effectiveMessages.slice(0, -keepRecent);\n keptMessages = effectiveMessages.slice(-keepRecent);\n }\n\n if (messagesToSummarize.length === 0) {\n return {\n summary: '',\n firstKeptIndex: 0,\n tokensBefore: this.estimateTotalTokens(effectiveMessages),\n tokensAfter: this.estimateTotalTokens(effectiveMessages),\n compacted: false,\n };\n }\n\n let preservedReasoning: ReasoningDetails | null = null;\n if (this.config.preserveReasoning) {\n preservedReasoning = extractLastReasoning(messagesToSummarize);\n if (preservedReasoning) {\n injectReasoningIntoFirstAssistant(keptMessages, preservedReasoning);\n }\n }\n\n const summary = await this.generateSummary(messagesToSummarize, instructions);\n const structuredSummary = this.config.mode === 'structured' \n ? generateStructuredSummary(messagesToSummarize)\n : undefined;\n\n const tokensBefore = this.estimateTotalTokens(effectiveMessages);\n const summaryTokens = estimateTokens(summary) + 20;\n const keptTokens = this.estimateTotalTokens(keptMessages);\n const tokensAfter = summaryTokens + keptTokens;\n\n let compactedUsage: MessageUsage | undefined;\n if (this.config.accumulateUsage) {\n compactedUsage = accumulateUsage(messagesToSummarize);\n }\n\n return {\n summary,\n firstKeptIndex: keptMessages.length > 0 ? effectiveMessages.length - keptMessages.length : 0,\n tokensBefore,\n tokensAfter,\n compacted: true,\n structuredSummary,\n compactedUsage,\n };\n }\n\n private async generateSummary(messages: AgentMessage[], instructions?: string): Promise<string> {\n if (this.model && (this.config.mode === 'abstractive' || this.config.mode === 'structured')) {\n try {\n if (this.config.mode === 'structured') {\n const structured = generateStructuredSummary(messages);\n return formatSummaryAsText(structured, true);\n } else {\n return await this.llmAbstractiveSummary(messages, instructions);\n }\n } catch (err) {\n console.warn('[Compactor] LLM summarization failed, falling back to extractive', err);\n }\n }\n\n return this.extractiveSummary(messages);\n }\n\n private async llmAbstractiveSummary(messages: AgentMessage[], instructions?: string): Promise<string> {\n if (!this.model) {\n throw new Error('Model not available');\n }\n\n const conversation = this.formatMessages(messages);\n const extra = instructions?.trim()\n ? `\\nAdditional focus from the user:\\n${instructions.trim()}\\n`\n : '';\n const prompt = `Summarize the following conversation in 2-3 concise sentences. Focus on:\n1. What the user was trying to accomplish\n2. Key decisions, outcomes, or solutions\n3. Any important context that should be preserved\n${extra}\nConversation:\n${conversation}\n\nSummary:`;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 30000);\n\n try {\n const summaryMessage: UserMessage = { role: 'user', content: prompt, timestamp: Date.now() };\n const result = await complete(this.model, { \n messages: [summaryMessage]\n }, {\n maxTokens: this.config.summaryMaxTokens,\n temperature: 0.3,\n signal: controller.signal as any,\n });\n\n const text = Array.isArray(result.content)\n ? result.content.filter((c: any) => c.type === 'text').map((c: any) => c.text).join('')\n : '';\n\n return text.trim();\n } catch (err) {\n if (err instanceof Error && err.name === 'AbortError') {\n throw new Error('LLM summarization timed out after 30 seconds');\n }\n throw err;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n private formatMessages(messages: AgentMessage[]): string {\n return messages\n .map(m => {\n const role = m.role;\n const raw = readAgentMessageContent(m);\n const content = typeof raw === 'string'\n ? raw\n : (raw as Array<{ type: string; text?: string }>).filter(c => c.type === 'text').map(c => c.text || '').join('\\n');\n return `[${role}]: ${content}`;\n })\n .join('\\n\\n');\n }\n\n private extractiveSummary(messages: AgentMessage[]): string {\n const userMessages = messages\n .filter(m => m.role === 'user')\n .slice(-5)\n .map(m => {\n const raw = readAgentMessageContent(m);\n if (typeof raw === 'string') return raw;\n if (Array.isArray(raw)) {\n return raw\n .filter(c => c.type === 'text')\n .map(c => (c as { text?: string }).text || '')\n .join('\\n');\n }\n return '';\n })\n .filter(t => t.length > 0)\n .join('; ');\n\n return `Previous conversation covered: ${userMessages.slice(0, 300)}...`;\n }\n\n estimateTotalTokens(messages: AgentMessage[]): number {\n return messages.reduce((sum, msg) => sum + estimateMessageTokens(msg), 0);\n }\n\n applyCompaction(\n messages: AgentMessage[],\n result: CompactionResult\n ): AgentMessage[] {\n if (!result.compacted || !result.summary) {\n return messages;\n }\n\n const summaryMessage: AgentMessage & { usage?: MessageUsage } = {\n role: 'user',\n content: [{ type: 'text', text: `[Previous conversation summary]: ${result.summary}` }],\n timestamp: Date.now(),\n };\n\n if (result.compactedUsage) {\n summaryMessage.usage = result.compactedUsage;\n }\n\n const keptMessages = messages.slice(result.firstKeptIndex);\n return [summaryMessage, ...keptMessages];\n }\n}\n"],"mappings":"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"}
@@ -32,11 +32,13 @@ export declare class SelfVerifyMiddleware {
32
32
  private config;
33
33
  private turnCount;
34
34
  private verificationRequested;
35
+ private hasUnverifiedEdits;
35
36
  constructor(config?: Partial<SelfVerifyConfig>);
36
37
  /**
37
38
  * Record a file edit operation
38
39
  */
39
40
  recordEdit(filePath: string, operation: 'write' | 'edit' | 'shell'): void;
41
+ recordVerification(toolName: string, args?: unknown): void;
40
42
  /**
41
43
  * Get edit count for a specific file
42
44
  */
@@ -56,6 +58,7 @@ export declare class SelfVerifyMiddleware {
56
58
  * Reset all tracking (e.g., after successful task completion)
57
59
  */
58
60
  reset(): void;
61
+ consumePostEditReminder(): string;
59
62
  /**
60
63
  * Get context injection for system prompt
61
64
  * Adds verification guidance based on current state
@@ -17,6 +17,7 @@ var SelfVerifyMiddleware = class {
17
17
  config;
18
18
  turnCount = 0;
19
19
  verificationRequested = false;
20
+ hasUnverifiedEdits = false;
20
21
  constructor(config = {}) {
21
22
  this.config = {
22
23
  ...DEFAULT_CONFIG,
@@ -39,12 +40,22 @@ var SelfVerifyMiddleware = class {
39
40
  lastEditTime: now,
40
41
  operations: [operation]
41
42
  });
43
+ this.hasUnverifiedEdits = true;
42
44
  log.debug({
43
45
  filePath,
44
46
  editCount: this.getEditCount(filePath),
45
47
  operation
46
48
  }, "File edit recorded");
47
49
  }
50
+ recordVerification(toolName, args) {
51
+ const name = toolName.toLowerCase();
52
+ if (name === "shell") {
53
+ const command = args && typeof args === "object" && typeof args.command === "string" ? args.command.toLowerCase() : "";
54
+ if (/\b(test|vitest|jest|mocha|ava|playwright|typecheck|tsc|build|lint)\b/.test(command)) this.hasUnverifiedEdits = false;
55
+ return;
56
+ }
57
+ if (name.includes("test") || name.includes("verify") || name.includes("lint")) this.hasUnverifiedEdits = false;
58
+ }
48
59
  /**
49
60
  * Get edit count for a specific file
50
61
  */
@@ -75,8 +86,15 @@ var SelfVerifyMiddleware = class {
75
86
  this.fileEdits.clear();
76
87
  this.turnCount = 0;
77
88
  this.verificationRequested = false;
89
+ this.hasUnverifiedEdits = false;
78
90
  log.debug("Self-verify middleware reset");
79
91
  }
92
+ consumePostEditReminder() {
93
+ if (!this.config.enablePreCompletionCheck || !this.hasUnverifiedEdits) return "";
94
+ if (this.verificationRequested) return "";
95
+ this.verificationRequested = true;
96
+ return ["Coder check: source files changed. Before final response, inspect the diff and run the smallest meaningful verification; if you cannot, explain why and state the remaining risk."].join("\n");
97
+ }
80
98
  /**
81
99
  * Get context injection for system prompt
82
100
  * Adds verification guidance based on current state
@@ -1 +1 @@
1
- {"version":3,"file":"self-verify.js","names":[],"sources":["../../../../src/agent/middleware/self-verify.ts"],"sourcesContent":["/**\n * Self-Verification Middleware\n *\n * Implements the \"Build & Self-Verify\" pattern from harness engineering:\n * - Intercepts agent before completion to remind verification\n * - Tracks file edits to detect potential issues\n * - Injects verification reminders into context\n *\n * Inspired by: https://blog.langchain.com/improving-deep-agents-with-harness-engineering/\n */\n\nimport { createLogger } from '../../utils/logger.js';\n\nconst log = createLogger('SelfVerifyMiddleware');\n\nexport interface FileEditRecord {\n path: string;\n editCount: number;\n lastEditTime: number;\n operations: string[]; // 'write', 'edit', 'shell' (for build commands)\n}\n\nexport interface SelfVerifyConfig {\n /** Max edits to same file before warning (default: 5) */\n maxEditsPerFile: number;\n /** Enable pre-completion verification check (default: true) */\n enablePreCompletionCheck: boolean;\n /** Min turns before triggering verification reminder (default: 3) */\n minTurnsForVerification: number;\n /** Reset counters on successful verification (default: true) */\n resetOnVerification: boolean;\n}\n\nconst DEFAULT_CONFIG: SelfVerifyConfig = {\n maxEditsPerFile: 5,\n enablePreCompletionCheck: true,\n minTurnsForVerification: 3,\n resetOnVerification: true,\n};\n\n/**\n * Tracks file modifications and provides verification guidance\n */\nexport class SelfVerifyMiddleware {\n private fileEdits: Map<string, FileEditRecord> = new Map();\n private config: SelfVerifyConfig;\n private turnCount = 0;\n private verificationRequested = false;\n\n constructor(config: Partial<SelfVerifyConfig> = {}) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n }\n\n /**\n * Record a file edit operation\n */\n recordEdit(filePath: string, operation: 'write' | 'edit' | 'shell'): void {\n const now = Date.now();\n const existing = this.fileEdits.get(filePath);\n\n if (existing) {\n existing.editCount++;\n existing.lastEditTime = now;\n if (!existing.operations.includes(operation)) {\n existing.operations.push(operation);\n }\n } else {\n this.fileEdits.set(filePath, {\n path: filePath,\n editCount: 1,\n lastEditTime: now,\n operations: [operation],\n });\n }\n\n log.debug({ filePath, editCount: this.getEditCount(filePath), operation }, 'File edit recorded');\n }\n\n /**\n * Get edit count for a specific file\n */\n getEditCount(filePath: string): number {\n return this.fileEdits.get(filePath)?.editCount || 0;\n }\n\n /**\n * Check if any file has excessive edits (potential doom loop)\n */\n hasExcessiveEdits(): { file: string; count: number } | null {\n for (const [path, record] of this.fileEdits.entries()) {\n if (record.editCount >= this.config.maxEditsPerFile) {\n return { file: path, count: record.editCount };\n }\n }\n return null;\n }\n\n /**\n * Increment turn counter\n */\n onTurnStart(): void {\n this.turnCount++;\n this.verificationRequested = false;\n }\n\n /**\n * Reset all tracking (e.g., after successful task completion)\n */\n reset(): void {\n this.fileEdits.clear();\n this.turnCount = 0;\n this.verificationRequested = false;\n log.debug('Self-verify middleware reset');\n }\n\n /**\n * Get context injection for system prompt\n * Adds verification guidance based on current state\n */\n getContextInjection(): string {\n const sections: string[] = [];\n\n // Add workflow guidance\n sections.push(this.buildWorkflowGuidance());\n\n // Add excessive edit warning if needed\n const excessive = this.hasExcessiveEdits();\n if (excessive) {\n sections.push(this.buildExcessiveEditWarning(excessive.file, excessive.count));\n }\n\n // Add pre-completion check reminder for long sessions\n if (this.shouldPromptForVerification()) {\n sections.push(this.buildPreCompletionReminder());\n }\n\n return sections.filter(Boolean).join('\\n\\n');\n }\n\n /**\n * Check if we should inject verification reminder\n */\n private shouldPromptForVerification(): boolean {\n if (!this.config.enablePreCompletionCheck) return false;\n if (this.turnCount < this.config.minTurnsForVerification) return false;\n if (this.verificationRequested) return false;\n return true;\n }\n\n /**\n * Mark verification as requested (to avoid spam)\n */\n markVerificationRequested(): void {\n this.verificationRequested = true;\n }\n\n /**\n * Build workflow guidance section\n */\n private buildWorkflowGuidance(): string {\n return `## Problem Solving Workflow\n\nFollow this iterative process for all tasks:\n\n1. **Plan**: Understand the task, read relevant files, and create a plan\n2. **Build**: Implement your solution with verification in mind\n3. **Verify**: Test your work, run checks, compare against requirements\n4. **Fix**: If issues found, analyze and fix them\n\n**Important**: Before declaring a task complete:\n- Re-read the original requirements\n- Verify your solution meets ALL requirements\n- Run any available tests or validation\n- Check for edge cases\n\nDo not skip verification. Incomplete verification leads to incorrect solutions.`;\n }\n\n /**\n * Build warning for excessive file edits (doom loop detection)\n */\n private buildExcessiveEditWarning(filePath: string, count: number): string {\n return `⚠️ **Pattern Alert**: You have edited \"${filePath}\" ${count} times.\n\nThis may indicate:\n- You're fixing symptoms rather than root causes\n- The approach needs reconsideration\n- Requirements may be unclear\n\n**Recommendation**: \n- Step back and re-read the original task\n- Consider if there's a better approach\n- Verify you understand the requirements correctly`;\n }\n\n /**\n * Build pre-completion verification reminder\n */\n private buildPreCompletionReminder(): string {\n this.markVerificationRequested();\n return `⏳ **Verification Check**: You've made ${this.turnCount} turns on this task.\n\nBefore completing:\n1. ✅ Verify your solution matches the original requirements\n2. ✅ Run tests or validation if available\n3. ✅ Check edge cases and error handling\n4. ✅ Review your changes one final time\n\nIf you're confident, proceed. If unsure, continue refining.`;\n }\n\n /**\n * Get summary of file edits for debugging\n */\n getEditSummary(): { totalFiles: number; totalEdits: number; topFiles: Array<{ path: string; count: number }> } {\n const entries = Array.from(this.fileEdits.entries());\n const totalEdits = entries.reduce((sum, [, record]) => sum + record.editCount, 0);\n const topFiles = entries\n .sort((a, b) => b[1].editCount - a[1].editCount)\n .slice(0, 5)\n .map(([path, record]) => ({ path, count: record.editCount }));\n\n return {\n totalFiles: entries.length,\n totalEdits,\n topFiles,\n };\n }\n\n /**\n * Update configuration\n */\n setConfig(config: Partial<SelfVerifyConfig>): void {\n this.config = { ...this.config, ...config };\n }\n\n /**\n * Get current configuration\n */\n getConfig(): SelfVerifyConfig {\n return { ...this.config };\n }\n}\n"],"mappings":";;;aAWqD;AAErD,MAAM,MAAM,aAAa,uBAAuB;AAoBhD,MAAM,iBAAmC;CACvC,iBAAiB;CACjB,0BAA0B;CAC1B,yBAAyB;CACzB,qBAAqB;CACtB;;;;AAKD,IAAa,uBAAb,MAAkC;CAChC,4BAAiD,IAAI,KAAK;CAC1D;CACA,YAAoB;CACpB,wBAAgC;CAEhC,YAAY,SAAoC,EAAE,EAAE;AAClD,OAAK,SAAS;GAAE,GAAG;GAAgB,GAAG;GAAQ;;;;;CAMhD,WAAW,UAAkB,WAA6C;EACxE,MAAM,MAAM,KAAK,KAAK;EACtB,MAAM,WAAW,KAAK,UAAU,IAAI,SAAS;AAE7C,MAAI,UAAU;AACZ,YAAS;AACT,YAAS,eAAe;AACxB,OAAI,CAAC,SAAS,WAAW,SAAS,UAAU,CAC1C,UAAS,WAAW,KAAK,UAAU;QAGrC,MAAK,UAAU,IAAI,UAAU;GAC3B,MAAM;GACN,WAAW;GACX,cAAc;GACd,YAAY,CAAC,UAAU;GACxB,CAAC;AAGJ,MAAI,MAAM;GAAE;GAAU,WAAW,KAAK,aAAa,SAAS;GAAE;GAAW,EAAE,qBAAqB;;;;;CAMlG,aAAa,UAA0B;AACrC,SAAO,KAAK,UAAU,IAAI,SAAS,EAAE,aAAa;;;;;CAMpD,oBAA4D;AAC1D,OAAK,MAAM,CAAC,MAAM,WAAW,KAAK,UAAU,SAAS,CACnD,KAAI,OAAO,aAAa,KAAK,OAAO,gBAClC,QAAO;GAAE,MAAM;GAAM,OAAO,OAAO;GAAW;AAGlD,SAAO;;;;;CAMT,cAAoB;AAClB,OAAK;AACL,OAAK,wBAAwB;;;;;CAM/B,QAAc;AACZ,OAAK,UAAU,OAAO;AACtB,OAAK,YAAY;AACjB,OAAK,wBAAwB;AAC7B,MAAI,MAAM,+BAA+B;;;;;;CAO3C,sBAA8B;EAC5B,MAAM,WAAqB,EAAE;AAG7B,WAAS,KAAK,KAAK,uBAAuB,CAAC;EAG3C,MAAM,YAAY,KAAK,mBAAmB;AAC1C,MAAI,UACF,UAAS,KAAK,KAAK,0BAA0B,UAAU,MAAM,UAAU,MAAM,CAAC;AAIhF,MAAI,KAAK,6BAA6B,CACpC,UAAS,KAAK,KAAK,4BAA4B,CAAC;AAGlD,SAAO,SAAS,OAAO,QAAQ,CAAC,KAAK,OAAO;;;;;CAM9C,8BAA+C;AAC7C,MAAI,CAAC,KAAK,OAAO,yBAA0B,QAAO;AAClD,MAAI,KAAK,YAAY,KAAK,OAAO,wBAAyB,QAAO;AACjE,MAAI,KAAK,sBAAuB,QAAO;AACvC,SAAO;;;;;CAMT,4BAAkC;AAChC,OAAK,wBAAwB;;;;;CAM/B,wBAAwC;AACtC,SAAO;;;;;;;;;;;;;;;;;;;;CAqBT,0BAAkC,UAAkB,OAAuB;AACzE,SAAO,0CAA0C,SAAS,IAAI,MAAM;;;;;;;;;;;;;;;CAgBtE,6BAA6C;AAC3C,OAAK,2BAA2B;AAChC,SAAO,yCAAyC,KAAK,UAAU;;;;;;;;;;;;;CAcjE,iBAA+G;EAC7G,MAAM,UAAU,MAAM,KAAK,KAAK,UAAU,SAAS,CAAC;EACpD,MAAM,aAAa,QAAQ,QAAQ,KAAK,GAAG,YAAY,MAAM,OAAO,WAAW,EAAE;EACjF,MAAM,WAAW,QACd,MAAM,GAAG,MAAM,EAAE,GAAG,YAAY,EAAE,GAAG,UAAU,CAC/C,MAAM,GAAG,EAAE,CACX,KAAK,CAAC,MAAM,aAAa;GAAE;GAAM,OAAO,OAAO;GAAW,EAAE;AAE/D,SAAO;GACL,YAAY,QAAQ;GACpB;GACA;GACD;;;;;CAMH,UAAU,QAAyC;AACjD,OAAK,SAAS;GAAE,GAAG,KAAK;GAAQ,GAAG;GAAQ;;;;;CAM7C,YAA8B;AAC5B,SAAO,EAAE,GAAG,KAAK,QAAQ"}
1
+ {"version":3,"file":"self-verify.js","names":[],"sources":["../../../../src/agent/middleware/self-verify.ts"],"sourcesContent":["/**\n * Self-Verification Middleware\n *\n * Implements the \"Build & Self-Verify\" pattern from harness engineering:\n * - Intercepts agent before completion to remind verification\n * - Tracks file edits to detect potential issues\n * - Injects verification reminders into context\n *\n * Inspired by: https://blog.langchain.com/improving-deep-agents-with-harness-engineering/\n */\n\nimport { createLogger } from '../../utils/logger.js';\n\nconst log = createLogger('SelfVerifyMiddleware');\n\nexport interface FileEditRecord {\n path: string;\n editCount: number;\n lastEditTime: number;\n operations: string[]; // 'write', 'edit', 'shell' (for build commands)\n}\n\nexport interface SelfVerifyConfig {\n /** Max edits to same file before warning (default: 5) */\n maxEditsPerFile: number;\n /** Enable pre-completion verification check (default: true) */\n enablePreCompletionCheck: boolean;\n /** Min turns before triggering verification reminder (default: 3) */\n minTurnsForVerification: number;\n /** Reset counters on successful verification (default: true) */\n resetOnVerification: boolean;\n}\n\nconst DEFAULT_CONFIG: SelfVerifyConfig = {\n maxEditsPerFile: 5,\n enablePreCompletionCheck: true,\n minTurnsForVerification: 3,\n resetOnVerification: true,\n};\n\n/**\n * Tracks file modifications and provides verification guidance\n */\nexport class SelfVerifyMiddleware {\n private fileEdits: Map<string, FileEditRecord> = new Map();\n private config: SelfVerifyConfig;\n private turnCount = 0;\n private verificationRequested = false;\n private hasUnverifiedEdits = false;\n\n constructor(config: Partial<SelfVerifyConfig> = {}) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n }\n\n /**\n * Record a file edit operation\n */\n recordEdit(filePath: string, operation: 'write' | 'edit' | 'shell'): void {\n const now = Date.now();\n const existing = this.fileEdits.get(filePath);\n\n if (existing) {\n existing.editCount++;\n existing.lastEditTime = now;\n if (!existing.operations.includes(operation)) {\n existing.operations.push(operation);\n }\n } else {\n this.fileEdits.set(filePath, {\n path: filePath,\n editCount: 1,\n lastEditTime: now,\n operations: [operation],\n });\n }\n\n this.hasUnverifiedEdits = true;\n log.debug({ filePath, editCount: this.getEditCount(filePath), operation }, 'File edit recorded');\n }\n\n recordVerification(toolName: string, args?: unknown): void {\n const name = toolName.toLowerCase();\n if (name === 'shell') {\n const command =\n args && typeof args === 'object' && typeof (args as { command?: unknown }).command === 'string'\n ? (args as { command: string }).command.toLowerCase()\n : '';\n if (/\\b(test|vitest|jest|mocha|ava|playwright|typecheck|tsc|build|lint)\\b/.test(command)) {\n this.hasUnverifiedEdits = false;\n }\n return;\n }\n if (name.includes('test') || name.includes('verify') || name.includes('lint')) {\n this.hasUnverifiedEdits = false;\n }\n }\n\n /**\n * Get edit count for a specific file\n */\n getEditCount(filePath: string): number {\n return this.fileEdits.get(filePath)?.editCount || 0;\n }\n\n /**\n * Check if any file has excessive edits (potential doom loop)\n */\n hasExcessiveEdits(): { file: string; count: number } | null {\n for (const [path, record] of this.fileEdits.entries()) {\n if (record.editCount >= this.config.maxEditsPerFile) {\n return { file: path, count: record.editCount };\n }\n }\n return null;\n }\n\n /**\n * Increment turn counter\n */\n onTurnStart(): void {\n this.turnCount++;\n this.verificationRequested = false;\n }\n\n /**\n * Reset all tracking (e.g., after successful task completion)\n */\n reset(): void {\n this.fileEdits.clear();\n this.turnCount = 0;\n this.verificationRequested = false;\n this.hasUnverifiedEdits = false;\n log.debug('Self-verify middleware reset');\n }\n\n consumePostEditReminder(): string {\n if (!this.config.enablePreCompletionCheck || !this.hasUnverifiedEdits) {\n return '';\n }\n if (this.verificationRequested) {\n return '';\n }\n this.verificationRequested = true;\n return [\n 'Coder check: source files changed. Before final response, inspect the diff and run the smallest meaningful verification; if you cannot, explain why and state the remaining risk.',\n ].join('\\n');\n }\n\n /**\n * Get context injection for system prompt\n * Adds verification guidance based on current state\n */\n getContextInjection(): string {\n const sections: string[] = [];\n\n // Add workflow guidance\n sections.push(this.buildWorkflowGuidance());\n\n // Add excessive edit warning if needed\n const excessive = this.hasExcessiveEdits();\n if (excessive) {\n sections.push(this.buildExcessiveEditWarning(excessive.file, excessive.count));\n }\n\n // Add pre-completion check reminder for long sessions\n if (this.shouldPromptForVerification()) {\n sections.push(this.buildPreCompletionReminder());\n }\n\n return sections.filter(Boolean).join('\\n\\n');\n }\n\n /**\n * Check if we should inject verification reminder\n */\n private shouldPromptForVerification(): boolean {\n if (!this.config.enablePreCompletionCheck) return false;\n if (this.turnCount < this.config.minTurnsForVerification) return false;\n if (this.verificationRequested) return false;\n return true;\n }\n\n /**\n * Mark verification as requested (to avoid spam)\n */\n markVerificationRequested(): void {\n this.verificationRequested = true;\n }\n\n /**\n * Build workflow guidance section\n */\n private buildWorkflowGuidance(): string {\n return `## Problem Solving Workflow\n\nFollow this iterative process for all tasks:\n\n1. **Plan**: Understand the task, read relevant files, and create a plan\n2. **Build**: Implement your solution with verification in mind\n3. **Verify**: Test your work, run checks, compare against requirements\n4. **Fix**: If issues found, analyze and fix them\n\n**Important**: Before declaring a task complete:\n- Re-read the original requirements\n- Verify your solution meets ALL requirements\n- Run any available tests or validation\n- Check for edge cases\n\nDo not skip verification. Incomplete verification leads to incorrect solutions.`;\n }\n\n /**\n * Build warning for excessive file edits (doom loop detection)\n */\n private buildExcessiveEditWarning(filePath: string, count: number): string {\n return `⚠️ **Pattern Alert**: You have edited \"${filePath}\" ${count} times.\n\nThis may indicate:\n- You're fixing symptoms rather than root causes\n- The approach needs reconsideration\n- Requirements may be unclear\n\n**Recommendation**: \n- Step back and re-read the original task\n- Consider if there's a better approach\n- Verify you understand the requirements correctly`;\n }\n\n /**\n * Build pre-completion verification reminder\n */\n private buildPreCompletionReminder(): string {\n this.markVerificationRequested();\n return `⏳ **Verification Check**: You've made ${this.turnCount} turns on this task.\n\nBefore completing:\n1. ✅ Verify your solution matches the original requirements\n2. ✅ Run tests or validation if available\n3. ✅ Check edge cases and error handling\n4. ✅ Review your changes one final time\n\nIf you're confident, proceed. If unsure, continue refining.`;\n }\n\n /**\n * Get summary of file edits for debugging\n */\n getEditSummary(): { totalFiles: number; totalEdits: number; topFiles: Array<{ path: string; count: number }> } {\n const entries = Array.from(this.fileEdits.entries());\n const totalEdits = entries.reduce((sum, [, record]) => sum + record.editCount, 0);\n const topFiles = entries\n .sort((a, b) => b[1].editCount - a[1].editCount)\n .slice(0, 5)\n .map(([path, record]) => ({ path, count: record.editCount }));\n\n return {\n totalFiles: entries.length,\n totalEdits,\n topFiles,\n };\n }\n\n /**\n * Update configuration\n */\n setConfig(config: Partial<SelfVerifyConfig>): void {\n this.config = { ...this.config, ...config };\n }\n\n /**\n * Get current configuration\n */\n getConfig(): SelfVerifyConfig {\n return { ...this.config };\n }\n}\n"],"mappings":"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"}
@@ -153,9 +153,27 @@ function installLifecycleHookListener(bus, lifecycleManager) {
153
153
  function installSystemReminderListener(bus, systemReminder) {
154
154
  bus.on("tool_execution_end", (event) => {
155
155
  const e = event;
156
- e.result = systemReminder.appendToResult(e.result);
156
+ e.result = systemReminder.appendToResult(e.result, e.toolName);
157
157
  });
158
158
  }
159
+ function appendTextToToolResult(result, text) {
160
+ if (!text.trim()) return result;
161
+ if (result && typeof result === "object") {
162
+ const res = result;
163
+ if (Array.isArray(res.content)) {
164
+ res.content = [...res.content, {
165
+ type: "text",
166
+ text: `\n${text}`
167
+ }];
168
+ return res;
169
+ }
170
+ if (typeof res.content === "string") {
171
+ res.content = `${res.content}\n${text}`;
172
+ return res;
173
+ }
174
+ }
175
+ return result;
176
+ }
159
177
  function installToolChainListener(bus, toolChainTracker) {
160
178
  bus.on("turn_start", (_event, context) => {
161
179
  toolChainTracker.startChain(context.sessionKey);
@@ -222,8 +240,15 @@ function installSelfVerifyListener(bus, selfVerifyMiddleware) {
222
240
  const name = toolName.toLowerCase();
223
241
  if (name.includes("write") && args.path) selfVerifyMiddleware.recordEdit(String(args.path), "write");
224
242
  else if (name.includes("edit") && args.path) selfVerifyMiddleware.recordEdit(String(args.path), "edit");
243
+ else selfVerifyMiddleware.recordVerification(toolName, args);
225
244
  });
226
- bus.on("turn_end", () => {
245
+ bus.on("tool_execution_end", (event) => {
246
+ const e = event;
247
+ const name = e.toolName?.toLowerCase() ?? "";
248
+ if (!name.includes("write") && !name.includes("edit")) return;
249
+ e.result = appendTextToToolResult(e.result, selfVerifyMiddleware.consumePostEditReminder());
250
+ });
251
+ bus.on("turn_start", () => {
227
252
  selfVerifyMiddleware.onTurnStart();
228
253
  });
229
254
  }
@@ -245,13 +270,13 @@ var AgentEventHandler = class {
245
270
  });
246
271
  installLifecycleHookListener(this.bus, config.lifecycleManager);
247
272
  installSystemReminderListener(this.bus, config.systemReminder);
273
+ installSelfVerifyListener(this.bus, config.selfVerifyMiddleware);
248
274
  installToolUsageListener(this.bus, config.toolUsageAnalyzer);
249
275
  installToolChainListener(this.bus, config.toolChainTracker);
250
276
  installErrorTrackingListener(this.bus, {
251
277
  errorTracker: config.errorTracker,
252
278
  errorPatternMatcher: config.errorPatternMatcher
253
279
  });
254
- installSelfVerifyListener(this.bus, config.selfVerifyMiddleware);
255
280
  }
256
281
  /** Dispatch a pi-agent event to all registered listeners. */
257
282
  handle(event, context) {
@@ -1 +1 @@
1
- {"version":3,"file":"agent-event-handler.js","names":[],"sources":["../../../../src/agent/orchestration/agent-event-handler.ts"],"sourcesContent":["/**\n * Agent Event Handler — coordinates listeners on pi-agent events.\n *\n * Previously a single god-handler with ten manager fields and one `switch`. Now a\n * thin façade around {@link SessionEventBus}: each concern (progress, lifecycle\n * hooks, tool-chain recording, error tracking, self-verify, etc.) registers its\n * own listener at construction time, and external code can add new listeners via\n * {@link AgentEventHandler.registerListener} without modifying this file (OCP).\n *\n * Ordering note: a few listeners are order-sensitive (notably the\n * `tool_execution_end` chain — `SystemReminder` mutates `event.result` in place\n * and downstream listeners read the mutated value). The `installX` calls below\n * preserve the exact order from the previous implementation.\n */\n\nimport type { AgentEvent } from '@earendil-works/pi-agent-core';\nimport type { SessionContext } from '../session/session-context.js';\nimport type { ProgressFeedbackManager } from '../lifecycle/progress.js';\nimport type { ToolErrorTracker } from '../tools/error-tracker.js';\nimport type { RequestLimiter } from '../models/request-limiter.js';\nimport type { LifecycleManager } from '../lifecycle/index.js';\nimport type { ToolChainTracker } from '../tools/chain-tracker.js';\nimport type { SelfVerifyMiddleware } from '../middleware/index.js';\nimport type { SystemReminder } from '../prompt/system-reminder.js';\nimport type { ToolUsageAnalyzer } from '../tools/usage-analyzer.js';\nimport type { ErrorPatternMatcher } from '../tools/error-pattern-matcher.js';\nimport { extractTextContent } from '../context/workspace.js';\nimport { createLogger } from '../../utils/logger.js';\n\nconst log = createLogger('AgentEventHandler');\n\n// ── Event bus ──────────────────────────────────────────────────────────────\n\nexport type SessionEventListener = (event: AgentEvent, context: SessionContext) => void;\nexport type SessionEventTypeFilter = AgentEvent['type'] | 'all';\n\n/**\n * Typed pub/sub for agent events. Listeners run in registration order; the bus\n * itself is synchronous — listeners that need async work must dispatch their own\n * promises (typically by awaiting and logging on error, like the lifecycle hooks).\n */\nexport class SessionEventBus {\n private readonly listeners = new Map<SessionEventTypeFilter, SessionEventListener[]>();\n\n on(type: SessionEventTypeFilter, listener: SessionEventListener): () => void {\n const bucket = this.listeners.get(type) ?? [];\n bucket.push(listener);\n this.listeners.set(type, bucket);\n return () => {\n const current = this.listeners.get(type);\n if (!current) return;\n const idx = current.indexOf(listener);\n if (idx >= 0) current.splice(idx, 1);\n };\n }\n\n dispatch(event: AgentEvent, context: SessionContext | null): void {\n if (!context) {\n if (event.type !== 'message_update') {\n log.warn(\n { eventType: event.type },\n `Agent event ignored (no SessionContext): ${event.type}`,\n );\n }\n return;\n }\n const exact = this.listeners.get(event.type);\n if (exact) {\n for (const listener of exact) {\n listener(event, context);\n }\n }\n const all = this.listeners.get('all');\n if (all) {\n for (const listener of all) {\n listener(event, context);\n }\n }\n }\n}\n\n// ── Built-in listener installers ───────────────────────────────────────────\n\nfunction installProgressListener(bus: SessionEventBus, progressManager: ProgressFeedbackManager): void {\n bus.on('agent_start', () => {\n log.debug('Agent turn started');\n progressManager.startTask();\n });\n bus.on('turn_start', () => {\n log.debug('Turn started');\n progressManager.onTurnStart();\n });\n bus.on('tool_execution_start', (event) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_start' }>;\n log.debug({ tool: e.toolName, args: e.args }, 'Tool execution started');\n progressManager.onToolStart(e.toolName, e.args || {});\n });\n bus.on('tool_execution_update', (event) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_update' }>;\n progressManager.onToolUpdate(e.toolName, e.partialResult);\n });\n bus.on('tool_execution_end', (event) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_end' }>;\n log.debug({ tool: e.toolName, isError: e.isError }, 'Tool execution complete');\n progressManager.onToolEnd(e.toolName, e.result, e.isError);\n });\n bus.on('agent_end', () => {\n progressManager.endTask();\n });\n}\n\nfunction installRequestLimitListener(\n bus: SessionEventBus,\n deps: {\n requestLimiter: RequestLimiter;\n progressManager: ProgressFeedbackManager;\n lifecycleManager: LifecycleManager;\n },\n): void {\n bus.on('agent_start', (_event, context) => {\n const result = deps.requestLimiter.recordRequest();\n\n deps.lifecycleManager\n .emit(\n 'llm_request',\n context.sessionKey,\n { requestNumber: result.count, maxRequests: result.limit },\n context,\n )\n .catch((err) => {\n const em = err instanceof Error ? err.message : String(err);\n log.warn(\n {\n err,\n errorMessage: em,\n sessionKey: context.sessionKey,\n requestNumber: result.count,\n maxRequests: result.limit,\n },\n `Lifecycle emit llm_request failed: ${em}`,\n );\n });\n\n deps.progressManager.onRequestLimitStatus(\n result.count,\n result.limit,\n result.remaining,\n result.isWarning,\n result.shouldStop,\n );\n\n if (result.shouldStop) {\n log.error(\n { count: result.count, limit: result.limit, sessionKey: context.sessionKey },\n `Request limit reached (${result.count}/${result.limit}) for session`,\n );\n }\n });\n\n bus.on('turn_end', () => {\n deps.requestLimiter.reset();\n });\n}\n\nfunction installLifecycleHookListener(bus: SessionEventBus, lifecycleManager: LifecycleManager): void {\n bus.on('message_end', (event, context) => {\n const e = event as Extract<AgentEvent, { type: 'message_end' }>;\n if (e.message?.role !== 'assistant') return;\n const content = e.message.content;\n const text = Array.isArray(content)\n ? extractTextContent(content as Array<{ type: string; text?: string }>)\n : String(content);\n log.debug({ contentLength: text.length }, 'Assistant response complete');\n\n lifecycleManager\n .emit(\n 'llm_response',\n context.sessionKey,\n { response: text, usage: (e.message as { usage?: unknown }).usage },\n context,\n )\n .catch((err) => {\n const em = err instanceof Error ? err.message : String(err);\n log.warn(\n { err, errorMessage: em, sessionKey: context.sessionKey, responseChars: text.length },\n `Lifecycle emit llm_response failed: ${em}`,\n );\n });\n });\n\n bus.on('tool_execution_start', (event, context) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_start' }>;\n lifecycleManager\n .emit(\n 'tool_call_start',\n context.sessionKey,\n {\n toolName: e.toolName,\n arguments: e.args || {},\n attemptNumber: 1,\n maxAttempts: 3,\n },\n context,\n )\n .catch((err) => {\n const em = err instanceof Error ? err.message : String(err);\n log.warn(\n { err, errorMessage: em, sessionKey: context.sessionKey, tool: e.toolName },\n `Lifecycle emit tool_call_start failed: ${em}`,\n );\n });\n });\n\n bus.on('tool_execution_end', (event, context) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_end' }>;\n const durationMs = (e as { durationMs?: number }).durationMs || 0;\n lifecycleManager\n .emit(\n 'tool_call_end',\n context.sessionKey,\n {\n toolName: e.toolName,\n success: !e.isError,\n result: e.result,\n error: e.isError ? String(e.result) : undefined,\n durationMs,\n },\n context,\n )\n .catch((err) => {\n const em = err instanceof Error ? err.message : String(err);\n log.warn(\n { err, errorMessage: em, sessionKey: context.sessionKey, tool: e.toolName },\n `Lifecycle hook tool_call_end failed: ${em}`,\n );\n });\n });\n}\n\nfunction installSystemReminderListener(bus: SessionEventBus, systemReminder: SystemReminder): void {\n // MUTATES `event.result` so downstream listeners (tool-chain, error-tracking) see\n // the decorated text. Must run AFTER progress + lifecycle hooks (they consume the\n // original) and BEFORE tool-chain + error-tracking.\n bus.on('tool_execution_end', (event) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_end' }> & { result: unknown };\n e.result = systemReminder.appendToResult(e.result);\n });\n}\n\nfunction installToolChainListener(bus: SessionEventBus, toolChainTracker: ToolChainTracker): void {\n bus.on('turn_start', (_event, context) => {\n // One chain per LLM turn (pi-agent emits turn_start each round; turn_end clears the chain).\n toolChainTracker.startChain(context.sessionKey);\n });\n bus.on('tool_execution_start', (event, context) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_start' }>;\n toolChainTracker.recordCall(context.sessionKey, e.toolName, e.args || {}, 0);\n });\n bus.on('tool_execution_end', (event, context) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_end' }>;\n const durationMs = (e as { durationMs?: number }).durationMs || 0;\n const chain = toolChainTracker.getCurrentChain(context.sessionKey);\n if (!chain) return;\n const lastNode = chain.nodes[chain.nodes.length - 1];\n if (lastNode && lastNode.toolName === e.toolName) {\n toolChainTracker.recordResult(\n context.sessionKey,\n lastNode.id,\n e.result,\n e.isError ? 'Tool execution failed' : undefined,\n durationMs,\n );\n }\n });\n bus.on('turn_end', (_event, context) => {\n toolChainTracker.endChain(context.sessionKey);\n });\n}\n\nfunction installToolUsageListener(bus: SessionEventBus, toolUsageAnalyzer: ToolUsageAnalyzer): void {\n bus.on('tool_execution_end', (event) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_end' }>;\n const durationMs = (e as { durationMs?: number }).durationMs || 0;\n toolUsageAnalyzer.recordUsage(e.toolName, !e.isError, durationMs);\n });\n}\n\nfunction extractErrorText(result: unknown): string {\n if (typeof result === 'string') return result;\n if (result && typeof result === 'object') {\n const obj = result as Record<string, unknown>;\n if (typeof obj.error === 'string') return obj.error;\n if (typeof obj.message === 'string') return obj.message;\n return JSON.stringify(result);\n }\n return String(result);\n}\n\nfunction installErrorTrackingListener(\n bus: SessionEventBus,\n deps: { errorTracker: ToolErrorTracker; errorPatternMatcher: ErrorPatternMatcher },\n): void {\n bus.on('tool_execution_end', (event) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_end' }>;\n if (!e.isError) return;\n const errorText = extractErrorText(e.result);\n deps.errorTracker.recordFailure(e.toolName, errorText);\n\n const errorMatch = deps.errorPatternMatcher.matchError(errorText);\n if (errorMatch.matched && errorMatch.pattern) {\n const preview = errorText.length > 120 ? `${errorText.slice(0, 120)}…` : errorText;\n log.warn(\n { tool: e.toolName, pattern: errorMatch.pattern.name, errorPreview: preview },\n `Tool error matched pattern \"${errorMatch.pattern.name}\" (${e.toolName}): ${preview}`,\n );\n }\n });\n bus.on('turn_end', () => {\n deps.errorTracker.reset();\n });\n}\n\nfunction installSelfVerifyListener(bus: SessionEventBus, selfVerifyMiddleware: SelfVerifyMiddleware): void {\n bus.on('tool_execution_start', (event) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_start' }>;\n const args = e.args;\n const toolName = e.toolName;\n if (!toolName || !args) return;\n const name = toolName.toLowerCase();\n if (name.includes('write') && args.path) {\n selfVerifyMiddleware.recordEdit(String(args.path), 'write');\n } else if (name.includes('edit') && args.path) {\n selfVerifyMiddleware.recordEdit(String(args.path), 'edit');\n }\n });\n bus.on('turn_end', () => {\n selfVerifyMiddleware.onTurnStart();\n });\n}\n\n// ── Public façade ───────────────────────────────────────────────────────────\n\nexport interface AgentEventHandlerConfig {\n progressManager: ProgressFeedbackManager;\n errorTracker: ToolErrorTracker;\n requestLimiter: RequestLimiter;\n lifecycleManager: LifecycleManager;\n toolChainTracker: ToolChainTracker;\n selfVerifyMiddleware: SelfVerifyMiddleware;\n systemReminder: SystemReminder;\n toolUsageAnalyzer: ToolUsageAnalyzer;\n errorPatternMatcher: ErrorPatternMatcher;\n}\n\n/**\n * Thin façade over {@link SessionEventBus} that pre-registers all built-in\n * listeners in their required order. `handle(event, ctx)` is the entry point\n * used by `AgentService.handleSessionEvent`; extensions can hook in via\n * {@link registerListener} without touching this class.\n */\nexport class AgentEventHandler {\n private readonly bus: SessionEventBus;\n\n constructor(config: AgentEventHandlerConfig) {\n this.bus = new SessionEventBus();\n\n // Order matters for `tool_execution_end`: SystemReminder mutates `event.result`\n // and ToolChain + ErrorTracking depend on the mutated value. Keep these in this\n // exact sequence.\n installProgressListener(this.bus, config.progressManager);\n installRequestLimitListener(this.bus, {\n requestLimiter: config.requestLimiter,\n progressManager: config.progressManager,\n lifecycleManager: config.lifecycleManager,\n });\n installLifecycleHookListener(this.bus, config.lifecycleManager);\n installSystemReminderListener(this.bus, config.systemReminder);\n installToolUsageListener(this.bus, config.toolUsageAnalyzer);\n installToolChainListener(this.bus, config.toolChainTracker);\n installErrorTrackingListener(this.bus, {\n errorTracker: config.errorTracker,\n errorPatternMatcher: config.errorPatternMatcher,\n });\n installSelfVerifyListener(this.bus, config.selfVerifyMiddleware);\n }\n\n /** Dispatch a pi-agent event to all registered listeners. */\n handle(event: AgentEvent, context: SessionContext | null): void {\n this.bus.dispatch(event, context);\n }\n\n /**\n * Register an additional listener (e.g. from an extension). Returns an\n * unsubscribe function. Listeners run after the built-ins in registration\n * order; if you need to run before a built-in, you must create your own\n * {@link SessionEventBus} instance.\n */\n registerListener(type: SessionEventTypeFilter, listener: SessionEventListener): () => void {\n return this.bus.on(type, listener);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"agent-event-handler.js","names":[],"sources":["../../../../src/agent/orchestration/agent-event-handler.ts"],"sourcesContent":["/**\n * Agent Event Handler — coordinates listeners on pi-agent events.\n *\n * Previously a single god-handler with ten manager fields and one `switch`. Now a\n * thin façade around {@link SessionEventBus}: each concern (progress, lifecycle\n * hooks, tool-chain recording, error tracking, self-verify, etc.) registers its\n * own listener at construction time, and external code can add new listeners via\n * {@link AgentEventHandler.registerListener} without modifying this file (OCP).\n *\n * Ordering note: a few listeners are order-sensitive (notably the\n * `tool_execution_end` chain — `SystemReminder` mutates `event.result` in place\n * and downstream listeners read the mutated value). The `installX` calls below\n * preserve the exact order from the previous implementation.\n */\n\nimport type { AgentEvent } from '@earendil-works/pi-agent-core';\nimport type { SessionContext } from '../session/session-context.js';\nimport type { ProgressFeedbackManager } from '../lifecycle/progress.js';\nimport type { ToolErrorTracker } from '../tools/error-tracker.js';\nimport type { RequestLimiter } from '../models/request-limiter.js';\nimport type { LifecycleManager } from '../lifecycle/index.js';\nimport type { ToolChainTracker } from '../tools/chain-tracker.js';\nimport type { SelfVerifyMiddleware } from '../middleware/index.js';\nimport type { SystemReminder } from '../prompt/system-reminder.js';\nimport type { ToolUsageAnalyzer } from '../tools/usage-analyzer.js';\nimport type { ErrorPatternMatcher } from '../tools/error-pattern-matcher.js';\nimport { extractTextContent } from '../context/workspace.js';\nimport { createLogger } from '../../utils/logger.js';\n\nconst log = createLogger('AgentEventHandler');\n\n// ── Event bus ──────────────────────────────────────────────────────────────\n\nexport type SessionEventListener = (event: AgentEvent, context: SessionContext) => void;\nexport type SessionEventTypeFilter = AgentEvent['type'] | 'all';\n\n/**\n * Typed pub/sub for agent events. Listeners run in registration order; the bus\n * itself is synchronous — listeners that need async work must dispatch their own\n * promises (typically by awaiting and logging on error, like the lifecycle hooks).\n */\nexport class SessionEventBus {\n private readonly listeners = new Map<SessionEventTypeFilter, SessionEventListener[]>();\n\n on(type: SessionEventTypeFilter, listener: SessionEventListener): () => void {\n const bucket = this.listeners.get(type) ?? [];\n bucket.push(listener);\n this.listeners.set(type, bucket);\n return () => {\n const current = this.listeners.get(type);\n if (!current) return;\n const idx = current.indexOf(listener);\n if (idx >= 0) current.splice(idx, 1);\n };\n }\n\n dispatch(event: AgentEvent, context: SessionContext | null): void {\n if (!context) {\n if (event.type !== 'message_update') {\n log.warn(\n { eventType: event.type },\n `Agent event ignored (no SessionContext): ${event.type}`,\n );\n }\n return;\n }\n const exact = this.listeners.get(event.type);\n if (exact) {\n for (const listener of exact) {\n listener(event, context);\n }\n }\n const all = this.listeners.get('all');\n if (all) {\n for (const listener of all) {\n listener(event, context);\n }\n }\n }\n}\n\n// ── Built-in listener installers ───────────────────────────────────────────\n\nfunction installProgressListener(bus: SessionEventBus, progressManager: ProgressFeedbackManager): void {\n bus.on('agent_start', () => {\n log.debug('Agent turn started');\n progressManager.startTask();\n });\n bus.on('turn_start', () => {\n log.debug('Turn started');\n progressManager.onTurnStart();\n });\n bus.on('tool_execution_start', (event) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_start' }>;\n log.debug({ tool: e.toolName, args: e.args }, 'Tool execution started');\n progressManager.onToolStart(e.toolName, e.args || {});\n });\n bus.on('tool_execution_update', (event) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_update' }>;\n progressManager.onToolUpdate(e.toolName, e.partialResult);\n });\n bus.on('tool_execution_end', (event) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_end' }>;\n log.debug({ tool: e.toolName, isError: e.isError }, 'Tool execution complete');\n progressManager.onToolEnd(e.toolName, e.result, e.isError);\n });\n bus.on('agent_end', () => {\n progressManager.endTask();\n });\n}\n\nfunction installRequestLimitListener(\n bus: SessionEventBus,\n deps: {\n requestLimiter: RequestLimiter;\n progressManager: ProgressFeedbackManager;\n lifecycleManager: LifecycleManager;\n },\n): void {\n bus.on('agent_start', (_event, context) => {\n const result = deps.requestLimiter.recordRequest();\n\n deps.lifecycleManager\n .emit(\n 'llm_request',\n context.sessionKey,\n { requestNumber: result.count, maxRequests: result.limit },\n context,\n )\n .catch((err) => {\n const em = err instanceof Error ? err.message : String(err);\n log.warn(\n {\n err,\n errorMessage: em,\n sessionKey: context.sessionKey,\n requestNumber: result.count,\n maxRequests: result.limit,\n },\n `Lifecycle emit llm_request failed: ${em}`,\n );\n });\n\n deps.progressManager.onRequestLimitStatus(\n result.count,\n result.limit,\n result.remaining,\n result.isWarning,\n result.shouldStop,\n );\n\n if (result.shouldStop) {\n log.error(\n { count: result.count, limit: result.limit, sessionKey: context.sessionKey },\n `Request limit reached (${result.count}/${result.limit}) for session`,\n );\n }\n });\n\n bus.on('turn_end', () => {\n deps.requestLimiter.reset();\n });\n}\n\nfunction installLifecycleHookListener(bus: SessionEventBus, lifecycleManager: LifecycleManager): void {\n bus.on('message_end', (event, context) => {\n const e = event as Extract<AgentEvent, { type: 'message_end' }>;\n if (e.message?.role !== 'assistant') return;\n const content = e.message.content;\n const text = Array.isArray(content)\n ? extractTextContent(content as Array<{ type: string; text?: string }>)\n : String(content);\n log.debug({ contentLength: text.length }, 'Assistant response complete');\n\n lifecycleManager\n .emit(\n 'llm_response',\n context.sessionKey,\n { response: text, usage: (e.message as { usage?: unknown }).usage },\n context,\n )\n .catch((err) => {\n const em = err instanceof Error ? err.message : String(err);\n log.warn(\n { err, errorMessage: em, sessionKey: context.sessionKey, responseChars: text.length },\n `Lifecycle emit llm_response failed: ${em}`,\n );\n });\n });\n\n bus.on('tool_execution_start', (event, context) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_start' }>;\n lifecycleManager\n .emit(\n 'tool_call_start',\n context.sessionKey,\n {\n toolName: e.toolName,\n arguments: e.args || {},\n attemptNumber: 1,\n maxAttempts: 3,\n },\n context,\n )\n .catch((err) => {\n const em = err instanceof Error ? err.message : String(err);\n log.warn(\n { err, errorMessage: em, sessionKey: context.sessionKey, tool: e.toolName },\n `Lifecycle emit tool_call_start failed: ${em}`,\n );\n });\n });\n\n bus.on('tool_execution_end', (event, context) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_end' }>;\n const durationMs = (e as { durationMs?: number }).durationMs || 0;\n lifecycleManager\n .emit(\n 'tool_call_end',\n context.sessionKey,\n {\n toolName: e.toolName,\n success: !e.isError,\n result: e.result,\n error: e.isError ? String(e.result) : undefined,\n durationMs,\n },\n context,\n )\n .catch((err) => {\n const em = err instanceof Error ? err.message : String(err);\n log.warn(\n { err, errorMessage: em, sessionKey: context.sessionKey, tool: e.toolName },\n `Lifecycle hook tool_call_end failed: ${em}`,\n );\n });\n });\n}\n\nfunction installSystemReminderListener(bus: SessionEventBus, systemReminder: SystemReminder): void {\n // MUTATES `event.result` so downstream listeners (tool-chain, error-tracking) see\n // the decorated text. Must run AFTER progress + lifecycle hooks (they consume the\n // original) and BEFORE tool-chain + error-tracking.\n bus.on('tool_execution_end', (event) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_end' }> & { result: unknown };\n e.result = systemReminder.appendToResult(e.result, e.toolName);\n });\n}\n\nfunction appendTextToToolResult(result: unknown, text: string): unknown {\n if (!text.trim()) {\n return result;\n }\n if (result && typeof result === 'object') {\n const res = result as Record<string, unknown>;\n if (Array.isArray(res.content)) {\n res.content = [\n ...res.content,\n { type: 'text', text: `\\n${text}` },\n ];\n return res;\n }\n if (typeof res.content === 'string') {\n res.content = `${res.content}\\n${text}`;\n return res;\n }\n }\n return result;\n}\n\nfunction installToolChainListener(bus: SessionEventBus, toolChainTracker: ToolChainTracker): void {\n bus.on('turn_start', (_event, context) => {\n // One chain per LLM turn (pi-agent emits turn_start each round; turn_end clears the chain).\n toolChainTracker.startChain(context.sessionKey);\n });\n bus.on('tool_execution_start', (event, context) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_start' }>;\n toolChainTracker.recordCall(context.sessionKey, e.toolName, e.args || {}, 0);\n });\n bus.on('tool_execution_end', (event, context) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_end' }>;\n const durationMs = (e as { durationMs?: number }).durationMs || 0;\n const chain = toolChainTracker.getCurrentChain(context.sessionKey);\n if (!chain) return;\n const lastNode = chain.nodes[chain.nodes.length - 1];\n if (lastNode && lastNode.toolName === e.toolName) {\n toolChainTracker.recordResult(\n context.sessionKey,\n lastNode.id,\n e.result,\n e.isError ? 'Tool execution failed' : undefined,\n durationMs,\n );\n }\n });\n bus.on('turn_end', (_event, context) => {\n toolChainTracker.endChain(context.sessionKey);\n });\n}\n\nfunction installToolUsageListener(bus: SessionEventBus, toolUsageAnalyzer: ToolUsageAnalyzer): void {\n bus.on('tool_execution_end', (event) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_end' }>;\n const durationMs = (e as { durationMs?: number }).durationMs || 0;\n toolUsageAnalyzer.recordUsage(e.toolName, !e.isError, durationMs);\n });\n}\n\nfunction extractErrorText(result: unknown): string {\n if (typeof result === 'string') return result;\n if (result && typeof result === 'object') {\n const obj = result as Record<string, unknown>;\n if (typeof obj.error === 'string') return obj.error;\n if (typeof obj.message === 'string') return obj.message;\n return JSON.stringify(result);\n }\n return String(result);\n}\n\nfunction installErrorTrackingListener(\n bus: SessionEventBus,\n deps: { errorTracker: ToolErrorTracker; errorPatternMatcher: ErrorPatternMatcher },\n): void {\n bus.on('tool_execution_end', (event) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_end' }>;\n if (!e.isError) return;\n const errorText = extractErrorText(e.result);\n deps.errorTracker.recordFailure(e.toolName, errorText);\n\n const errorMatch = deps.errorPatternMatcher.matchError(errorText);\n if (errorMatch.matched && errorMatch.pattern) {\n const preview = errorText.length > 120 ? `${errorText.slice(0, 120)}…` : errorText;\n log.warn(\n { tool: e.toolName, pattern: errorMatch.pattern.name, errorPreview: preview },\n `Tool error matched pattern \"${errorMatch.pattern.name}\" (${e.toolName}): ${preview}`,\n );\n }\n });\n bus.on('turn_end', () => {\n deps.errorTracker.reset();\n });\n}\n\nfunction installSelfVerifyListener(bus: SessionEventBus, selfVerifyMiddleware: SelfVerifyMiddleware): void {\n bus.on('tool_execution_start', (event) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_start' }>;\n const args = e.args;\n const toolName = e.toolName;\n if (!toolName || !args) return;\n const name = toolName.toLowerCase();\n if (name.includes('write') && args.path) {\n selfVerifyMiddleware.recordEdit(String(args.path), 'write');\n } else if (name.includes('edit') && args.path) {\n selfVerifyMiddleware.recordEdit(String(args.path), 'edit');\n } else {\n selfVerifyMiddleware.recordVerification(toolName, args);\n }\n });\n bus.on('tool_execution_end', (event) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_end' }> & { result: unknown };\n const name = e.toolName?.toLowerCase() ?? '';\n if (!name.includes('write') && !name.includes('edit')) {\n return;\n }\n e.result = appendTextToToolResult(e.result, selfVerifyMiddleware.consumePostEditReminder());\n });\n bus.on('turn_start', () => {\n selfVerifyMiddleware.onTurnStart();\n });\n}\n\n// ── Public façade ───────────────────────────────────────────────────────────\n\nexport interface AgentEventHandlerConfig {\n progressManager: ProgressFeedbackManager;\n errorTracker: ToolErrorTracker;\n requestLimiter: RequestLimiter;\n lifecycleManager: LifecycleManager;\n toolChainTracker: ToolChainTracker;\n selfVerifyMiddleware: SelfVerifyMiddleware;\n systemReminder: SystemReminder;\n toolUsageAnalyzer: ToolUsageAnalyzer;\n errorPatternMatcher: ErrorPatternMatcher;\n}\n\n/**\n * Thin façade over {@link SessionEventBus} that pre-registers all built-in\n * listeners in their required order. `handle(event, ctx)` is the entry point\n * used by `AgentService.handleSessionEvent`; extensions can hook in via\n * {@link registerListener} without touching this class.\n */\nexport class AgentEventHandler {\n private readonly bus: SessionEventBus;\n\n constructor(config: AgentEventHandlerConfig) {\n this.bus = new SessionEventBus();\n\n // Order matters for `tool_execution_end`: SystemReminder mutates `event.result`\n // and ToolChain + ErrorTracking depend on the mutated value. Keep these in this\n // exact sequence.\n installProgressListener(this.bus, config.progressManager);\n installRequestLimitListener(this.bus, {\n requestLimiter: config.requestLimiter,\n progressManager: config.progressManager,\n lifecycleManager: config.lifecycleManager,\n });\n installLifecycleHookListener(this.bus, config.lifecycleManager);\n installSystemReminderListener(this.bus, config.systemReminder);\n installSelfVerifyListener(this.bus, config.selfVerifyMiddleware);\n installToolUsageListener(this.bus, config.toolUsageAnalyzer);\n installToolChainListener(this.bus, config.toolChainTracker);\n installErrorTrackingListener(this.bus, {\n errorTracker: config.errorTracker,\n errorPatternMatcher: config.errorPatternMatcher,\n });\n }\n\n /** Dispatch a pi-agent event to all registered listeners. */\n handle(event: AgentEvent, context: SessionContext | null): void {\n this.bus.dispatch(event, context);\n }\n\n /**\n * Register an additional listener (e.g. from an extension). Returns an\n * unsubscribe function. Listeners run after the built-ins in registration\n * order; if you need to run before a built-in, you must create your own\n * {@link SessionEventBus} instance.\n */\n registerListener(type: SessionEventTypeFilter, listener: SessionEventListener): () => void {\n return this.bus.on(type, listener);\n }\n}\n"],"mappings":";;;;aA2BqD;AAErD,MAAM,MAAM,aAAa,oBAAoB;;;;;;AAY7C,IAAa,kBAAb,MAA6B;CAC3B,4BAA6B,IAAI,KAAqD;CAEtF,GAAG,MAA8B,UAA4C;EAC3E,MAAM,SAAS,KAAK,UAAU,IAAI,KAAK,IAAI,EAAE;AAC7C,SAAO,KAAK,SAAS;AACrB,OAAK,UAAU,IAAI,MAAM,OAAO;AAChC,eAAa;GACX,MAAM,UAAU,KAAK,UAAU,IAAI,KAAK;AACxC,OAAI,CAAC,QAAS;GACd,MAAM,MAAM,QAAQ,QAAQ,SAAS;AACrC,OAAI,OAAO,EAAG,SAAQ,OAAO,KAAK,EAAE;;;CAIxC,SAAS,OAAmB,SAAsC;AAChE,MAAI,CAAC,SAAS;AACZ,OAAI,MAAM,SAAS,iBACjB,KAAI,KACF,EAAE,WAAW,MAAM,MAAM,EACzB,4CAA4C,MAAM,OACnD;AAEH;;EAEF,MAAM,QAAQ,KAAK,UAAU,IAAI,MAAM,KAAK;AAC5C,MAAI,MACF,MAAK,MAAM,YAAY,MACrB,UAAS,OAAO,QAAQ;EAG5B,MAAM,MAAM,KAAK,UAAU,IAAI,MAAM;AACrC,MAAI,IACF,MAAK,MAAM,YAAY,IACrB,UAAS,OAAO,QAAQ;;;AAQhC,SAAS,wBAAwB,KAAsB,iBAAgD;AACrG,KAAI,GAAG,qBAAqB;AAC1B,MAAI,MAAM,qBAAqB;AAC/B,kBAAgB,WAAW;GAC3B;AACF,KAAI,GAAG,oBAAoB;AACzB,MAAI,MAAM,eAAe;AACzB,kBAAgB,aAAa;GAC7B;AACF,KAAI,GAAG,yBAAyB,UAAU;EACxC,MAAM,IAAI;AACV,MAAI,MAAM;GAAE,MAAM,EAAE;GAAU,MAAM,EAAE;GAAM,EAAE,yBAAyB;AACvE,kBAAgB,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC;GACrD;AACF,KAAI,GAAG,0BAA0B,UAAU;EACzC,MAAM,IAAI;AACV,kBAAgB,aAAa,EAAE,UAAU,EAAE,cAAc;GACzD;AACF,KAAI,GAAG,uBAAuB,UAAU;EACtC,MAAM,IAAI;AACV,MAAI,MAAM;GAAE,MAAM,EAAE;GAAU,SAAS,EAAE;GAAS,EAAE,0BAA0B;AAC9E,kBAAgB,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ;GAC1D;AACF,KAAI,GAAG,mBAAmB;AACxB,kBAAgB,SAAS;GACzB;;AAGJ,SAAS,4BACP,KACA,MAKM;AACN,KAAI,GAAG,gBAAgB,QAAQ,YAAY;EACzC,MAAM,SAAS,KAAK,eAAe,eAAe;AAElD,OAAK,iBACF,KACC,eACA,QAAQ,YACR;GAAE,eAAe,OAAO;GAAO,aAAa,OAAO;GAAO,EAC1D,QACD,CACA,OAAO,QAAQ;GACd,MAAM,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC3D,OAAI,KACF;IACE;IACA,cAAc;IACd,YAAY,QAAQ;IACpB,eAAe,OAAO;IACtB,aAAa,OAAO;IACrB,EACD,sCAAsC,KACvC;IACD;AAEJ,OAAK,gBAAgB,qBACnB,OAAO,OACP,OAAO,OACP,OAAO,WACP,OAAO,WACP,OAAO,WACR;AAED,MAAI,OAAO,WACT,KAAI,MACF;GAAE,OAAO,OAAO;GAAO,OAAO,OAAO;GAAO,YAAY,QAAQ;GAAY,EAC5E,0BAA0B,OAAO,MAAM,GAAG,OAAO,MAAM,eACxD;GAEH;AAEF,KAAI,GAAG,kBAAkB;AACvB,OAAK,eAAe,OAAO;GAC3B;;AAGJ,SAAS,6BAA6B,KAAsB,kBAA0C;AACpG,KAAI,GAAG,gBAAgB,OAAO,YAAY;EACxC,MAAM,IAAI;AACV,MAAI,EAAE,SAAS,SAAS,YAAa;EACrC,MAAM,UAAU,EAAE,QAAQ;EAC1B,MAAM,OAAO,MAAM,QAAQ,QAAQ,GAC/B,mBAAmB,QAAkD,GACrE,OAAO,QAAQ;AACnB,MAAI,MAAM,EAAE,eAAe,KAAK,QAAQ,EAAE,8BAA8B;AAExE,mBACG,KACC,gBACA,QAAQ,YACR;GAAE,UAAU;GAAM,OAAQ,EAAE,QAAgC;GAAO,EACnE,QACD,CACA,OAAO,QAAQ;GACd,MAAM,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC3D,OAAI,KACF;IAAE;IAAK,cAAc;IAAI,YAAY,QAAQ;IAAY,eAAe,KAAK;IAAQ,EACrF,uCAAuC,KACxC;IACD;GACJ;AAEF,KAAI,GAAG,yBAAyB,OAAO,YAAY;EACjD,MAAM,IAAI;AACV,mBACG,KACC,mBACA,QAAQ,YACR;GACE,UAAU,EAAE;GACZ,WAAW,EAAE,QAAQ,EAAE;GACvB,eAAe;GACf,aAAa;GACd,EACD,QACD,CACA,OAAO,QAAQ;GACd,MAAM,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC3D,OAAI,KACF;IAAE;IAAK,cAAc;IAAI,YAAY,QAAQ;IAAY,MAAM,EAAE;IAAU,EAC3E,0CAA0C,KAC3C;IACD;GACJ;AAEF,KAAI,GAAG,uBAAuB,OAAO,YAAY;EAC/C,MAAM,IAAI;EACV,MAAM,aAAc,EAA8B,cAAc;AAChE,mBACG,KACC,iBACA,QAAQ,YACR;GACE,UAAU,EAAE;GACZ,SAAS,CAAC,EAAE;GACZ,QAAQ,EAAE;GACV,OAAO,EAAE,UAAU,OAAO,EAAE,OAAO,GAAG,KAAA;GACtC;GACD,EACD,QACD,CACA,OAAO,QAAQ;GACd,MAAM,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC3D,OAAI,KACF;IAAE;IAAK,cAAc;IAAI,YAAY,QAAQ;IAAY,MAAM,EAAE;IAAU,EAC3E,wCAAwC,KACzC;IACD;GACJ;;AAGJ,SAAS,8BAA8B,KAAsB,gBAAsC;AAIjG,KAAI,GAAG,uBAAuB,UAAU;EACtC,MAAM,IAAI;AACV,IAAE,SAAS,eAAe,eAAe,EAAE,QAAQ,EAAE,SAAS;GAC9D;;AAGJ,SAAS,uBAAuB,QAAiB,MAAuB;AACtE,KAAI,CAAC,KAAK,MAAM,CACd,QAAO;AAET,KAAI,UAAU,OAAO,WAAW,UAAU;EACxC,MAAM,MAAM;AACZ,MAAI,MAAM,QAAQ,IAAI,QAAQ,EAAE;AAC9B,OAAI,UAAU,CACZ,GAAG,IAAI,SACP;IAAE,MAAM;IAAQ,MAAM,KAAK;IAAQ,CACpC;AACD,UAAO;;AAET,MAAI,OAAO,IAAI,YAAY,UAAU;AACnC,OAAI,UAAU,GAAG,IAAI,QAAQ,IAAI;AACjC,UAAO;;;AAGX,QAAO;;AAGT,SAAS,yBAAyB,KAAsB,kBAA0C;AAChG,KAAI,GAAG,eAAe,QAAQ,YAAY;AAExC,mBAAiB,WAAW,QAAQ,WAAW;GAC/C;AACF,KAAI,GAAG,yBAAyB,OAAO,YAAY;EACjD,MAAM,IAAI;AACV,mBAAiB,WAAW,QAAQ,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,EAAE;GAC5E;AACF,KAAI,GAAG,uBAAuB,OAAO,YAAY;EAC/C,MAAM,IAAI;EACV,MAAM,aAAc,EAA8B,cAAc;EAChE,MAAM,QAAQ,iBAAiB,gBAAgB,QAAQ,WAAW;AAClE,MAAI,CAAC,MAAO;EACZ,MAAM,WAAW,MAAM,MAAM,MAAM,MAAM,SAAS;AAClD,MAAI,YAAY,SAAS,aAAa,EAAE,SACtC,kBAAiB,aACf,QAAQ,YACR,SAAS,IACT,EAAE,QACF,EAAE,UAAU,0BAA0B,KAAA,GACtC,WACD;GAEH;AACF,KAAI,GAAG,aAAa,QAAQ,YAAY;AACtC,mBAAiB,SAAS,QAAQ,WAAW;GAC7C;;AAGJ,SAAS,yBAAyB,KAAsB,mBAA4C;AAClG,KAAI,GAAG,uBAAuB,UAAU;EACtC,MAAM,IAAI;EACV,MAAM,aAAc,EAA8B,cAAc;AAChE,oBAAkB,YAAY,EAAE,UAAU,CAAC,EAAE,SAAS,WAAW;GACjE;;AAGJ,SAAS,iBAAiB,QAAyB;AACjD,KAAI,OAAO,WAAW,SAAU,QAAO;AACvC,KAAI,UAAU,OAAO,WAAW,UAAU;EACxC,MAAM,MAAM;AACZ,MAAI,OAAO,IAAI,UAAU,SAAU,QAAO,IAAI;AAC9C,MAAI,OAAO,IAAI,YAAY,SAAU,QAAO,IAAI;AAChD,SAAO,KAAK,UAAU,OAAO;;AAE/B,QAAO,OAAO,OAAO;;AAGvB,SAAS,6BACP,KACA,MACM;AACN,KAAI,GAAG,uBAAuB,UAAU;EACtC,MAAM,IAAI;AACV,MAAI,CAAC,EAAE,QAAS;EAChB,MAAM,YAAY,iBAAiB,EAAE,OAAO;AAC5C,OAAK,aAAa,cAAc,EAAE,UAAU,UAAU;EAEtD,MAAM,aAAa,KAAK,oBAAoB,WAAW,UAAU;AACjE,MAAI,WAAW,WAAW,WAAW,SAAS;GAC5C,MAAM,UAAU,UAAU,SAAS,MAAM,GAAG,UAAU,MAAM,GAAG,IAAI,CAAC,KAAK;AACzE,OAAI,KACF;IAAE,MAAM,EAAE;IAAU,SAAS,WAAW,QAAQ;IAAM,cAAc;IAAS,EAC7E,+BAA+B,WAAW,QAAQ,KAAK,KAAK,EAAE,SAAS,KAAK,UAC7E;;GAEH;AACF,KAAI,GAAG,kBAAkB;AACvB,OAAK,aAAa,OAAO;GACzB;;AAGJ,SAAS,0BAA0B,KAAsB,sBAAkD;AACzG,KAAI,GAAG,yBAAyB,UAAU;EACxC,MAAM,IAAI;EACV,MAAM,OAAO,EAAE;EACf,MAAM,WAAW,EAAE;AACnB,MAAI,CAAC,YAAY,CAAC,KAAM;EACxB,MAAM,OAAO,SAAS,aAAa;AACnC,MAAI,KAAK,SAAS,QAAQ,IAAI,KAAK,KACjC,sBAAqB,WAAW,OAAO,KAAK,KAAK,EAAE,QAAQ;WAClD,KAAK,SAAS,OAAO,IAAI,KAAK,KACvC,sBAAqB,WAAW,OAAO,KAAK,KAAK,EAAE,OAAO;MAE1D,sBAAqB,mBAAmB,UAAU,KAAK;GAEzD;AACF,KAAI,GAAG,uBAAuB,UAAU;EACtC,MAAM,IAAI;EACV,MAAM,OAAO,EAAE,UAAU,aAAa,IAAI;AAC1C,MAAI,CAAC,KAAK,SAAS,QAAQ,IAAI,CAAC,KAAK,SAAS,OAAO,CACnD;AAEF,IAAE,SAAS,uBAAuB,EAAE,QAAQ,qBAAqB,yBAAyB,CAAC;GAC3F;AACF,KAAI,GAAG,oBAAoB;AACzB,uBAAqB,aAAa;GAClC;;;;;;;;AAuBJ,IAAa,oBAAb,MAA+B;CAC7B;CAEA,YAAY,QAAiC;AAC3C,OAAK,MAAM,IAAI,iBAAiB;AAKhC,0BAAwB,KAAK,KAAK,OAAO,gBAAgB;AACzD,8BAA4B,KAAK,KAAK;GACpC,gBAAgB,OAAO;GACvB,iBAAiB,OAAO;GACxB,kBAAkB,OAAO;GAC1B,CAAC;AACF,+BAA6B,KAAK,KAAK,OAAO,iBAAiB;AAC/D,gCAA8B,KAAK,KAAK,OAAO,eAAe;AAC9D,4BAA0B,KAAK,KAAK,OAAO,qBAAqB;AAChE,2BAAyB,KAAK,KAAK,OAAO,kBAAkB;AAC5D,2BAAyB,KAAK,KAAK,OAAO,iBAAiB;AAC3D,+BAA6B,KAAK,KAAK;GACrC,cAAc,OAAO;GACrB,qBAAqB,OAAO;GAC7B,CAAC;;;CAIJ,OAAO,OAAmB,SAAsC;AAC9D,OAAK,IAAI,SAAS,OAAO,QAAQ;;;;;;;;CASnC,iBAAiB,MAA8B,UAA4C;AACzF,SAAO,KAAK,IAAI,GAAG,MAAM,SAAS"}
@@ -34,13 +34,13 @@ const CORE_TOOL_ORDER = [
34
34
  "execute_code"
35
35
  ];
36
36
  const CORE_TOOL_SUMMARIES = {
37
- read_file: "Read file contents",
38
- write_file: "Create or overwrite files",
39
- edit_file: "Make precise edits to files",
40
- grep: "Search file contents for patterns",
37
+ read_file: "Read targeted file contents; prefer this over shell cat/sed for inspection",
38
+ write_file: "Create new files or intentional complete rewrites",
39
+ edit_file: "Make precise, minimal edits to existing files",
40
+ grep: "Search file contents for literals, errors, config values, and docs",
41
41
  find: "Find files by glob pattern",
42
42
  list_dir: "List directory contents",
43
- shell: "Run shell commands (supports background via yieldMs/background)",
43
+ shell: "Run tests, type checks, builds, package scripts, and safe inspection commands",
44
44
  web_search: "Search the web",
45
45
  web_fetch: "Fetch and extract readable content from a URL",
46
46
  web_extract: "Extract structured content from a URL",
@@ -95,9 +95,20 @@ function buildToolingSection(params) {
95
95
  toolLines.push(summary ? `- ${name}: ${summary}` : `- ${name}`);
96
96
  }
97
97
  const shellToolName = resolveToolName("shell");
98
+ const readToolName = resolveToolName("read_file");
99
+ const editToolName = resolveToolName("edit_file");
100
+ const writeToolName = resolveToolName("write_file");
101
+ const grepToolName = resolveToolName("grep");
102
+ const findToolName = resolveToolName("find");
98
103
  const hasDelegate = availableTools.has("delegate_task");
99
104
  const hasWorkflow = availableTools.has("workflow");
100
105
  const hasToolManual = availableTools.has("tool_manual");
106
+ const hasRead = availableTools.has("read_file");
107
+ const hasEdit = availableTools.has("edit_file");
108
+ const hasWrite = availableTools.has("write_file");
109
+ const hasGrep = availableTools.has("grep");
110
+ const hasFind = availableTools.has("find");
111
+ const hasShell = availableTools.has("shell");
101
112
  const orchestrationLines = [];
102
113
  if (hasDelegate) orchestrationLines.push("- Sub-agent delegation → use `delegate_task(goal, context?, toolset?)` for focused parallel work; results return to you automatically.");
103
114
  if (hasWorkflow) orchestrationLines.push("- Multi-phase workflows → use `workflow` to start persisted runs with subagents when a script defines phases.");
@@ -107,6 +118,11 @@ function buildToolingSection(params) {
107
118
  "Tool names are case-sensitive. Call tools exactly as listed.",
108
119
  toolLines.length > 0 ? toolLines.join("\n") : "- No tools are registered for this session.",
109
120
  `For long waits, avoid rapid poll loops: use \`${shellToolName}\` with enough yieldMs or poll in reasonable intervals.`,
121
+ hasRead ? `Use \`${readToolName}\` for targeted file inspection before editing; do not rely on filenames alone.` : "",
122
+ hasEdit ? `Use \`${editToolName}\` for small source changes; keep patches focused.` : "",
123
+ hasWrite ? `Use \`${writeToolName}\` only for new files or intentional complete rewrites.` : "",
124
+ hasGrep || hasFind ? `Use ${[hasGrep ? `\`${grepToolName}\`` : "", hasFind ? `\`${findToolName}\`` : ""].filter(Boolean).join(" and ")} for text/file discovery; use code graph or symbol tools when available for definitions and call relationships.` : "",
125
+ hasShell ? `Use \`${shellToolName}\` for verification and safe inspection, not routine file editing.` : "",
110
126
  hasToolManual ? "Some complex tools have built-in manuals. Use `tool_manual(tool)` before non-trivial use." : "",
111
127
  hasDelegate ? "If a task is complex or long-running, delegate it. Completion is push-based: the sub-agent result returns when done." : "",
112
128
  ...orchestrationLines,
@@ -1 +1 @@
1
- {"version":3,"file":"tooling.js","names":[],"sources":["../../../../../src/agent/prompt/sections/tooling.ts"],"sourcesContent":["const CORE_TOOL_ORDER = [\n 'read_file',\n 'write_file',\n 'edit_file',\n 'grep',\n 'find',\n 'list_dir',\n 'shell',\n 'web_search',\n 'web_fetch',\n 'web_extract',\n 'browser_use',\n 'send_message',\n 'send_media',\n 'memory_search',\n 'memory_get',\n 'session_search',\n 'curated_memory',\n 'session_status',\n 'tool_manual',\n 'delegate_task',\n 'workflow',\n 'automation',\n 'skills_list',\n 'skill_view',\n 'skill_manage',\n 'todo',\n 'clarify',\n 'text_to_speech',\n 'image',\n 'image_generate',\n 'create_share',\n 'execute_code',\n] as const;\n\nconst CORE_TOOL_SUMMARIES: Record<string, string> = {\n read_file: 'Read file contents',\n write_file: 'Create or overwrite files',\n edit_file: 'Make precise edits to files',\n grep: 'Search file contents for patterns',\n find: 'Find files by glob pattern',\n list_dir: 'List directory contents',\n shell: 'Run shell commands (supports background via yieldMs/background)',\n web_search: 'Search the web',\n web_fetch: 'Fetch and extract readable content from a URL',\n web_extract: 'Extract structured content from a URL',\n browser_use: 'Control the configured browser',\n send_message: 'Send messages to the current channel',\n send_media: 'Send media attachments to the current channel',\n memory_search: 'Semantic search over indexed memory sources',\n memory_get: 'Read specific lines from memory sources returned by search',\n session_search: 'Search other chat sessions or list recent sessions',\n curated_memory: 'Read/write structured notes under agent home memories/',\n session_status: 'Show session usage/time/model state',\n tool_manual: 'Load built-in usage manuals for complex tools',\n delegate_task: 'Spawn an isolated sub-agent for a delegated task',\n workflow: 'Start a persisted multi-phase workflow run',\n automation: 'Manage product automations',\n skills_list: 'List available skills',\n skill_view: 'Load a skill SKILL.md or sub-documents',\n skill_manage: 'Create or update skills on disk',\n todo: 'Manage in-session todo items',\n clarify: 'Ask the user a blocking clarification question',\n text_to_speech: 'Send a standalone voice message',\n image: 'Analyze an image with the configured vision model',\n image_generate: 'Generate images with the configured image model',\n create_share: 'Create a shareable link for workspace files',\n execute_code: 'Run code in a sandbox with a restricted tool subset',\n};\n\nexport function buildToolingSection(params: {\n toolNames?: string[];\n toolSummaries?: Record<string, string>;\n}): string {\n const rawToolNames = (params.toolNames ?? []).map((tool) => tool.trim()).filter(Boolean);\n const canonicalByNormalized = new Map<string, string>();\n for (const name of rawToolNames) {\n const normalized = name.toLowerCase();\n if (!canonicalByNormalized.has(normalized)) {\n canonicalByNormalized.set(normalized, name);\n }\n }\n const resolveToolName = (normalized: string) =>\n canonicalByNormalized.get(normalized) ?? normalized;\n\n const normalizedTools = rawToolNames.map((tool) => tool.toLowerCase());\n const availableTools = new Set(normalizedTools);\n\n const externalToolSummaries = new Map<string, string>();\n for (const [key, value] of Object.entries(params.toolSummaries ?? {})) {\n const normalized = key.trim().toLowerCase();\n if (!normalized || !value?.trim()) {\n continue;\n }\n externalToolSummaries.set(normalized, value.trim());\n }\n\n const extraTools = Array.from(\n new Set(normalizedTools.filter((tool) => !CORE_TOOL_ORDER.includes(tool as (typeof CORE_TOOL_ORDER)[number]))),\n );\n const enabledTools = CORE_TOOL_ORDER.filter((tool) => availableTools.has(tool));\n const toolLines = enabledTools.map((tool) => {\n const summary = CORE_TOOL_SUMMARIES[tool] ?? externalToolSummaries.get(tool);\n const name = resolveToolName(tool);\n return summary ? `- ${name}: ${summary}` : `- ${name}`;\n });\n for (const tool of extraTools.toSorted()) {\n const summary = CORE_TOOL_SUMMARIES[tool] ?? externalToolSummaries.get(tool);\n const name = resolveToolName(tool);\n toolLines.push(summary ? `- ${name}: ${summary}` : `- ${name}`);\n }\n\n const shellToolName = resolveToolName('shell');\n const hasDelegate = availableTools.has('delegate_task');\n const hasWorkflow = availableTools.has('workflow');\n const hasToolManual = availableTools.has('tool_manual');\n\n const orchestrationLines: string[] = [];\n if (hasDelegate) {\n orchestrationLines.push(\n '- Sub-agent delegation → use `delegate_task(goal, context?, toolset?)` for focused parallel work; results return to you automatically.',\n );\n }\n if (hasWorkflow) {\n orchestrationLines.push(\n '- Multi-phase workflows → use `workflow` to start persisted runs with subagents when a script defines phases.',\n );\n }\n\n return [\n '## Tooling',\n 'Tool availability (filtered by policy):',\n 'Tool names are case-sensitive. Call tools exactly as listed.',\n toolLines.length > 0\n ? toolLines.join('\\n')\n : '- No tools are registered for this session.',\n `For long waits, avoid rapid poll loops: use \\`${shellToolName}\\` with enough yieldMs or poll in reasonable intervals.`,\n hasToolManual\n ? 'Some complex tools have built-in manuals. Use `tool_manual(tool)` before non-trivial use.'\n : '',\n hasDelegate\n ? 'If a task is complex or long-running, delegate it. Completion is push-based: the sub-agent result returns when done.'\n : '',\n ...orchestrationLines,\n 'Do not poll delegate/workflow status in a tight loop; check on-demand or wait for completion.',\n ]\n .filter(Boolean)\n .join('\\n');\n}\n\nexport function hasSkillsTools(toolNames: string[]): boolean {\n const normalized = new Set(toolNames.map((t) => t.toLowerCase()));\n return normalized.has('skills_list') || normalized.has('skill_view');\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"tooling.js","names":[],"sources":["../../../../../src/agent/prompt/sections/tooling.ts"],"sourcesContent":["const CORE_TOOL_ORDER = [\n 'read_file',\n 'write_file',\n 'edit_file',\n 'grep',\n 'find',\n 'list_dir',\n 'shell',\n 'web_search',\n 'web_fetch',\n 'web_extract',\n 'browser_use',\n 'send_message',\n 'send_media',\n 'memory_search',\n 'memory_get',\n 'session_search',\n 'curated_memory',\n 'session_status',\n 'tool_manual',\n 'delegate_task',\n 'workflow',\n 'automation',\n 'skills_list',\n 'skill_view',\n 'skill_manage',\n 'todo',\n 'clarify',\n 'text_to_speech',\n 'image',\n 'image_generate',\n 'create_share',\n 'execute_code',\n] as const;\n\nconst CORE_TOOL_SUMMARIES: Record<string, string> = {\n read_file: 'Read targeted file contents; prefer this over shell cat/sed for inspection',\n write_file: 'Create new files or intentional complete rewrites',\n edit_file: 'Make precise, minimal edits to existing files',\n grep: 'Search file contents for literals, errors, config values, and docs',\n find: 'Find files by glob pattern',\n list_dir: 'List directory contents',\n shell: 'Run tests, type checks, builds, package scripts, and safe inspection commands',\n web_search: 'Search the web',\n web_fetch: 'Fetch and extract readable content from a URL',\n web_extract: 'Extract structured content from a URL',\n browser_use: 'Control the configured browser',\n send_message: 'Send messages to the current channel',\n send_media: 'Send media attachments to the current channel',\n memory_search: 'Semantic search over indexed memory sources',\n memory_get: 'Read specific lines from memory sources returned by search',\n session_search: 'Search other chat sessions or list recent sessions',\n curated_memory: 'Read/write structured notes under agent home memories/',\n session_status: 'Show session usage/time/model state',\n tool_manual: 'Load built-in usage manuals for complex tools',\n delegate_task: 'Spawn an isolated sub-agent for a delegated task',\n workflow: 'Start a persisted multi-phase workflow run',\n automation: 'Manage product automations',\n skills_list: 'List available skills',\n skill_view: 'Load a skill SKILL.md or sub-documents',\n skill_manage: 'Create or update skills on disk',\n todo: 'Manage in-session todo items',\n clarify: 'Ask the user a blocking clarification question',\n text_to_speech: 'Send a standalone voice message',\n image: 'Analyze an image with the configured vision model',\n image_generate: 'Generate images with the configured image model',\n create_share: 'Create a shareable link for workspace files',\n execute_code: 'Run code in a sandbox with a restricted tool subset',\n};\n\nexport function buildToolingSection(params: {\n toolNames?: string[];\n toolSummaries?: Record<string, string>;\n}): string {\n const rawToolNames = (params.toolNames ?? []).map((tool) => tool.trim()).filter(Boolean);\n const canonicalByNormalized = new Map<string, string>();\n for (const name of rawToolNames) {\n const normalized = name.toLowerCase();\n if (!canonicalByNormalized.has(normalized)) {\n canonicalByNormalized.set(normalized, name);\n }\n }\n const resolveToolName = (normalized: string) =>\n canonicalByNormalized.get(normalized) ?? normalized;\n\n const normalizedTools = rawToolNames.map((tool) => tool.toLowerCase());\n const availableTools = new Set(normalizedTools);\n\n const externalToolSummaries = new Map<string, string>();\n for (const [key, value] of Object.entries(params.toolSummaries ?? {})) {\n const normalized = key.trim().toLowerCase();\n if (!normalized || !value?.trim()) {\n continue;\n }\n externalToolSummaries.set(normalized, value.trim());\n }\n\n const extraTools = Array.from(\n new Set(normalizedTools.filter((tool) => !CORE_TOOL_ORDER.includes(tool as (typeof CORE_TOOL_ORDER)[number]))),\n );\n const enabledTools = CORE_TOOL_ORDER.filter((tool) => availableTools.has(tool));\n const toolLines = enabledTools.map((tool) => {\n const summary = CORE_TOOL_SUMMARIES[tool] ?? externalToolSummaries.get(tool);\n const name = resolveToolName(tool);\n return summary ? `- ${name}: ${summary}` : `- ${name}`;\n });\n for (const tool of extraTools.toSorted()) {\n const summary = CORE_TOOL_SUMMARIES[tool] ?? externalToolSummaries.get(tool);\n const name = resolveToolName(tool);\n toolLines.push(summary ? `- ${name}: ${summary}` : `- ${name}`);\n }\n\n const shellToolName = resolveToolName('shell');\n const readToolName = resolveToolName('read_file');\n const editToolName = resolveToolName('edit_file');\n const writeToolName = resolveToolName('write_file');\n const grepToolName = resolveToolName('grep');\n const findToolName = resolveToolName('find');\n const hasDelegate = availableTools.has('delegate_task');\n const hasWorkflow = availableTools.has('workflow');\n const hasToolManual = availableTools.has('tool_manual');\n const hasRead = availableTools.has('read_file');\n const hasEdit = availableTools.has('edit_file');\n const hasWrite = availableTools.has('write_file');\n const hasGrep = availableTools.has('grep');\n const hasFind = availableTools.has('find');\n const hasShell = availableTools.has('shell');\n\n const orchestrationLines: string[] = [];\n if (hasDelegate) {\n orchestrationLines.push(\n '- Sub-agent delegation → use `delegate_task(goal, context?, toolset?)` for focused parallel work; results return to you automatically.',\n );\n }\n if (hasWorkflow) {\n orchestrationLines.push(\n '- Multi-phase workflows → use `workflow` to start persisted runs with subagents when a script defines phases.',\n );\n }\n\n return [\n '## Tooling',\n 'Tool availability (filtered by policy):',\n 'Tool names are case-sensitive. Call tools exactly as listed.',\n toolLines.length > 0\n ? toolLines.join('\\n')\n : '- No tools are registered for this session.',\n `For long waits, avoid rapid poll loops: use \\`${shellToolName}\\` with enough yieldMs or poll in reasonable intervals.`,\n hasRead\n ? `Use \\`${readToolName}\\` for targeted file inspection before editing; do not rely on filenames alone.`\n : '',\n hasEdit\n ? `Use \\`${editToolName}\\` for small source changes; keep patches focused.`\n : '',\n hasWrite\n ? `Use \\`${writeToolName}\\` only for new files or intentional complete rewrites.`\n : '',\n hasGrep || hasFind\n ? `Use ${[\n hasGrep ? `\\`${grepToolName}\\`` : '',\n hasFind ? `\\`${findToolName}\\`` : '',\n ].filter(Boolean).join(' and ')} for text/file discovery; use code graph or symbol tools when available for definitions and call relationships.`\n : '',\n hasShell\n ? `Use \\`${shellToolName}\\` for verification and safe inspection, not routine file editing.`\n : '',\n hasToolManual\n ? 'Some complex tools have built-in manuals. Use `tool_manual(tool)` before non-trivial use.'\n : '',\n hasDelegate\n ? 'If a task is complex or long-running, delegate it. Completion is push-based: the sub-agent result returns when done.'\n : '',\n ...orchestrationLines,\n 'Do not poll delegate/workflow status in a tight loop; check on-demand or wait for completion.',\n ]\n .filter(Boolean)\n .join('\\n');\n}\n\nexport function hasSkillsTools(toolNames: string[]): boolean {\n const normalized = new Set(toolNames.map((t) => t.toLowerCase()));\n return normalized.has('skills_list') || normalized.has('skill_view');\n}\n"],"mappings":";AAAA,MAAM,kBAAkB;CACtB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,MAAM,sBAA8C;CAClD,WAAW;CACX,YAAY;CACZ,WAAW;CACX,MAAM;CACN,MAAM;CACN,UAAU;CACV,OAAO;CACP,YAAY;CACZ,WAAW;CACX,aAAa;CACb,aAAa;CACb,cAAc;CACd,YAAY;CACZ,eAAe;CACf,YAAY;CACZ,gBAAgB;CAChB,gBAAgB;CAChB,gBAAgB;CAChB,aAAa;CACb,eAAe;CACf,UAAU;CACV,YAAY;CACZ,aAAa;CACb,YAAY;CACZ,cAAc;CACd,MAAM;CACN,SAAS;CACT,gBAAgB;CAChB,OAAO;CACP,gBAAgB;CAChB,cAAc;CACd,cAAc;CACf;AAED,SAAgB,oBAAoB,QAGzB;CACT,MAAM,gBAAgB,OAAO,aAAa,EAAE,EAAE,KAAK,SAAS,KAAK,MAAM,CAAC,CAAC,OAAO,QAAQ;CACxF,MAAM,wCAAwB,IAAI,KAAqB;AACvD,MAAK,MAAM,QAAQ,cAAc;EAC/B,MAAM,aAAa,KAAK,aAAa;AACrC,MAAI,CAAC,sBAAsB,IAAI,WAAW,CACxC,uBAAsB,IAAI,YAAY,KAAK;;CAG/C,MAAM,mBAAmB,eACvB,sBAAsB,IAAI,WAAW,IAAI;CAE3C,MAAM,kBAAkB,aAAa,KAAK,SAAS,KAAK,aAAa,CAAC;CACtE,MAAM,iBAAiB,IAAI,IAAI,gBAAgB;CAE/C,MAAM,wCAAwB,IAAI,KAAqB;AACvD,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,iBAAiB,EAAE,CAAC,EAAE;EACrE,MAAM,aAAa,IAAI,MAAM,CAAC,aAAa;AAC3C,MAAI,CAAC,cAAc,CAAC,OAAO,MAAM,CAC/B;AAEF,wBAAsB,IAAI,YAAY,MAAM,MAAM,CAAC;;CAGrD,MAAM,aAAa,MAAM,KACvB,IAAI,IAAI,gBAAgB,QAAQ,SAAS,CAAC,gBAAgB,SAAS,KAAyC,CAAC,CAAC,CAC/G;CAED,MAAM,YADe,gBAAgB,QAAQ,SAAS,eAAe,IAAI,KAAK,CAChD,CAAC,KAAK,SAAS;EAC3C,MAAM,UAAU,oBAAoB,SAAS,sBAAsB,IAAI,KAAK;EAC5E,MAAM,OAAO,gBAAgB,KAAK;AAClC,SAAO,UAAU,KAAK,KAAK,IAAI,YAAY,KAAK;GAChD;AACF,MAAK,MAAM,QAAQ,WAAW,UAAU,EAAE;EACxC,MAAM,UAAU,oBAAoB,SAAS,sBAAsB,IAAI,KAAK;EAC5E,MAAM,OAAO,gBAAgB,KAAK;AAClC,YAAU,KAAK,UAAU,KAAK,KAAK,IAAI,YAAY,KAAK,OAAO;;CAGjE,MAAM,gBAAgB,gBAAgB,QAAQ;CAC9C,MAAM,eAAe,gBAAgB,YAAY;CACjD,MAAM,eAAe,gBAAgB,YAAY;CACjD,MAAM,gBAAgB,gBAAgB,aAAa;CACnD,MAAM,eAAe,gBAAgB,OAAO;CAC5C,MAAM,eAAe,gBAAgB,OAAO;CAC5C,MAAM,cAAc,eAAe,IAAI,gBAAgB;CACvD,MAAM,cAAc,eAAe,IAAI,WAAW;CAClD,MAAM,gBAAgB,eAAe,IAAI,cAAc;CACvD,MAAM,UAAU,eAAe,IAAI,YAAY;CAC/C,MAAM,UAAU,eAAe,IAAI,YAAY;CAC/C,MAAM,WAAW,eAAe,IAAI,aAAa;CACjD,MAAM,UAAU,eAAe,IAAI,OAAO;CAC1C,MAAM,UAAU,eAAe,IAAI,OAAO;CAC1C,MAAM,WAAW,eAAe,IAAI,QAAQ;CAE5C,MAAM,qBAA+B,EAAE;AACvC,KAAI,YACF,oBAAmB,KACjB,yIACD;AAEH,KAAI,YACF,oBAAmB,KACjB,gHACD;AAGH,QAAO;EACL;EACA;EACA;EACA,UAAU,SAAS,IACf,UAAU,KAAK,KAAK,GACpB;EACJ,iDAAiD,cAAc;EAC/D,UACI,SAAS,aAAa,mFACtB;EACJ,UACI,SAAS,aAAa,sDACtB;EACJ,WACI,SAAS,cAAc,2DACvB;EACJ,WAAW,UACP,OAAO,CACL,UAAU,KAAK,aAAa,MAAM,IAClC,UAAU,KAAK,aAAa,MAAM,GACnC,CAAC,OAAO,QAAQ,CAAC,KAAK,QAAQ,CAAC,mHAChC;EACJ,WACI,SAAS,cAAc,sEACvB;EACJ,gBACI,8FACA;EACJ,cACI,yHACA;EACJ,GAAG;EACH;EACD,CACE,OAAO,QAAQ,CACf,KAAK,KAAK;;AAGf,SAAgB,eAAe,WAA8B;CAC3D,MAAM,aAAa,IAAI,IAAI,UAAU,KAAK,MAAM,EAAE,aAAa,CAAC,CAAC;AACjE,QAAO,WAAW,IAAI,cAAc,IAAI,WAAW,IAAI,aAAa"}