@ai-agent-forge/agent-forge 0.88.0 → 0.88.1

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 (360) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/bundled-plugins/agent-loop-policy/agent-forge.json +19 -0
  3. package/bundled-plugins/agent-loop-policy/dist/entry.d.ts +28 -0
  4. package/bundled-plugins/agent-loop-policy/dist/entry.d.ts.map +1 -0
  5. package/bundled-plugins/agent-loop-policy/dist/entry.js +69 -0
  6. package/bundled-plugins/agent-loop-policy/dist/entry.js.map +1 -0
  7. package/bundled-plugins/agent-loop-policy/dist/loop-guard.d.ts +199 -0
  8. package/bundled-plugins/agent-loop-policy/dist/loop-guard.d.ts.map +1 -0
  9. package/bundled-plugins/agent-loop-policy/dist/loop-guard.js +208 -0
  10. package/bundled-plugins/agent-loop-policy/dist/loop-guard.js.map +1 -0
  11. package/bundled-plugins/agent-loop-policy/dist/run-loop-strategy.d.ts +17 -0
  12. package/bundled-plugins/agent-loop-policy/dist/run-loop-strategy.d.ts.map +1 -0
  13. package/bundled-plugins/agent-loop-policy/dist/run-loop-strategy.js +18 -0
  14. package/bundled-plugins/agent-loop-policy/dist/run-loop-strategy.js.map +1 -0
  15. package/bundled-plugins/agent-loop-policy/plugin.json +7 -0
  16. package/bundled-plugins/coding-tools/agent-forge.json +19 -0
  17. package/{dist/core/tools → bundled-plugins/coding-tools/dist}/bash.d.ts +5 -44
  18. package/bundled-plugins/coding-tools/dist/bash.d.ts.map +1 -0
  19. package/{dist/core/tools → bundled-plugins/coding-tools/dist}/bash.js +15 -119
  20. package/bundled-plugins/coding-tools/dist/bash.js.map +1 -0
  21. package/bundled-plugins/coding-tools/dist/capability.d.ts +25 -0
  22. package/bundled-plugins/coding-tools/dist/capability.d.ts.map +1 -0
  23. package/bundled-plugins/coding-tools/dist/capability.js +98 -0
  24. package/bundled-plugins/coding-tools/dist/capability.js.map +1 -0
  25. package/bundled-plugins/coding-tools/dist/config.d.ts +8 -0
  26. package/bundled-plugins/coding-tools/dist/config.d.ts.map +1 -0
  27. package/bundled-plugins/coding-tools/dist/config.js +8 -0
  28. package/bundled-plugins/coding-tools/dist/config.js.map +1 -0
  29. package/{dist/core/tools → bundled-plugins/coding-tools/dist}/edit.d.ts +3 -3
  30. package/bundled-plugins/coding-tools/dist/edit.d.ts.map +1 -0
  31. package/{dist/core/tools → bundled-plugins/coding-tools/dist}/edit.js +12 -10
  32. package/bundled-plugins/coding-tools/dist/edit.js.map +1 -0
  33. package/bundled-plugins/coding-tools/dist/entry.d.ts +14 -0
  34. package/bundled-plugins/coding-tools/dist/entry.d.ts.map +1 -0
  35. package/bundled-plugins/coding-tools/dist/entry.js +23 -0
  36. package/bundled-plugins/coding-tools/dist/entry.js.map +1 -0
  37. package/{dist/core/tools → bundled-plugins/coding-tools/dist}/find.d.ts +4 -4
  38. package/bundled-plugins/coding-tools/dist/find.d.ts.map +1 -0
  39. package/{dist/core/tools → bundled-plugins/coding-tools/dist}/find.js +10 -8
  40. package/bundled-plugins/coding-tools/dist/find.js.map +1 -0
  41. package/{dist/core/tools → bundled-plugins/coding-tools/dist}/grep.d.ts +3 -3
  42. package/bundled-plugins/coding-tools/dist/grep.d.ts.map +1 -0
  43. package/{dist/core/tools → bundled-plugins/coding-tools/dist}/grep.js +10 -8
  44. package/bundled-plugins/coding-tools/dist/grep.js.map +1 -0
  45. package/bundled-plugins/coding-tools/dist/index.d.ts +17 -0
  46. package/bundled-plugins/coding-tools/dist/index.d.ts.map +1 -0
  47. package/bundled-plugins/coding-tools/dist/index.js +17 -0
  48. package/bundled-plugins/coding-tools/dist/index.js.map +1 -0
  49. package/{dist/core/tools → bundled-plugins/coding-tools/dist}/ls.d.ts +3 -3
  50. package/bundled-plugins/coding-tools/dist/ls.d.ts.map +1 -0
  51. package/{dist/core/tools → bundled-plugins/coding-tools/dist}/ls.js +8 -6
  52. package/{dist/core/tools → bundled-plugins/coding-tools/dist}/ls.js.map +1 -1
  53. package/{dist/core/tools → bundled-plugins/coding-tools/dist}/powershell.d.ts +2 -1
  54. package/bundled-plugins/coding-tools/dist/powershell.d.ts.map +1 -0
  55. package/{dist/core/tools → bundled-plugins/coding-tools/dist}/powershell.js +6 -3
  56. package/bundled-plugins/coding-tools/dist/powershell.js.map +1 -0
  57. package/{dist/core/tools → bundled-plugins/coding-tools/dist}/read.d.ts +3 -3
  58. package/bundled-plugins/coding-tools/dist/read.d.ts.map +1 -0
  59. package/{dist/core/tools → bundled-plugins/coding-tools/dist}/read.js +15 -13
  60. package/bundled-plugins/coding-tools/dist/read.js.map +1 -0
  61. package/bundled-plugins/coding-tools/dist/types.d.ts +74 -0
  62. package/bundled-plugins/coding-tools/dist/types.d.ts.map +1 -0
  63. package/bundled-plugins/coding-tools/dist/types.js +28 -0
  64. package/bundled-plugins/coding-tools/dist/types.js.map +1 -0
  65. package/{dist/core/tools → bundled-plugins/coding-tools/dist}/write.d.ts +2 -2
  66. package/bundled-plugins/coding-tools/dist/write.d.ts.map +1 -0
  67. package/{dist/core/tools → bundled-plugins/coding-tools/dist}/write.js +11 -9
  68. package/bundled-plugins/coding-tools/dist/write.js.map +1 -0
  69. package/bundled-plugins/coding-tools/plugin.json +10 -0
  70. package/bundled-plugins/compaction-policy/agent-forge.json +19 -0
  71. package/bundled-plugins/compaction-policy/dist/branch-summary-policy.d.ts +6 -0
  72. package/bundled-plugins/compaction-policy/dist/branch-summary-policy.d.ts.map +1 -0
  73. package/bundled-plugins/compaction-policy/dist/branch-summary-policy.js +19 -0
  74. package/bundled-plugins/compaction-policy/dist/branch-summary-policy.js.map +1 -0
  75. package/bundled-plugins/compaction-policy/dist/compaction-policy.d.ts +6 -0
  76. package/bundled-plugins/compaction-policy/dist/compaction-policy.d.ts.map +1 -0
  77. package/bundled-plugins/compaction-policy/dist/compaction-policy.js +38 -0
  78. package/bundled-plugins/compaction-policy/dist/compaction-policy.js.map +1 -0
  79. package/bundled-plugins/compaction-policy/dist/entry.d.ts +13 -0
  80. package/bundled-plugins/compaction-policy/dist/entry.d.ts.map +1 -0
  81. package/bundled-plugins/compaction-policy/dist/entry.js +23 -0
  82. package/bundled-plugins/compaction-policy/dist/entry.js.map +1 -0
  83. package/bundled-plugins/compaction-policy/plugin.json +10 -0
  84. package/bundled-plugins/mcp-http/dist/index.d.ts +8 -0
  85. package/bundled-plugins/mcp-http/dist/index.d.ts.map +1 -0
  86. package/bundled-plugins/mcp-http/dist/index.js +8 -0
  87. package/bundled-plugins/mcp-http/dist/index.js.map +1 -0
  88. package/bundled-plugins/mcp-http/dist/mcp-http.d.ts.map +1 -0
  89. package/{dist/plugins → bundled-plugins/mcp-http/dist}/mcp-http.js +1 -1
  90. package/bundled-plugins/mcp-http/dist/mcp-http.js.map +1 -0
  91. package/bundled-plugins/overflow-policy/agent-forge.json +19 -0
  92. package/bundled-plugins/overflow-policy/dist/entry.d.ts +16 -0
  93. package/bundled-plugins/overflow-policy/dist/entry.d.ts.map +1 -0
  94. package/bundled-plugins/overflow-policy/dist/entry.js +35 -0
  95. package/bundled-plugins/overflow-policy/dist/entry.js.map +1 -0
  96. package/bundled-plugins/overflow-policy/plugin.json +10 -0
  97. package/bundled-plugins/provider-failover/agent-forge.json +19 -0
  98. package/bundled-plugins/provider-failover/dist/entry.d.ts +8 -0
  99. package/bundled-plugins/provider-failover/dist/entry.d.ts.map +1 -0
  100. package/bundled-plugins/provider-failover/dist/entry.js +8 -0
  101. package/bundled-plugins/provider-failover/dist/entry.js.map +1 -0
  102. package/bundled-plugins/provider-failover/dist/provider-failover.d.ts +122 -0
  103. package/bundled-plugins/provider-failover/dist/provider-failover.d.ts.map +1 -0
  104. package/{dist/core/extensions → bundled-plugins/provider-failover/dist}/provider-failover.js +143 -56
  105. package/bundled-plugins/provider-failover/dist/provider-failover.js.map +1 -0
  106. package/bundled-plugins/provider-failover/plugin.json +7 -0
  107. package/bundled-plugins/retry-policy/agent-forge.json +19 -0
  108. package/bundled-plugins/retry-policy/dist/entry.d.ts +16 -0
  109. package/bundled-plugins/retry-policy/dist/entry.d.ts.map +1 -0
  110. package/bundled-plugins/retry-policy/dist/entry.js +34 -0
  111. package/bundled-plugins/retry-policy/dist/entry.js.map +1 -0
  112. package/bundled-plugins/retry-policy/plugin.json +10 -0
  113. package/dist/bundle/.build-manifest.json +61 -117
  114. package/dist/bundle/chunks/{anthropic-messages-ALDZBUKI.js → anthropic-messages-PLAKXR73.js} +1 -1
  115. package/dist/bundle/chunks/{azure-openai-responses-A2WJWZFB.js → azure-openai-responses-OUWQ2AZD.js} +1 -1
  116. package/dist/bundle/chunks/{chunk-CIX3FSBK.js → chunk-A3JYRWB6.js} +1 -1
  117. package/dist/bundle/chunks/{chunk-SQFG22WN.js → chunk-AC2FIS4G.js} +1 -1
  118. package/dist/bundle/chunks/chunk-AQLNTW2B.js +664 -0
  119. package/dist/bundle/chunks/chunk-D3ARGZFR.js +13 -0
  120. package/dist/bundle/chunks/{chunk-YJCQVXW5.js → chunk-HTEVUY7F.js} +1 -1
  121. package/dist/bundle/chunks/{chunk-Y7HXTASS.js → chunk-HV64ASMT.js} +1 -1
  122. package/dist/bundle/chunks/chunk-LZOEQITZ.js +855 -0
  123. package/dist/bundle/chunks/{chunk-7ZY7IXZD.js → chunk-TRDNDS6A.js} +1 -1
  124. package/dist/bundle/chunks/chunk-Z72JWUZ4.js +4 -0
  125. package/dist/bundle/chunks/{google-generative-ai-NIB4WONV.js → google-generative-ai-DLII6DAT.js} +1 -1
  126. package/dist/bundle/chunks/{google-vertex-DCPYUEUO.js → google-vertex-G7BZL7GM.js} +1 -1
  127. package/dist/bundle/chunks/image-resize-worker.js +2 -2
  128. package/dist/bundle/chunks/{mistral-conversations-OC6LA5XL.js → mistral-conversations-JXUK24ID.js} +1 -1
  129. package/dist/bundle/chunks/{openai-codex-responses-IO2X4Y7L.js → openai-codex-responses-5GSGFIPG.js} +1 -1
  130. package/dist/bundle/chunks/{openai-completions-XYQQHETU.js → openai-completions-EI2I6DTG.js} +1 -1
  131. package/dist/bundle/chunks/{openai-responses-DLIRN3CI.js → openai-responses-AUJQ3NWT.js} +1 -1
  132. package/dist/bundle/chunks/{pi-messages-E6RRYBO7.js → pi-messages-PWU2VJMR.js} +1 -1
  133. package/dist/bundle/chunks/virtual-modules-WF6WRHP7.js +5 -0
  134. package/dist/bundle/cli.js +1 -1
  135. package/dist/bundle/client.js +1 -1
  136. package/dist/bundle/index.js +3 -3
  137. package/dist/bundle/rpc-entry.js +1 -1
  138. package/dist/capabilities/builtin.d.ts +1 -1
  139. package/dist/capabilities/builtin.d.ts.map +1 -1
  140. package/dist/capabilities/builtin.js +5 -0
  141. package/dist/capabilities/builtin.js.map +1 -1
  142. package/dist/cli/plugin-command.d.ts.map +1 -1
  143. package/dist/cli/plugin-command.js +17 -1
  144. package/dist/cli/plugin-command.js.map +1 -1
  145. package/dist/config.d.ts +2 -3
  146. package/dist/config.d.ts.map +1 -1
  147. package/dist/config.js +2 -4
  148. package/dist/config.js.map +1 -1
  149. package/dist/core/agent-session-bash.d.ts +1 -1
  150. package/dist/core/agent-session-bash.d.ts.map +1 -1
  151. package/dist/core/agent-session-bash.js +1 -1
  152. package/dist/core/agent-session-bash.js.map +1 -1
  153. package/dist/core/agent-session-compaction.d.ts.map +1 -1
  154. package/dist/core/agent-session-compaction.js +14 -1
  155. package/dist/core/agent-session-compaction.js.map +1 -1
  156. package/dist/core/agent-session-internal.d.ts +1 -1
  157. package/dist/core/agent-session-internal.d.ts.map +1 -1
  158. package/dist/core/agent-session-internal.js.map +1 -1
  159. package/dist/core/agent-session-loop.d.ts +4 -3
  160. package/dist/core/agent-session-loop.d.ts.map +1 -1
  161. package/dist/core/agent-session-loop.js +9 -6
  162. package/dist/core/agent-session-loop.js.map +1 -1
  163. package/dist/core/agent-session-retry.d.ts +1 -1
  164. package/dist/core/agent-session-retry.d.ts.map +1 -1
  165. package/dist/core/agent-session-retry.js +32 -3
  166. package/dist/core/agent-session-retry.js.map +1 -1
  167. package/dist/core/agent-session-services.d.ts.map +1 -1
  168. package/dist/core/agent-session-services.js +30 -9
  169. package/dist/core/agent-session-services.js.map +1 -1
  170. package/dist/core/agent-session-tools.d.ts.map +1 -1
  171. package/dist/core/agent-session-tools.js +5 -1
  172. package/dist/core/agent-session-tools.js.map +1 -1
  173. package/dist/core/agent-session.d.ts +12 -13
  174. package/dist/core/agent-session.d.ts.map +1 -1
  175. package/dist/core/agent-session.js +56 -34
  176. package/dist/core/agent-session.js.map +1 -1
  177. package/dist/core/bash-executor.d.ts +1 -1
  178. package/dist/core/bash-executor.d.ts.map +1 -1
  179. package/dist/core/bash-executor.js.map +1 -1
  180. package/dist/core/extensions/bootstrap-agent-loop.d.ts +23 -151
  181. package/dist/core/extensions/bootstrap-agent-loop.d.ts.map +1 -1
  182. package/dist/core/extensions/bootstrap-agent-loop.js +28 -290
  183. package/dist/core/extensions/bootstrap-agent-loop.js.map +1 -1
  184. package/dist/core/extensions/capability-runtime-types.d.ts +5 -3
  185. package/dist/core/extensions/capability-runtime-types.d.ts.map +1 -1
  186. package/dist/core/extensions/capability-runtime-types.js.map +1 -1
  187. package/dist/core/extensions/capability-runtime.d.ts.map +1 -1
  188. package/dist/core/extensions/capability-runtime.js +42 -6
  189. package/dist/core/extensions/capability-runtime.js.map +1 -1
  190. package/dist/core/extensions/index.d.ts +12 -5
  191. package/dist/core/extensions/index.d.ts.map +1 -1
  192. package/dist/core/extensions/index.js +12 -5
  193. package/dist/core/extensions/index.js.map +1 -1
  194. package/dist/core/extensions/virtual-modules.d.ts.map +1 -1
  195. package/dist/core/extensions/virtual-modules.js +65 -0
  196. package/dist/core/extensions/virtual-modules.js.map +1 -1
  197. package/dist/core/facade/coding-agent-facade.d.ts +1 -1
  198. package/dist/core/facade/coding-agent-facade.d.ts.map +1 -1
  199. package/dist/core/facade/coding-agent-facade.js.map +1 -1
  200. package/dist/core/i18n/locales/zh-cn/core-contract-core.d.ts +1 -1
  201. package/dist/core/i18n/locales/zh-cn/core-contract-core.d.ts.map +1 -1
  202. package/dist/core/i18n/locales/zh-cn/core-contract-core.js +1 -1
  203. package/dist/core/i18n/locales/zh-cn/core-contract-core.js.map +1 -1
  204. package/dist/core/i18n/locales/zh-cn/core-contract-extensions.d.ts.map +1 -1
  205. package/dist/core/i18n/locales/zh-cn/core-contract-extensions.js +2 -4
  206. package/dist/core/i18n/locales/zh-cn/core-contract-extensions.js.map +1 -1
  207. package/dist/core/i18n/locales/zh-cn/core-errors.d.ts.map +1 -1
  208. package/dist/core/i18n/locales/zh-cn/core-errors.js +3 -1
  209. package/dist/core/i18n/locales/zh-cn/core-errors.js.map +1 -1
  210. package/dist/core/sdk.d.ts +10 -3
  211. package/dist/core/sdk.d.ts.map +1 -1
  212. package/dist/core/sdk.js +263 -77
  213. package/dist/core/sdk.js.map +1 -1
  214. package/dist/core/tools/bash-operations.d.ts +41 -0
  215. package/dist/core/tools/bash-operations.d.ts.map +1 -0
  216. package/dist/core/tools/bash-operations.js +113 -0
  217. package/dist/core/tools/bash-operations.js.map +1 -0
  218. package/dist/core/tools/index.d.ts +12 -36
  219. package/dist/core/tools/index.d.ts.map +1 -1
  220. package/dist/core/tools/index.js +1 -117
  221. package/dist/core/tools/index.js.map +1 -1
  222. package/dist/core/tools/readme-path.d.ts +3 -0
  223. package/dist/core/tools/readme-path.d.ts.map +1 -0
  224. package/dist/core/tools/readme-path.js +15 -0
  225. package/dist/core/tools/readme-path.js.map +1 -0
  226. package/dist/index.d.ts +3 -19
  227. package/dist/index.d.ts.map +1 -1
  228. package/dist/index.js +6 -39
  229. package/dist/index.js.map +1 -1
  230. package/dist/modes/interactive/components/tool-execution.d.ts +0 -1
  231. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  232. package/dist/modes/interactive/components/tool-execution.js +9 -25
  233. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  234. package/dist/plugins/ecosystem/cli.d.ts.map +1 -1
  235. package/dist/plugins/ecosystem/cli.js +14 -2
  236. package/dist/plugins/ecosystem/cli.js.map +1 -1
  237. package/dist/plugins/ecosystem/node-npm-registry-source.d.ts +7 -2
  238. package/dist/plugins/ecosystem/node-npm-registry-source.d.ts.map +1 -1
  239. package/dist/plugins/ecosystem/node-npm-registry-source.js +64 -9
  240. package/dist/plugins/ecosystem/node-npm-registry-source.js.map +1 -1
  241. package/dist/plugins/ecosystem/node-plugin-acquisition-host.d.ts.map +1 -1
  242. package/dist/plugins/ecosystem/node-plugin-acquisition-host.js +6 -0
  243. package/dist/plugins/ecosystem/node-plugin-acquisition-host.js.map +1 -1
  244. package/dist/profiles/builtin-suites.d.ts +5 -1
  245. package/dist/profiles/builtin-suites.d.ts.map +1 -1
  246. package/dist/profiles/builtin-suites.js +6 -1
  247. package/dist/profiles/builtin-suites.js.map +1 -1
  248. package/examples/extensions/bash-spawn-hook/index.ts +7 -2
  249. package/examples/extensions/built-in-tool-renderer/index.ts +4 -2
  250. package/examples/extensions/minimal-mode/index.ts +2 -2
  251. package/examples/extensions/truncated-tool/index.ts +1 -1
  252. package/examples/extensions/with-deps/package-lock.json +2 -2
  253. package/examples/extensions/with-deps/package.json +1 -1
  254. package/examples/extensions/with-deps/plugin.json +1 -1
  255. package/package.json +3 -2
  256. package/dist/bundle/chunks/chunk-6ZKDNZHA.js +0 -145
  257. package/dist/bundle/chunks/chunk-HLJEVRWP.js +0 -13
  258. package/dist/bundle/chunks/chunk-YGHQME4V.js +0 -1456
  259. package/dist/bundle/chunks/virtual-modules-KZ5DRH7Q.js +0 -2
  260. package/dist/core/extensions/bootstrap-branch-summary-policy.d.ts +0 -7
  261. package/dist/core/extensions/bootstrap-branch-summary-policy.d.ts.map +0 -1
  262. package/dist/core/extensions/bootstrap-branch-summary-policy.js +0 -28
  263. package/dist/core/extensions/bootstrap-branch-summary-policy.js.map +0 -1
  264. package/dist/core/extensions/bootstrap-compaction-policy.d.ts +0 -7
  265. package/dist/core/extensions/bootstrap-compaction-policy.d.ts.map +0 -1
  266. package/dist/core/extensions/bootstrap-compaction-policy.js +0 -48
  267. package/dist/core/extensions/bootstrap-compaction-policy.js.map +0 -1
  268. package/dist/core/extensions/bootstrap-overflow-policy.d.ts +0 -7
  269. package/dist/core/extensions/bootstrap-overflow-policy.d.ts.map +0 -1
  270. package/dist/core/extensions/bootstrap-overflow-policy.js +0 -30
  271. package/dist/core/extensions/bootstrap-overflow-policy.js.map +0 -1
  272. package/dist/core/extensions/bootstrap-retry-policy.d.ts +0 -7
  273. package/dist/core/extensions/bootstrap-retry-policy.d.ts.map +0 -1
  274. package/dist/core/extensions/bootstrap-retry-policy.js +0 -29
  275. package/dist/core/extensions/bootstrap-retry-policy.js.map +0 -1
  276. package/dist/core/extensions/bootstrap-tools.d.ts +0 -20
  277. package/dist/core/extensions/bootstrap-tools.d.ts.map +0 -1
  278. package/dist/core/extensions/bootstrap-tools.js +0 -36
  279. package/dist/core/extensions/bootstrap-tools.js.map +0 -1
  280. package/dist/core/extensions/provider-failover.d.ts +0 -110
  281. package/dist/core/extensions/provider-failover.d.ts.map +0 -1
  282. package/dist/core/extensions/provider-failover.js.map +0 -1
  283. package/dist/core/tools/bash.d.ts.map +0 -1
  284. package/dist/core/tools/bash.js.map +0 -1
  285. package/dist/core/tools/edit.d.ts.map +0 -1
  286. package/dist/core/tools/edit.js.map +0 -1
  287. package/dist/core/tools/find.d.ts.map +0 -1
  288. package/dist/core/tools/find.js.map +0 -1
  289. package/dist/core/tools/grep.d.ts.map +0 -1
  290. package/dist/core/tools/grep.js.map +0 -1
  291. package/dist/core/tools/ls.d.ts.map +0 -1
  292. package/dist/core/tools/powershell.d.ts.map +0 -1
  293. package/dist/core/tools/powershell.js.map +0 -1
  294. package/dist/core/tools/read.d.ts.map +0 -1
  295. package/dist/core/tools/read.js.map +0 -1
  296. package/dist/core/tools/write.d.ts.map +0 -1
  297. package/dist/core/tools/write.js.map +0 -1
  298. package/dist/plugins/mcp-http.d.ts.map +0 -1
  299. package/dist/plugins/mcp-http.js.map +0 -1
  300. package/dist/profile/child-facade.d.ts +0 -44
  301. package/dist/profile/child-facade.d.ts.map +0 -1
  302. package/dist/profile/child-facade.js +0 -56
  303. package/dist/profile/child-facade.js.map +0 -1
  304. package/dist/profile/dependency-wiring.d.ts +0 -48
  305. package/dist/profile/dependency-wiring.d.ts.map +0 -1
  306. package/dist/profile/dependency-wiring.js +0 -56
  307. package/dist/profile/dependency-wiring.js.map +0 -1
  308. package/dist/profile/domain-recovery.d.ts +0 -60
  309. package/dist/profile/domain-recovery.d.ts.map +0 -1
  310. package/dist/profile/domain-recovery.js +0 -94
  311. package/dist/profile/domain-recovery.js.map +0 -1
  312. package/dist/profile/first-roles.d.ts +0 -30
  313. package/dist/profile/first-roles.d.ts.map +0 -1
  314. package/dist/profile/first-roles.js +0 -83
  315. package/dist/profile/first-roles.js.map +0 -1
  316. package/dist/profile/goal-plugin.d.ts +0 -38
  317. package/dist/profile/goal-plugin.d.ts.map +0 -1
  318. package/dist/profile/goal-plugin.js +0 -132
  319. package/dist/profile/goal-plugin.js.map +0 -1
  320. package/dist/profile/plan-plugin.d.ts +0 -44
  321. package/dist/profile/plan-plugin.d.ts.map +0 -1
  322. package/dist/profile/plan-plugin.js +0 -157
  323. package/dist/profile/plan-plugin.js.map +0 -1
  324. package/dist/profile/policy-manifest.d.ts +0 -53
  325. package/dist/profile/policy-manifest.d.ts.map +0 -1
  326. package/dist/profile/policy-manifest.js +0 -53
  327. package/dist/profile/policy-manifest.js.map +0 -1
  328. package/dist/profile/role-assembly.d.ts +0 -47
  329. package/dist/profile/role-assembly.d.ts.map +0 -1
  330. package/dist/profile/role-assembly.js +0 -31
  331. package/dist/profile/role-assembly.js.map +0 -1
  332. package/dist/profile/role-catalog.d.ts +0 -43
  333. package/dist/profile/role-catalog.d.ts.map +0 -1
  334. package/dist/profile/role-catalog.js +0 -132
  335. package/dist/profile/role-catalog.js.map +0 -1
  336. package/dist/profile/role-orchestrator.d.ts +0 -123
  337. package/dist/profile/role-orchestrator.d.ts.map +0 -1
  338. package/dist/profile/role-orchestrator.js +0 -195
  339. package/dist/profile/role-orchestrator.js.map +0 -1
  340. package/dist/profile/role-runner.d.ts +0 -99
  341. package/dist/profile/role-runner.d.ts.map +0 -1
  342. package/dist/profile/role-runner.js +0 -79
  343. package/dist/profile/role-runner.js.map +0 -1
  344. package/dist/profile/root-facade.d.ts +0 -36
  345. package/dist/profile/root-facade.d.ts.map +0 -1
  346. package/dist/profile/root-facade.js +0 -49
  347. package/dist/profile/root-facade.js.map +0 -1
  348. package/dist/profile/skill-discovery-wiring.d.ts +0 -58
  349. package/dist/profile/skill-discovery-wiring.d.ts.map +0 -1
  350. package/dist/profile/skill-discovery-wiring.js +0 -80
  351. package/dist/profile/skill-discovery-wiring.js.map +0 -1
  352. package/dist/profile/state-store.d.ts +0 -62
  353. package/dist/profile/state-store.d.ts.map +0 -1
  354. package/dist/profile/state-store.js +0 -144
  355. package/dist/profile/state-store.js.map +0 -1
  356. package/dist/profile/todo-plugin.d.ts +0 -37
  357. package/dist/profile/todo-plugin.d.ts.map +0 -1
  358. package/dist/profile/todo-plugin.js +0 -137
  359. package/dist/profile/todo-plugin.js.map +0 -1
  360. /package/{dist/plugins → bundled-plugins/mcp-http/dist}/mcp-http.d.ts +0 -0
