@namzu/sdk 51.1.0 → 52.0.0

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 (323) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/dist/agents/QueryAgent.d.ts.map +1 -1
  3. package/dist/agents/QueryAgent.js +2 -0
  4. package/dist/agents/QueryAgent.js.map +1 -1
  5. package/dist/agents/SupervisorAgent.d.ts +1 -1
  6. package/dist/agents/SupervisorAgent.d.ts.map +1 -1
  7. package/dist/agents/SupervisorAgent.js.map +1 -1
  8. package/dist/agents/examples/SupervisorAgent.d.ts +42 -0
  9. package/dist/agents/examples/SupervisorAgent.d.ts.map +1 -1
  10. package/dist/agents/examples/SupervisorAgent.js +80 -4
  11. package/dist/agents/examples/SupervisorAgent.js.map +1 -1
  12. package/dist/agents/index.d.ts +1 -1
  13. package/dist/agents/index.d.ts.map +1 -1
  14. package/dist/agents/index.js.map +1 -1
  15. package/dist/authorization/protected-paths.d.ts +36 -0
  16. package/dist/authorization/protected-paths.d.ts.map +1 -0
  17. package/dist/authorization/protected-paths.js +107 -0
  18. package/dist/authorization/protected-paths.js.map +1 -0
  19. package/dist/authorization/readonly-commands.d.ts +39 -0
  20. package/dist/authorization/readonly-commands.d.ts.map +1 -0
  21. package/dist/authorization/readonly-commands.js +797 -0
  22. package/dist/authorization/readonly-commands.js.map +1 -0
  23. package/dist/authorization/readonly-flags.d.ts +19 -0
  24. package/dist/authorization/readonly-flags.d.ts.map +1 -0
  25. package/dist/authorization/readonly-flags.js +178 -0
  26. package/dist/authorization/readonly-flags.js.map +1 -0
  27. package/dist/authorization/removal.d.ts +52 -0
  28. package/dist/authorization/removal.d.ts.map +1 -0
  29. package/dist/authorization/removal.js +283 -0
  30. package/dist/authorization/removal.js.map +1 -0
  31. package/dist/authorization/shell-paths.d.ts +66 -0
  32. package/dist/authorization/shell-paths.d.ts.map +1 -0
  33. package/dist/authorization/shell-paths.js +697 -0
  34. package/dist/authorization/shell-paths.js.map +1 -0
  35. package/dist/bridge/acp/update.d.ts.map +1 -1
  36. package/dist/bridge/acp/update.js +1 -0
  37. package/dist/bridge/acp/update.js.map +1 -1
  38. package/dist/compaction/context-window.d.ts +24 -0
  39. package/dist/compaction/context-window.d.ts.map +1 -1
  40. package/dist/compaction/context-window.js +34 -0
  41. package/dist/compaction/context-window.js.map +1 -1
  42. package/dist/compaction/manual.d.ts.map +1 -1
  43. package/dist/compaction/manual.js +10 -2
  44. package/dist/compaction/manual.js.map +1 -1
  45. package/dist/compaction/plan.d.ts +4 -0
  46. package/dist/compaction/plan.d.ts.map +1 -1
  47. package/dist/compaction/plan.js +6 -2
  48. package/dist/compaction/plan.js.map +1 -1
  49. package/dist/compaction/verifier.d.ts +11 -1
  50. package/dist/compaction/verifier.d.ts.map +1 -1
  51. package/dist/compaction/verifier.js +19 -7
  52. package/dist/compaction/verifier.js.map +1 -1
  53. package/dist/config/runtime.d.ts +55 -7
  54. package/dist/config/runtime.d.ts.map +1 -1
  55. package/dist/config/runtime.js +23 -2
  56. package/dist/config/runtime.js.map +1 -1
  57. package/dist/connector/web/guarded-fetch.d.ts.map +1 -1
  58. package/dist/connector/web/guarded-fetch.js +24 -0
  59. package/dist/connector/web/guarded-fetch.js.map +1 -1
  60. package/dist/connector/web/index.d.ts +1 -1
  61. package/dist/connector/web/index.d.ts.map +1 -1
  62. package/dist/connector/web/page.d.ts +59 -0
  63. package/dist/connector/web/page.d.ts.map +1 -0
  64. package/dist/connector/web/page.js +112 -0
  65. package/dist/connector/web/page.js.map +1 -0
  66. package/dist/connector/web/types.d.ts +47 -0
  67. package/dist/connector/web/types.d.ts.map +1 -1
  68. package/dist/connector/web/types.js.map +1 -1
  69. package/dist/constants/continuation.d.ts +8 -0
  70. package/dist/constants/continuation.d.ts.map +1 -1
  71. package/dist/constants/continuation.js +8 -0
  72. package/dist/constants/continuation.js.map +1 -1
  73. package/dist/constants/plugin/index.d.ts +1 -1
  74. package/dist/constants/plugin/index.d.ts.map +1 -1
  75. package/dist/constants/plugin/index.js +1 -1
  76. package/dist/constants/plugin/index.js.map +1 -1
  77. package/dist/manager/agent/lifecycle.d.ts +7 -0
  78. package/dist/manager/agent/lifecycle.d.ts.map +1 -1
  79. package/dist/manager/agent/lifecycle.js +11 -2
  80. package/dist/manager/agent/lifecycle.js.map +1 -1
  81. package/dist/manager/session/turn-recorder.d.ts.map +1 -1
  82. package/dist/manager/session/turn-recorder.js +4 -2
  83. package/dist/manager/session/turn-recorder.js.map +1 -1
  84. package/dist/plugin/index.d.ts +1 -1
  85. package/dist/plugin/index.d.ts.map +1 -1
  86. package/dist/plugin/index.js +1 -1
  87. package/dist/plugin/index.js.map +1 -1
  88. package/dist/plugin/lifecycle.d.ts +6 -0
  89. package/dist/plugin/lifecycle.d.ts.map +1 -1
  90. package/dist/plugin/lifecycle.js +98 -114
  91. package/dist/plugin/lifecycle.js.map +1 -1
  92. package/dist/plugin/shell-hook.d.ts +90 -22
  93. package/dist/plugin/shell-hook.d.ts.map +1 -1
  94. package/dist/plugin/shell-hook.js +434 -41
  95. package/dist/plugin/shell-hook.js.map +1 -1
  96. package/dist/public-runtime.d.ts +4 -1
  97. package/dist/public-runtime.d.ts.map +1 -1
  98. package/dist/public-runtime.js +4 -1
  99. package/dist/public-runtime.js.map +1 -1
  100. package/dist/public-types.d.ts +2 -2
  101. package/dist/public-types.d.ts.map +1 -1
  102. package/dist/runtime/jobs/registry.d.ts +21 -0
  103. package/dist/runtime/jobs/registry.d.ts.map +1 -1
  104. package/dist/runtime/jobs/registry.js +15 -2
  105. package/dist/runtime/jobs/registry.js.map +1 -1
  106. package/dist/runtime/query/executor/tool-call-admission.d.ts +10 -0
  107. package/dist/runtime/query/executor/tool-call-admission.d.ts.map +1 -1
  108. package/dist/runtime/query/executor/tool-call-admission.js +74 -13
  109. package/dist/runtime/query/executor/tool-call-admission.js.map +1 -1
  110. package/dist/runtime/query/executor.d.ts +41 -2
  111. package/dist/runtime/query/executor.d.ts.map +1 -1
  112. package/dist/runtime/query/executor.js +190 -6
  113. package/dist/runtime/query/executor.js.map +1 -1
  114. package/dist/runtime/query/finalize-turn.d.ts +6 -0
  115. package/dist/runtime/query/finalize-turn.d.ts.map +1 -1
  116. package/dist/runtime/query/finalize-turn.js +1 -1
  117. package/dist/runtime/query/finalize-turn.js.map +1 -1
  118. package/dist/runtime/query/index.d.ts +29 -0
  119. package/dist/runtime/query/index.d.ts.map +1 -1
  120. package/dist/runtime/query/index.js +15 -3
  121. package/dist/runtime/query/index.js.map +1 -1
  122. package/dist/runtime/query/iteration/index.d.ts +31 -0
  123. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  124. package/dist/runtime/query/iteration/index.js +216 -7
  125. package/dist/runtime/query/iteration/index.js.map +1 -1
  126. package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts +58 -0
  127. package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts.map +1 -0
  128. package/dist/runtime/query/iteration/phases/compaction-breaker.js +91 -0
  129. package/dist/runtime/query/iteration/phases/compaction-breaker.js.map +1 -0
  130. package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
  131. package/dist/runtime/query/iteration/phases/compaction.js +89 -7
  132. package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
  133. package/dist/runtime/query/iteration/phases/context.d.ts +10 -0
  134. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  135. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  136. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  137. package/dist/runtime/query/iteration/phases/tool-review.js +165 -2
  138. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  139. package/dist/runtime/query/path-escalation.d.ts +36 -0
  140. package/dist/runtime/query/path-escalation.d.ts.map +1 -0
  141. package/dist/runtime/query/path-escalation.js +184 -0
  142. package/dist/runtime/query/path-escalation.js.map +1 -0
  143. package/dist/runtime/query/plugin-hooks.d.ts.map +1 -1
  144. package/dist/runtime/query/plugin-hooks.js +8 -0
  145. package/dist/runtime/query/plugin-hooks.js.map +1 -1
  146. package/dist/runtime/query/reminders.d.ts +35 -0
  147. package/dist/runtime/query/reminders.d.ts.map +1 -0
  148. package/dist/runtime/query/reminders.js +7 -0
  149. package/dist/runtime/query/reminders.js.map +1 -0
  150. package/dist/runtime/query/resume-pending.d.ts.map +1 -1
  151. package/dist/runtime/query/resume-pending.js +26 -0
  152. package/dist/runtime/query/resume-pending.js.map +1 -1
  153. package/dist/runtime/query/review-policy.d.ts +26 -0
  154. package/dist/runtime/query/review-policy.d.ts.map +1 -1
  155. package/dist/runtime/query/review-policy.js +79 -5
  156. package/dist/runtime/query/review-policy.js.map +1 -1
  157. package/dist/runtime/query/tool-output-budget.d.ts +46 -0
  158. package/dist/runtime/query/tool-output-budget.d.ts.map +1 -1
  159. package/dist/runtime/query/tool-output-budget.js +85 -1
  160. package/dist/runtime/query/tool-output-budget.js.map +1 -1
  161. package/dist/runtime/query/tooling.d.ts +1 -0
  162. package/dist/runtime/query/tooling.d.ts.map +1 -1
  163. package/dist/runtime/query/tooling.js +5 -0
  164. package/dist/runtime/query/tooling.js.map +1 -1
  165. package/dist/session/paths.d.ts +5 -1
  166. package/dist/session/paths.d.ts.map +1 -1
  167. package/dist/session/paths.js +8 -0
  168. package/dist/session/paths.js.map +1 -1
  169. package/dist/store/memory/disk.d.ts.map +1 -1
  170. package/dist/store/memory/disk.js +2 -2
  171. package/dist/store/memory/disk.js.map +1 -1
  172. package/dist/store/memory/markdown.d.ts.map +1 -1
  173. package/dist/store/memory/markdown.js +2 -2
  174. package/dist/store/memory/markdown.js.map +1 -1
  175. package/dist/store/memory/naming.d.ts +9 -0
  176. package/dist/store/memory/naming.d.ts.map +1 -1
  177. package/dist/store/memory/naming.js +12 -0
  178. package/dist/store/memory/naming.js.map +1 -1
  179. package/dist/tools/builtins/bash.d.ts +30 -1
  180. package/dist/tools/builtins/bash.d.ts.map +1 -1
  181. package/dist/tools/builtins/bash.js +170 -26
  182. package/dist/tools/builtins/bash.js.map +1 -1
  183. package/dist/tools/builtins/content-fingerprint.d.ts +2 -0
  184. package/dist/tools/builtins/content-fingerprint.d.ts.map +1 -1
  185. package/dist/tools/builtins/content-fingerprint.js +4 -0
  186. package/dist/tools/builtins/content-fingerprint.js.map +1 -1
  187. package/dist/tools/builtins/edit.d.ts.map +1 -1
  188. package/dist/tools/builtins/edit.js +60 -18
  189. package/dist/tools/builtins/edit.js.map +1 -1
  190. package/dist/tools/builtins/grep.d.ts +8 -1
  191. package/dist/tools/builtins/grep.d.ts.map +1 -1
  192. package/dist/tools/builtins/grep.js +152 -33
  193. package/dist/tools/builtins/grep.js.map +1 -1
  194. package/dist/tools/builtins/read-file.d.ts +1 -0
  195. package/dist/tools/builtins/read-file.d.ts.map +1 -1
  196. package/dist/tools/builtins/read-file.js +44 -5
  197. package/dist/tools/builtins/read-file.js.map +1 -1
  198. package/dist/tools/builtins/read-render.d.ts +52 -0
  199. package/dist/tools/builtins/read-render.d.ts.map +1 -1
  200. package/dist/tools/builtins/read-render.js +143 -0
  201. package/dist/tools/builtins/read-render.js.map +1 -1
  202. package/dist/tools/builtins/web.d.ts +13 -2
  203. package/dist/tools/builtins/web.d.ts.map +1 -1
  204. package/dist/tools/builtins/web.js +225 -32
  205. package/dist/tools/builtins/web.js.map +1 -1
  206. package/dist/tools/coordinator/ask-user-question.d.ts +2 -0
  207. package/dist/tools/coordinator/ask-user-question.d.ts.map +1 -1
  208. package/dist/tools/coordinator/ask-user-question.js +256 -92
  209. package/dist/tools/coordinator/ask-user-question.js.map +1 -1
  210. package/dist/tools/coordinator/question-options.d.ts +1 -0
  211. package/dist/tools/coordinator/question-options.d.ts.map +1 -1
  212. package/dist/tools/coordinator/question-options.js +6 -1
  213. package/dist/tools/coordinator/question-options.js.map +1 -1
  214. package/dist/tools/defineTool.d.ts +4 -1
  215. package/dist/tools/defineTool.d.ts.map +1 -1
  216. package/dist/tools/defineTool.js +6 -1
  217. package/dist/tools/defineTool.js.map +1 -1
  218. package/dist/tools/paths.d.ts +6 -0
  219. package/dist/tools/paths.d.ts.map +1 -1
  220. package/dist/tools/paths.js +1 -1
  221. package/dist/tools/paths.js.map +1 -1
  222. package/dist/types/agent/query.d.ts +3 -0
  223. package/dist/types/agent/query.d.ts.map +1 -1
  224. package/dist/types/errors/catalog.js +1 -1
  225. package/dist/types/errors/catalog.js.map +1 -1
  226. package/dist/types/hitl/index.d.ts +104 -0
  227. package/dist/types/hitl/index.d.ts.map +1 -1
  228. package/dist/types/hitl/index.js.map +1 -1
  229. package/dist/types/message/index.d.ts +1 -1
  230. package/dist/types/message/index.d.ts.map +1 -1
  231. package/dist/types/message/index.js +2 -0
  232. package/dist/types/message/index.js.map +1 -1
  233. package/dist/types/plugin/index.d.ts +93 -19
  234. package/dist/types/plugin/index.d.ts.map +1 -1
  235. package/dist/types/plugin/index.js +3 -0
  236. package/dist/types/plugin/index.js.map +1 -1
  237. package/dist/types/provider/errors.d.ts +19 -0
  238. package/dist/types/provider/errors.d.ts.map +1 -1
  239. package/dist/types/provider/errors.js +24 -0
  240. package/dist/types/provider/errors.js.map +1 -1
  241. package/dist/types/session/events.d.ts +10 -2
  242. package/dist/types/session/events.d.ts.map +1 -1
  243. package/dist/types/session/events.js.map +1 -1
  244. package/dist/types/session/records.d.ts +9 -9
  245. package/dist/types/session/records.d.ts.map +1 -1
  246. package/dist/types/session/records.js +1 -0
  247. package/dist/types/session/records.js.map +1 -1
  248. package/dist/types/session/stop-reason.d.ts +5 -0
  249. package/dist/types/session/stop-reason.d.ts.map +1 -1
  250. package/dist/types/tool/index.d.ts +52 -0
  251. package/dist/types/tool/index.d.ts.map +1 -1
  252. package/dist/types/tool/index.js.map +1 -1
  253. package/package.json +4 -2
  254. package/src/agents/QueryAgent.ts +5 -1
  255. package/src/agents/SupervisorAgent.ts +1 -1
  256. package/src/agents/examples/SupervisorAgent.ts +217 -106
  257. package/src/agents/index.ts +1 -1
  258. package/src/authorization/protected-paths.ts +110 -0
  259. package/src/authorization/readonly-commands.ts +771 -0
  260. package/src/authorization/readonly-flags.ts +196 -0
  261. package/src/authorization/removal.ts +326 -0
  262. package/src/authorization/shell-paths.ts +927 -0
  263. package/src/bridge/acp/update.ts +1 -0
  264. package/src/compaction/context-window.ts +41 -0
  265. package/src/compaction/manual.ts +10 -2
  266. package/src/compaction/plan.ts +11 -2
  267. package/src/compaction/verifier.ts +28 -3
  268. package/src/config/runtime.ts +23 -2
  269. package/src/connector/web/guarded-fetch.ts +26 -0
  270. package/src/connector/web/index.ts +3 -0
  271. package/src/connector/web/page.ts +130 -0
  272. package/src/connector/web/types.ts +47 -0
  273. package/src/constants/continuation.ts +10 -0
  274. package/src/constants/plugin/index.ts +1 -1
  275. package/src/manager/agent/lifecycle.ts +12 -4
  276. package/src/manager/session/turn-recorder.ts +10 -2
  277. package/src/plugin/index.ts +1 -0
  278. package/src/plugin/lifecycle.ts +107 -119
  279. package/src/plugin/shell-hook.ts +477 -43
  280. package/src/public-runtime.ts +8 -0
  281. package/src/public-types.ts +4 -0
  282. package/src/runtime/jobs/registry.ts +39 -2
  283. package/src/runtime/query/executor/tool-call-admission.ts +79 -13
  284. package/src/runtime/query/executor.ts +242 -8
  285. package/src/runtime/query/finalize-turn.ts +7 -1
  286. package/src/runtime/query/index.ts +45 -3
  287. package/src/runtime/query/iteration/index.ts +251 -12
  288. package/src/runtime/query/iteration/phases/compaction-breaker.ts +106 -0
  289. package/src/runtime/query/iteration/phases/compaction.ts +99 -12
  290. package/src/runtime/query/iteration/phases/context.ts +11 -0
  291. package/src/runtime/query/iteration/phases/tool-review.ts +177 -2
  292. package/src/runtime/query/path-escalation.ts +204 -0
  293. package/src/runtime/query/plugin-hooks.ts +8 -0
  294. package/src/runtime/query/reminders.ts +45 -0
  295. package/src/runtime/query/resume-pending.ts +27 -0
  296. package/src/runtime/query/review-policy.ts +106 -5
  297. package/src/runtime/query/tool-output-budget.ts +115 -1
  298. package/src/runtime/query/tooling.ts +6 -0
  299. package/src/session/paths.ts +11 -1
  300. package/src/store/memory/disk.ts +2 -1
  301. package/src/store/memory/markdown.ts +2 -1
  302. package/src/store/memory/naming.ts +13 -0
  303. package/src/tools/builtins/bash.ts +203 -34
  304. package/src/tools/builtins/content-fingerprint.ts +5 -0
  305. package/src/tools/builtins/edit.ts +69 -19
  306. package/src/tools/builtins/grep.ts +159 -38
  307. package/src/tools/builtins/read-file.ts +48 -5
  308. package/src/tools/builtins/read-render.ts +193 -0
  309. package/src/tools/builtins/web.ts +258 -33
  310. package/src/tools/coordinator/ask-user-question.ts +309 -103
  311. package/src/tools/coordinator/question-options.ts +8 -1
  312. package/src/tools/defineTool.ts +11 -2
  313. package/src/tools/paths.ts +1 -1
  314. package/src/types/agent/query.ts +3 -0
  315. package/src/types/errors/catalog.ts +1 -1
  316. package/src/types/hitl/index.ts +104 -0
  317. package/src/types/message/index.ts +2 -0
  318. package/src/types/plugin/index.ts +101 -18
  319. package/src/types/provider/errors.ts +33 -0
  320. package/src/types/session/events.ts +10 -2
  321. package/src/types/session/records.ts +1 -0
  322. package/src/types/session/stop-reason.ts +5 -0
  323. package/src/types/tool/index.ts +53 -2
