@xopcai/xopc 0.0.120 → 0.0.122

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 (403) hide show
  1. package/README.md +23 -3
  2. package/README.zh-CN.md +23 -3
  3. package/dist/browser-ext/manifest.json +1 -1
  4. package/dist/extensions/dashscope/src/image-generation-provider.d.ts +6 -5
  5. package/dist/extensions/dashscope/src/image-generation-provider.js +40 -64
  6. package/dist/extensions/dashscope/src/image-generation-provider.js.map +1 -1
  7. package/dist/extensions/dashscope/src/index.d.ts +1 -1
  8. package/dist/extensions/dashscope/src/index.js +2 -2
  9. package/dist/extensions/dashscope/xopc.extension.json +3 -2
  10. package/dist/extensions/minimax/src/image-generation-provider.d.ts +7 -5
  11. package/dist/extensions/minimax/src/image-generation-provider.js +43 -37
  12. package/dist/extensions/minimax/src/image-generation-provider.js.map +1 -1
  13. package/dist/extensions/minimax/src/index.d.ts +1 -1
  14. package/dist/extensions/minimax/src/index.js +2 -2
  15. package/dist/extensions/minimax/xopc.extension.json +1 -1
  16. package/dist/extensions/telegram/xopc.extension.json +1 -1
  17. package/dist/gateway/static/root/assets/Combination-HAlzriaz.js +41 -0
  18. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-APTlX9Sq.js → agent-avatar-dicebear-CK3s5cJo.js} +1 -1
  19. package/dist/gateway/static/root/assets/agents-SsQrj9B-.js +2 -0
  20. package/dist/gateway/static/root/assets/{app-management-settings-panel-DTKc5HHT.js → app-management-settings-panel-CWsNHtTs.js} +1 -1
  21. package/dist/gateway/static/root/assets/{appearance-settings-BTciFrE9.js → appearance-settings-C4b8y6iZ.js} +1 -1
  22. package/dist/gateway/static/root/assets/apps-page-CYb6fame.js +1 -0
  23. package/dist/gateway/static/root/assets/{archive-plugin-CiHaUMII.js → archive-plugin-DAtNW3Ia.js} +1 -1
  24. package/dist/gateway/static/root/assets/{attachment-load-t4gSrNjL.js → attachment-load-Dm-RY54y.js} +1 -1
  25. package/dist/gateway/static/root/assets/automation-suggestion-card-BKHewSVT.js +1 -0
  26. package/dist/gateway/static/root/assets/automations-page-DpOOq1nk.js +1 -0
  27. package/dist/gateway/static/root/assets/{binary-plugins-BAbSldW5.js → binary-plugins-BG6Xnk71.js} +1 -1
  28. package/dist/gateway/static/root/assets/{block-editor-BNr_82xl.js → block-editor-DfndiO3h.js} +28 -28
  29. package/dist/gateway/static/root/assets/browser-settings-page-9nXxgh4u.js +8 -0
  30. package/dist/gateway/static/root/assets/{capability-presets-api-tzD702Gg.js → capability-presets-api-Cx93GLdw.js} +1 -1
  31. package/dist/gateway/static/root/assets/{capability-presets-settings-panel-AGhTueAc.js → capability-presets-settings-panel-D528_YFy.js} +1 -1
  32. package/dist/gateway/static/root/assets/{channel-recipient-api-DL_KzwGE.js → channel-recipient-api-DdPTtEs8.js} +1 -1
  33. package/dist/gateway/static/root/assets/channels-settings-Ck-3h5lI.js +1 -0
  34. package/dist/gateway/static/root/assets/connectors-page-DL-fAqE_.js +2 -0
  35. package/dist/gateway/static/root/assets/directory-picker-path-field-D835EHJd.js +1 -0
  36. package/dist/gateway/static/root/assets/dist-Mys7eFJ5.js +1 -0
  37. package/dist/gateway/static/root/assets/dist-wO9a2_ID.js +1 -0
  38. package/dist/gateway/static/root/assets/dreaming-settings-CCa1F7Fg.js +1 -0
  39. package/dist/gateway/static/root/assets/{extension-debug-page-Dl2kO0Sf.js → extension-debug-page-W2ITCPSA.js} +1 -1
  40. package/dist/gateway/static/root/assets/{extension-page-DmbLPsx_.js → extension-page-DrvUytvn.js} +1 -1
  41. package/dist/gateway/static/root/assets/extension-settings-page-qM8aLT5u.js +1 -0
  42. package/dist/gateway/static/root/assets/{fetch-B3ME5Eri.js → fetch-ovkvM8KQ.js} +1 -1
  43. package/dist/gateway/static/root/assets/{gateway-config-api-DI9_BBTo.js → gateway-config-api-BB3biuD0.js} +1 -1
  44. package/dist/gateway/static/root/assets/gateway-settings-mmmnIH27.js +1 -0
  45. package/dist/gateway/static/root/assets/{gateway-startup-retry-CSZQTz6K.js → gateway-startup-retry-CanQ8UcR.js} +1 -1
  46. package/dist/gateway/static/root/assets/goal-create-dialog-CNqNdEkQ.js +1 -0
  47. package/dist/gateway/static/root/assets/goal-detail-page-Ceu5zrN6.js +1 -0
  48. package/dist/gateway/static/root/assets/{goals-config-api-Bhzqrp2w.js → goals-config-api-BBOX6mUl.js} +1 -1
  49. package/dist/gateway/static/root/assets/goals-page-BP1kfvWv.js +2 -0
  50. package/dist/gateway/static/root/assets/goals-settings-Bh3A7OZV.js +1 -0
  51. package/dist/gateway/static/root/assets/heartbeat-settings-YoYgKlfX.js +1 -0
  52. package/dist/gateway/static/root/assets/index-DdqSO9R2.js +191 -0
  53. package/dist/gateway/static/root/assets/index-jGB-2nI-.css +1 -0
  54. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-kMp08SlX.js → keyboard-shortcuts-settings-B4TKJHla.js} +1 -1
  55. package/dist/gateway/static/root/assets/logs-page-BgVk5DvZ.js +2 -0
  56. package/dist/gateway/static/root/assets/{markdown-split-Bc0w40vz.js → markdown-split-DcFtdLci.js} +1 -1
  57. package/dist/gateway/static/root/assets/{media-plugins-DJ_HvgIy.js → media-plugins-B6SmeLVB.js} +1 -1
  58. package/dist/gateway/static/root/assets/messages-B5O4OIEo.js +3 -0
  59. package/dist/gateway/static/root/assets/models-hub-panel-BU0x8PGP.js +2 -0
  60. package/dist/gateway/static/root/assets/notes-page-D6JYVP4I.js +1 -0
  61. package/dist/gateway/static/root/assets/notes-workbench-mqRJYnJS.js +2 -0
  62. package/dist/gateway/static/root/assets/{preview-open-alternatives-BbA7sRd_.js → preview-open-alternatives-C29O19s7.js} +1 -1
  63. package/dist/gateway/static/root/assets/project-detail-page-DOTgzoeh.js +1 -0
  64. package/dist/gateway/static/root/assets/projects-page-DGUIb4y5.js +1 -0
  65. package/dist/gateway/static/root/assets/remote-access-hub-DVKKUj3u.js +1 -0
  66. package/dist/gateway/static/root/assets/{save-bar-controls-Dividh61.js → save-bar-controls-B4ad9GfB.js} +1 -1
  67. package/dist/gateway/static/root/assets/schedule-field-DdYncfLR.js +1 -0
  68. package/dist/gateway/static/root/assets/schema-form-BMTrDE-7.js +1 -0
  69. package/dist/gateway/static/root/assets/segmented-styles-BL8oLBEp.js +1 -0
  70. package/dist/gateway/static/root/assets/sessions-page-BVbzUWxW.js +1 -0
  71. package/dist/gateway/static/root/assets/settings-advanced-gate-DrNNoSqE.js +1 -0
  72. package/dist/gateway/static/root/assets/{settings-collapsible-section-BMCOfuE_.js → settings-collapsible-section-MwTW2eK4.js} +1 -1
  73. package/dist/gateway/static/root/assets/{settings-form-section-DBkGwpIN.js → settings-form-section-Dg6LBbcr.js} +1 -1
  74. package/dist/gateway/static/root/assets/settings-page-CMDqgeMR.js +1 -0
  75. package/dist/gateway/static/root/assets/settings-page-layout-BX4T-OLF.js +1 -0
  76. package/dist/gateway/static/root/assets/setup-status-panel-Ce1pcFtR.js +1 -0
  77. package/dist/gateway/static/root/assets/share-preview-page-DWE-dUFh.js +2 -0
  78. package/dist/gateway/static/root/assets/shares-settings-aKSraR8l.js +1 -0
  79. package/dist/gateway/static/root/assets/skill-reload-api-CS-FU_B-.js +1 -0
  80. package/dist/gateway/static/root/assets/skills-page-BaF3u0LA.js +2 -0
  81. package/dist/gateway/static/root/assets/{system-settings-panel-CrmEnyX4.js → system-settings-panel-Dd6Y35Cm.js} +1 -1
  82. package/dist/gateway/static/root/assets/{typed-models-lib-BAsGn25g.js → typed-models-lib-Cx6XV5A2.js} +1 -1
  83. package/dist/gateway/static/root/assets/{use-channel-catalog-C7vcAoGb.js → use-channel-catalog-D4sMbmy7.js} +1 -1
  84. package/dist/gateway/static/root/assets/user-profile-settings-panel-Bx-hjMQc.js +1 -0
  85. package/dist/gateway/static/root/assets/{utils-Dl9efgnV.js → utils-D7b_vZnv.js} +1 -1
  86. package/dist/gateway/static/root/assets/voice-api-key-field-CkhbRt1q.js +1 -0
  87. package/dist/gateway/static/root/assets/workflow-run-setup-panel--TdohF5N.js +3 -0
  88. package/dist/gateway/static/root/assets/workflow-start-dialog-B0lavx_o.js +1 -0
  89. package/dist/gateway/static/root/assets/workflows-page-CZR05Bu_.js +18 -0
  90. package/dist/gateway/static/root/index.html +7 -8
  91. package/dist/package.js +1 -1
  92. package/dist/src/agent/agent-manager.d.ts +2 -0
  93. package/dist/src/agent/agent-manager.js +47 -7
  94. package/dist/src/agent/agent-manager.js.map +1 -1
  95. package/dist/src/agent/context/project-context.d.ts +19 -0
  96. package/dist/src/agent/context/project-context.js +90 -0
  97. package/dist/src/agent/context/project-context.js.map +1 -0
  98. package/dist/src/agent/embedded/xopc-auth-storage.d.ts +1 -5
  99. package/dist/src/agent/embedded/xopc-auth-storage.js +2 -8
  100. package/dist/src/agent/embedded/xopc-auth-storage.js.map +1 -1
  101. package/dist/src/agent/embedded/xopc-tools-bridge.js +23 -0
  102. package/dist/src/agent/embedded/xopc-tools-bridge.js.map +1 -1
  103. package/dist/src/agent/goals/checklist-judge.js +1 -1
  104. package/dist/src/agent/goals/checklist-judge.js.map +1 -1
  105. package/dist/src/agent/goals/goal-locale.js +2 -2
  106. package/dist/src/agent/goals/judge.js +1 -1
  107. package/dist/src/agent/goals/judge.js.map +1 -1
  108. package/dist/src/agent/image/tool-model-config.d.ts +2 -0
  109. package/dist/src/agent/image/tool-model-config.js +2 -2
  110. package/dist/src/agent/image/tool-model-config.js.map +1 -1
  111. package/dist/src/agent/image/understanding/pi-ai-provider.js +1 -1
  112. package/dist/src/agent/image/understanding/pi-ai-provider.js.map +1 -1
  113. package/dist/src/agent/memory/compaction.d.ts +1 -1
  114. package/dist/src/agent/memory/compaction.js +1 -1
  115. package/dist/src/agent/memory/compaction.js.map +1 -1
  116. package/dist/src/agent/memory/types.d.ts +1 -0
  117. package/dist/src/agent/middleware/self-verify.d.ts +3 -0
  118. package/dist/src/agent/middleware/self-verify.js +18 -0
  119. package/dist/src/agent/middleware/self-verify.js.map +1 -1
  120. package/dist/src/agent/orchestration/agent-event-handler.js +28 -3
  121. package/dist/src/agent/orchestration/agent-event-handler.js.map +1 -1
  122. package/dist/src/agent/prompt/sections/tooling.js +21 -5
  123. package/dist/src/agent/prompt/sections/tooling.js.map +1 -1
  124. package/dist/src/agent/prompt/service-prompt-builder.d.ts +1 -0
  125. package/dist/src/agent/prompt/service-prompt-builder.js +3 -0
  126. package/dist/src/agent/prompt/service-prompt-builder.js.map +1 -1
  127. package/dist/src/agent/prompt/system-prompt-params.d.ts +2 -0
  128. package/dist/src/agent/prompt/system-prompt-params.js +1 -0
  129. package/dist/src/agent/prompt/system-prompt-params.js.map +1 -1
  130. package/dist/src/agent/prompt/system-prompt.d.ts +2 -0
  131. package/dist/src/agent/prompt/system-prompt.js +14 -1
  132. package/dist/src/agent/prompt/system-prompt.js.map +1 -1
  133. package/dist/src/agent/sandbox/path-policy.js +30 -6
  134. package/dist/src/agent/sandbox/path-policy.js.map +1 -1
  135. package/dist/src/agent/service/btw-query.js +1 -1
  136. package/dist/src/agent/service/btw-query.js.map +1 -1
  137. package/dist/src/agent/service.js +4 -3
  138. package/dist/src/agent/service.js.map +1 -1
  139. package/dist/src/agent/session/session-config-service.js +6 -1
  140. package/dist/src/agent/session/session-config-service.js.map +1 -1
  141. package/dist/src/agent/session/session-hydrator.js +7 -3
  142. package/dist/src/agent/session/session-hydrator.js.map +1 -1
  143. package/dist/src/agent/session/session-inspector.js +9 -5
  144. package/dist/src/agent/session/session-inspector.js.map +1 -1
  145. package/dist/src/agent/starter-agents.js +18 -47
  146. package/dist/src/agent/starter-agents.js.map +1 -1
  147. package/dist/src/agent/tools/image-generate-tool.js +12 -1
  148. package/dist/src/agent/tools/image-generate-tool.js.map +1 -1
  149. package/dist/src/agent/tools/session-search-tool.js +1 -1
  150. package/dist/src/agent/tools/session-search-tool.js.map +1 -1
  151. package/dist/src/agent/tools/web-extract.js +1 -1
  152. package/dist/src/agent/tools/web-extract.js.map +1 -1
  153. package/dist/src/agent/workflow/index.js +2 -2
  154. package/dist/src/agent/workflow/runtime.js +1 -1
  155. package/dist/src/agent-manifest/index.js +2 -2
  156. package/dist/src/agent-manifest/schema.d.ts +66 -0
  157. package/dist/src/agent-manifest/schema.js +28 -2
  158. package/dist/src/agent-manifest/schema.js.map +1 -1
  159. package/dist/src/automations/api/routes.js +13 -2
  160. package/dist/src/automations/api/routes.js.map +1 -1
  161. package/dist/src/automations/domain/types.d.ts +2 -0
  162. package/dist/src/automations/domain/validation.d.ts +3 -0
  163. package/dist/src/automations/domain/validation.js +1 -0
  164. package/dist/src/automations/domain/validation.js.map +1 -1
  165. package/dist/src/automations/draft/automation-draft-service.js +1 -1
  166. package/dist/src/automations/draft/automation-draft-service.js.map +1 -1
  167. package/dist/src/automations/draft/automation-draft-validator.d.ts +1 -0
  168. package/dist/src/automations/service/action-executor.js +3 -1
  169. package/dist/src/automations/service/action-executor.js.map +1 -1
  170. package/dist/src/automations/service/automation-service.d.ts +4 -1
  171. package/dist/src/automations/service/automation-service.js +2 -2
  172. package/dist/src/automations/service/automation-service.js.map +1 -1
  173. package/dist/src/automations/storage/automation-repository.d.ts +3 -1
  174. package/dist/src/automations/storage/automation-repository.js +7 -5
  175. package/dist/src/automations/storage/automation-repository.js.map +1 -1
  176. package/dist/src/automations/storage/automation-run-repository.d.ts +1 -0
  177. package/dist/src/automations/storage/automation-run-repository.js +12 -1
  178. package/dist/src/automations/storage/automation-run-repository.js.map +1 -1
  179. package/dist/src/chat-commands/context.js +2 -1
  180. package/dist/src/chat-commands/context.js.map +1 -1
  181. package/dist/src/cli/command-loaders.js +1 -0
  182. package/dist/src/cli/command-loaders.js.map +1 -1
  183. package/dist/src/cli/command-manifest.js +4 -0
  184. package/dist/src/cli/command-manifest.js.map +1 -1
  185. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  186. package/dist/src/cli/commands/goal.js +27 -4
  187. package/dist/src/cli/commands/goal.js.map +1 -1
  188. package/dist/src/cli/commands/onboard/model.js +161 -11
  189. package/dist/src/cli/commands/onboard/model.js.map +1 -1
  190. package/dist/src/cli/commands/project.d.ts +4 -0
  191. package/dist/src/cli/commands/project.js +239 -0
  192. package/dist/src/cli/commands/project.js.map +1 -0
  193. package/dist/src/config/default-model.d.ts +11 -0
  194. package/dist/src/config/default-model.js +19 -0
  195. package/dist/src/config/default-model.js.map +1 -0
  196. package/dist/src/config/index.js +2 -2
  197. package/dist/src/config/schema.d.ts +71 -11
  198. package/dist/src/config/schema.js +57 -22
  199. package/dist/src/config/schema.js.map +1 -1
  200. package/dist/src/connectors/registries/smithery.js +10 -9
  201. package/dist/src/connectors/registries/smithery.js.map +1 -1
  202. package/dist/src/daemon/install-plan.js +2 -1
  203. package/dist/src/daemon/install-plan.js.map +1 -1
  204. package/dist/src/gateway/global-defaults-admin.js +6 -16
  205. package/dist/src/gateway/global-defaults-admin.js.map +1 -1
  206. package/dist/src/gateway/hono/lib/config-payload.d.ts +24 -0
  207. package/dist/src/gateway/hono/routes/agents.js +1 -79
  208. package/dist/src/gateway/hono/routes/agents.js.map +1 -1
  209. package/dist/src/gateway/hono/routes/goals.js +51 -4
  210. package/dist/src/gateway/hono/routes/goals.js.map +1 -1
  211. package/dist/src/gateway/hono/routes/index.js +2 -0
  212. package/dist/src/gateway/hono/routes/index.js.map +1 -1
  213. package/dist/src/gateway/hono/routes/memory.js +5 -1
  214. package/dist/src/gateway/hono/routes/memory.js.map +1 -1
  215. package/dist/src/gateway/hono/routes/models.js +132 -14
  216. package/dist/src/gateway/hono/routes/models.js.map +1 -1
  217. package/dist/src/gateway/hono/routes/notes.js +5 -0
  218. package/dist/src/gateway/hono/routes/notes.js.map +1 -1
  219. package/dist/src/gateway/hono/routes/projects.d.ts +3 -0
  220. package/dist/src/gateway/hono/routes/projects.js +578 -0
  221. package/dist/src/gateway/hono/routes/projects.js.map +1 -0
  222. package/dist/src/gateway/hono/routes/sessions.js +47 -7
  223. package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
  224. package/dist/src/gateway/hono/routes/voice.js +1 -1
  225. package/dist/src/gateway/hono/routes/voice.js.map +1 -1
  226. package/dist/src/gateway/hono/routes/workflows.js +27 -4
  227. package/dist/src/gateway/hono/routes/workflows.js.map +1 -1
  228. package/dist/src/gateway/image-capabilities.d.ts +11 -1
  229. package/dist/src/gateway/image-capabilities.js +32 -43
  230. package/dist/src/gateway/image-capabilities.js.map +1 -1
  231. package/dist/src/gateway/service.d.ts +3 -0
  232. package/dist/src/gateway/service.js +10 -3
  233. package/dist/src/gateway/service.js.map +1 -1
  234. package/dist/src/gateway/workspace-editor-path.js +9 -1
  235. package/dist/src/gateway/workspace-editor-path.js.map +1 -1
  236. package/dist/src/generated/bundled-image-generation-providers.d.ts +4 -2
  237. package/dist/src/generated/bundled-image-generation-providers.js +6 -4
  238. package/dist/src/generated/bundled-image-generation-providers.js.map +1 -1
  239. package/dist/src/goals/goal-service.d.ts +1 -1
  240. package/dist/src/goals/goal-service.js +2 -1
  241. package/dist/src/goals/goal-service.js.map +1 -1
  242. package/dist/src/goals/goal-store.d.ts +1 -1
  243. package/dist/src/goals/goal-store.js +12 -6
  244. package/dist/src/goals/goal-store.js.map +1 -1
  245. package/dist/src/goals/goal-workflow-judge.js +1 -1
  246. package/dist/src/goals/types.d.ts +3 -0
  247. package/dist/src/i18n/goals-bundle.js +1 -1
  248. package/dist/src/i18n/index.js +1 -1
  249. package/dist/src/i18n/share-tool-bundle.js +2 -2
  250. package/dist/src/media/paths.js +3 -4
  251. package/dist/src/media/paths.js.map +1 -1
  252. package/dist/src/notes/agent-context.js +2 -2
  253. package/dist/src/notes/service.js +1 -1
  254. package/dist/src/notes/service.js.map +1 -1
  255. package/dist/src/projects/automation-bind.d.ts +1 -0
  256. package/dist/src/projects/automation-bind.js +16 -0
  257. package/dist/src/projects/automation-bind.js.map +1 -0
  258. package/dist/src/projects/goal-bind.d.ts +4 -0
  259. package/dist/src/projects/goal-bind.js +40 -0
  260. package/dist/src/projects/goal-bind.js.map +1 -0
  261. package/dist/src/projects/index.d.ts +11 -0
  262. package/dist/src/projects/index.js +11 -0
  263. package/dist/src/projects/memory-bind.d.ts +1 -0
  264. package/dist/src/projects/memory-bind.js +16 -0
  265. package/dist/src/projects/memory-bind.js.map +1 -0
  266. package/dist/src/projects/project-agent.d.ts +10 -0
  267. package/dist/src/projects/project-agent.js +27 -0
  268. package/dist/src/projects/project-agent.js.map +1 -0
  269. package/dist/src/projects/project-loop.d.ts +58 -0
  270. package/dist/src/projects/project-loop.js +118 -0
  271. package/dist/src/projects/project-loop.js.map +1 -0
  272. package/dist/src/projects/project-service.d.ts +26 -0
  273. package/dist/src/projects/project-service.js +94 -0
  274. package/dist/src/projects/project-service.js.map +1 -0
  275. package/dist/src/projects/project-store.d.ts +31 -0
  276. package/dist/src/projects/project-store.js +193 -0
  277. package/dist/src/projects/project-store.js.map +1 -0
  278. package/dist/src/projects/session-bind.d.ts +5 -0
  279. package/dist/src/projects/session-bind.js +46 -0
  280. package/dist/src/projects/session-bind.js.map +1 -0
  281. package/dist/src/projects/types.d.ts +60 -0
  282. package/dist/src/projects/types.js +1 -0
  283. package/dist/src/projects/workflow-bind.d.ts +1 -0
  284. package/dist/src/projects/workflow-bind.js +16 -0
  285. package/dist/src/projects/workflow-bind.js.map +1 -0
  286. package/dist/src/projects/workspace.d.ts +5 -0
  287. package/dist/src/projects/workspace.js +25 -0
  288. package/dist/src/projects/workspace.js.map +1 -0
  289. package/dist/src/providers/domestic-presets.d.ts +615 -0
  290. package/dist/src/providers/domestic-presets.js +834 -0
  291. package/dist/src/providers/domestic-presets.js.map +1 -0
  292. package/dist/src/providers/env-keys.js +29 -5
  293. package/dist/src/providers/env-keys.js.map +1 -1
  294. package/dist/src/providers/extension-stream-bridge.js +1 -1
  295. package/dist/src/providers/extension-stream-bridge.js.map +1 -1
  296. package/dist/src/providers/index.d.ts +5 -5
  297. package/dist/src/providers/index.js +101 -21
  298. package/dist/src/providers/index.js.map +1 -1
  299. package/dist/src/providers/model-discovery.d.ts +17 -0
  300. package/dist/src/providers/model-discovery.js +61 -0
  301. package/dist/src/providers/model-discovery.js.map +1 -0
  302. package/dist/src/providers/model-registry.d.ts +1 -1
  303. package/dist/src/providers/model-registry.js +1 -1
  304. package/dist/src/providers/model-registry.js.map +1 -1
  305. package/dist/src/providers/presentation.js +185 -14
  306. package/dist/src/providers/presentation.js.map +1 -1
  307. package/dist/src/session/index.d.ts +1 -1
  308. package/dist/src/session/index.js +3 -3
  309. package/dist/src/session/session-title.js +1 -1
  310. package/dist/src/session/session-title.js.map +1 -1
  311. package/dist/src/session/session-workspace.d.ts +4 -2
  312. package/dist/src/session/session-workspace.js +10 -3
  313. package/dist/src/session/session-workspace.js.map +1 -1
  314. package/dist/src/session/types.d.ts +3 -0
  315. package/dist/src/session/types.js.map +1 -1
  316. package/dist/src/storage/sqlite/memory-records-repository.d.ts +3 -0
  317. package/dist/src/storage/sqlite/memory-records-repository.js +16 -5
  318. package/dist/src/storage/sqlite/memory-records-repository.js.map +1 -1
  319. package/dist/src/storage/sqlite/migrations/019_projects.sql +40 -0
  320. package/dist/src/storage/sqlite/migrations/020_project_default_agent.sql +1 -0
  321. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  322. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  323. package/dist/src/storage/sqlite/migrations/runner.js.map +1 -1
  324. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  325. package/dist/src/storage/sqlite/notes-repository.js +20 -4
  326. package/dist/src/storage/sqlite/notes-repository.js.map +1 -1
  327. package/dist/src/storage/sqlite/row-mappers.d.ts +2 -0
  328. package/dist/src/storage/sqlite/row-mappers.js +2 -0
  329. package/dist/src/storage/sqlite/row-mappers.js.map +1 -1
  330. package/dist/src/storage/sqlite/schema.js +1 -1
  331. package/dist/src/storage/sqlite/session-repository.js +10 -5
  332. package/dist/src/storage/sqlite/session-repository.js.map +1 -1
  333. package/dist/src/storage/sqlite/transcript-repository.js +8 -0
  334. package/dist/src/storage/sqlite/transcript-repository.js.map +1 -1
  335. package/dist/src/tui/backends/embedded-backend.js +1 -1
  336. package/dist/src/tui/tui-commands.js +145 -0
  337. package/dist/src/tui/tui-commands.js.map +1 -1
  338. package/dist/src/tui/xopc-tui-keybindings.d.ts +1 -1
  339. package/dist/src/voice/tts/summarize.js +1 -1
  340. package/dist/src/voice/tts/summarize.js.map +1 -1
  341. package/dist/src/workflows/domain/run.d.ts +1 -0
  342. package/dist/src/workflows/domain/run.js.map +1 -1
  343. package/dist/src/workflows/draft/workflow-draft-service.js +1 -1
  344. package/dist/src/workflows/draft/workflow-draft-service.js.map +1 -1
  345. package/dist/src/workflows/index.js +1 -1
  346. package/dist/src/workflows/service/workflow-run-service.d.ts +1 -0
  347. package/dist/src/workflows/service/workflow-run-service.js +18 -4
  348. package/dist/src/workflows/service/workflow-run-service.js.map +1 -1
  349. package/dist/src/workflows/service/workflow-run-service.types.d.ts +2 -0
  350. package/dist/src/workflows/service/workflow-session-bridge.d.ts +1 -0
  351. package/dist/src/workflows/service/workflow-session-bridge.js +3 -0
  352. package/dist/src/workflows/service/workflow-session-bridge.js.map +1 -1
  353. package/dist/src/workflows/store/run-index-store.d.ts +1 -0
  354. package/dist/src/workflows/store/run-index-store.js +13 -3
  355. package/dist/src/workflows/store/run-index-store.js.map +1 -1
  356. package/dist/src/workflows/store/run-store.d.ts +3 -1
  357. package/dist/src/workflows/store/run-store.js +5 -2
  358. package/dist/src/workflows/store/run-store.js.map +1 -1
  359. package/package.json +15 -6
  360. package/dist/gateway/static/root/assets/Combination-BzaXOLd9.js +0 -41
  361. package/dist/gateway/static/root/assets/agents-CFW_FS_V.js +0 -2
  362. package/dist/gateway/static/root/assets/apps-page-DT52OrbM.js +0 -1
  363. package/dist/gateway/static/root/assets/automation-suggestion-card-CtysjSoQ.js +0 -1
  364. package/dist/gateway/static/root/assets/automations-page-B2YvT3ES.js +0 -1
  365. package/dist/gateway/static/root/assets/browser-settings-page-BQEXPhDU.js +0 -8
  366. package/dist/gateway/static/root/assets/channels-settings-DRTutLlN.js +0 -1
  367. package/dist/gateway/static/root/assets/connectors-page-DneJVuge.js +0 -2
  368. package/dist/gateway/static/root/assets/dist-By6Rh2ee.js +0 -1
  369. package/dist/gateway/static/root/assets/dist-CkobR7mk.js +0 -1
  370. package/dist/gateway/static/root/assets/dist-DokQNVMQ.js +0 -1
  371. package/dist/gateway/static/root/assets/dreaming-settings-BEwWA1yf.js +0 -1
  372. package/dist/gateway/static/root/assets/extension-settings-page-BS2rx40e.js +0 -1
  373. package/dist/gateway/static/root/assets/gateway-settings-DAFI_T0W.js +0 -1
  374. package/dist/gateway/static/root/assets/goal-detail-page-DLuz4jC9.js +0 -1
  375. package/dist/gateway/static/root/assets/goals-page-DTAfv9Ft.js +0 -2
  376. package/dist/gateway/static/root/assets/goals-settings-CWN2iirQ.js +0 -1
  377. package/dist/gateway/static/root/assets/heartbeat-settings-CkkkHvOi.js +0 -1
  378. package/dist/gateway/static/root/assets/index-CnjzMw5i.js +0 -191
  379. package/dist/gateway/static/root/assets/index-E1QinYKp.css +0 -1
  380. package/dist/gateway/static/root/assets/logs-page-CDXDlS_W.js +0 -2
  381. package/dist/gateway/static/root/assets/messages-CISb1HLv.js +0 -3
  382. package/dist/gateway/static/root/assets/models-hub-panel-BTwxMIgZ.js +0 -2
  383. package/dist/gateway/static/root/assets/note-detail-page-BBfOeB0m.js +0 -2
  384. package/dist/gateway/static/root/assets/note-image-lightbox-4zntqpZq.js +0 -1
  385. package/dist/gateway/static/root/assets/note-time-hm6JO_Wi.js +0 -1
  386. package/dist/gateway/static/root/assets/notes-page-VocWOjoK.js +0 -1
  387. package/dist/gateway/static/root/assets/remote-access-hub-CXwjuD_Q.js +0 -1
  388. package/dist/gateway/static/root/assets/schedule-field-pRheF2CO.js +0 -1
  389. package/dist/gateway/static/root/assets/schema-form-D6PiPxfr.js +0 -1
  390. package/dist/gateway/static/root/assets/sessions-page-CpcVySQK.js +0 -1
  391. package/dist/gateway/static/root/assets/settings-advanced-gate-BVy_TawT.js +0 -1
  392. package/dist/gateway/static/root/assets/settings-page-CZpNyCWn.js +0 -1
  393. package/dist/gateway/static/root/assets/settings-page-layout-NxoW5Y57.js +0 -1
  394. package/dist/gateway/static/root/assets/setup-status-panel-MVHLLu_a.js +0 -1
  395. package/dist/gateway/static/root/assets/share-preview-page-B0suFCfj.js +0 -2
  396. package/dist/gateway/static/root/assets/shares-settings-C6DBbQGo.js +0 -1
  397. package/dist/gateway/static/root/assets/skill-reload-api-6hJmhwSn.js +0 -1
  398. package/dist/gateway/static/root/assets/skills-page-Im1MA0Iw.js +0 -2
  399. package/dist/gateway/static/root/assets/user-profile-settings-panel-QAQWGelF.js +0 -1
  400. package/dist/gateway/static/root/assets/voice-api-key-field-D-6XueTv.js +0 -1
  401. package/dist/gateway/static/root/assets/voice-player-store-CMC5NOJp.js +0 -1
  402. package/dist/gateway/static/root/assets/workflow-run-setup-panel-DVPllR4k.js +0 -3
  403. package/dist/gateway/static/root/assets/workflows-page-B94WISTW.js +0 -18