@@ -0,0 +1,208 @@
1
+ import { createHash } from "node:crypto";
2
+ /** 滑动窗口大小:护栏只回看最近 N 个工具调用签名(跨回合累计)。 */
3
+ export const REPEATED_TOOL_CALL_WINDOW_SIZE = 6;
4
+ /** 提醒阈值:窗口尾部连续相同签名达到该次数时提醒模型(不停机,两级响应第一级)。 */
5
+ export const REPEATED_TOOL_CALL_WARN_THRESHOLD = 3;
6
+ /** 停机阈值:提醒后仍连续相同达到该次数才请求停机(两级响应第二级兜底)。 */
7
+ export const REPEATED_TOOL_CALL_STOP_THRESHOLD = 5;
8
+ /** 停机提示消息的 customType(用户可见面,随 agent_end 的 messages 带出)。 */
9
+ export const REPEATED_TOOL_CALL_STOP_CUSTOM_TYPE = "loop_guard_stop";
10
+ /** 提醒消息的 customType(用户可见面经 sendMessage 持久化+渲染;模型面注入复用同一标记)。 */
11
+ export const REPEATED_TOOL_CALL_WARN_CUSTOM_TYPE = "loop_guard_warn";
12
+ /** 提示消息中参数串的最大长度,超出截断(只影响提示文本,不影响检测签名)。 */
13
+ export const REPEATED_TOOL_CALL_NOTICE_MAX_ARGS_CHARS = 200;
14
+ /**
15
+ * 参数稳定字符串化:对象键递归排序后逐层拼接(数组保持顺序),循环引用输出
16
+ * 固定占位。只用于签名比较与提示文本,不是通用 JSON 序列化器。
17
+ */
18
+ export function stableSerializeValue(value, seen = new WeakSet()) {
19
+ if (value === null)
20
+ return "null";
21
+ switch (typeof value) {
22
+ case "string":
23
+ return JSON.stringify(value);
24
+ case "number":
25
+ return String(value);
26
+ case "boolean":
27
+ return String(value);
28
+ case "undefined":
29
+ return "undefined";
30
+ case "bigint":
31
+ return `bigint:${String(value)}`;
32
+ case "function":
33
+ return "function";
34
+ case "symbol":
35
+ return "symbol";
36
+ case "object":
37
+ break;
38
+ default:
39
+ return "unknown";
40
+ }
41
+ if (seen.has(value))
42
+ return '"[cycle]"';
43
+ seen.add(value);
44
+ const serialized = Array.isArray(value)
45
+ ? `[${value.map((item) => stableSerializeValue(item, seen)).join(",")}]`
46
+ : (() => {
47
+ const record = value;
48
+ const keys = Object.keys(record).sort();
49
+ return `{${keys.map((key) => `${JSON.stringify(key)}:${stableSerializeValue(record[key], seen)}`).join(",")}}`;
50
+ })();
51
+ seen.delete(value);
52
+ return serialized;
53
+ }
54
+ /**
55
+ * 工具结果哈希(签名第三要素):拼接结果的 text 内容后做全量 SHA-256(十六进制),
56
+ * isError 一并纳入(同参同果且同错误状态才计重复)。任何字节差异都产生不同哈希,
57
+ * 因此长结果(前缀稳定、尾部变化)的合法轮询不会被误判成"同果"——签名不受任何
58
+ * 截断影响。无文本结果(纯图像等)与结果缺席都返回空串占位:签名退化为两要素
59
+ * (工具名 + 参数),这是已知局限——无文本的同参结果彼此不可区分。
60
+ * 调用与结果按 toolCallId 对应,正常流程中每个调用都有结果;无结果返回空串。
61
+ */
62
+ export function toolResultHash(result) {
63
+ if (!result)
64
+ return "";
65
+ const text = result.content
66
+ .filter((block) => block.type === "text")
67
+ .map((block) => block.text)
68
+ .join("\n");
69
+ if (text === "")
70
+ return "";
71
+ return `${result.isError ? "error" : "ok"}:${createHash("sha256").update(text).digest("hex")}`;
72
+ }
73
+ /** 工具调用签名:工具名 + 参数稳定字符串化 + 结果哈希。 */
74
+ export function repeatedToolCallSignature(toolName, args, resultHash) {
75
+ return `${JSON.stringify(toolName)}:${stableSerializeValue(args)}:${resultHash}`;
76
+ }
77
+ /** 参数摘要截断:超出 {@link REPEATED_TOOL_CALL_NOTICE_MAX_ARGS_CHARS} 打截断标记(用户可见消息与日志通道共用)。 */
78
+ function truncateNoticeArgs(argsText) {
79
+ return argsText.length > REPEATED_TOOL_CALL_NOTICE_MAX_ARGS_CHARS
80
+ ? `${argsText.slice(0, REPEATED_TOOL_CALL_NOTICE_MAX_ARGS_CHARS)}...(truncated)`
81
+ : argsText;
82
+ }
83
+ /** 停机提示消息:随停机回合的 newMessages 带出(agent_end 可见),并经 deliverNotice 投递到宿主公共面。 */
84
+ export function createRepeatedToolCallStopNotice(toolName, argsText, stopThreshold) {
85
+ const truncated = truncateNoticeArgs(argsText);
86
+ return {
87
+ role: "custom",
88
+ customType: REPEATED_TOOL_CALL_STOP_CUSTOM_TYPE,
89
+ content: `Loop guard: stopped after the same tool call (${toolName}) was repeated ${stopThreshold} times in a row. Args: ${truncated}`,
90
+ display: true,
91
+ timestamp: Date.now(),
92
+ };
93
+ }
94
+ /**
95
+ * 提醒通知消息(用户可见面,loop_guard_warn):经 deliverWarn 投递(默认安装接
96
+ * `SessionAPI.sendMessage`,持久化 + 实时渲染),与停机通知(loop_guard_stop)
97
+ * 的 customType 区分。提醒不进入 newMessages——模型面提醒由 transformContext
98
+ * 注入承担,用户面提醒由本消息的持久化+渲染承担。
99
+ */
100
+ export function createRepeatedToolCallWarnNotice(toolName, argsText, warnThreshold) {
101
+ const truncated = truncateNoticeArgs(argsText);
102
+ return {
103
+ role: "custom",
104
+ customType: REPEATED_TOOL_CALL_WARN_CUSTOM_TYPE,
105
+ content: `Loop guard: the same tool call (${toolName}) was repeated ${warnThreshold} times in a row with the same result. Args: ${truncated}`,
106
+ display: true,
107
+ timestamp: Date.now(),
108
+ };
109
+ }
110
+ /**
111
+ * 模型面提醒消息(transformContext 注入用):display:false 的 custom 消息,
112
+ * 经宿主 convertToLlm 转为 user 文本进入下一次模型请求。只存在于该次请求视图
113
+ * (不写 context.messages、不进 newMessages,故不持久化);customType 复用
114
+ * loop_guard_warn 标记来源。
115
+ */
116
+ export function createRepeatedToolCallWarnReminder(warnThreshold) {
117
+ return {
118
+ role: "custom",
119
+ customType: REPEATED_TOOL_CALL_WARN_CUSTOM_TYPE,
120
+ content: `You have repeated the identical tool call with an identical result ${warnThreshold} times. If you are waiting for external state to change, change your approach instead of retrying the same call.`,
121
+ display: false,
122
+ timestamp: Date.now(),
123
+ };
124
+ }
125
+ /**
126
+ * 默认重复工具调用护栏工厂(两级响应)。算法与阈值逐字迁移自宿主内嵌实现的
127
+ * 同名工厂;两条链路由 `entry.ts` 经公共扩展面 `api.registerLoopHook` 注册为
128
+ * 默认链路(transformContext 注入链注册在默认上下文策略链之后,提醒落到最终
129
+ * 请求视图尾部);`disabledPluginIds` 跳过本插件时均不注册,护栏随之关闭。
130
+ */
131
+ export function createRepeatedToolCallStopPolicy(options = {}) {
132
+ const windowSize = options.windowSize ?? REPEATED_TOOL_CALL_WINDOW_SIZE;
133
+ const warnThreshold = options.warnThreshold ?? REPEATED_TOOL_CALL_WARN_THRESHOLD;
134
+ const stopThreshold = options.stopThreshold ?? REPEATED_TOOL_CALL_STOP_THRESHOLD;
135
+ const window = [];
136
+ let lastToolName = "";
137
+ let lastArgsText = "";
138
+ /** 待注入的提醒事实:warn 时置位,transformContext 消费即清除(只注入一次)。 */
139
+ let pendingWarn;
140
+ /** 当前连续重复段已提醒闩:同段不重复提醒;段断开(尾部连续段短于提醒阈值)时复位。 */
141
+ let warnedForRun = false;
142
+ /** 窗口尾部连续相同签名的段长。 */
143
+ const trailingRunLength = () => {
144
+ const last = window[window.length - 1];
145
+ if (last === undefined)
146
+ return 0;
147
+ let run = 0;
148
+ for (let i = window.length - 1; i >= 0 && window[i] === last; i -= 1)
149
+ run += 1;
150
+ return run;
151
+ };
152
+ const shouldStopAfterTurn = (context) => {
153
+ const toolCalls = context.message.content.filter((block) => block.type === "toolCall");
154
+ // 三要素签名需要本轮工具结果:结果缺席时无法判定"同果",该轮不进窗口,
155
+ // 避免把合法轮询误判成重复。
156
+ if (toolCalls.length > 0 && context.toolResults.length > 0) {
157
+ const resultById = new Map(context.toolResults.map((result) => [result.toolCallId, result]));
158
+ for (const call of toolCalls) {
159
+ window.push(repeatedToolCallSignature(call.name, call.arguments, toolResultHash(resultById.get(call.id))));
160
+ lastToolName = call.name;
161
+ lastArgsText = stableSerializeValue(call.arguments);
162
+ }
163
+ }
164
+ if (window.length > windowSize) {
165
+ window.splice(0, window.length - windowSize);
166
+ }
167
+ const run = trailingRunLength();
168
+ if (run >= stopThreshold) {
169
+ // 第二级兜底:硬停,走停机全通道(newMessages + deliverNotice + onStop)。
170
+ // 先于提醒判定:stopThreshold 被显式覆盖到不大于 warnThreshold 时停机优先。
171
+ pendingWarn = undefined;
172
+ const notice = createRepeatedToolCallStopNotice(lastToolName, lastArgsText, stopThreshold);
173
+ context.newMessages.push(notice);
174
+ options.deliverNotice?.(notice);
175
+ // 日志可见通道(D-072 6(a)):与用户可见提示成对发出;onStop 属宿主接线,
176
+ // 策略本身不知道任何具体日志实现。
177
+ options.onStop?.({ toolName: lastToolName, argsSummary: truncateNoticeArgs(lastArgsText), stopThreshold });
178
+ return true;
179
+ }
180
+ if (run < warnThreshold) {
181
+ // 连续段断开:模型改变行为。复位已提醒闩并丢弃未消费的待注入提醒
182
+ // (正常流程中它已在下一轮请求前被消费;此处只兜底运行中断的残留)。
183
+ warnedForRun = false;
184
+ pendingWarn = undefined;
185
+ return false;
186
+ }
187
+ // 第一级:提醒不停机。标记待注入(transformContext 链路消费),经
188
+ // deliverWarn/onWarn 双通道可见;同一连续段只提醒一次。
189
+ if (!warnedForRun) {
190
+ warnedForRun = true;
191
+ pendingWarn = { toolName: lastToolName, argsSummary: truncateNoticeArgs(lastArgsText), warnThreshold };
192
+ options.deliverWarn?.(createRepeatedToolCallWarnNotice(lastToolName, lastArgsText, warnThreshold));
193
+ options.onWarn?.(pendingWarn);
194
+ }
195
+ return false;
196
+ };
197
+ const transformContext = async (messages, _signal) => {
198
+ const info = pendingWarn;
199
+ if (!info)
200
+ return undefined;
201
+ // 消费即清除:提醒只注入一次;重复继续发生不重复注入。非变异地追加到
202
+ // 请求视图尾部(不写 context.messages,故不持久化、不进 newMessages)。
203
+ pendingWarn = undefined;
204
+ return [...messages, createRepeatedToolCallWarnReminder(info.warnThreshold)];
205
+ };
206
+ return { shouldStopAfterTurn, transformContext };
207
+ }
208
+ //# sourceMappingURL=loop-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loop-guard.js","sourceRoot":"","sources":["../src/loop-guard.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\n\n/**\n * 默认重复工具调用护栏(主对话循环护栏,两级响应)。逐字迁移自宿主内嵌\n * `bootstrap-agent-loop.ts` 的护栏切片(D-082 批 3:迁出为\n * `agent-forge.plugin.agent-loop-policy` 插件包,算法、阈值与消息文本保持不变)。\n *\n * 每个完成的回合从 assistant 消息中按序取出工具调用,签名(工具名 + 键序参数\n * + 本轮结果哈希,结果按 toolCallId 对应)追加进滑动窗口并裁剪到 windowSize;\n * 然后按窗口尾部连续相同签名段(含同一回合内多次相同调用)做两级响应:\n *\n * - 第一级(达到 warnThreshold,缺省 3):不停机,改为提醒——内部标记\n * pendingWarn,由 {@link createRepeatedToolCallStopPolicy} 返回的\n * transformContext 链路在下一轮请求前把提醒消息追加到请求视图尾部;同时把\n * 用户可见提醒经 deliverWarn 投递、事实经 onWarn 记日志。同一连续重复段只\n * 提醒一次(warnedForRun 闩);连续段断开(模型改变行为)即复位。\n * - 第二级(达到 stopThreshold,缺省 5):请求停机——先向 stop 上下文的\n * newMessages 追加一条用户可见的停机消息并交给 deliverNotice(禁止静默停\n * 机),再经 onStop 记日志。判据不可能完美,因此不可逆停机不是第一响应。\n *\n * 同参不同果(合法轮询,如等待外部状态就绪)不计重复:结果要素是全量哈希,\n * 任何字节差异(包括 >200 字符长结果仅尾部变化)都产生不同签名;无文本结果\n * 退化为两要素(见 {@link toolResultHash} 局限声明)。无工具调用或结果缺席的\n * 轮次不进窗口。\n */\n\n/** 结构化镜像 `@agent-forge/ai` 的 TextContent(内核类型不跨入插件面)。 */\nexport interface TextContent {\n\ttype: \"text\";\n\ttext: string;\n}\n\n/** 结构化镜像 `@agent-forge/ai` 的 ImageContent(内核类型不跨入插件面)。 */\nexport interface ImageContent {\n\ttype: \"image\";\n\tdata: string;\n\tmimeType: string;\n}\n\n/**\n * 结构化镜像宿主 `core/messages.ts` 的 CustomMessage(D-082 批 3):护栏的三种\n * 通知消息与宿主自定义消息同形,经 `SessionAPI.sendMessage` 投递后由宿主按\n * role:\"custom\" 持久化与渲染。字段与宿主逐字一致,漂移由宿主 e2e(通知持久化\n * 与渲染事件断言)拦截。\n */\nexport interface CustomMessage<T = unknown> {\n\trole: \"custom\";\n\tcustomType: string;\n\tcontent: string | (TextContent | ImageContent)[];\n\tdisplay: boolean;\n\tdetails?: T;\n\ttimestamp: number;\n}\n\n/** 结构化镜像 `@agent-forge/ai` 的 ToolCall(护栏只消费 id/name/arguments)。 */\nexport interface ToolCall {\n\ttype: \"toolCall\";\n\tid: string;\n\tname: string;\n\targuments: Record<string, unknown>;\n}\n\n/** 结构化镜像 `@agent-forge/ai` 的 ToolResultMessage(护栏只消费 content/isError)。 */\nexport interface ToolResultMessage {\n\trole: \"toolResult\";\n\ttoolCallId: string;\n\ttoolName: string;\n\tcontent: (TextContent | ImageContent)[];\n\tisError: boolean;\n\ttimestamp: number;\n}\n\n/**\n * 结构化镜像 `@agent-forge/agent-core` 的 ShouldStopAfterTurnContext(内核类型\n * 不跨入 plugin-sdk,D-075 S2;插件只消费 message/toolResults/newMessages)。\n * 宿主把同一个运行时对象交给内核回调与本插件链路,字段读取语义不变。\n */\nexport interface ShouldStopAfterTurnContext {\n\t/** The assistant message that completed the turn. */\n\tmessage: { content: readonly (ToolCall | { type: string })[] };\n\t/** Tool result messages passed to the preceding `turn_end` event. */\n\ttoolResults: readonly ToolResultMessage[];\n\t/** Current agent context after the turn (consumed by the host, not the guard). */\n\tcontext: unknown;\n\t/** Messages this loop invocation returns if it exits at this point. */\n\tnewMessages: CustomMessage[];\n}\n\n/** 滑动窗口大小:护栏只回看最近 N 个工具调用签名(跨回合累计)。 */\nexport const REPEATED_TOOL_CALL_WINDOW_SIZE = 6;\n/** 提醒阈值:窗口尾部连续相同签名达到该次数时提醒模型(不停机,两级响应第一级)。 */\nexport const REPEATED_TOOL_CALL_WARN_THRESHOLD = 3;\n/** 停机阈值:提醒后仍连续相同达到该次数才请求停机(两级响应第二级兜底)。 */\nexport const REPEATED_TOOL_CALL_STOP_THRESHOLD = 5;\n/** 停机提示消息的 customType(用户可见面,随 agent_end 的 messages 带出)。 */\nexport const REPEATED_TOOL_CALL_STOP_CUSTOM_TYPE = \"loop_guard_stop\";\n/** 提醒消息的 customType(用户可见面经 sendMessage 持久化+渲染;模型面注入复用同一标记)。 */\nexport const REPEATED_TOOL_CALL_WARN_CUSTOM_TYPE = \"loop_guard_warn\";\n/** 提示消息中参数串的最大长度,超出截断(只影响提示文本,不影响检测签名)。 */\nexport const REPEATED_TOOL_CALL_NOTICE_MAX_ARGS_CHARS = 200;\n\n/** {@link createRepeatedToolCallStopPolicy} 的窗口与两级阈值参数(缺省用上方命名常量)。 */\nexport interface RepeatedToolCallStopPolicyOptions {\n\t/** 滑动窗口大小,缺省 {@link REPEATED_TOOL_CALL_WINDOW_SIZE}。 */\n\twindowSize?: number;\n\t/** 提醒阈值(第一级,不停机),缺省 {@link REPEATED_TOOL_CALL_WARN_THRESHOLD}。 */\n\twarnThreshold?: number;\n\t/** 停机阈值(第二级兜底),缺省 {@link REPEATED_TOOL_CALL_STOP_THRESHOLD}。显式设为不大于提醒阈值时停机优先。 */\n\tstopThreshold?: number;\n\t/**\n\t * 提醒时的日志面回调(与 {@link onStop} 同纪律:info 只携带脱敏后的事实——\n\t * 工具名、截断参数摘要、提醒阈值,不含完整工具参数)。只在第一级实际提醒的\n\t * 回合调用一次;缺省不接 = 行为不变。\n\t */\n\tonWarn?: (info: RepeatedToolCallWarnInfo) => void;\n\t/**\n\t * 停机时的日志面回调(D-072 治理纪律 6(a):用户可见 + 日志可见双通道)。\n\t * 只在策略实际请求停机的回合调用一次;缺省不接 = 行为不变。info 只携带\n\t * 脱敏后的事实(工具名、截断参数摘要、阈值),不含完整工具参数。\n\t */\n\tonStop?: (info: RepeatedToolCallStopInfo) => void;\n\t/**\n\t * 提醒通知投递缝:策略把提醒消息交给宿主选定的公共面。默认安装把它接到\n\t * `SessionAPI.sendMessage`(持久化 + message_start 实时渲染);缺席时提醒\n\t * 仍经 transformContext 注入模型请求(模型面不依赖该缝)。\n\t */\n\tdeliverWarn?: (notice: CustomMessage) => void;\n\t/**\n\t * 停机通知投递缝:策略把停机提示消息交给宿主选定的公共面。默认安装把它接到\n\t * `SessionAPI.sendMessage`(运行中排队、run 收尾 flush 落为 custom_message\n\t * 条目——持久化 + message_start 实时渲染);缺席时停机事实仅随 agent_end 的\n\t * newMessages 带出(行为不变)。\n\t */\n\tdeliverNotice?: (notice: CustomMessage) => void;\n}\n\n/** 提醒事实(脱敏):{@link RepeatedToolCallStopPolicyOptions.onWarn} 的载荷。 */\nexport interface RepeatedToolCallWarnInfo {\n\t/** 触发提醒的最后一个工具名。 */\n\ttoolName: string;\n\t/** 截断后的参数稳定序列化摘要(复用 {@link REPEATED_TOOL_CALL_NOTICE_MAX_ARGS_CHARS})。 */\n\targsSummary: string;\n\t/** 达到的连续重复提醒阈值。 */\n\twarnThreshold: number;\n}\n\n/** 停机事实(脱敏):{@link RepeatedToolCallStopPolicyOptions.onStop} 的载荷。 */\nexport interface RepeatedToolCallStopInfo {\n\t/** 触发停机的最后一个工具名。 */\n\ttoolName: string;\n\t/** 截断后的参数稳定序列化摘要(复用 {@link REPEATED_TOOL_CALL_NOTICE_MAX_ARGS_CHARS})。 */\n\targsSummary: string;\n\t/** 达到的连续重复停机阈值。 */\n\tstopThreshold: number;\n}\n\n/**\n * 参数稳定字符串化:对象键递归排序后逐层拼接(数组保持顺序),循环引用输出\n * 固定占位。只用于签名比较与提示文本,不是通用 JSON 序列化器。\n */\nexport function stableSerializeValue(value: unknown, seen: WeakSet<object> = new WeakSet()): string {\n\tif (value === null) return \"null\";\n\tswitch (typeof value) {\n\t\tcase \"string\":\n\t\t\treturn JSON.stringify(value);\n\t\tcase \"number\":\n\t\t\treturn String(value);\n\t\tcase \"boolean\":\n\t\t\treturn String(value);\n\t\tcase \"undefined\":\n\t\t\treturn \"undefined\";\n\t\tcase \"bigint\":\n\t\t\treturn `bigint:${String(value)}`;\n\t\tcase \"function\":\n\t\t\treturn \"function\";\n\t\tcase \"symbol\":\n\t\t\treturn \"symbol\";\n\t\tcase \"object\":\n\t\t\tbreak;\n\t\tdefault:\n\t\t\treturn \"unknown\";\n\t}\n\tif (seen.has(value)) return '\"[cycle]\"';\n\tseen.add(value);\n\tconst serialized = Array.isArray(value)\n\t\t? `[${value.map((item) => stableSerializeValue(item, seen)).join(\",\")}]`\n\t\t: (() => {\n\t\t\t\tconst record = value as Record<string, unknown>;\n\t\t\t\tconst keys = Object.keys(record).sort();\n\t\t\t\treturn `{${keys.map((key) => `${JSON.stringify(key)}:${stableSerializeValue(record[key], seen)}`).join(\",\")}}`;\n\t\t\t})();\n\tseen.delete(value);\n\treturn serialized;\n}\n\n/**\n * 工具结果哈希(签名第三要素):拼接结果的 text 内容后做全量 SHA-256(十六进制),\n * isError 一并纳入(同参同果且同错误状态才计重复)。任何字节差异都产生不同哈希,\n * 因此长结果(前缀稳定、尾部变化)的合法轮询不会被误判成\"同果\"——签名不受任何\n * 截断影响。无文本结果(纯图像等)与结果缺席都返回空串占位:签名退化为两要素\n * (工具名 + 参数),这是已知局限——无文本的同参结果彼此不可区分。\n * 调用与结果按 toolCallId 对应,正常流程中每个调用都有结果;无结果返回空串。\n */\nexport function toolResultHash(result: ToolResultMessage | undefined): string {\n\tif (!result) return \"\";\n\tconst text = result.content\n\t\t.filter((block): block is TextContent => block.type === \"text\")\n\t\t.map((block) => block.text)\n\t\t.join(\"\\n\");\n\tif (text === \"\") return \"\";\n\treturn `${result.isError ? \"error\" : \"ok\"}:${createHash(\"sha256\").update(text).digest(\"hex\")}`;\n}\n\n/** 工具调用签名:工具名 + 参数稳定字符串化 + 结果哈希。 */\nexport function repeatedToolCallSignature(toolName: string, args: unknown, resultHash: string): string {\n\treturn `${JSON.stringify(toolName)}:${stableSerializeValue(args)}:${resultHash}`;\n}\n\n/** 参数摘要截断:超出 {@link REPEATED_TOOL_CALL_NOTICE_MAX_ARGS_CHARS} 打截断标记(用户可见消息与日志通道共用)。 */\nfunction truncateNoticeArgs(argsText: string): string {\n\treturn argsText.length > REPEATED_TOOL_CALL_NOTICE_MAX_ARGS_CHARS\n\t\t? `${argsText.slice(0, REPEATED_TOOL_CALL_NOTICE_MAX_ARGS_CHARS)}...(truncated)`\n\t\t: argsText;\n}\n\n/** 停机提示消息:随停机回合的 newMessages 带出(agent_end 可见),并经 deliverNotice 投递到宿主公共面。 */\nexport function createRepeatedToolCallStopNotice(\n\ttoolName: string,\n\targsText: string,\n\tstopThreshold: number,\n): CustomMessage {\n\tconst truncated = truncateNoticeArgs(argsText);\n\treturn {\n\t\trole: \"custom\",\n\t\tcustomType: REPEATED_TOOL_CALL_STOP_CUSTOM_TYPE,\n\t\tcontent: `Loop guard: stopped after the same tool call (${toolName}) was repeated ${stopThreshold} times in a row. Args: ${truncated}`,\n\t\tdisplay: true,\n\t\ttimestamp: Date.now(),\n\t};\n}\n\n/**\n * 提醒通知消息(用户可见面,loop_guard_warn):经 deliverWarn 投递(默认安装接\n * `SessionAPI.sendMessage`,持久化 + 实时渲染),与停机通知(loop_guard_stop)\n * 的 customType 区分。提醒不进入 newMessages——模型面提醒由 transformContext\n * 注入承担,用户面提醒由本消息的持久化+渲染承担。\n */\nexport function createRepeatedToolCallWarnNotice(\n\ttoolName: string,\n\targsText: string,\n\twarnThreshold: number,\n): CustomMessage {\n\tconst truncated = truncateNoticeArgs(argsText);\n\treturn {\n\t\trole: \"custom\",\n\t\tcustomType: REPEATED_TOOL_CALL_WARN_CUSTOM_TYPE,\n\t\tcontent: `Loop guard: the same tool call (${toolName}) was repeated ${warnThreshold} times in a row with the same result. Args: ${truncated}`,\n\t\tdisplay: true,\n\t\ttimestamp: Date.now(),\n\t};\n}\n\n/**\n * 模型面提醒消息(transformContext 注入用):display:false 的 custom 消息,\n * 经宿主 convertToLlm 转为 user 文本进入下一次模型请求。只存在于该次请求视图\n * (不写 context.messages、不进 newMessages,故不持久化);customType 复用\n * loop_guard_warn 标记来源。\n */\nexport function createRepeatedToolCallWarnReminder(warnThreshold: number): CustomMessage {\n\treturn {\n\t\trole: \"custom\",\n\t\tcustomType: REPEATED_TOOL_CALL_WARN_CUSTOM_TYPE,\n\t\tcontent: `You have repeated the identical tool call with an identical result ${warnThreshold} times. If you are waiting for external state to change, change your approach instead of retrying the same call.`,\n\t\tdisplay: false,\n\t\ttimestamp: Date.now(),\n\t};\n}\n\n/** {@link createRepeatedToolCallStopPolicy} 返回的两条链路(共享同一滑动窗口与两级状态)。 */\nexport interface RepeatedToolCallGuard {\n\t/**\n\t * \"shouldStopAfterTurn\" 链路:两级判定的决策面(返回 true 即请求停机)。\n\t */\n\tshouldStopAfterTurn: (context: ShouldStopAfterTurnContext) => boolean;\n\t/**\n\t * \"transformContext\" 链路:提醒注入面。有待注入提醒时返回追加了提醒消息的\n\t * 新数组(进入下一次模型请求视图),否则返回 undefined(保留输入)。\n\t */\n\ttransformContext: (messages: readonly unknown[], signal?: AbortSignal) => Promise<readonly unknown[] | undefined>;\n}\n\n/**\n * 默认重复工具调用护栏工厂(两级响应)。算法与阈值逐字迁移自宿主内嵌实现的\n * 同名工厂;两条链路由 `entry.ts` 经公共扩展面 `api.registerLoopHook` 注册为\n * 默认链路(transformContext 注入链注册在默认上下文策略链之后,提醒落到最终\n * 请求视图尾部);`disabledPluginIds` 跳过本插件时均不注册,护栏随之关闭。\n */\nexport function createRepeatedToolCallStopPolicy(\n\toptions: RepeatedToolCallStopPolicyOptions = {},\n): RepeatedToolCallGuard {\n\tconst windowSize = options.windowSize ?? REPEATED_TOOL_CALL_WINDOW_SIZE;\n\tconst warnThreshold = options.warnThreshold ?? REPEATED_TOOL_CALL_WARN_THRESHOLD;\n\tconst stopThreshold = options.stopThreshold ?? REPEATED_TOOL_CALL_STOP_THRESHOLD;\n\tconst window: string[] = [];\n\tlet lastToolName = \"\";\n\tlet lastArgsText = \"\";\n\t/** 待注入的提醒事实:warn 时置位,transformContext 消费即清除(只注入一次)。 */\n\tlet pendingWarn: RepeatedToolCallWarnInfo | undefined;\n\t/** 当前连续重复段已提醒闩:同段不重复提醒;段断开(尾部连续段短于提醒阈值)时复位。 */\n\tlet warnedForRun = false;\n\n\t/** 窗口尾部连续相同签名的段长。 */\n\tconst trailingRunLength = (): number => {\n\t\tconst last = window[window.length - 1];\n\t\tif (last === undefined) return 0;\n\t\tlet run = 0;\n\t\tfor (let i = window.length - 1; i >= 0 && window[i] === last; i -= 1) run += 1;\n\t\treturn run;\n\t};\n\n\tconst shouldStopAfterTurn = (context: ShouldStopAfterTurnContext): boolean => {\n\t\tconst toolCalls = context.message.content.filter((block): block is ToolCall => block.type === \"toolCall\");\n\t\t// 三要素签名需要本轮工具结果:结果缺席时无法判定\"同果\",该轮不进窗口,\n\t\t// 避免把合法轮询误判成重复。\n\t\tif (toolCalls.length > 0 && context.toolResults.length > 0) {\n\t\t\tconst resultById = new Map(context.toolResults.map((result) => [result.toolCallId, result]));\n\t\t\tfor (const call of toolCalls) {\n\t\t\t\twindow.push(repeatedToolCallSignature(call.name, call.arguments, toolResultHash(resultById.get(call.id))));\n\t\t\t\tlastToolName = call.name;\n\t\t\t\tlastArgsText = stableSerializeValue(call.arguments);\n\t\t\t}\n\t\t}\n\t\tif (window.length > windowSize) {\n\t\t\twindow.splice(0, window.length - windowSize);\n\t\t}\n\t\tconst run = trailingRunLength();\n\t\tif (run >= stopThreshold) {\n\t\t\t// 第二级兜底:硬停,走停机全通道(newMessages + deliverNotice + onStop)。\n\t\t\t// 先于提醒判定:stopThreshold 被显式覆盖到不大于 warnThreshold 时停机优先。\n\t\t\tpendingWarn = undefined;\n\t\t\tconst notice = createRepeatedToolCallStopNotice(lastToolName, lastArgsText, stopThreshold);\n\t\t\tcontext.newMessages.push(notice);\n\t\t\toptions.deliverNotice?.(notice);\n\t\t\t// 日志可见通道(D-072 6(a)):与用户可见提示成对发出;onStop 属宿主接线,\n\t\t\t// 策略本身不知道任何具体日志实现。\n\t\t\toptions.onStop?.({ toolName: lastToolName, argsSummary: truncateNoticeArgs(lastArgsText), stopThreshold });\n\t\t\treturn true;\n\t\t}\n\t\tif (run < warnThreshold) {\n\t\t\t// 连续段断开:模型改变行为。复位已提醒闩并丢弃未消费的待注入提醒\n\t\t\t// (正常流程中它已在下一轮请求前被消费;此处只兜底运行中断的残留)。\n\t\t\twarnedForRun = false;\n\t\t\tpendingWarn = undefined;\n\t\t\treturn false;\n\t\t}\n\t\t// 第一级:提醒不停机。标记待注入(transformContext 链路消费),经\n\t\t// deliverWarn/onWarn 双通道可见;同一连续段只提醒一次。\n\t\tif (!warnedForRun) {\n\t\t\twarnedForRun = true;\n\t\t\tpendingWarn = { toolName: lastToolName, argsSummary: truncateNoticeArgs(lastArgsText), warnThreshold };\n\t\t\toptions.deliverWarn?.(createRepeatedToolCallWarnNotice(lastToolName, lastArgsText, warnThreshold));\n\t\t\toptions.onWarn?.(pendingWarn);\n\t\t}\n\t\treturn false;\n\t};\n\n\tconst transformContext = async (\n\t\tmessages: readonly unknown[],\n\t\t_signal?: AbortSignal,\n\t): Promise<readonly unknown[] | undefined> => {\n\t\tconst info = pendingWarn;\n\t\tif (!info) return undefined;\n\t\t// 消费即清除:提醒只注入一次;重复继续发生不重复注入。非变异地追加到\n\t\t// 请求视图尾部(不写 context.messages,故不持久化、不进 newMessages)。\n\t\tpendingWarn = undefined;\n\t\treturn [...messages, createRepeatedToolCallWarnReminder(info.warnThreshold)];\n\t};\n\n\treturn { shouldStopAfterTurn, transformContext };\n}\n"]}
@@ -0,0 +1,17 @@
1
+ import type { AgentRunLoopStrategyV1 } from "@agent-forge/plugin-sdk";
2
+ /**
3
+ * 默认 run-loop strategy 名。逐字等于宿主保留的同名常量
4
+ * (`packages/agent-forge/src/core/extensions/bootstrap-agent-loop.ts`
5
+ * `DEFAULT_RUN_LOOP_STRATEGY_NAME`,D-082 批 3 拆分后两处同名同值:宿主用它做
6
+ * 会话配置缺省名,本插件用它注册缺省策略;宿主侧测试与本包测试各自钉值)。
7
+ */
8
+ export declare const DEFAULT_RUN_LOOP_STRATEGY_NAME = "default";
9
+ /**
10
+ * 默认延续策略(D-082 批 3 迁出,逐字等价于宿主内嵌装配的直通实现):对宿主
11
+ * 提出的每个 post-run 延续候选照单放行。宿主在自己提出的候选之外钳制策略结果
12
+ * (`continueLoop: true` 而宿主未提出延续时被钳为停机),因此直通即逐字节复现
13
+ * 内嵌时代宿主自判的延续决策。第三方经 `api.registerRunLoopStrategy` 注册同名
14
+ * 或他名策略并通过会话 `runLoopStrategyName` 选择即可替换。
15
+ */
16
+ export declare const defaultRunLoopStrategy: AgentRunLoopStrategyV1;
17
+ //# sourceMappingURL=run-loop-strategy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-loop-strategy.d.ts","sourceRoot":"","sources":["../src/run-loop-strategy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAEtE;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,YAAY,CAAC;AAExD;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,EAAE,sBAEpC,CAAC","sourcesContent":["import type { AgentRunLoopStrategyV1 } from \"@agent-forge/plugin-sdk\";\n\n/**\n * 默认 run-loop strategy 名。逐字等于宿主保留的同名常量\n * (`packages/agent-forge/src/core/extensions/bootstrap-agent-loop.ts`\n * `DEFAULT_RUN_LOOP_STRATEGY_NAME`,D-082 批 3 拆分后两处同名同值:宿主用它做\n * 会话配置缺省名,本插件用它注册缺省策略;宿主侧测试与本包测试各自钉值)。\n */\nexport const DEFAULT_RUN_LOOP_STRATEGY_NAME = \"default\";\n\n/**\n * 默认延续策略(D-082 批 3 迁出,逐字等价于宿主内嵌装配的直通实现):对宿主\n * 提出的每个 post-run 延续候选照单放行。宿主在自己提出的候选之外钳制策略结果\n * (`continueLoop: true` 而宿主未提出延续时被钳为停机),因此直通即逐字节复现\n * 内嵌时代宿主自判的延续决策。第三方经 `api.registerRunLoopStrategy` 注册同名\n * 或他名策略并通过会话 `runLoopStrategyName` 选择即可替换。\n */\nexport const defaultRunLoopStrategy: AgentRunLoopStrategyV1 = {\n\tdecideNextCycle: (request) => ({ continueLoop: request.hostWillContinue }),\n};\n"]}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * 默认 run-loop strategy 名。逐字等于宿主保留的同名常量
3
+ * (`packages/agent-forge/src/core/extensions/bootstrap-agent-loop.ts`
4
+ * `DEFAULT_RUN_LOOP_STRATEGY_NAME`,D-082 批 3 拆分后两处同名同值:宿主用它做
5
+ * 会话配置缺省名,本插件用它注册缺省策略;宿主侧测试与本包测试各自钉值)。
6
+ */
7
+ export const DEFAULT_RUN_LOOP_STRATEGY_NAME = "default";
8
+ /**
9
+ * 默认延续策略(D-082 批 3 迁出,逐字等价于宿主内嵌装配的直通实现):对宿主
10
+ * 提出的每个 post-run 延续候选照单放行。宿主在自己提出的候选之外钳制策略结果
11
+ * (`continueLoop: true` 而宿主未提出延续时被钳为停机),因此直通即逐字节复现
12
+ * 内嵌时代宿主自判的延续决策。第三方经 `api.registerRunLoopStrategy` 注册同名
13
+ * 或他名策略并通过会话 `runLoopStrategyName` 选择即可替换。
14
+ */
15
+ export const defaultRunLoopStrategy = {
16
+ decideNextCycle: (request) => ({ continueLoop: request.hostWillContinue }),
17
+ };
18
+ //# sourceMappingURL=run-loop-strategy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-loop-strategy.js","sourceRoot":"","sources":["../src/run-loop-strategy.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG,SAAS,CAAC;AAExD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAA2B;IAC7D,eAAe,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,YAAY,EAAE,OAAO,CAAC,gBAAgB,EAAE,CAAC;CAC1E,CAAC","sourcesContent":["import type { AgentRunLoopStrategyV1 } from \"@agent-forge/plugin-sdk\";\n\n/**\n * 默认 run-loop strategy 名。逐字等于宿主保留的同名常量\n * (`packages/agent-forge/src/core/extensions/bootstrap-agent-loop.ts`\n * `DEFAULT_RUN_LOOP_STRATEGY_NAME`,D-082 批 3 拆分后两处同名同值:宿主用它做\n * 会话配置缺省名,本插件用它注册缺省策略;宿主侧测试与本包测试各自钉值)。\n */\nexport const DEFAULT_RUN_LOOP_STRATEGY_NAME = \"default\";\n\n/**\n * 默认延续策略(D-082 批 3 迁出,逐字等价于宿主内嵌装配的直通实现):对宿主\n * 提出的每个 post-run 延续候选照单放行。宿主在自己提出的候选之外钳制策略结果\n * (`continueLoop: true` 而宿主未提出延续时被钳为停机),因此直通即逐字节复现\n * 内嵌时代宿主自判的延续决策。第三方经 `api.registerRunLoopStrategy` 注册同名\n * 或他名策略并通过会话 `runLoopStrategyName` 选择即可替换。\n */\nexport const defaultRunLoopStrategy: AgentRunLoopStrategyV1 = {\n\tdecideNextCycle: (request) => ({ continueLoop: request.hostWillContinue }),\n};\n"]}
@@ -0,0 +1,7 @@
1
+ {
2
+ "id": "agent-forge.plugin.agent-loop-policy",
3
+ "version": "0.88.1",
4
+ "apiVersion": "1-draft",
5
+ "entry": "./dist/entry.js",
6
+ "hostVersion": ">=0.88.0"
7
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "packageFormatVersion": 1,
3
+ "id": "agent-forge.plugin.coding-tools",
4
+ "pluginVersion": "0.88.1",
5
+ "apiVersion": "1",
6
+ "minHostVersion": "0.88.0",
7
+ "platform": {
8
+ "os": [
9
+ "windows",
10
+ "macos",
11
+ "linux"
12
+ ],
13
+ "runtime": [
14
+ {
15
+ "name": "node"
16
+ }
17
+ ]
18
+ }
19
+ }
@@ -1,8 +1,9 @@
1
- import type { AgentTool } from "@agent-forge/agent-core";
1
+ import { type BashOperations, type BashSpawnContext, type BashSpawnHook, createLocalBashOperations, createLocalShellOperations } from "@agent-forge/host/bash-operations";
2
+ import type { ToolDefinition } from "@agent-forge/host/tool-definition";
3
+ import { type TruncationResult } from "@agent-forge/host/truncate";
2
4
  import { type Static, Type } from "typebox";