@@ -49,6 +49,7 @@ const STOP_REASONS: Readonly<Record<string, AcpStopReason>> = {
49
49
  plan_rejected: 'refused',
50
50
  answer_rejected: 'refused',
51
51
  structured_output_failed: 'error',
52
+ output_limit: 'error',
52
53
  error: 'error',
53
54
  provider_error: 'error',
54
55
  }
@@ -105,6 +105,47 @@ export function lookupContextWindow(model: string | undefined): number | undefin
105
105
  return undefined
106
106
  }
107
107
 
108
+ /** Output room the trigger keeps free: the model's output cap, at most this many tokens. */
109
+ export const TRIGGER_OUTPUT_RESERVE_CAP_TOKENS = 20_000
110
+ /** Extra headroom below the reserved window before a summary is due. */
111
+ export const TRIGGER_BUFFER_TOKENS = 13_000
112
+ /**
113
+ * The derived trigger never drops below this fraction of the window. The
114
+ * reference subtracts 33,000 tokens outright; on a window under about 66,000
115
+ * that is most of it (or more than all of it), and a summary on every
116
+ * iteration is not a trigger.
117
+ */
118
+ export const MIN_DERIVED_TRIGGER_FRACTION = 0.5
119
+
120
+ /**
121
+ * The fraction of the window at which older history is summarised.
122
+ *
123
+ * A host that set `triggerThreshold` gets exactly that. Otherwise the
124
+ * trigger is the one Claude Code uses: the window, less room kept for the
125
+ * reply (the model's output cap, at most 20,000 tokens), less a 13,000-token
126
+ * buffer. On a 200,000-token window that is 167,000 tokens (83.5%); on a
127
+ * 1,000,000-token window, 967,000 (96.7%).
128
+ *
129
+ * `maxOutputTokens` is what the driver declares for the model; absent, the
130
+ * full 20,000 is reserved.
131
+ */
132
+ export function resolveTriggerThreshold(
133
+ configured: number | undefined,
134
+ windowTokens: number,
135
+ maxOutputTokens?: number,
136
+ ): number {
137
+ if (configured !== undefined) return configured
138
+ if (!(windowTokens > 0)) return MIN_DERIVED_TRIGGER_FRACTION
139
+ const reserve = Math.min(
140
+ maxOutputTokens !== undefined && maxOutputTokens > 0
141
+ ? maxOutputTokens
142
+ : TRIGGER_OUTPUT_RESERVE_CAP_TOKENS,
143
+ TRIGGER_OUTPUT_RESERVE_CAP_TOKENS,
144
+ )
145
+ const fraction = (windowTokens - reserve - TRIGGER_BUFFER_TOKENS) / windowTokens
146
+ return Math.min(1, Math.max(MIN_DERIVED_TRIGGER_FRACTION, fraction))
147
+ }
148
+
108
149
  export interface ResolvedContextWindow {
109
150
  readonly tokens: number
110
151
  /**
@@ -198,7 +198,11 @@ export async function compactNow(input: CompactNowInput): Promise<CompactionResu
198
198
  usage = accumulateTokenUsage(usage, reported)
199
199
  },
200
200
  input.model ?? '',
201
- { signal: input.signal, streamIdleTimeoutMs: input.streamIdleTimeoutMs },
201
+ {
202
+ signal: input.signal,
203
+ streamIdleTimeoutMs: input.streamIdleTimeoutMs,
204
+ instructions: input.instructions,
205
+ },
202
206
  )
203
207
 
204
208
  const { messages, summary } = splice(preservedSystem, body, retainedOlder, plan.recentMessages)
@@ -281,7 +285,11 @@ export async function compactRegion(input: CompactRegionInput): Promise<Compacti
281
285
  usage = accumulateTokenUsage(usage, reported)
282
286
  },
283
287
  input.model ?? '',
284
- { signal: input.signal, streamIdleTimeoutMs: input.streamIdleTimeoutMs },
288
+ {
289
+ signal: input.signal,
290
+ streamIdleTimeoutMs: input.streamIdleTimeoutMs,
291
+ instructions: input.instructions,
292
+ },
285
293
  )
286
294
 
287
295
  // Same cross-run ownership as compactNow: a selected region has been
@@ -1,5 +1,6 @@
1
1
  import type { CompactionConfig } from '../config/runtime.js'
2
2
  import type { Message } from '../types/message/index.js'
3
+ import { resolveTriggerThreshold } from './context-window.js'
3
4
  import { findSafeTrimIndex } from './dangling.js'
4
5
  import { buildGoal } from './salience/goal.js'
5
6
  import { scoreMessages } from './salience/score.js'
@@ -64,6 +65,8 @@ export interface CompactionPlanInput {
64
65
  readonly messages: readonly Message[]
65
66
  readonly config: CompactionConfig
66
67
  readonly contextWindowTokens: number
68
+ /** The model's output cap, when known; sizes the derived trigger. */
69
+ readonly maxOutputTokens?: number
67
70
  readonly estimatedTokens: number
68
71
  readonly force?: boolean
69
72
  /**
@@ -137,6 +140,8 @@ export function planSalienceWorkingSet(input: {
137
140
  readonly messages: readonly Message[]
138
141
  readonly config: CompactionConfig
139
142
  readonly contextWindowTokens: number
143
+ /** The model's output cap, when known; sizes the derived trigger. */
144
+ readonly maxOutputTokens?: number
140
145
  readonly estimatedTokens: number
141
146
  readonly openTasks?: readonly string[]
142
147
  }): Extract<CompactionPlan, { kind: 'cleared' }> & { readonly working: WorkingSetPlan } {
@@ -161,7 +166,9 @@ export function planSalienceWorkingSet(input: {
161
166
  stubbedCount: working.stubbedCount,
162
167
  charsReclaimed: working.charsReclaimed,
163
168
  reclaimedTokens: working.reclaimedTokens,
164
- reliefWasEnough: (estimatedTokens - working.reclaimedTokens) / budget < config.triggerThreshold,
169
+ reliefWasEnough:
170
+ (estimatedTokens - working.reclaimedTokens) / budget <
171
+ resolveTriggerThreshold(config.triggerThreshold, budget, input.maxOutputTokens),
165
172
  working,
166
173
  }
167
174
  }
@@ -189,7 +196,9 @@ export function planCompaction(input: CompactionPlanInput): CompactionPlan {
189
196
  // would declare success after clearing one result and hand back a
190
197
  // history that overflows again on the retry.
191
198
  const reliefWasEnough =
192
- !input.force && (estimatedTokens - reclaimedTokens) / budget < config.triggerThreshold
199
+ !input.force &&
200
+ (estimatedTokens - reclaimedTokens) / budget <
201
+ resolveTriggerThreshold(config.triggerThreshold, budget, input.maxOutputTokens)
193
202
  return {
194
203
  kind: 'cleared',
195
204
  messages: edit.messages,
@@ -127,6 +127,26 @@ export interface CompactionVerificationOptions {
127
127
  * only for explicit unbounded compatibility.
128
128
  */
129
129
  readonly streamIdleTimeoutMs?: number
130
+ /**
131
+ * What the person asked the summary to pay attention to, in their words
132
+ * (`/compact keep the failing test names`). Given to the verifier model as
133
+ * a request to add whatever of it the extracted state lacks. Naming
134
+ * instructions makes the model step run even when `llmVerification` is off,
135
+ * because it is the only step that can act on free text.
136
+ */
137
+ readonly instructions?: string
138
+ }
139
+
140
+ /** Longest instruction text passed on; a pasted page is not an instruction. */
141
+ export const MAX_COMPACTION_INSTRUCTIONS_CHARS = 2_000
142
+
143
+ function withInstructions(prompt: string, instructions: string | undefined): string {
144
+ const text = instructions?.trim().slice(0, MAX_COMPACTION_INSTRUCTIONS_CHARS)
145
+ if (!text) return prompt
146
+ return `${prompt}
147
+
148
+ The person who asked for this summary also said what it must keep: ${text}
149
+ Treat that as part of "important information". If the structured state lacks something it asks for and the excerpt has it, list that too.`
130
150
  }
131
151
 
132
152
  export async function buildVerifiedSummary(
@@ -157,6 +177,7 @@ export async function buildVerifiedSummary(
157
177
  onUsage,
158
178
  model,
159
179
  options.signal,
180
+ options.instructions,
160
181
  )
161
182
  }
162
183
 
@@ -174,6 +195,7 @@ export async function buildVerifiedSummaryWithBoundedProvider(
174
195
  onUsage?: UsageSink,
175
196
  model?: string,
176
197
  signal?: AbortSignal,
198
+ instructions?: string,
177
199
  ): Promise<string> {
178
200
  signal?.throwIfAborted()
179
201
  return await buildVerifiedSummaryWithProvider(
@@ -184,6 +206,7 @@ export async function buildVerifiedSummaryWithBoundedProvider(
184
206
  onUsage,
185
207
  model,
186
208
  signal,
209
+ instructions,
187
210
  )
188
211
  }
189
212
 
@@ -195,14 +218,16 @@ async function buildVerifiedSummaryWithProvider(
195
218
  onUsage?: UsageSink,
196
219
  model?: string,
197
220
  signal?: AbortSignal,
221
+ instructions?: string,
198
222
  ): Promise<string> {
199
223
  const serialized = serializeState(manager.getState())
224
+ const steered = Boolean(instructions?.trim())
200
225
 
201
- if (manager.slotCount() >= config.richStateThreshold) {
226
+ if (!steered && manager.slotCount() >= config.richStateThreshold) {
202
227
  return serialized
203
228
  }
204
229
 
205
- if (!config.llmVerification) {
230
+ if (!steered && !config.llmVerification) {
206
231
  return serialized
207
232
  }
208
233
 
@@ -215,7 +240,7 @@ async function buildVerifiedSummaryWithProvider(
215
240
  const verificationMessages: Message[] = [
216
241
  {
217
242
  role: 'system' as const,
218
- content: VERIFICATION_PROMPT,
243
+ content: withInstructions(VERIFICATION_PROMPT, instructions),
219
244
  },
220
245
  {
221
246
  role: 'user' as const,
@@ -41,8 +41,16 @@ export const CompactionConfigSchema = z.object({
41
41
  * window-pressure instead of being a silent no-op.
42
42
  */
43
43
  contextWindowTokens: z.number().int().positive().optional(),
44
- /** Fraction of the context window at which a compaction pass fires. */
45
- triggerThreshold: z.number().min(0).max(1).default(0.7),
44
+ /**
45
+ * Fraction of the context window at which older history is summarised.
46
+ *
47
+ * Absent, the trigger is derived from the window: the window less room for
48
+ * the reply (the model's output cap, at most 20,000 tokens) less a
49
+ * 13,000-token buffer, so 83.5% of a 200,000-token window and 96.7% of a
50
+ * 1,000,000-token one (see `resolveTriggerThreshold`). It used to default
51
+ * to a flat `0.7`; pass that to keep the earlier behaviour.
52
+ */
53
+ triggerThreshold: z.number().min(0).max(1).optional(),
46
54
  /**
47
55
  * Fraction the pass must get the context BELOW to be worth keeping.
48
56
  *
@@ -58,6 +66,19 @@ export const CompactionConfigSchema = z.object({
58
66
  * failure mode once the trigger actually fires.
59
67
  */
60
68
  resetThreshold: z.number().min(0).max(1).default(0.4),
69
+ /**
70
+ * Give up on automatic compaction when it stops working.
71
+ *
72
+ * Three failed passes in a row switch it off for the rest of the turn.
73
+ * Three compactions in a row, each followed by the window filling again
74
+ * within three iterations, stop the turn: the loop refills the context
75
+ * faster than a summary can empty it. Each is announced once, as a
76
+ * `compaction_failed` event.
77
+ *
78
+ * Off unless set. Without it a pass that sheds nothing is retried on every
79
+ * iteration, and a turn that refills the window is never stopped for it.
80
+ */
81
+ circuitBreaker: z.boolean().optional(),
61
82
  keepRecentMessages: z.number().int().positive().default(4),
62
83
  /**
63
84
  * Size the retained tail by TOKENS instead of by message count.
@@ -8,6 +8,7 @@ import {
8
8
  validateConnectorMaxResponseBytes,
9
9
  validateConnectorTimeoutMs,
10
10
  } from '../http-operation.js'
11
+ import { isSameSiteRedirect } from './page.js'
11
12
  import {
12
13
  type WebFetchProvider,
13
14
  WebFetchRefusedError,
@@ -304,6 +305,31 @@ export class GuardedFetchProvider implements WebFetchProvider {
304
305
  const next = new URL(location, current)
305
306
  cancelResponseBody(response, new Error('following redirect'))
306
307
  operation.throwIfStopped()
308
+ if (request.redirects === 'same-host' && !isSameSiteRedirect(current, next)) {
309
+ // Handed back, not followed. It is still checked: a caller that
310
+ // goes there next goes through this guard again, but a target
311
+ // that can never be fetched is better refused here than offered.
312
+ try {
313
+ await this.assertAllowed(next.toString(), operation)
314
+ } catch (err) {
315
+ operation.throwIfStopped()
316
+ if (err instanceof WebFetchRefusedError) {
317
+ throw new WebFetchRefusedError(
318
+ `A redirect from "${current.toString()}" pointed at "${next.toString()}", which was refused: ${err.message}`,
319
+ { url: next.toString(), reason: 'redirect-target' },
320
+ )
321
+ }
322
+ throw err
323
+ }
324
+ return {
325
+ url: current.toString(),
326
+ status: response.status,
327
+ body: '',
328
+ truncated: false,
329
+ redirects,
330
+ redirect: { url: next.toString(), status: response.status },
331
+ }
332
+ }
307
333
  try {
308
334
  current = await this.assertAllowed(next.toString(), operation)
309
335
  } catch (err) {
@@ -6,7 +6,10 @@ export { GuardedFetchProvider, isPrivateAddress } from './guarded-fetch.js'
6
6
  export type { GuardedFetchConfig } from './guarded-fetch.js'
7
7
  export { WebFetchRefusedError } from './types.js'
8
8
  export type {
9
+ WebContentExtractor,
10
+ WebExtractRequest,
9
11
  WebFetchProvider,
12
+ WebFetchRedirectPolicy,
10
13
  WebFetchRefusalReason,
11
14
  WebFetchRequest,
12
15
  WebFetchResult,
@@ -0,0 +1,130 @@
1
+ /**
2
+ * What `web_fetch` does with a page after the guard has let it through:
3
+ * decide which redirects stay on the same site, turn HTML into markdown, and
4
+ * keep a page for a quarter of an hour so a second question about it does not
5
+ * fetch it again.
6
+ *
7
+ * None of it touches the network or a model. The guard (`guarded-fetch.ts`)
8
+ * decides what may be fetched; this decides what the model is shown.
9
+ */
10
+
11
+ /** How long a fetched page is kept. */
12
+ export const WEB_PAGE_CACHE_TTL_MS = 15 * 60 * 1000
13
+ /** Most the cache holds, counted in bytes of page text. */
14
+ export const WEB_PAGE_CACHE_MAX_BYTES = 50 * 1024 * 1024
15
+ /** Most HTML handed to the converter; the rest is dropped with a marker. */
16
+ export const WEB_PAGE_CONVERT_MAX_CHARS = 1024 * 1024
17
+ /** Most page text a model is asked about in one call. */
18
+ export const WEB_PAGE_MODEL_MAX_CHARS = 100_000
19
+ /** Marker the reference-shaped cuts end with. */
20
+ export const WEB_PAGE_TRUNCATED_MARKER = '[Content truncated due to length...]'
21
+
22
+ /**
23
+ * A redirect stays on the site when only the path or query changes (and
24
+ * `www.` comes or goes). `http:` to `https:` on the same host and default
25
+ * port is the same site too: the reference upgrades every URL to `https:`
26
+ * before it asks, so that hop never reaches it.
27
+ */
28
+ export function isSameSiteRedirect(from: URL, to: URL): boolean {
29
+ const upgrade = from.protocol === 'http:' && to.protocol === 'https:'
30
+ if (to.protocol !== from.protocol && !upgrade) return false
31
+ if (to.port !== from.port) return false
32
+ if (to.username !== '' || to.password !== '') return false
33
+ const bare = (host: string): string => host.replace(/^www\./u, '')
34
+ return bare(to.hostname) === bare(from.hostname)
35
+ }
36
+
37
+ type Converter = { turndown(html: string): string }
38
+ let converter: Promise<Converter> | undefined
39
+
40
+ function loadConverter(): Promise<Converter> {
41
+ converter ??= import('turndown').then((module) => {
42
+ const Turndown = module.default
43
+ const service = new Turndown()
44
+ service.remove(['style', 'script', 'noscript', 'iframe'])
45
+ return service
46
+ })
47
+ return converter
48
+ }
49
+
50
+ /**
51
+ * HTML as markdown. Style, script, noscript and iframe elements are dropped,
52
+ * and the input is cut at {@link WEB_PAGE_CONVERT_MAX_CHARS}. Never throws: a
53
+ * page the converter cannot read comes back as the HTML it was.
54
+ */
55
+ export async function htmlToMarkdown(html: string): Promise<string> {
56
+ try {
57
+ const markdown = (await loadConverter()).turndown(html.slice(0, WEB_PAGE_CONVERT_MAX_CHARS))
58
+ return html.length > WEB_PAGE_CONVERT_MAX_CHARS
59
+ ? `${markdown}\n\n${WEB_PAGE_TRUNCATED_MARKER}`
60
+ : markdown
61
+ } catch {
62
+ return html
63
+ }
64
+ }
65
+
66
+ export interface CachedPage {
67
+ readonly content: string
68
+ readonly status: number
69
+ readonly contentType?: string
70
+ /** Where the fetch ended up. */
71
+ readonly url: string
72
+ readonly redirects: readonly string[]
73
+ readonly truncated: boolean
74
+ /** Bytes of text the page held before conversion. */
75
+ readonly bytes: number
76
+ }
77
+
78
+ interface Entry {
79
+ readonly page: CachedPage
80
+ readonly expiresAt: number
81
+ readonly size: number
82
+ }
83
+
84
+ /**
85
+ * Pages by the URL that was asked for, for {@link WEB_PAGE_CACHE_TTL_MS}.
86
+ * Least recently used goes first when the byte cap is reached. The clock is
87
+ * `Date.now`, so a test with fake timers moves it.
88
+ */
89
+ export class WebPageCache {
90
+ private readonly entries = new Map<string, Entry>()
91
+ private bytes = 0
92
+
93
+ constructor(
94
+ private readonly ttlMs: number = WEB_PAGE_CACHE_TTL_MS,
95
+ private readonly maxBytes: number = WEB_PAGE_CACHE_MAX_BYTES,
96
+ ) {}
97
+
98
+ get(url: string): CachedPage | undefined {
99
+ const entry = this.entries.get(url)
100
+ if (entry === undefined) return undefined
101
+ if (entry.expiresAt <= Date.now()) {
102
+ this.remove(url)
103
+ return undefined
104
+ }
105
+ // Re-inserted so the map's order is the order of use.
106
+ this.entries.delete(url)
107
+ this.entries.set(url, entry)
108
+ return entry.page
109
+ }
110
+
111
+ set(url: string, page: CachedPage): void {
112
+ this.remove(url)
113
+ const size = Math.max(1, Buffer.byteLength(page.content, 'utf8'))
114
+ if (size > this.maxBytes) return
115
+ this.entries.set(url, { page, expiresAt: Date.now() + this.ttlMs, size })
116
+ this.bytes += size
117
+ for (const [oldest, entry] of this.entries) {
118
+ if (this.bytes <= this.maxBytes) break
119
+ this.entries.delete(oldest)
120
+ this.bytes -= entry.size
121
+ }
122
+ }
123
+
124
+ private remove(url: string): void {
125
+ const entry = this.entries.get(url)
126
+ if (entry === undefined) return
127
+ this.entries.delete(url)
128
+ this.bytes -= entry.size
129
+ }
130
+ }
@@ -15,11 +15,25 @@
15
15
  * `GuardedFetchProvider` for what "guarded" is doing the work of.
16
16
  */
17
17
 
18
+ /**
19
+ * What a fetch does with a redirect.
20
+ *
21
+ * `follow` (the default) follows each hop after checking it. `same-host`
22
+ * follows only a hop that stays on the same site (same scheme and port,
23
+ * same host up to a leading `www.`, no credentials) and hands any other back
24
+ * unfollowed in {@link WebFetchResult.redirect}, so the caller decides whether
25
+ * to go there: a page that moved to another host is not the page that was
26
+ * asked for.
27
+ */
28
+ export type WebFetchRedirectPolicy = 'follow' | 'same-host'
29
+
18
30
  export interface WebFetchRequest {
19
31
  readonly url: string
20
32
  /** Extra headers. The provider may refuse or strip some; see the guard. */
21
33
  readonly headers?: Readonly<Record<string, string>>
22
34
  readonly signal?: AbortSignal
35
+ /** Defaults to `follow`. A provider that does not know `same-host` follows. */
36
+ readonly redirects?: WebFetchRedirectPolicy
23
37
  }
24
38
 
25
39
  export interface WebFetchResult {
@@ -38,6 +52,12 @@ export interface WebFetchResult {
38
52
  readonly truncated: boolean
39
53
  /** Every URL in the redirect chain, in order, including the final one. */
40
54
  readonly redirects: readonly string[]
55
+ /**
56
+ * A redirect that was not followed (see {@link WebFetchRedirectPolicy}).
57
+ * `url` is where the server pointed, already checked against the same
58
+ * rules as any fetch, and `body` is empty.
59
+ */
60
+ readonly redirect?: { readonly url: string; readonly status: number }
41
61
  }
42
62
 
43
63
  /** Why a fetch was refused before it left the process. */
@@ -62,6 +82,33 @@ export interface WebFetchProvider {
62
82
  fetch(request: WebFetchRequest): Promise<WebFetchResult>
63
83
  }
64
84
 
85
+ /** One question about one page, for a model to answer. */
86
+ export interface WebExtractRequest {
87
+ readonly url: string
88
+ /** What the caller wants from the page. */
89
+ readonly prompt: string
90
+ /** The page as markdown (or its raw text), already cut to the size a model should read. */
91
+ readonly content: string
92
+ /** The system instruction to run `body` under. */
93
+ readonly instruction: string
94
+ /** The user message: the page and the question, built by the tool. */
95
+ readonly body: string
96
+ readonly signal?: AbortSignal
97
+ readonly sessionId: string
98
+ readonly turnId: string
99
+ }
100
+
101
+ /**
102
+ * Answers a prompt from a page with a small model.
103
+ *
104
+ * Supplied by the host: this kernel runs no model of its own here. Resolves
105
+ * the answer, or `undefined` / rejects when it cannot; `web_fetch` then
106
+ * returns the page itself, as it did before there was an extractor.
107
+ */
108
+ export interface WebContentExtractor {
109
+ extract(request: WebExtractRequest): Promise<string | undefined>
110
+ }
111
+
65
112
  export interface WebSearchRequest {
66
113
  readonly query: string
67
114
  readonly limit?: number
@@ -19,3 +19,13 @@
19
19
  */
20
20
  export const AUTO_CONTINUATION_USER_MESSAGE =
21
21
  'Continue exactly where you left off. Do not repeat content you already wrote — pick up at the next token.'
22
+
23
+ /**
24
+ * The nudge for a reply cut off while the model was still reasoning, before
25
+ * it wrote any text or called a tool. There is no partial answer to pick up,
26
+ * so this one asks for a shorter think instead of a continuation. Like
27
+ * {@link AUTO_CONTINUATION_USER_MESSAGE}, it is transparent when a turn's
28
+ * result is assembled.
29
+ */
30
+ export const REASONING_CUT_USER_MESSAGE =
31
+ 'The output limit was reached while you were still reasoning, before any text or tool call. Continue from the conversation above: think much more briefly and give your answer or next tool call well within the limit.'
@@ -34,7 +34,7 @@ export const MAX_CONNECTORS_PER_PLUGIN = 10
34
34
  export const MAX_PERSONAS_PER_PLUGIN = 5
35
35
 
36
36
  /** Hook execution */
37
- export const HOOK_TIMEOUT_MS = 5_000
37
+ export const HOOK_TIMEOUT_MS = 600_000
38
38
 
39
39
  /**
40
40
  * Priority a hook gets when it does not declare one.
@@ -346,6 +346,16 @@ export class AgentManager {
346
346
  return this.startMessage(options, context, listener)
347
347
  }
348
348
 
349
+ /**
350
+ * The refusal for a delegation past the depth limit. It tells the model what
351
+ * to do instead (the reference terminal's rule): a bare limit leaves it
352
+ * retrying the same spawn, while an instruction to do the work itself ends the
353
+ * attempt. The limit is `AgentManagerConfig.maxDepth`; there is no env override.
354
+ */
355
+ private depthLimitMessage(depth: number): string {
356
+ return `Max task depth ${this.config.maxDepth} exceeded (current: ${depth}). Do this task yourself with your own tools instead of delegating it to another agent.`
357
+ }
358
+
349
359
  private async enqueueMessage(
350
360
  options: SendMessageOptions,
351
361
  context: AgentTaskContext,
@@ -353,7 +363,7 @@ export class AgentManager {
353
363
  ): Promise<AgentTask> {
354
364
  context.parentAbortController.signal.throwIfAborted()
355
365
  if (context.depth >= this.config.maxDepth)
356
- throw new Error(`Max task depth ${this.config.maxDepth} exceeded`)
366
+ throw new Error(this.depthLimitMessage(context.depth))
357
367
  if (options.tenantId !== context.tenantId)
358
368
  throw new Error('Tenant mismatch: cross-tenant spawn rejected')
359
369
  const definition = this.registry.getOrThrow(options.agentId)
@@ -527,9 +537,7 @@ export class AgentManager {
527
537
  await options.beforeStart?.()
528
538
  queuedTask?.childAbortController.signal.throwIfAborted()
529
539
  if (context.depth >= this.config.maxDepth) {
530
- throw new Error(
531
- `Max task depth ${this.config.maxDepth} exceeded (current: ${context.depth}). Recursive agent delegation is limited to prevent resource exhaustion.`,
532
- )
540
+ throw new Error(this.depthLimitMessage(context.depth))
533
541
  }
534
542
 
535
543
  if (options.tenantId !== context.tenantId) {
@@ -1,4 +1,7 @@
1
- import { AUTO_CONTINUATION_USER_MESSAGE } from '../../constants/continuation.js'
1
+ import {
2
+ AUTO_CONTINUATION_USER_MESSAGE,
3
+ REASONING_CUT_USER_MESSAGE,
4
+ } from '../../constants/continuation.js'
2
5
  import { EMPTY_TOKEN_USAGE } from '../../constants/limits.js'
3
6
  import { NAMZU } from '../../constants/telemetry/index.js'
4
7
  import { resolveModelPricing } from '../../pricing/index.js'
@@ -567,7 +570,12 @@ export class TurnRecorder {
567
570
  if (msg.content !== null) chunks.push(msg.content)
568
571
  continue
569
572
  }
570
- if (msg.role === 'user' && msg.content === AUTO_CONTINUATION_USER_MESSAGE) continue
573
+ if (
574
+ msg.role === 'user' &&
575
+ (msg.content === AUTO_CONTINUATION_USER_MESSAGE ||
576
+ msg.content === REASONING_CUT_USER_MESSAGE)
577
+ )
578
+ continue
571
579
  break
572
580
  }
573
581
  if (chunks.length > 0) this.#turn.result = chunks.reverse().join('')
@@ -12,6 +12,7 @@ export {
12
12
  runShellHook,
13
13
  SHELL_HOOK_EVENTS,
14
14
  SHELL_HOOKS_PLUGIN_ID,
15
+ shellHookIfMatches,
15
16
  shellHookMatches,
16
17
  shellHookVerdict,
17
18
  } from './shell-hook.js'