@@ -1 +1 @@
1
- {"version":3,"file":"checklist-judge.js","names":[],"sources":["../../../../src/agent/goals/checklist-judge.ts"],"sourcesContent":["import type { UserMessage } from '@earendil-works/pi-ai';\nimport { complete } from '@earendil-works/pi-ai';\n\nimport { resolveModel } from '../../providers/index.js';\n\nimport {\n DEFAULT_JUDGE_TIMEOUT_MS,\n extractAssistantText,\n getAssistantMessageErrorReason,\n resolveGoalJudgeApiKey,\n stripCodeFences,\n truncateGoalText,\n} from './judge.js';\nimport type { GoalUiLocale } from './goal-locale.js';\nimport {\n goalUiLocaleOrFallback,\n judgeReasonText,\n judgeResponseLanguageNote,\n JUDGE_REASON_EN,\n localizeJudgeReasonText,\n} from './goal-locale.js';\n\nconst GOAL_TRUNC = 4000;\nconst RESPONSE_SNIPPET = 4000;\nconst HISTORY_TRUNC = 24_000;\n\nconst DECOMPOSE_SYSTEM = (\n 'You break a user goal into an EXTREMELY detailed checklist of concrete, verifiable completion criteria. ' +\n 'Bias toward more items. Each item is one factual statement about finished work.\\n\\n' +\n 'If existing acceptance criteria are provided, return only missing supplemental criteria. ' +\n 'Do not repeat, rewrite, or replace existing criteria.\\n\\n' +\n 'Reply ONLY with a single JSON object on one line:\\n' +\n '{\"items\":[{\"text\":\"...\"},...]}\\n' +\n 'Use at least 3 items when the goal warrants it.'\n);\n\nconst DECOMPOSE_USER = 'Goal:\\n{goal}\\n\\nReturn JSON only.';\n\nconst EVALUATE_CHECKLIST_SYSTEM = (\n 'You evaluate an autonomous agent\\'s progress on a goal with a numbered checklist.\\n' +\n 'For each pending item, decide if evidence in the agent snippet and/or history excerpt shows it is satisfied.\\n' +\n 'Flip pending→completed only with clear evidence; pending→impossible only if truly unachievable here.\\n' +\n 'Do not regress completed/impossible items — omit them from updates.\\n\\n' +\n 'Reply ONLY with one JSON object on one line:\\n' +\n '{\"verdict\":\"continue|done|blocked|needs_input\",\"confidence\":0.0,\"reason\":\"...\",' +\n '\"nextAction\":\"...\",\"missingEvidence\":[\"...\"],\"userQuestion\":\"...\",' +\n '\"updates\":[{\"index\":1,\"status\":\"completed|impossible\",\"evidence\":\"short citation\"}],' +\n '\"new_items\":[{\"text\":\"...\"}]}\\n' +\n 'Use verdict=done only when all checklist criteria are complete/impossible. ' +\n 'Use needs_input when the next step requires user input. Use blocked when external progress is impossible.'\n);\n\nfunction extractJsonObject(raw: string): Record<string, unknown> | null {\n if (!raw?.trim()) return null;\n const text = stripCodeFences(raw);\n try {\n const data = JSON.parse(text) as unknown;\n return data && typeof data === 'object' && !Array.isArray(data) ? (data as Record<string, unknown>) : null;\n } catch {\n const start = text.indexOf('{');\n const end = text.lastIndexOf('}');\n if (start === -1 || end <= start) return null;\n try {\n const data = JSON.parse(text.slice(start, end + 1)) as unknown;\n return data && typeof data === 'object' && !Array.isArray(data) ? (data as Record<string, unknown>) : null;\n } catch {\n return null;\n }\n }\n}\n\nexport type DecomposeChecklistResult = {\n items: { text: string }[];\n parseFailed: boolean;\n errorReason?: string;\n};\n\nexport async function decomposeGoalChecklist(opts: {\n goal: string;\n existingChecklist?: string;\n judgeModelRef: string;\n signal?: AbortSignal;\n judgeTimeoutMs?: number;\n uiLocale?: GoalUiLocale;\n}): Promise<DecomposeChecklistResult> {\n const locale = goalUiLocaleOrFallback(opts.uiLocale);\n const goal = opts.goal.trim();\n if (!goal)\n return { items: [], parseFailed: false, errorReason: judgeReasonText('empty_goal', locale) };\n\n let model: ReturnType<typeof resolveModel>;\n try {\n model = resolveModel(opts.judgeModelRef);\n } catch {\n return {\n items: [],\n parseFailed: false,\n errorReason: judgeReasonText('judge_model_not_configured', locale),\n };\n }\n\n const timeoutMs =\n typeof opts.judgeTimeoutMs === 'number' && Number.isFinite(opts.judgeTimeoutMs)\n ? Math.max(5_000, Math.min(120_000, Math.floor(opts.judgeTimeoutMs)))\n : DEFAULT_JUDGE_TIMEOUT_MS;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n const merged = opts.signal ? AbortSignal.any([opts.signal, controller.signal]) : controller.signal;\n\n try {\n const existingChecklist = opts.existingChecklist?.trim();\n const decomposeUser = DECOMPOSE_USER.replace('{goal}', truncateGoalText(goal, GOAL_TRUNC)) +\n (existingChecklist\n ? `\\n\\nExisting acceptance criteria:\\n${truncateGoalText(existingChecklist, GOAL_TRUNC)}\\n\\nReturn only missing supplemental criteria as JSON.`\n : '');\n const user: UserMessage = {\n role: 'user',\n content:\n `${DECOMPOSE_SYSTEM}\\n\\n${decomposeUser}` +\n judgeResponseLanguageNote(locale),\n timestamp: Date.now(),\n };\n const apiKey = await resolveGoalJudgeApiKey(model);\n const result = await complete(\n model,\n { messages: [user] },\n { apiKey, maxTokens: 2000, temperature: 0, signal: merged },\n );\n const errorReason = getAssistantMessageErrorReason(result);\n if (errorReason) {\n return {\n items: [],\n parseFailed: false,\n errorReason: judgeReasonText('decompose_call_failed', locale),\n };\n }\n\n const text = extractAssistantText(result.content);\n const data = extractJsonObject(text);\n if (!data)\n return {\n items: [],\n parseFailed: true,\n errorReason: judgeReasonText('decompose_reply_not_json', locale),\n };\n const rawItems = data.items ?? data.checklist;\n if (!Array.isArray(rawItems))\n return {\n items: [],\n parseFailed: true,\n errorReason: judgeReasonText('missing_items_array', locale),\n };\n const items: { text: string }[] = [];\n for (const row of rawItems) {\n if (typeof row === 'string') {\n const t = row.trim();\n if (t) items.push({ text: t });\n } else if (row && typeof row === 'object' && !Array.isArray(row)) {\n const t = String((row as { text?: string }).text ?? '').trim();\n if (t) items.push({ text: t });\n }\n }\n if (!items.length)\n return { items: [], parseFailed: true, errorReason: judgeReasonText('empty_checklist', locale) };\n return { items, parseFailed: false };\n } catch {\n return {\n items: [],\n parseFailed: false,\n errorReason: judgeReasonText('decompose_call_failed', locale),\n };\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport type ChecklistJudgeUpdate = {\n index: number;\n status: 'completed' | 'impossible';\n evidence?: string | null;\n};\n\nexport type ChecklistEvaluateParsed = {\n verdict: 'continue' | 'done' | 'blocked' | 'needs_input';\n confidence: number;\n updates: ChecklistJudgeUpdate[];\n newItems: { text: string }[];\n reason: string;\n nextAction?: string;\n missingEvidence?: string[];\n userQuestion?: string;\n};\n\nexport type EvaluateChecklistResult = {\n parsed: ChecklistEvaluateParsed;\n parseFailed: boolean;\n};\n\nexport async function evaluateGoalChecklistJudge(opts: {\n goal: string;\n numberedChecklist: string;\n lastResponse: string;\n historyExcerpt: string;\n goalContextExcerpt?: string;\n judgeModelRef: string;\n signal?: AbortSignal;\n judgeTimeoutMs?: number;\n uiLocale?: GoalUiLocale;\n}): Promise<EvaluateChecklistResult> {\n const locale = goalUiLocaleOrFallback(opts.uiLocale);\n let model: ReturnType<typeof resolveModel>;\n try {\n model = resolveModel(opts.judgeModelRef);\n } catch {\n return {\n parsed: {\n verdict: 'continue',\n confidence: 0,\n updates: [],\n newItems: [],\n reason: judgeReasonText('judge_model_not_configured', locale),\n },\n parseFailed: false,\n };\n }\n\n const userBody =\n `Goal:\\n${truncateGoalText(opts.goal, 2000)}\\n\\n` +\n `Current checklist (1-based indices):\\n${opts.numberedChecklist}\\n\\n` +\n `Original goal context and attachments:\\n${truncateGoalText(opts.goalContextExcerpt ?? '', 4000) || '(none)'}\\n\\n` +\n `Agent's most recent response (snippet):\\n${truncateGoalText(opts.lastResponse, RESPONSE_SNIPPET)}\\n\\n` +\n `Recent conversation excerpt (JSON or text, may be truncated):\\n` +\n `${truncateGoalText(opts.historyExcerpt, HISTORY_TRUNC) || '(none)'}`;\n\n const timeoutMs =\n typeof opts.judgeTimeoutMs === 'number' && Number.isFinite(opts.judgeTimeoutMs)\n ? Math.max(5_000, Math.min(120_000, Math.floor(opts.judgeTimeoutMs)))\n : DEFAULT_JUDGE_TIMEOUT_MS;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n const merged = opts.signal ? AbortSignal.any([opts.signal, controller.signal]) : controller.signal;\n\n try {\n const user: UserMessage = {\n role: 'user',\n content: `${EVALUATE_CHECKLIST_SYSTEM}\\n\\n${userBody}` + judgeResponseLanguageNote(locale),\n timestamp: Date.now(),\n };\n const apiKey = await resolveGoalJudgeApiKey(model);\n const result = await complete(\n model,\n { messages: [user] },\n { apiKey, maxTokens: 1500, temperature: 0, signal: merged },\n );\n const errorReason = getAssistantMessageErrorReason(result);\n if (errorReason) {\n return {\n parsed: {\n verdict: 'continue',\n confidence: 0,\n updates: [],\n newItems: [],\n reason: judgeReasonText('judge_call_failed', locale),\n },\n parseFailed: false,\n };\n }\n\n const text = extractAssistantText(result.content);\n const data = extractJsonObject(text);\n if (!data) {\n return {\n parsed: {\n verdict: 'continue',\n confidence: 0,\n updates: [],\n newItems: [],\n reason: judgeReasonText('judge_reply_not_json', locale),\n },\n parseFailed: true,\n };\n }\n const updatesRaw = data.updates;\n const newRaw = data.new_items ?? data.newItems;\n const verdictRaw = String(data.verdict ?? '').trim().toLowerCase();\n const verdict =\n verdictRaw === 'done' || verdictRaw === 'blocked' || verdictRaw === 'needs_input'\n ? verdictRaw\n : 'continue';\n const confidenceRaw = Number(data.confidence);\n const confidence = Number.isFinite(confidenceRaw) ? Math.max(0, Math.min(1, confidenceRaw)) : 0;\n const nextAction = typeof data.nextAction === 'string' ? data.nextAction.trim() : undefined;\n const userQuestion = typeof data.userQuestion === 'string' ? data.userQuestion.trim() : undefined;\n const missingEvidenceRaw = data.missingEvidence ?? data.missing_evidence;\n const trimmed = typeof data.reason === 'string' ? data.reason.trim() : '';\n const inner = trimmed || JUDGE_REASON_EN.no_reason_provided;\n const reason = localizeJudgeReasonText(inner, locale);\n const updates: ChecklistJudgeUpdate[] = [];\n if (Array.isArray(updatesRaw)) {\n for (const u of updatesRaw) {\n if (!u || typeof u !== 'object' || Array.isArray(u)) continue;\n const rec = u as Record<string, unknown>;\n const idx1 = Number(rec.index);\n if (!Number.isFinite(idx1)) continue;\n const idx0 = Math.floor(idx1) - 1;\n const st = String(rec.status ?? '').trim().toLowerCase();\n if (st !== 'completed' && st !== 'impossible') continue;\n const evidence = typeof rec.evidence === 'string' ? rec.evidence : null;\n updates.push({ index: idx0, status: st, evidence });\n }\n }\n const newItems: { text: string }[] = [];\n if (Array.isArray(newRaw)) {\n for (const row of newRaw) {\n if (typeof row === 'string') {\n const t = row.trim();\n if (t) newItems.push({ text: t });\n } else if (row && typeof row === 'object' && !Array.isArray(row)) {\n const t = String((row as { text?: string }).text ?? '').trim();\n if (t) newItems.push({ text: t });\n }\n }\n }\n const missingEvidence: string[] = [];\n if (Array.isArray(missingEvidenceRaw)) {\n for (const item of missingEvidenceRaw) {\n if (typeof item === 'string' && item.trim()) missingEvidence.push(item.trim());\n }\n }\n return {\n parsed: {\n verdict,\n confidence,\n updates,\n newItems,\n reason: reason || judgeReasonText('no_reason_provided', locale),\n ...(nextAction ? { nextAction } : {}),\n ...(missingEvidence.length ? { missingEvidence } : {}),\n ...(userQuestion ? { userQuestion } : {}),\n },\n parseFailed: false,\n };\n } catch {\n return {\n parsed: {\n verdict: 'continue',\n confidence: 0,\n updates: [],\n newItems: [],\n reason: judgeReasonText('judge_call_failed', locale),\n },\n parseFailed: false,\n };\n } finally {\n clearTimeout(timer);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"checklist-judge.js","names":[],"sources":["../../../../src/agent/goals/checklist-judge.ts"],"sourcesContent":["import type { UserMessage } from '@earendil-works/pi-ai';\nimport { complete } from '@earendil-works/pi-ai/compat';\n\nimport { resolveModel } from '../../providers/index.js';\n\nimport {\n DEFAULT_JUDGE_TIMEOUT_MS,\n extractAssistantText,\n getAssistantMessageErrorReason,\n resolveGoalJudgeApiKey,\n stripCodeFences,\n truncateGoalText,\n} from './judge.js';\nimport type { GoalUiLocale } from './goal-locale.js';\nimport {\n goalUiLocaleOrFallback,\n judgeReasonText,\n judgeResponseLanguageNote,\n JUDGE_REASON_EN,\n localizeJudgeReasonText,\n} from './goal-locale.js';\n\nconst GOAL_TRUNC = 4000;\nconst RESPONSE_SNIPPET = 4000;\nconst HISTORY_TRUNC = 24_000;\n\nconst DECOMPOSE_SYSTEM = (\n 'You break a user goal into an EXTREMELY detailed checklist of concrete, verifiable completion criteria. ' +\n 'Bias toward more items. Each item is one factual statement about finished work.\\n\\n' +\n 'If existing acceptance criteria are provided, return only missing supplemental criteria. ' +\n 'Do not repeat, rewrite, or replace existing criteria.\\n\\n' +\n 'Reply ONLY with a single JSON object on one line:\\n' +\n '{\"items\":[{\"text\":\"...\"},...]}\\n' +\n 'Use at least 3 items when the goal warrants it.'\n);\n\nconst DECOMPOSE_USER = 'Goal:\\n{goal}\\n\\nReturn JSON only.';\n\nconst EVALUATE_CHECKLIST_SYSTEM = (\n 'You evaluate an autonomous agent\\'s progress on a goal with a numbered checklist.\\n' +\n 'For each pending item, decide if evidence in the agent snippet and/or history excerpt shows it is satisfied.\\n' +\n 'Flip pending→completed only with clear evidence; pending→impossible only if truly unachievable here.\\n' +\n 'Do not regress completed/impossible items — omit them from updates.\\n\\n' +\n 'Reply ONLY with one JSON object on one line:\\n' +\n '{\"verdict\":\"continue|done|blocked|needs_input\",\"confidence\":0.0,\"reason\":\"...\",' +\n '\"nextAction\":\"...\",\"missingEvidence\":[\"...\"],\"userQuestion\":\"...\",' +\n '\"updates\":[{\"index\":1,\"status\":\"completed|impossible\",\"evidence\":\"short citation\"}],' +\n '\"new_items\":[{\"text\":\"...\"}]}\\n' +\n 'Use verdict=done only when all checklist criteria are complete/impossible. ' +\n 'Use needs_input when the next step requires user input. Use blocked when external progress is impossible.'\n);\n\nfunction extractJsonObject(raw: string): Record<string, unknown> | null {\n if (!raw?.trim()) return null;\n const text = stripCodeFences(raw);\n try {\n const data = JSON.parse(text) as unknown;\n return data && typeof data === 'object' && !Array.isArray(data) ? (data as Record<string, unknown>) : null;\n } catch {\n const start = text.indexOf('{');\n const end = text.lastIndexOf('}');\n if (start === -1 || end <= start) return null;\n try {\n const data = JSON.parse(text.slice(start, end + 1)) as unknown;\n return data && typeof data === 'object' && !Array.isArray(data) ? (data as Record<string, unknown>) : null;\n } catch {\n return null;\n }\n }\n}\n\nexport type DecomposeChecklistResult = {\n items: { text: string }[];\n parseFailed: boolean;\n errorReason?: string;\n};\n\nexport async function decomposeGoalChecklist(opts: {\n goal: string;\n existingChecklist?: string;\n judgeModelRef: string;\n signal?: AbortSignal;\n judgeTimeoutMs?: number;\n uiLocale?: GoalUiLocale;\n}): Promise<DecomposeChecklistResult> {\n const locale = goalUiLocaleOrFallback(opts.uiLocale);\n const goal = opts.goal.trim();\n if (!goal)\n return { items: [], parseFailed: false, errorReason: judgeReasonText('empty_goal', locale) };\n\n let model: ReturnType<typeof resolveModel>;\n try {\n model = resolveModel(opts.judgeModelRef);\n } catch {\n return {\n items: [],\n parseFailed: false,\n errorReason: judgeReasonText('judge_model_not_configured', locale),\n };\n }\n\n const timeoutMs =\n typeof opts.judgeTimeoutMs === 'number' && Number.isFinite(opts.judgeTimeoutMs)\n ? Math.max(5_000, Math.min(120_000, Math.floor(opts.judgeTimeoutMs)))\n : DEFAULT_JUDGE_TIMEOUT_MS;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n const merged = opts.signal ? AbortSignal.any([opts.signal, controller.signal]) : controller.signal;\n\n try {\n const existingChecklist = opts.existingChecklist?.trim();\n const decomposeUser = DECOMPOSE_USER.replace('{goal}', truncateGoalText(goal, GOAL_TRUNC)) +\n (existingChecklist\n ? `\\n\\nExisting acceptance criteria:\\n${truncateGoalText(existingChecklist, GOAL_TRUNC)}\\n\\nReturn only missing supplemental criteria as JSON.`\n : '');\n const user: UserMessage = {\n role: 'user',\n content:\n `${DECOMPOSE_SYSTEM}\\n\\n${decomposeUser}` +\n judgeResponseLanguageNote(locale),\n timestamp: Date.now(),\n };\n const apiKey = await resolveGoalJudgeApiKey(model);\n const result = await complete(\n model,\n { messages: [user] },\n { apiKey, maxTokens: 2000, temperature: 0, signal: merged },\n );\n const errorReason = getAssistantMessageErrorReason(result);\n if (errorReason) {\n return {\n items: [],\n parseFailed: false,\n errorReason: judgeReasonText('decompose_call_failed', locale),\n };\n }\n\n const text = extractAssistantText(result.content);\n const data = extractJsonObject(text);\n if (!data)\n return {\n items: [],\n parseFailed: true,\n errorReason: judgeReasonText('decompose_reply_not_json', locale),\n };\n const rawItems = data.items ?? data.checklist;\n if (!Array.isArray(rawItems))\n return {\n items: [],\n parseFailed: true,\n errorReason: judgeReasonText('missing_items_array', locale),\n };\n const items: { text: string }[] = [];\n for (const row of rawItems) {\n if (typeof row === 'string') {\n const t = row.trim();\n if (t) items.push({ text: t });\n } else if (row && typeof row === 'object' && !Array.isArray(row)) {\n const t = String((row as { text?: string }).text ?? '').trim();\n if (t) items.push({ text: t });\n }\n }\n if (!items.length)\n return { items: [], parseFailed: true, errorReason: judgeReasonText('empty_checklist', locale) };\n return { items, parseFailed: false };\n } catch {\n return {\n items: [],\n parseFailed: false,\n errorReason: judgeReasonText('decompose_call_failed', locale),\n };\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport type ChecklistJudgeUpdate = {\n index: number;\n status: 'completed' | 'impossible';\n evidence?: string | null;\n};\n\nexport type ChecklistEvaluateParsed = {\n verdict: 'continue' | 'done' | 'blocked' | 'needs_input';\n confidence: number;\n updates: ChecklistJudgeUpdate[];\n newItems: { text: string }[];\n reason: string;\n nextAction?: string;\n missingEvidence?: string[];\n userQuestion?: string;\n};\n\nexport type EvaluateChecklistResult = {\n parsed: ChecklistEvaluateParsed;\n parseFailed: boolean;\n};\n\nexport async function evaluateGoalChecklistJudge(opts: {\n goal: string;\n numberedChecklist: string;\n lastResponse: string;\n historyExcerpt: string;\n goalContextExcerpt?: string;\n judgeModelRef: string;\n signal?: AbortSignal;\n judgeTimeoutMs?: number;\n uiLocale?: GoalUiLocale;\n}): Promise<EvaluateChecklistResult> {\n const locale = goalUiLocaleOrFallback(opts.uiLocale);\n let model: ReturnType<typeof resolveModel>;\n try {\n model = resolveModel(opts.judgeModelRef);\n } catch {\n return {\n parsed: {\n verdict: 'continue',\n confidence: 0,\n updates: [],\n newItems: [],\n reason: judgeReasonText('judge_model_not_configured', locale),\n },\n parseFailed: false,\n };\n }\n\n const userBody =\n `Goal:\\n${truncateGoalText(opts.goal, 2000)}\\n\\n` +\n `Current checklist (1-based indices):\\n${opts.numberedChecklist}\\n\\n` +\n `Original goal context and attachments:\\n${truncateGoalText(opts.goalContextExcerpt ?? '', 4000) || '(none)'}\\n\\n` +\n `Agent's most recent response (snippet):\\n${truncateGoalText(opts.lastResponse, RESPONSE_SNIPPET)}\\n\\n` +\n `Recent conversation excerpt (JSON or text, may be truncated):\\n` +\n `${truncateGoalText(opts.historyExcerpt, HISTORY_TRUNC) || '(none)'}`;\n\n const timeoutMs =\n typeof opts.judgeTimeoutMs === 'number' && Number.isFinite(opts.judgeTimeoutMs)\n ? Math.max(5_000, Math.min(120_000, Math.floor(opts.judgeTimeoutMs)))\n : DEFAULT_JUDGE_TIMEOUT_MS;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n const merged = opts.signal ? AbortSignal.any([opts.signal, controller.signal]) : controller.signal;\n\n try {\n const user: UserMessage = {\n role: 'user',\n content: `${EVALUATE_CHECKLIST_SYSTEM}\\n\\n${userBody}` + judgeResponseLanguageNote(locale),\n timestamp: Date.now(),\n };\n const apiKey = await resolveGoalJudgeApiKey(model);\n const result = await complete(\n model,\n { messages: [user] },\n { apiKey, maxTokens: 1500, temperature: 0, signal: merged },\n );\n const errorReason = getAssistantMessageErrorReason(result);\n if (errorReason) {\n return {\n parsed: {\n verdict: 'continue',\n confidence: 0,\n updates: [],\n newItems: [],\n reason: judgeReasonText('judge_call_failed', locale),\n },\n parseFailed: false,\n };\n }\n\n const text = extractAssistantText(result.content);\n const data = extractJsonObject(text);\n if (!data) {\n return {\n parsed: {\n verdict: 'continue',\n confidence: 0,\n updates: [],\n newItems: [],\n reason: judgeReasonText('judge_reply_not_json', locale),\n },\n parseFailed: true,\n };\n }\n const updatesRaw = data.updates;\n const newRaw = data.new_items ?? data.newItems;\n const verdictRaw = String(data.verdict ?? '').trim().toLowerCase();\n const verdict =\n verdictRaw === 'done' || verdictRaw === 'blocked' || verdictRaw === 'needs_input'\n ? verdictRaw\n : 'continue';\n const confidenceRaw = Number(data.confidence);\n const confidence = Number.isFinite(confidenceRaw) ? Math.max(0, Math.min(1, confidenceRaw)) : 0;\n const nextAction = typeof data.nextAction === 'string' ? data.nextAction.trim() : undefined;\n const userQuestion = typeof data.userQuestion === 'string' ? data.userQuestion.trim() : undefined;\n const missingEvidenceRaw = data.missingEvidence ?? data.missing_evidence;\n const trimmed = typeof data.reason === 'string' ? data.reason.trim() : '';\n const inner = trimmed || JUDGE_REASON_EN.no_reason_provided;\n const reason = localizeJudgeReasonText(inner, locale);\n const updates: ChecklistJudgeUpdate[] = [];\n if (Array.isArray(updatesRaw)) {\n for (const u of updatesRaw) {\n if (!u || typeof u !== 'object' || Array.isArray(u)) continue;\n const rec = u as Record<string, unknown>;\n const idx1 = Number(rec.index);\n if (!Number.isFinite(idx1)) continue;\n const idx0 = Math.floor(idx1) - 1;\n const st = String(rec.status ?? '').trim().toLowerCase();\n if (st !== 'completed' && st !== 'impossible') continue;\n const evidence = typeof rec.evidence === 'string' ? rec.evidence : null;\n updates.push({ index: idx0, status: st, evidence });\n }\n }\n const newItems: { text: string }[] = [];\n if (Array.isArray(newRaw)) {\n for (const row of newRaw) {\n if (typeof row === 'string') {\n const t = row.trim();\n if (t) newItems.push({ text: t });\n } else if (row && typeof row === 'object' && !Array.isArray(row)) {\n const t = String((row as { text?: string }).text ?? '').trim();\n if (t) newItems.push({ text: t });\n }\n }\n }\n const missingEvidence: string[] = [];\n if (Array.isArray(missingEvidenceRaw)) {\n for (const item of missingEvidenceRaw) {\n if (typeof item === 'string' && item.trim()) missingEvidence.push(item.trim());\n }\n }\n return {\n parsed: {\n verdict,\n confidence,\n updates,\n newItems,\n reason: reason || judgeReasonText('no_reason_provided', locale),\n ...(nextAction ? { nextAction } : {}),\n ...(missingEvidence.length ? { missingEvidence } : {}),\n ...(userQuestion ? { userQuestion } : {}),\n },\n parseFailed: false,\n };\n } catch {\n return {\n parsed: {\n verdict: 'continue',\n confidence: 0,\n updates: [],\n newItems: [],\n reason: judgeReasonText('judge_call_failed', locale),\n },\n parseFailed: false,\n };\n } finally {\n clearTimeout(timer);\n }\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
- import { formatI18n } from "../../i18n/format.js";
2
- import { SERVER_LOCALES, isServerLocale, normalizeServerLocale, serverLocaleOrFallback } from "../../i18n/locale.js";
3
1
  import { checklistCounts } from "./checklist-types.js";
4
2
  import { renderChecklistPlain } from "./state.js";
3
+ import { formatI18n } from "../../i18n/format.js";
4
+ import { SERVER_LOCALES, isServerLocale, normalizeServerLocale, serverLocaleOrFallback } from "../../i18n/locale.js";
5
5
  import { JUDGE_REASON_EN, goalsChecklistProgressSuffix, goalsContinuationChecklistTemplate, goalsContinuationPlainTemplate, goalsEvaluateCopy, goalsJudgeReason, goalsJudgeResponseLanguageNote } from "../../i18n/goals-bundle.js";
6
6
  //#region src/agent/goals/goal-locale.ts
7
7
  const GOAL_UI_LOCALES = SERVER_LOCALES;
@@ -1,7 +1,7 @@
1
1
  import { getApiKey, init_providers, resolveModel } from "../../providers/index.js";
2
2
  import { JUDGE_REASON_EN } from "../../i18n/goals-bundle.js";
3
3
  import { goalUiLocaleOrFallback, judgeFreeformBuiltinMessages, judgeResponseLanguageNote, localizeJudgeReasonText } from "./goal-locale.js";
4
- import { complete } from "@earendil-works/pi-ai";
4
+ import { complete } from "@earendil-works/pi-ai/compat";
5
5
  //#region src/agent/goals/judge.ts
6
6
  init_providers();
7
7
  const JUDGE_RESPONSE_SNIPPET_CHARS = 4e3;
@@ -1 +1 @@
1
- {"version":3,"file":"judge.js","names":[],"sources":["../../../../src/agent/goals/judge.ts"],"sourcesContent":["import type { UserMessage } from '@earendil-works/pi-ai';\nimport { complete } from '@earendil-works/pi-ai';\n\nimport { getApiKey, resolveModel } from '../../providers/index.js';\n\nimport type { GoalUiLocale } from './goal-locale.js';\nimport {\n goalUiLocaleOrFallback,\n judgeFreeformBuiltinMessages,\n JUDGE_REASON_EN,\n judgeResponseLanguageNote,\n localizeJudgeReasonText,\n} from './goal-locale.js';\n\nconst JUDGE_RESPONSE_SNIPPET_CHARS = 4000;\n\n/**\n * Extract visible text from a pi-ai `AssistantMessage.content` array.\n * Handles `TextContent` (`type: 'text'`) and falls back to `ThinkingContent`\n * (`type: 'thinking'`) when the text blocks are empty — reasoning models\n * (DeepSeek-R1, Qwen-thinking, etc.) may place the entire response inside\n * thinking blocks, leaving the text portion blank.\n */\nexport function extractAssistantText(content: unknown): string {\n if (!Array.isArray(content)) return '';\n\n let textParts = '';\n let thinkingParts = '';\n\n for (const block of content) {\n if (!block || typeof block !== 'object') continue;\n const typed = block as Record<string, unknown>;\n const blockType = typed.type;\n if (blockType === 'text' && typeof typed.text === 'string') {\n textParts += typed.text;\n } else if (blockType === 'thinking') {\n const thinking =\n typeof typed.thinking === 'string'\n ? typed.thinking\n : typeof typed.text === 'string'\n ? typed.text\n : '';\n thinkingParts += thinking;\n }\n }\n\n // Prefer text blocks; fall back to thinking blocks when text is empty.\n return textParts.trim() ? textParts : thinkingParts;\n}\n\n/**\n * Strip markdown code fences (opening AND closing) from raw model output.\n * Handles `` ```json ``, `` ``` ``, and trailing `` ``` `` with optional whitespace.\n */\nexport function stripCodeFences(raw: string): string {\n let text = raw.trim();\n\n // Remove opening code fence: ```<optional-lang>\\n\n const openMatch = text.match(/^`{3,}[^\\n]*\\n?/);\n if (openMatch) {\n text = text.slice(openMatch[0].length);\n }\n\n // Remove closing code fence: \\n```<optional-whitespace>\n const closeMatch = text.match(/\\n?`{3,}\\s*$/);\n if (closeMatch) {\n text = text.slice(0, -closeMatch[0].length);\n }\n\n return text.trim();\n}\n\n/** Mirrors `hermes_cli/goals.py` — strict judge, JSON-only reply. */\nexport const JUDGE_SYSTEM_PROMPT =\n 'You are a strict judge evaluating whether an autonomous agent has ' +\n \"achieved a user's stated goal. You receive the goal text and the \" +\n \"agent's most recent response. Your only job is to decide whether \" +\n 'the goal is fully satisfied based on that response.\\n\\n' +\n 'A goal is DONE only when:\\n' +\n '- The response explicitly confirms the goal was completed, OR\\n' +\n '- The response clearly shows the final deliverable was produced, OR\\n' +\n '- The response explains the goal is unachievable / blocked / needs ' +\n 'user input (treat this as DONE with reason describing the block).\\n\\n' +\n 'Otherwise the goal is NOT done — CONTINUE.\\n\\n' +\n 'Reply ONLY with a single JSON object on one line:\\n' +\n '{\"done\": <true|false>, \"reason\": \"<one-sentence rationale>\"}';\n\nexport const JUDGE_USER_PROMPT_TEMPLATE =\n 'Goal:\\n{goal}\\n\\n' + \"Agent's most recent response:\\n{response}\\n\\n\" + 'Is the goal satisfied?';\n\nexport const DEFAULT_JUDGE_TIMEOUT_MS = 60_000;\n\nexport function truncateGoalText(text: string, limit: number): string {\n if (!text) return '';\n if (text.length <= limit) return text;\n return text.slice(0, limit) + '… [truncated]';\n}\n\nexport async function resolveGoalJudgeApiKey(\n model: ReturnType<typeof resolveModel>,\n): Promise<string | undefined> {\n try {\n return await getApiKey(model.provider);\n } catch {\n return undefined;\n }\n}\n\nexport function getAssistantMessageErrorReason(message: unknown): string | null {\n if (!message || typeof message !== 'object') return null;\n\n const record = message as Record<string, unknown>;\n const stopReason = record.stopReason;\n const errorMessage = typeof record.errorMessage === 'string' ? record.errorMessage.trim() : '';\n\n if (stopReason === 'error') {\n return errorMessage || 'Judge model call failed.';\n }\n\n return null;\n}\n\n/** Parse judge JSON — fail-open to **continue** (Hermes semantics). */\nexport function parseJudgeResponseFailOpen(raw: string): {\n done: boolean;\n reason: string;\n parseFailed: boolean;\n} {\n if (!raw?.trim()) {\n return { done: false, reason: JUDGE_REASON_EN.judge_returned_empty, parseFailed: true };\n }\n\n const text = stripCodeFences(raw);\n\n let data: Record<string, unknown> | null = null;\n try {\n data = JSON.parse(text) as Record<string, unknown>;\n } catch {\n const start = text.indexOf('{');\n const end = text.lastIndexOf('}');\n if (start !== -1 && end > start) {\n try {\n data = JSON.parse(text.slice(start, end + 1)) as Record<string, unknown>;\n } catch {\n data = null;\n }\n }\n }\n\n if (!data || typeof data !== 'object') {\n return { done: false, reason: JUDGE_REASON_EN.judge_reply_not_json, parseFailed: true };\n }\n\n const doneVal = data.done;\n let done: boolean;\n if (typeof doneVal === 'string') {\n done = ['true', 'yes', '1', 'done'].includes(doneVal.trim().toLowerCase());\n } else {\n done = Boolean(doneVal);\n }\n const reason = typeof data.reason === 'string' ? data.reason.trim() : '';\n return { done, reason: reason || JUDGE_REASON_EN.no_reason_provided, parseFailed: false };\n}\n\nexport type GoalJudgeVerdict = 'done' | 'continue' | 'skipped';\n\n/**\n * Ask the configured model whether the goal is satisfied.\n * Fail-open: any error → `continue` (Hermes — turn budget is the backstop).\n */\nexport async function judgeGoalHermesStyle(\n goal: string,\n lastResponse: string,\n judgeModelRef: string,\n signal?: AbortSignal,\n opts?: { judgeTimeoutMs?: number; uiLocale?: GoalUiLocale },\n): Promise<{ verdict: GoalJudgeVerdict; reason: string; parseFailed: boolean }> {\n const locale = goalUiLocaleOrFallback(opts?.uiLocale);\n const b = judgeFreeformBuiltinMessages(locale);\n\n if (!goal.trim()) {\n return { verdict: 'skipped', reason: b.emptyGoal, parseFailed: false };\n }\n if (!lastResponse.trim()) {\n return { verdict: 'continue', reason: b.emptyResponse, parseFailed: false };\n }\n\n let model: ReturnType<typeof resolveModel>;\n try {\n model = resolveModel(judgeModelRef);\n } catch {\n return { verdict: 'continue', reason: b.noModel, parseFailed: false };\n }\n\n const userContent =\n JUDGE_USER_PROMPT_TEMPLATE.replace('{goal}', truncateGoalText(goal, 2000)).replace(\n '{response}',\n truncateGoalText(lastResponse, JUDGE_RESPONSE_SNIPPET_CHARS),\n ) + judgeResponseLanguageNote(locale);\n\n const timeoutMs =\n typeof opts?.judgeTimeoutMs === 'number' && Number.isFinite(opts.judgeTimeoutMs)\n ? Math.max(5_000, Math.min(120_000, Math.floor(opts.judgeTimeoutMs)))\n : DEFAULT_JUDGE_TIMEOUT_MS;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n const merged = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;\n\n try {\n const combinedUser: UserMessage = {\n role: 'user',\n content: `${JUDGE_SYSTEM_PROMPT}\\n\\n${userContent}`,\n timestamp: Date.now(),\n };\n\n const apiKey = await resolveGoalJudgeApiKey(model);\n const result = await complete(\n model,\n {\n messages: [combinedUser],\n },\n { apiKey, maxTokens: 200, temperature: 0, signal: merged },\n );\n const errorReason = getAssistantMessageErrorReason(result);\n if (errorReason) {\n return { verdict: 'continue', reason: b.callFailed, parseFailed: false };\n }\n\n const text = extractAssistantText(result.content);\n const { done, reason, parseFailed } = parseJudgeResponseFailOpen(text);\n const reasonOut = localizeJudgeReasonText(reason, locale);\n return { verdict: done ? 'done' : 'continue', reason: reasonOut, parseFailed };\n } catch {\n return { verdict: 'continue', reason: b.callFailed, parseFailed: false };\n } finally {\n clearTimeout(timer);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"judge.js","names":[],"sources":["../../../../src/agent/goals/judge.ts"],"sourcesContent":["import type { UserMessage } from '@earendil-works/pi-ai';\nimport { complete } from '@earendil-works/pi-ai/compat';\n\nimport { getApiKey, resolveModel } from '../../providers/index.js';\n\nimport type { GoalUiLocale } from './goal-locale.js';\nimport {\n goalUiLocaleOrFallback,\n judgeFreeformBuiltinMessages,\n JUDGE_REASON_EN,\n judgeResponseLanguageNote,\n localizeJudgeReasonText,\n} from './goal-locale.js';\n\nconst JUDGE_RESPONSE_SNIPPET_CHARS = 4000;\n\n/**\n * Extract visible text from a pi-ai `AssistantMessage.content` array.\n * Handles `TextContent` (`type: 'text'`) and falls back to `ThinkingContent`\n * (`type: 'thinking'`) when the text blocks are empty — reasoning models\n * (DeepSeek-R1, Qwen-thinking, etc.) may place the entire response inside\n * thinking blocks, leaving the text portion blank.\n */\nexport function extractAssistantText(content: unknown): string {\n if (!Array.isArray(content)) return '';\n\n let textParts = '';\n let thinkingParts = '';\n\n for (const block of content) {\n if (!block || typeof block !== 'object') continue;\n const typed = block as Record<string, unknown>;\n const blockType = typed.type;\n if (blockType === 'text' && typeof typed.text === 'string') {\n textParts += typed.text;\n } else if (blockType === 'thinking') {\n const thinking =\n typeof typed.thinking === 'string'\n ? typed.thinking\n : typeof typed.text === 'string'\n ? typed.text\n : '';\n thinkingParts += thinking;\n }\n }\n\n // Prefer text blocks; fall back to thinking blocks when text is empty.\n return textParts.trim() ? textParts : thinkingParts;\n}\n\n/**\n * Strip markdown code fences (opening AND closing) from raw model output.\n * Handles `` ```json ``, `` ``` ``, and trailing `` ``` `` with optional whitespace.\n */\nexport function stripCodeFences(raw: string): string {\n let text = raw.trim();\n\n // Remove opening code fence: ```<optional-lang>\\n\n const openMatch = text.match(/^`{3,}[^\\n]*\\n?/);\n if (openMatch) {\n text = text.slice(openMatch[0].length);\n }\n\n // Remove closing code fence: \\n```<optional-whitespace>\n const closeMatch = text.match(/\\n?`{3,}\\s*$/);\n if (closeMatch) {\n text = text.slice(0, -closeMatch[0].length);\n }\n\n return text.trim();\n}\n\n/** Mirrors `hermes_cli/goals.py` — strict judge, JSON-only reply. */\nexport const JUDGE_SYSTEM_PROMPT =\n 'You are a strict judge evaluating whether an autonomous agent has ' +\n \"achieved a user's stated goal. You receive the goal text and the \" +\n \"agent's most recent response. Your only job is to decide whether \" +\n 'the goal is fully satisfied based on that response.\\n\\n' +\n 'A goal is DONE only when:\\n' +\n '- The response explicitly confirms the goal was completed, OR\\n' +\n '- The response clearly shows the final deliverable was produced, OR\\n' +\n '- The response explains the goal is unachievable / blocked / needs ' +\n 'user input (treat this as DONE with reason describing the block).\\n\\n' +\n 'Otherwise the goal is NOT done — CONTINUE.\\n\\n' +\n 'Reply ONLY with a single JSON object on one line:\\n' +\n '{\"done\": <true|false>, \"reason\": \"<one-sentence rationale>\"}';\n\nexport const JUDGE_USER_PROMPT_TEMPLATE =\n 'Goal:\\n{goal}\\n\\n' + \"Agent's most recent response:\\n{response}\\n\\n\" + 'Is the goal satisfied?';\n\nexport const DEFAULT_JUDGE_TIMEOUT_MS = 60_000;\n\nexport function truncateGoalText(text: string, limit: number): string {\n if (!text) return '';\n if (text.length <= limit) return text;\n return text.slice(0, limit) + '… [truncated]';\n}\n\nexport async function resolveGoalJudgeApiKey(\n model: ReturnType<typeof resolveModel>,\n): Promise<string | undefined> {\n try {\n return await getApiKey(model.provider);\n } catch {\n return undefined;\n }\n}\n\nexport function getAssistantMessageErrorReason(message: unknown): string | null {\n if (!message || typeof message !== 'object') return null;\n\n const record = message as Record<string, unknown>;\n const stopReason = record.stopReason;\n const errorMessage = typeof record.errorMessage === 'string' ? record.errorMessage.trim() : '';\n\n if (stopReason === 'error') {\n return errorMessage || 'Judge model call failed.';\n }\n\n return null;\n}\n\n/** Parse judge JSON — fail-open to **continue** (Hermes semantics). */\nexport function parseJudgeResponseFailOpen(raw: string): {\n done: boolean;\n reason: string;\n parseFailed: boolean;\n} {\n if (!raw?.trim()) {\n return { done: false, reason: JUDGE_REASON_EN.judge_returned_empty, parseFailed: true };\n }\n\n const text = stripCodeFences(raw);\n\n let data: Record<string, unknown> | null = null;\n try {\n data = JSON.parse(text) as Record<string, unknown>;\n } catch {\n const start = text.indexOf('{');\n const end = text.lastIndexOf('}');\n if (start !== -1 && end > start) {\n try {\n data = JSON.parse(text.slice(start, end + 1)) as Record<string, unknown>;\n } catch {\n data = null;\n }\n }\n }\n\n if (!data || typeof data !== 'object') {\n return { done: false, reason: JUDGE_REASON_EN.judge_reply_not_json, parseFailed: true };\n }\n\n const doneVal = data.done;\n let done: boolean;\n if (typeof doneVal === 'string') {\n done = ['true', 'yes', '1', 'done'].includes(doneVal.trim().toLowerCase());\n } else {\n done = Boolean(doneVal);\n }\n const reason = typeof data.reason === 'string' ? data.reason.trim() : '';\n return { done, reason: reason || JUDGE_REASON_EN.no_reason_provided, parseFailed: false };\n}\n\nexport type GoalJudgeVerdict = 'done' | 'continue' | 'skipped';\n\n/**\n * Ask the configured model whether the goal is satisfied.\n * Fail-open: any error → `continue` (Hermes — turn budget is the backstop).\n */\nexport async function judgeGoalHermesStyle(\n goal: string,\n lastResponse: string,\n judgeModelRef: string,\n signal?: AbortSignal,\n opts?: { judgeTimeoutMs?: number; uiLocale?: GoalUiLocale },\n): Promise<{ verdict: GoalJudgeVerdict; reason: string; parseFailed: boolean }> {\n const locale = goalUiLocaleOrFallback(opts?.uiLocale);\n const b = judgeFreeformBuiltinMessages(locale);\n\n if (!goal.trim()) {\n return { verdict: 'skipped', reason: b.emptyGoal, parseFailed: false };\n }\n if (!lastResponse.trim()) {\n return { verdict: 'continue', reason: b.emptyResponse, parseFailed: false };\n }\n\n let model: ReturnType<typeof resolveModel>;\n try {\n model = resolveModel(judgeModelRef);\n } catch {\n return { verdict: 'continue', reason: b.noModel, parseFailed: false };\n }\n\n const userContent =\n JUDGE_USER_PROMPT_TEMPLATE.replace('{goal}', truncateGoalText(goal, 2000)).replace(\n '{response}',\n truncateGoalText(lastResponse, JUDGE_RESPONSE_SNIPPET_CHARS),\n ) + judgeResponseLanguageNote(locale);\n\n const timeoutMs =\n typeof opts?.judgeTimeoutMs === 'number' && Number.isFinite(opts.judgeTimeoutMs)\n ? Math.max(5_000, Math.min(120_000, Math.floor(opts.judgeTimeoutMs)))\n : DEFAULT_JUDGE_TIMEOUT_MS;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n const merged = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;\n\n try {\n const combinedUser: UserMessage = {\n role: 'user',\n content: `${JUDGE_SYSTEM_PROMPT}\\n\\n${userContent}`,\n timestamp: Date.now(),\n };\n\n const apiKey = await resolveGoalJudgeApiKey(model);\n const result = await complete(\n model,\n {\n messages: [combinedUser],\n },\n { apiKey, maxTokens: 200, temperature: 0, signal: merged },\n );\n const errorReason = getAssistantMessageErrorReason(result);\n if (errorReason) {\n return { verdict: 'continue', reason: b.callFailed, parseFailed: false };\n }\n\n const text = extractAssistantText(result.content);\n const { done, reason, parseFailed } = parseJudgeResponseFailOpen(text);\n const reasonOut = localizeJudgeReasonText(reason, locale);\n return { verdict: done ? 'done' : 'continue', reason: reasonOut, parseFailed };\n } catch {\n return { verdict: 'continue', reason: b.callFailed, parseFailed: false };\n } finally {\n clearTimeout(timer);\n }\n}\n"],"mappings":"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"}
@@ -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":"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"}
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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"}
@@ -34,6 +34,7 @@ export interface MemoryScope {
34
34
  agentId: string;
35
35
  workspaceId?: string;
36
36
  sessionKey?: string;
37
+ projectId?: string;
37
38
  }
38
39
  export interface MemoryCitation {
39
40
  providerId: string;
@@ -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) {