3
- import { type ShellConfig } from "../../utils/shell.ts";
4
- import type { ToolDefinition } from "./tool-definition.ts";
5
- import { type TruncationResult } from "./truncate.ts";
5
+ import { type AgentTool } from "./types.ts";
6
+ export { type BashOperations, type BashSpawnContext, type BashSpawnHook, createLocalBashOperations, createLocalShellOperations, };
6
7
  declare const bashSchema: Type.TObject<{
7
8
  command: Type.TString;
8
9
  timeout: Type.TOptional<Type.TNumber>;
@@ -16,45 +17,6 @@ export interface BashToolDetails {
16
17
  truncation?: TruncationResult;
17
18
  fullOutputPath?: string;
18
19
  }
19
- /**
20
- * Pluggable operations for the bash tool.
21
- * Override these to delegate command execution to remote systems (for example SSH).
22
- */
23
- export interface BashOperations {
24
- /**
25
- * Execute a command and stream output.
26
- * @param command The command to execute
27
- * @param cwd Working directory
28
- * @param options Execution options
29
- * @returns Promise resolving to the exit code. Report signal terminations as 128 + signal number;
30
- * a null exit code is treated as a failed command.
31
- */
32
- exec: (command: string, cwd: string, options: {
33
- onData: (data: Buffer) => void;
34
- signal?: AbortSignal;
35
- timeout?: number;
36
- env?: NodeJS.ProcessEnv;
37
- }) => Promise<{
38
- exitCode: number | null;
39
- }>;
40
- }
41
- /** Shared process execution used by the built-in shell tools. */
42
- export declare function createLocalShellOperations(shellName: string, resolveShellConfig: () => ShellConfig): BashOperations;
43
- /**
44
- * Create bash operations using Agent Forge's built-in local shell execution backend.
45
- *
46
- * This is useful for extensions that intercept user_bash and still want Agent Forge's
47
- * standard local shell behavior while wrapping or rewriting commands.
48
- */
49
- export declare function createLocalBashOperations(options?: {
50
- shellPath?: string;
51
- }): BashOperations;
52
- export interface BashSpawnContext {
53
- command: string;
54
- cwd: string;
55
- env: NodeJS.ProcessEnv;
56
- }
57
- export type BashSpawnHook = (context: BashSpawnContext) => BashSpawnContext;
58
20
  export interface BashToolOptions {
59
21
  /** Custom operations for command execution. Default: local shell */
60
22
  operations?: BashOperations;
@@ -84,5 +46,4 @@ export interface ShellToolConfig {
84
46
  export declare function createShellToolDefinition(cwd: string, config: ShellToolConfig, options?: BashToolOptions): ToolDefinition<typeof bashSchema, BashToolDetails | undefined, BashRenderState>;
85
47
  export declare function createBashToolDefinition(cwd: string, options?: BashToolOptions): ToolDefinition<typeof bashSchema, BashToolDetails | undefined, BashRenderState>;
86
48
  export declare function createBashTool(cwd: string, options?: BashToolOptions): AgentTool<typeof bashSchema>;
87
- export {};
88
49
  //# sourceMappingURL=bash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../src/bash.ts"],"names":[],"mappings":"AAGA,OAAO,EACN,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,yBAAyB,EACzB,0BAA0B,EAC1B,MAAM,mCAAmC,CAAC;AAO3C,OAAO,KAAK,EAAE,cAAc,EAA+C,MAAM,mCAAmC,CAAC;AACrH,OAAO,EAAoD,KAAK,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAGrH,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,KAAK,SAAS,EAAsB,MAAM,YAAY,CAAC;AAIhE,OAAO,EACN,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,yBAAyB,EACzB,0BAA0B,GAC1B,CAAC;AAEF,QAAA,MAAM,UAAU;;;EAGd,CAAC;AAEH,eAAO,MAAM,gCAAgC;;;CAGnC,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AAEtD,MAAM,WAAW,eAAe;IAC/B,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AA8BD,MAAM,WAAW,eAAe;IAC/B,oEAAoE;IACpE,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,mFAAmF;IACnF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wGAAwG;IACxG,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,2DAA2D;IAC3D,SAAS,CAAC,EAAE,aAAa,CAAC;CAC1B;AAKD,MAAM,MAAM,eAAe,GAAG;IAC7B,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC,OAAO,GAAG,SAAS,CAAC;CACrC,CAAC;AAoHF,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,cAAc,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,yBAAyB,CACxC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,eAAe,EACvB,OAAO,CAAC,EAAE,eAAe,GACvB,cAAc,CAAC,OAAO,UAAU,EAAE,eAAe,GAAG,SAAS,EAAE,eAAe,CAAC,CA0LjF;AAYD,wBAAgB,wBAAwB,CACvC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,eAAe,GACvB,cAAc,CAAC,OAAO,UAAU,EAAE,eAAe,GAAG,SAAS,EAAE,eAAe,CAAC,CAEjF;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CAQnG","sourcesContent":["// Migrated from packages/agent-forge/src/core/tools/bash.ts (batch 1, copy-then-delete);\n// host modules are imported through supply keys (@agent-forge/host/*).\n\nimport {\n\ttype BashOperations,\n\ttype BashSpawnContext,\n\ttype BashSpawnHook,\n\tcreateLocalBashOperations,\n\tcreateLocalShellOperations,\n} from \"@agent-forge/host/bash-operations\";\nimport { t } from \"@agent-forge/host/i18n\";\nimport { keyHint } from \"@agent-forge/host/keybinding-hints\";\nimport { OutputAccumulator } from \"@agent-forge/host/output-accumulator\";\nimport { getTextOutput, invalidArgText, str } from \"@agent-forge/host/render-utils\";\nimport { getShellEnv } from \"@agent-forge/host/shell\";\nimport { theme } from \"@agent-forge/host/theme\";\nimport type { ToolDefinition, ToolExecuteContext, ToolRenderResultOptions } from \"@agent-forge/host/tool-definition\";\nimport { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, type TruncationResult } from \"@agent-forge/host/truncate\";\nimport { truncateToVisualLines } from \"@agent-forge/host/visual-truncate\";\nimport { Container, Text, truncateToWidth } from \"@agent-forge/tui\";\nimport { type Static, Type } from \"typebox\";\nimport { type AgentTool, wrapToolDefinition } from \"./types.ts\";\n\n// Operations layer shared with plugins via the \"@agent-forge/host/bash-operations\"\n// virtual module; re-exported so the existing host import surface keeps resolving.\nexport {\n\ttype BashOperations,\n\ttype BashSpawnContext,\n\ttype BashSpawnHook,\n\tcreateLocalBashOperations,\n\tcreateLocalShellOperations,\n};\n\nconst bashSchema = Type.Object({\n\tcommand: Type.String({ description: \"Shell command to execute\" }),\n\ttimeout: Type.Optional(Type.Number({ description: \"Timeout in seconds (optional, no default timeout)\" })),\n});\n\nexport const bashToolSystemPromptContribution = {\n\tsnippet: \"Execute bash commands (ls, grep, find, etc.)\",\n\tguidelines: [\"You can inspect AGENT_FORGE_* environment variables for current model and session details.\"],\n} as const;\n\nexport type BashToolInput = Static<typeof bashSchema>;\n\nexport interface BashToolDetails {\n\ttruncation?: TruncationResult;\n\tfullOutputPath?: string;\n}\n\nfunction resolveSpawnContext(\n\tcommand: string,\n\tcwd: string,\n\tspawnHook: BashSpawnHook | undefined,\n\texposeSessionEnvironment: boolean,\n\tctx: ToolExecuteContext | undefined,\n): BashSpawnContext {\n\tconst env = { ...getShellEnv() };\n\tdelete env.AGENT_FORGE_SESSION_ID;\n\tdelete env.AGENT_FORGE_SESSION_FILE;\n\tdelete env.AGENT_FORGE_PROVIDER;\n\tdelete env.AGENT_FORGE_MODEL;\n\tdelete env.AGENT_FORGE_REASONING_LEVEL;\n\tif (exposeSessionEnvironment && ctx) {\n\t\tconst model = ctx.model;\n\t\tenv.AGENT_FORGE_SESSION_ID = ctx.sessionManager.getSessionId();\n\t\tconst sessionFile = ctx.sessionManager.getSessionFile();\n\t\tif (sessionFile) env.AGENT_FORGE_SESSION_FILE = sessionFile;\n\t\tif (model) {\n\t\t\tenv.AGENT_FORGE_PROVIDER = model.provider;\n\t\t\tenv.AGENT_FORGE_MODEL = model.id;\n\t\t}\n\t\tif (ctx.thinkingLevel) env.AGENT_FORGE_REASONING_LEVEL = ctx.thinkingLevel;\n\t}\n\tconst baseContext: BashSpawnContext = { command, cwd, env };\n\treturn spawnHook ? spawnHook(baseContext) : baseContext;\n}\n\nexport interface BashToolOptions {\n\t/** Custom operations for command execution. Default: local shell */\n\toperations?: BashOperations;\n\t/** Command prefix prepended to every command (for example shell setup commands) */\n\tcommandPrefix?: string;\n\t/** Optional explicit shell path from settings */\n\tshellPath?: string;\n\t/** Expose current Agent Forge session metadata as AGENT_FORGE_* environment variables. Default: true */\n\texposeSessionEnvironment?: boolean;\n\t/** Hook to adjust command, cwd, or env before execution */\n\tspawnHook?: BashSpawnHook;\n}\n\nconst BASH_PREVIEW_LINES = 5;\nconst BASH_UPDATE_THROTTLE_MS = 100;\n\nexport type BashRenderState = {\n\tstartedAt: number | undefined;\n\tendedAt: number | undefined;\n\tinterval: NodeJS.Timeout | undefined;\n};\n\ntype BashResultRenderState = {\n\tcachedWidth: number | undefined;\n\tcachedLines: string[] | undefined;\n};\n\nclass BashResultRenderComponent extends Container {\n\tstate: BashResultRenderState = {\n\t\tcachedWidth: undefined,\n\t\tcachedLines: undefined,\n\t};\n}\n\nfunction formatDuration(ms: number): string {\n\tconst seconds = ms / 1000;\n\tif (seconds < 60) return `${seconds.toFixed(1)}s`;\n\n\tconst totalSeconds = Math.floor(seconds);\n\tconst minutes = Math.floor(totalSeconds / 60);\n\tconst remainder = totalSeconds % 60;\n\tif (minutes < 60) return `${minutes}m ${remainder}s`;\n\n\treturn `${Math.floor(minutes / 60)}h ${minutes % 60}m ${remainder}s`;\n}\n\nfunction formatShellCall(args: { command?: string; timeout?: number } | undefined, prompt: string): string {\n\tconst command = str(args?.command);\n\tconst timeout = args?.timeout as number | undefined;\n\tconst timeoutSuffix = timeout ? theme.fg(\"muted\", ` (timeout ${timeout}s)`) : \"\";\n\tconst commandDisplay = command === null ? invalidArgText(theme) : command ? command : theme.fg(\"toolOutput\", \"...\");\n\treturn theme.fg(\"toolTitle\", theme.bold(`${prompt} ${commandDisplay}`)) + timeoutSuffix;\n}\n\nfunction rebuildBashResultRenderComponent(\n\tcomponent: BashResultRenderComponent,\n\tresult: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tdetails?: BashToolDetails;\n\t},\n\toptions: ToolRenderResultOptions,\n\tshowImages: boolean,\n\tstartedAt: number | undefined,\n\tendedAt: number | undefined,\n): void {\n\tconst state = component.state;\n\tcomponent.clear();\n\n\tlet output = getTextOutput(result, showImages).trim();\n\tconst truncation = result.details?.truncation;\n\tconst fullOutputPath = result.details?.fullOutputPath;\n\tif (!options.isPartial && truncation?.truncated && fullOutputPath && output.endsWith(\"]\")) {\n\t\tconst footerStart = output.lastIndexOf(\"\\n\\n[\");\n\t\tif (footerStart !== -1 && output.slice(footerStart).includes(fullOutputPath)) {\n\t\t\toutput = output.slice(0, footerStart).trimEnd();\n\t\t}\n\t}\n\n\tif (output) {\n\t\tconst styledOutput = output\n\t\t\t.split(\"\\n\")\n\t\t\t.map((line) => theme.fg(\"toolOutput\", line))\n\t\t\t.join(\"\\n\");\n\n\t\tif (options.expanded) {\n\t\t\tcomponent.addChild(new Text(`\\n${styledOutput}`, 0, 0));\n\t\t} else {\n\t\t\tcomponent.addChild({\n\t\t\t\trender: (width: number) => {\n\t\t\t\t\t// Cache the complete output: this renders on every frame for every bash result in the transcript.\n\t\t\t\t\tif (state.cachedLines === undefined || state.cachedWidth !== width) {\n\t\t\t\t\t\tconst preview = truncateToVisualLines(styledOutput, BASH_PREVIEW_LINES, width);\n\t\t\t\t\t\tconst hintLines: string[] = [];\n\t\t\t\t\t\tif (preview.skippedCount > 0) {\n\t\t\t\t\t\t\tconst hint =\n\t\t\t\t\t\t\t\ttheme.fg(\"muted\", `... (${preview.skippedCount} earlier lines,`) +\n\t\t\t\t\t\t\t\t` ${keyHint(\"app.tools.expand\", \"to expand\")}${theme.fg(\"muted\", \")\")}`;\n\t\t\t\t\t\t\thintLines.push(truncateToWidth(hint, width, \"...\"));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstate.cachedLines = [\"\", ...hintLines, ...preview.visualLines];\n\t\t\t\t\t\tstate.cachedWidth = width;\n\t\t\t\t\t}\n\t\t\t\t\treturn state.cachedLines;\n\t\t\t\t},\n\t\t\t\tinvalidate: () => {\n\t\t\t\t\tstate.cachedWidth = undefined;\n\t\t\t\t\tstate.cachedLines = undefined;\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\t}\n\n\tif (truncation?.truncated || fullOutputPath) {\n\t\tconst warnings: string[] = [];\n\t\tif (fullOutputPath) {\n\t\t\twarnings.push(`Full output: ${fullOutputPath}`);\n\t\t}\n\t\tif (truncation?.truncated) {\n\t\t\tif (truncation.truncatedBy === \"lines\") {\n\t\t\t\twarnings.push(`Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines`);\n\t\t\t} else {\n\t\t\t\twarnings.push(\n\t\t\t\t\t`Truncated: ${truncation.outputLines} lines shown (${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit)`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tcomponent.addChild(new Text(`\\n${theme.fg(\"warning\", `[${warnings.join(\". \")}]`)}`, 0, 0));\n\t}\n\n\tif (startedAt !== undefined) {\n\t\tconst label = options.isPartial ? \"Elapsed\" : \"Took\";\n\t\tconst endTime = endedAt ?? Date.now();\n\t\tcomponent.addChild(new Text(`\\n${theme.fg(\"muted\", `${label} ${formatDuration(endTime - startedAt)}`)}`, 0, 0));\n\t}\n}\n\nexport interface ShellToolConfig {\n\tname: string;\n\tlabel: string;\n\tshellName: string;\n\tprompt: string;\n\tpromptSnippet: string;\n\tpromptGuidelines?: readonly string[];\n\ttempFilePrefix: string;\n}\n\nexport function createShellToolDefinition(\n\tcwd: string,\n\tconfig: ShellToolConfig,\n\toptions?: BashToolOptions,\n): ToolDefinition<typeof bashSchema, BashToolDetails | undefined, BashRenderState> {\n\tconst ops = options?.operations ?? createLocalBashOperations({ shellPath: options?.shellPath });\n\tconst commandPrefix = options?.commandPrefix;\n\tconst exposeSessionEnvironment = options?.exposeSessionEnvironment ?? true;\n\tconst spawnHook = options?.spawnHook;\n\treturn {\n\t\tname: config.name,\n\t\tlabel: config.label,\n\t\tdescription: `Execute a ${config.shellName} command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`,\n\t\tpromptSnippet: config.promptSnippet,\n\t\tpromptGuidelines: exposeSessionEnvironment && config.promptGuidelines ? [...config.promptGuidelines] : undefined,\n\t\tparameters: bashSchema,\n\t\tconstrainedSampling: { type: \"json_schema\", strict: \"prefer\" },\n\t\tasync execute(\n\t\t\t_toolCallId,\n\t\t\t{ command, timeout }: { command: string; timeout?: number },\n\t\t\tsignal?: AbortSignal,\n\t\t\tonUpdate?,\n\t\t\tctx?: ToolExecuteContext,\n\t\t) {\n\t\t\tconst resolvedCommand = commandPrefix ? `${commandPrefix}\\n${command}` : command;\n\t\t\tconst spawnContext = resolveSpawnContext(\n\t\t\t\tresolvedCommand,\n\t\t\t\tctx?.cwd || cwd,\n\t\t\t\tspawnHook,\n\t\t\t\texposeSessionEnvironment,\n\t\t\t\tctx,\n\t\t\t);\n\t\t\tconst output = new OutputAccumulator({ tempFilePrefix: config.tempFilePrefix });\n\t\t\tlet acceptingOutput = true;\n\t\t\tlet updateTimer: NodeJS.Timeout | undefined;\n\t\t\tlet updateDirty = false;\n\t\t\tlet lastUpdateAt = 0;\n\n\t\t\tconst emitOutputUpdate = () => {\n\t\t\t\tif (!onUpdate || !updateDirty) return;\n\t\t\t\tupdateDirty = false;\n\t\t\t\tlastUpdateAt = Date.now();\n\t\t\t\tconst snapshot = output.snapshot({ persistIfTruncated: true });\n\t\t\t\tonUpdate({\n\t\t\t\t\tcontent: [{ type: \"text\", text: snapshot.content || \"\" }],\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\ttruncation: snapshot.truncation.truncated ? snapshot.truncation : undefined,\n\t\t\t\t\t\tfullOutputPath: snapshot.fullOutputPath,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t};\n\n\t\t\tconst clearUpdateTimer = () => {\n\t\t\t\tif (updateTimer) {\n\t\t\t\t\tclearTimeout(updateTimer);\n\t\t\t\t\tupdateTimer = undefined;\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tconst scheduleOutputUpdate = () => {\n\t\t\t\tif (!onUpdate) return;\n\t\t\t\tupdateDirty = true;\n\t\t\t\tconst delay = BASH_UPDATE_THROTTLE_MS - (Date.now() - lastUpdateAt);\n\t\t\t\tif (delay <= 0) {\n\t\t\t\t\tclearUpdateTimer();\n\t\t\t\t\temitOutputUpdate();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tupdateTimer ??= setTimeout(() => {\n\t\t\t\t\tupdateTimer = undefined;\n\t\t\t\t\temitOutputUpdate();\n\t\t\t\t}, delay);\n\t\t\t};\n\n\t\t\tif (onUpdate) {\n\t\t\t\tonUpdate({ content: [], details: undefined });\n\t\t\t}\n\n\t\t\tconst handleData = (data: Buffer) => {\n\t\t\t\tif (!acceptingOutput) return;\n\t\t\t\toutput.append(data);\n\t\t\t\tscheduleOutputUpdate();\n\t\t\t};\n\n\t\t\tconst finishOutput = async () => {\n\t\t\t\tacceptingOutput = false;\n\t\t\t\toutput.finish();\n\t\t\t\tclearUpdateTimer();\n\t\t\t\temitOutputUpdate();\n\t\t\t\tconst snapshot = output.snapshot({ persistIfTruncated: true });\n\t\t\t\tawait output.closeTempFile();\n\t\t\t\treturn snapshot;\n\t\t\t};\n\n\t\t\tconst formatOutput = (snapshot: Awaited<ReturnType<typeof finishOutput>>, emptyText = \"(no output)\") => {\n\t\t\t\tconst truncation = snapshot.truncation;\n\t\t\t\tlet text = snapshot.content || emptyText;\n\t\t\t\tlet details: BashToolDetails | undefined;\n\t\t\t\tif (truncation.truncated) {\n\t\t\t\t\tdetails = { truncation, fullOutputPath: snapshot.fullOutputPath };\n\t\t\t\t\tconst startLine = truncation.totalLines - truncation.outputLines + 1;\n\t\t\t\t\tconst endLine = truncation.totalLines;\n\t\t\t\t\tif (truncation.lastLinePartial) {\n\t\t\t\t\t\tconst lastLineSize = formatSize(output.getLastLineBytes());\n\t\t\t\t\t\ttext += `\\n\\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${snapshot.fullOutputPath}]`;\n\t\t\t\t\t} else if (truncation.truncatedBy === \"lines\") {\n\t\t\t\t\t\ttext += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${snapshot.fullOutputPath}]`;\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttext += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${snapshot.fullOutputPath}]`;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn { text, details };\n\t\t\t};\n\n\t\t\tconst appendStatus = (text: string, status: string) => `${text ? `${text}\\n\\n` : \"\"}${status}`;\n\n\t\t\ttry {\n\t\t\t\tlet exitCode: number | null;\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await ops.exec(spawnContext.command, spawnContext.cwd, {\n\t\t\t\t\t\tonData: handleData,\n\t\t\t\t\t\tsignal,\n\t\t\t\t\t\ttimeout,\n\t\t\t\t\t\tenv: spawnContext.env,\n\t\t\t\t\t});\n\t\t\t\t\texitCode = result.exitCode;\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst snapshot = await finishOutput();\n\t\t\t\t\tconst { text } = formatOutput(snapshot, \"\");\n\t\t\t\t\tif (err instanceof Error && err.message === \"aborted\") {\n\t\t\t\t\t\tthrow new Error(appendStatus(text, t(\"Command aborted\")));\n\t\t\t\t\t}\n\t\t\t\t\tif (err instanceof Error && err.message.startsWith(\"timeout:\")) {\n\t\t\t\t\t\tconst timeoutSecs = err.message.split(\":\")[1];\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\tappendStatus(text, t(\"Command timed out after {seconds} seconds\", { seconds: timeoutSecs })),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tthrow err;\n\t\t\t\t}\n\n\t\t\t\tconst snapshot = await finishOutput();\n\t\t\t\tconst { text: outputText, details } = formatOutput(snapshot);\n\t\t\t\tif (exitCode === null) {\n\t\t\t\t\tthrow new Error(appendStatus(outputText, t(\"Command terminated without an exit code\")));\n\t\t\t\t}\n\t\t\t\tif (exitCode !== 0) {\n\t\t\t\t\tthrow new Error(appendStatus(outputText, t(\"Command exited with code {exitCode}\", { exitCode })));\n\t\t\t\t}\n\t\t\t\treturn { content: [{ type: \"text\", text: outputText }], details };\n\t\t\t} finally {\n\t\t\t\tclearUpdateTimer();\n\t\t\t}\n\t\t},\n\t\trenderCall(args, _theme, context) {\n\t\t\tconst state = context.state;\n\t\t\tif (context.executionStarted && state.startedAt === undefined) {\n\t\t\t\tstate.startedAt = Date.now();\n\t\t\t\tstate.endedAt = undefined;\n\t\t\t}\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatShellCall(args, config.prompt));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, _theme, context) {\n\t\t\tconst state = context.state;\n\t\t\tif (state.startedAt !== undefined && options.isPartial && !state.interval) {\n\t\t\t\tstate.interval = setInterval(() => context.invalidate(), 1000);\n\t\t\t}\n\t\t\tif (!options.isPartial || context.isError) {\n\t\t\t\tstate.endedAt ??= Date.now();\n\t\t\t\tif (state.interval) {\n\t\t\t\t\tclearInterval(state.interval);\n\t\t\t\t\tstate.interval = undefined;\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst component =\n\t\t\t\t(context.lastComponent as BashResultRenderComponent | undefined) ?? new BashResultRenderComponent();\n\t\t\trebuildBashResultRenderComponent(\n\t\t\t\tcomponent,\n\t\t\t\tresult,\n\t\t\t\toptions,\n\t\t\t\tcontext.showImages,\n\t\t\t\tstate.startedAt,\n\t\t\t\tstate.endedAt,\n\t\t\t);\n\t\t\tcomponent.invalidate();\n\t\t\treturn component;\n\t\t},\n\t};\n}\n\nconst bashToolConfig: ShellToolConfig = {\n\tname: \"bash\",\n\tlabel: \"bash\",\n\tshellName: \"bash\",\n\tprompt: \"$\",\n\tpromptSnippet: bashToolSystemPromptContribution.snippet,\n\tpromptGuidelines: bashToolSystemPromptContribution.guidelines,\n\ttempFilePrefix: \"agent-forge-bash\",\n};\n\nexport function createBashToolDefinition(\n\tcwd: string,\n\toptions?: BashToolOptions,\n): ToolDefinition<typeof bashSchema, BashToolDetails | undefined, BashRenderState> {\n\treturn createShellToolDefinition(cwd, bashToolConfig, options);\n}\n\nexport function createBashTool(cwd: string, options?: BashToolOptions): AgentTool<typeof bashSchema> {\n\tconst definition = createBashToolDefinition(cwd, options);\n\tconst tool = wrapToolDefinition(definition);\n\tObject.assign(tool, {\n\t\tpromptSnippet: definition.promptSnippet,\n\t\tpromptGuidelines: definition.promptGuidelines,\n\t});\n\treturn tool;\n}\n"]}
@@ -1,33 +1,20 @@
1
- import { constants } from "node:fs";
2
- import { access as fsAccess } from "node:fs/promises";
3
- import { constants as osConstants } from "node:os";
1
+ // Migrated from packages/agent-forge/src/core/tools/bash.ts (batch 1, copy-then-delete);
2
+ // host modules are imported through supply keys (@agent-forge/host/*).
3
+ import { createLocalBashOperations, createLocalShellOperations, } from "@agent-forge/host/bash-operations";
4
+ import { t } from "@agent-forge/host/i18n";
5
+ import { keyHint } from "@agent-forge/host/keybinding-hints";
6
+ import { OutputAccumulator } from "@agent-forge/host/output-accumulator";
7
+ import { getTextOutput, invalidArgText, str } from "@agent-forge/host/render-utils";
8
+ import { getShellEnv } from "@agent-forge/host/shell";
9
+ import { theme } from "@agent-forge/host/theme";
10
+ import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize } from "@agent-forge/host/truncate";
11
+ import { truncateToVisualLines } from "@agent-forge/host/visual-truncate";
4
12
  import { Container, Text, truncateToWidth } from "@agent-forge/tui";
5
- import { spawn } from "child_process";
6
13
  import { Type } from "typebox";
7
- import { keyHint } from "../../modes/interactive/components/keybinding-hints.js";
8
- import { truncateToVisualLines } from "../../modes/interactive/components/visual-truncate.js";
9
- import { theme } from "../../modes/interactive/theme/theme.js";
10
- import { waitForChildProcess } from "../../utils/child-process.js";
11
- import { getShellConfig, getShellEnv, killProcessTree, trackDetachedChildPid, untrackDetachedChildPid, } from "../../utils/shell.js";
12
- import { t } from "../i18n/index.js";
13
- import { OutputAccumulator } from "./output-accumulator.js";
14
- import { getTextOutput, invalidArgText, str } from "./render-utils.js";
15
- import { wrapToolDefinition } from "./tool-definition-wrapper.js";
16
- import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize } from "./truncate.js";
17
- const MAX_TIMEOUT_MS = 2_147_483_647;
18
- const MAX_TIMEOUT_SECONDS = MAX_TIMEOUT_MS / 1000;
19
- function resolveTimeoutMs(timeout) {
20
- if (timeout === undefined)
21
- return undefined;
22
- if (!Number.isFinite(timeout) || timeout <= 0) {
23
- throw new Error(t("Invalid timeout: must be a finite number of seconds"));
24
- }
25
- const timeoutMs = timeout * 1000;
26
- if (timeoutMs > MAX_TIMEOUT_MS) {
27
- throw new Error(t("Invalid timeout: maximum is {maxSeconds} seconds", { maxSeconds: MAX_TIMEOUT_SECONDS }));
28
- }
29
- return timeoutMs;
30
- }
14
+ import { wrapToolDefinition } from "./types.js";
15
+ // Operations layer shared with plugins via the "@agent-forge/host/bash-operations"
16
+ // virtual module; re-exported so the existing host import surface keeps resolving.
17
+ export { createLocalBashOperations, createLocalShellOperations, };
31
18
  const bashSchema = Type.Object({
32
19
  command: Type.String({ description: "Shell command to execute" }),
33
20
  timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (optional, no default timeout)" })),
@@ -36,97 +23,6 @@ export const bashToolSystemPromptContribution = {
36
23
  snippet: "Execute bash commands (ls, grep, find, etc.)",
37
24
  guidelines: ["You can inspect AGENT_FORGE_* environment variables for current model and session details."],
38
25
  };
39
- /** Shared process execution used by the built-in shell tools. */
40
- export function createLocalShellOperations(shellName, resolveShellConfig) {
41
- return {
42
- exec: async (command, cwd, { onData, signal, timeout, env }) => {
43
- const timeoutMs = resolveTimeoutMs(timeout);
44
- if (signal?.aborted) {
45
- throw new Error("aborted");
46
- }
47
- const shellConfig = resolveShellConfig();
48
- try {
49
- await fsAccess(cwd, constants.F_OK);
50
- }
51
- catch {
52
- throw new Error(t("Working directory does not exist: {cwd}\nCannot execute {shellName} commands.", {
53
- cwd,
54
- shellName,
55
- }));
56
- }
57
- const commandFromStdin = shellConfig.commandTransport === "stdin";
58
- const child = spawn(shellConfig.shell, commandFromStdin ? shellConfig.args : [...shellConfig.args, command], {
59
- cwd,
60
- detached: process.platform !== "win32",
61
- env: env ?? getShellEnv(),
62
- stdio: [commandFromStdin ? "pipe" : "ignore", "pipe", "pipe"],
63
- windowsHide: true,
64
- });
65
- if (commandFromStdin) {
66
- child.stdin?.on("error", () => { });
67
- child.stdin?.end(command);
68
- }
69
- if (child.pid)
70
- trackDetachedChildPid(child.pid);
71
- let timedOut = false;
72
- let timeoutHandle;
73
- const onAbort = () => {
74
- if (child.pid)
75
- killProcessTree(child.pid);
76
- };
77
- try {
78
- // Set timeout if provided.
79
- if (timeoutMs !== undefined) {
80
- timeoutHandle = setTimeout(() => {
81
- timedOut = true;
82
- if (child.pid)
83
- killProcessTree(child.pid);
84
- }, timeoutMs);
85
- }
86
- // Stream stdout and stderr.
87
- child.stdout?.on("data", onData);
88
- child.stderr?.on("data", onData);
89
- // Handle abort signal by killing the entire process tree.
90
- if (signal) {
91
- if (signal.aborted)
92
- onAbort();
93
- else
94
- signal.addEventListener("abort", onAbort, { once: true });
95
- }
96
- // Handle shell spawn errors and wait for the process to terminate without hanging
97
- // on inherited stdio handles held by detached descendants.
98
- const exitCode = await waitForChildProcess(child);
99
- if (signal?.aborted) {
100
- throw new Error("aborted");
101
- }
102
- if (timedOut) {
103
- throw new Error(`timeout:${timeout}`);
104
- }
105
- // A signal-killed shell has no exit code. Use the standard shell convention so
106
- // callers do not mistake the termination for a successful command.
107
- const signalCode = child.signalCode;
108
- return { exitCode: exitCode ?? (signalCode ? 128 + (osConstants.signals[signalCode] ?? 0) : 1) };
109
- }
110
- finally {
111
- if (child.pid)
112
- untrackDetachedChildPid(child.pid);
113
- if (timeoutHandle)
114
- clearTimeout(timeoutHandle);
115
- if (signal)
116
- signal.removeEventListener("abort", onAbort);
117
- }
118
- },
119
- };
120
- }
121
- /**
122
- * Create bash operations using Agent Forge's built-in local shell execution backend.
123
- *
124
- * This is useful for extensions that intercept user_bash and still want Agent Forge's
125
- * standard local shell behavior while wrapping or rewriting commands.
126
- */
127
- export function createLocalBashOperations(options) {
128
- return createLocalShellOperations("bash", () => getShellConfig(options?.shellPath));
129
- }
130
26
  function resolveSpawnContext(command, cwd, spawnHook, exposeSessionEnvironment, ctx) {
131
27
  const env = { ...getShellEnv() };
132
28
  delete env.AGENT_FORGE_SESSION_ID;