@namzu/sdk 61.3.0 → 62.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 (307) hide show
  1. package/CHANGELOG.md +61 -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/bridge/acp/permission.d.ts +8 -0
  6. package/dist/bridge/acp/permission.d.ts.map +1 -1
  7. package/dist/bridge/acp/permission.js.map +1 -1
  8. package/dist/bridge/acp/server.d.ts.map +1 -1
  9. package/dist/bridge/acp/server.js +8 -1
  10. package/dist/bridge/acp/server.js.map +1 -1
  11. package/dist/constants/tools/index.d.ts +2 -0
  12. package/dist/constants/tools/index.d.ts.map +1 -1
  13. package/dist/constants/tools/index.js +23 -0
  14. package/dist/constants/tools/index.js.map +1 -1
  15. package/dist/eval/harness-protection.js +1 -1
  16. package/dist/eval/harness-protection.js.map +1 -1
  17. package/dist/learning/cycle.d.ts +5 -3
  18. package/dist/learning/cycle.d.ts.map +1 -1
  19. package/dist/learning/cycle.js +11 -13
  20. package/dist/learning/cycle.js.map +1 -1
  21. package/dist/learning/guidance.d.ts +74 -0
  22. package/dist/learning/guidance.d.ts.map +1 -0
  23. package/dist/learning/guidance.js +259 -0
  24. package/dist/learning/guidance.js.map +1 -0
  25. package/dist/learning/skill.d.ts +2 -2
  26. package/dist/learning/skill.d.ts.map +1 -1
  27. package/dist/learning/skill.js +6 -6
  28. package/dist/learning/skill.js.map +1 -1
  29. package/dist/learning/store.d.ts.map +1 -1
  30. package/dist/learning/store.js +6 -6
  31. package/dist/learning/store.js.map +1 -1
  32. package/dist/pals/communication/ingress-schema.d.ts +8 -8
  33. package/dist/pals/communication/schema.d.ts +2 -2
  34. package/dist/peers/client.d.ts +2 -3
  35. package/dist/peers/client.d.ts.map +1 -1
  36. package/dist/peers/client.js +2 -3
  37. package/dist/peers/client.js.map +1 -1
  38. package/dist/peers/endpoint.d.ts +1 -2
  39. package/dist/peers/endpoint.d.ts.map +1 -1
  40. package/dist/peers/endpoint.js +1 -2
  41. package/dist/peers/endpoint.js.map +1 -1
  42. package/dist/peers/protocol.d.ts.map +1 -1
  43. package/dist/peers/protocol.js +1 -0
  44. package/dist/peers/protocol.js.map +1 -1
  45. package/dist/prompt/coding-agent-doctrine.d.ts +4 -4
  46. package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
  47. package/dist/prompt/coding-agent-doctrine.js +5 -3
  48. package/dist/prompt/coding-agent-doctrine.js.map +1 -1
  49. package/dist/prompt/index.d.ts +0 -4
  50. package/dist/prompt/index.d.ts.map +1 -1
  51. package/dist/prompt/index.js +0 -2
  52. package/dist/prompt/index.js.map +1 -1
  53. package/dist/provider/fallback.d.ts.map +1 -1
  54. package/dist/provider/fallback.js +3 -0
  55. package/dist/provider/fallback.js.map +1 -1
  56. package/dist/provider/idle-timeout.d.ts.map +1 -1
  57. package/dist/provider/idle-timeout.js +5 -0
  58. package/dist/provider/idle-timeout.js.map +1 -1
  59. package/dist/provider/model-window-suffix.d.ts.map +1 -1
  60. package/dist/provider/model-window-suffix.js +1 -0
  61. package/dist/provider/model-window-suffix.js.map +1 -1
  62. package/dist/provider/retry.d.ts.map +1 -1
  63. package/dist/provider/retry.js +5 -0
  64. package/dist/provider/retry.js.map +1 -1
  65. package/dist/provider/step-stopped.d.ts +19 -0
  66. package/dist/provider/step-stopped.d.ts.map +1 -0
  67. package/dist/provider/step-stopped.js +25 -0
  68. package/dist/provider/step-stopped.js.map +1 -0
  69. package/dist/provider/token-budget.d.ts.map +1 -1
  70. package/dist/provider/token-budget.js +6 -1
  71. package/dist/provider/token-budget.js.map +1 -1
  72. package/dist/public-runtime.d.ts +4 -15
  73. package/dist/public-runtime.d.ts.map +1 -1
  74. package/dist/public-runtime.js +3 -14
  75. package/dist/public-runtime.js.map +1 -1
  76. package/dist/public-tools.d.ts +0 -2
  77. package/dist/public-tools.d.ts.map +1 -1
  78. package/dist/public-tools.js +0 -2
  79. package/dist/public-tools.js.map +1 -1
  80. package/dist/public-types.d.ts +3 -21
  81. package/dist/public-types.d.ts.map +1 -1
  82. package/dist/runtime/query/iteration/index.d.ts +17 -0
  83. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  84. package/dist/runtime/query/iteration/index.js +168 -5
  85. package/dist/runtime/query/iteration/index.js.map +1 -1
  86. package/dist/runtime/query/iteration/phases/per-call-review.d.ts.map +1 -1
  87. package/dist/runtime/query/iteration/phases/per-call-review.js +4 -5
  88. package/dist/runtime/query/iteration/phases/per-call-review.js.map +1 -1
  89. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  90. package/dist/runtime/query/iteration/phases/tool-review.js +6 -3
  91. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  92. package/dist/runtime/query/iteration/stream-turn.d.ts +9 -1
  93. package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
  94. package/dist/runtime/query/iteration/stream-turn.js +48 -2
  95. package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
  96. package/dist/runtime/query/resume-pending.d.ts.map +1 -1
  97. package/dist/runtime/query/resume-pending.js +7 -4
  98. package/dist/runtime/query/resume-pending.js.map +1 -1
  99. package/dist/runtime/query/review-policy.d.ts +40 -8
  100. package/dist/runtime/query/review-policy.d.ts.map +1 -1
  101. package/dist/runtime/query/review-policy.js +139 -24
  102. package/dist/runtime/query/review-policy.js.map +1 -1
  103. package/dist/runtime/query/turn/completion-audit.d.ts +32 -0
  104. package/dist/runtime/query/turn/completion-audit.d.ts.map +1 -0
  105. package/dist/runtime/query/turn/completion-audit.js +56 -0
  106. package/dist/runtime/query/turn/completion-audit.js.map +1 -0
  107. package/dist/runtime/query/turn/stall-monitor.d.ts +78 -0
  108. package/dist/runtime/query/turn/stall-monitor.d.ts.map +1 -0
  109. package/dist/runtime/query/turn/stall-monitor.js +101 -0
  110. package/dist/runtime/query/turn/stall-monitor.js.map +1 -0
  111. package/dist/session/paths.d.ts +1 -2
  112. package/dist/session/paths.d.ts.map +1 -1
  113. package/dist/session/paths.js +1 -4
  114. package/dist/session/paths.js.map +1 -1
  115. package/dist/tools/builtins/bash.d.ts +2 -1
  116. package/dist/tools/builtins/bash.d.ts.map +1 -1
  117. package/dist/tools/builtins/bash.js +5 -2
  118. package/dist/tools/builtins/bash.js.map +1 -1
  119. package/dist/tools/builtins/computer-use.d.ts +80 -0
  120. package/dist/tools/builtins/computer-use.d.ts.map +1 -1
  121. package/dist/tools/builtins/computer-use.js +172 -9
  122. package/dist/tools/builtins/computer-use.js.map +1 -1
  123. package/dist/tools/builtins/content-fingerprint.d.ts +13 -0
  124. package/dist/tools/builtins/content-fingerprint.d.ts.map +1 -1
  125. package/dist/tools/builtins/content-fingerprint.js +25 -0
  126. package/dist/tools/builtins/content-fingerprint.js.map +1 -1
  127. package/dist/tools/builtins/edit.d.ts.map +1 -1
  128. package/dist/tools/builtins/edit.js +7 -2
  129. package/dist/tools/builtins/edit.js.map +1 -1
  130. package/dist/tools/builtins/shell-read-credit.d.ts +15 -0
  131. package/dist/tools/builtins/shell-read-credit.d.ts.map +1 -0
  132. package/dist/tools/builtins/shell-read-credit.js +161 -0
  133. package/dist/tools/builtins/shell-read-credit.js.map +1 -0
  134. package/dist/tools/paths.d.ts +15 -2
  135. package/dist/tools/paths.d.ts.map +1 -1
  136. package/dist/tools/paths.js +17 -2
  137. package/dist/tools/paths.js.map +1 -1
  138. package/dist/turn/evidence-recall.d.ts +1 -1
  139. package/dist/turn/evidence-recall.d.ts.map +1 -1
  140. package/dist/turn/evidence-recall.js +5 -14
  141. package/dist/turn/evidence-recall.js.map +1 -1
  142. package/dist/types/acp/index.d.ts +6 -0
  143. package/dist/types/acp/index.d.ts.map +1 -1
  144. package/dist/types/agent/base.d.ts +4 -0
  145. package/dist/types/agent/base.d.ts.map +1 -1
  146. package/dist/types/hitl/index.d.ts +8 -0
  147. package/dist/types/hitl/index.d.ts.map +1 -1
  148. package/dist/types/hitl/index.js.map +1 -1
  149. package/dist/types/ids/index.d.ts +1 -1
  150. package/dist/types/message/index.d.ts +1 -1
  151. package/dist/types/message/index.d.ts.map +1 -1
  152. package/dist/types/message/index.js +1 -0
  153. package/dist/types/message/index.js.map +1 -1
  154. package/dist/types/provider/interface.d.ts +12 -0
  155. package/dist/types/provider/interface.d.ts.map +1 -1
  156. package/dist/types/session/config.d.ts +25 -0
  157. package/dist/types/session/config.d.ts.map +1 -1
  158. package/dist/types/session/records.d.ts.map +1 -1
  159. package/dist/types/session/records.js +2 -0
  160. package/dist/types/session/records.js.map +1 -1
  161. package/dist/types/session/turn.d.ts +1 -1
  162. package/dist/types/session/turn.d.ts.map +1 -1
  163. package/dist/types/session/turn.js.map +1 -1
  164. package/package.json +1 -1
  165. package/src/agents/QueryAgent.ts +2 -0
  166. package/src/bridge/acp/permission.ts +8 -0
  167. package/src/bridge/acp/server.ts +7 -1
  168. package/src/constants/tools/index.ts +27 -0
  169. package/src/eval/harness-protection.ts +1 -1
  170. package/src/learning/cycle.ts +15 -16
  171. package/src/learning/guidance.ts +339 -0
  172. package/src/learning/skill.ts +7 -7
  173. package/src/learning/store.ts +5 -6
  174. package/src/peers/client.ts +2 -3
  175. package/src/peers/endpoint.ts +1 -2
  176. package/src/peers/protocol.ts +1 -0
  177. package/src/prompt/coding-agent-doctrine.ts +5 -3
  178. package/src/prompt/index.ts +0 -4
  179. package/src/provider/fallback.ts +7 -0
  180. package/src/provider/idle-timeout.ts +8 -0
  181. package/src/provider/model-window-suffix.ts +1 -0
  182. package/src/provider/retry.ts +8 -0
  183. package/src/provider/step-stopped.ts +27 -0
  184. package/src/provider/token-budget.ts +6 -1
  185. package/src/public-runtime.ts +10 -28
  186. package/src/public-tools.ts +0 -3
  187. package/src/public-types.ts +5 -137
  188. package/src/runtime/query/iteration/index.ts +207 -5
  189. package/src/runtime/query/iteration/phases/per-call-review.ts +7 -5
  190. package/src/runtime/query/iteration/phases/tool-review.ts +5 -3
  191. package/src/runtime/query/iteration/stream-turn.ts +51 -1
  192. package/src/runtime/query/resume-pending.ts +11 -4
  193. package/src/runtime/query/review-policy.ts +157 -24
  194. package/src/runtime/query/turn/completion-audit.ts +71 -0
  195. package/src/runtime/query/turn/stall-monitor.ts +158 -0
  196. package/src/session/paths.ts +2 -6
  197. package/src/tools/builtins/bash.ts +6 -2
  198. package/src/tools/builtins/computer-use.ts +245 -7
  199. package/src/tools/builtins/content-fingerprint.ts +25 -0
  200. package/src/tools/builtins/edit.ts +11 -2
  201. package/src/tools/builtins/shell-read-credit.ts +159 -0
  202. package/src/tools/paths.ts +18 -2
  203. package/src/turn/evidence-recall.ts +7 -15
  204. package/src/types/acp/index.ts +6 -0
  205. package/src/types/agent/base.ts +4 -0
  206. package/src/types/hitl/index.ts +8 -0
  207. package/src/types/ids/index.ts +1 -1
  208. package/src/types/message/index.ts +1 -0
  209. package/src/types/provider/interface.ts +13 -0
  210. package/src/types/session/config.ts +27 -0
  211. package/src/types/session/records.ts +2 -0
  212. package/src/types/session/turn.ts +2 -1
  213. package/dist/learning/deprecated.d.ts +0 -86
  214. package/dist/learning/deprecated.d.ts.map +0 -1
  215. package/dist/learning/deprecated.js +0 -22
  216. package/dist/learning/deprecated.js.map +0 -1
  217. package/dist/manager/resident/activity.d.ts +0 -74
  218. package/dist/manager/resident/activity.d.ts.map +0 -1
  219. package/dist/manager/resident/activity.js +0 -125
  220. package/dist/manager/resident/activity.js.map +0 -1
  221. package/dist/manager/resident/agenda.d.ts +0 -167
  222. package/dist/manager/resident/agenda.d.ts.map +0 -1
  223. package/dist/manager/resident/agenda.js +0 -588
  224. package/dist/manager/resident/agenda.js.map +0 -1
  225. package/dist/manager/resident/consumption.d.ts +0 -98
  226. package/dist/manager/resident/consumption.d.ts.map +0 -1
  227. package/dist/manager/resident/consumption.js +0 -237
  228. package/dist/manager/resident/consumption.js.map +0 -1
  229. package/dist/manager/resident/delivery-window.d.ts +0 -28
  230. package/dist/manager/resident/delivery-window.d.ts.map +0 -1
  231. package/dist/manager/resident/delivery-window.js +0 -80
  232. package/dist/manager/resident/delivery-window.js.map +0 -1
  233. package/dist/manager/resident/evidence-recall.d.ts +0 -30
  234. package/dist/manager/resident/evidence-recall.d.ts.map +0 -1
  235. package/dist/manager/resident/evidence-recall.js +0 -297
  236. package/dist/manager/resident/evidence-recall.js.map +0 -1
  237. package/dist/manager/resident/history-disk.d.ts +0 -10
  238. package/dist/manager/resident/history-disk.d.ts.map +0 -1
  239. package/dist/manager/resident/history-disk.js +0 -50
  240. package/dist/manager/resident/history-disk.js.map +0 -1
  241. package/dist/manager/resident/history.d.ts +0 -115
  242. package/dist/manager/resident/history.d.ts.map +0 -1
  243. package/dist/manager/resident/history.js +0 -203
  244. package/dist/manager/resident/history.js.map +0 -1
  245. package/dist/manager/resident/host.d.ts +0 -112
  246. package/dist/manager/resident/host.d.ts.map +0 -1
  247. package/dist/manager/resident/host.js +0 -254
  248. package/dist/manager/resident/host.js.map +0 -1
  249. package/dist/manager/resident/initiative.d.ts +0 -86
  250. package/dist/manager/resident/initiative.d.ts.map +0 -1
  251. package/dist/manager/resident/initiative.js +0 -131
  252. package/dist/manager/resident/initiative.js.map +0 -1
  253. package/dist/manager/resident/loop.d.ts +0 -49
  254. package/dist/manager/resident/loop.d.ts.map +0 -1
  255. package/dist/manager/resident/loop.js +0 -75
  256. package/dist/manager/resident/loop.js.map +0 -1
  257. package/dist/manager/resident/outbox.d.ts +0 -124
  258. package/dist/manager/resident/outbox.d.ts.map +0 -1
  259. package/dist/manager/resident/outbox.js +0 -216
  260. package/dist/manager/resident/outbox.js.map +0 -1
  261. package/dist/manager/resident/proposal.d.ts +0 -65
  262. package/dist/manager/resident/proposal.d.ts.map +0 -1
  263. package/dist/manager/resident/proposal.js +0 -77
  264. package/dist/manager/resident/proposal.js.map +0 -1
  265. package/dist/manager/resident/store.d.ts +0 -108
  266. package/dist/manager/resident/store.d.ts.map +0 -1
  267. package/dist/manager/resident/store.js +0 -205
  268. package/dist/manager/resident/store.js.map +0 -1
  269. package/dist/manager/resident/tool-evidence.d.ts +0 -112
  270. package/dist/manager/resident/tool-evidence.d.ts.map +0 -1
  271. package/dist/manager/resident/tool-evidence.js +0 -289
  272. package/dist/manager/resident/tool-evidence.js.map +0 -1
  273. package/dist/prompt/resident-learning.d.ts +0 -29
  274. package/dist/prompt/resident-learning.d.ts.map +0 -1
  275. package/dist/prompt/resident-learning.js +0 -126
  276. package/dist/prompt/resident-learning.js.map +0 -1
  277. package/dist/prompt/resident-step.d.ts +0 -42
  278. package/dist/prompt/resident-step.d.ts.map +0 -1
  279. package/dist/prompt/resident-step.js +0 -137
  280. package/dist/prompt/resident-step.js.map +0 -1
  281. package/dist/tools/resident-history.d.ts +0 -12
  282. package/dist/tools/resident-history.d.ts.map +0 -1
  283. package/dist/tools/resident-history.js +0 -82
  284. package/dist/tools/resident-history.js.map +0 -1
  285. package/dist/tools/resident-tool-evidence.d.ts +0 -9
  286. package/dist/tools/resident-tool-evidence.d.ts.map +0 -1
  287. package/dist/tools/resident-tool-evidence.js +0 -61
  288. package/dist/tools/resident-tool-evidence.js.map +0 -1
  289. package/src/learning/deprecated.ts +0 -124
  290. package/src/manager/resident/activity.ts +0 -211
  291. package/src/manager/resident/agenda.ts +0 -937
  292. package/src/manager/resident/consumption.ts +0 -335
  293. package/src/manager/resident/delivery-window.ts +0 -99
  294. package/src/manager/resident/evidence-recall.ts +0 -369
  295. package/src/manager/resident/history-disk.ts +0 -63
  296. package/src/manager/resident/history.ts +0 -348
  297. package/src/manager/resident/host.ts +0 -390
  298. package/src/manager/resident/initiative.ts +0 -231
  299. package/src/manager/resident/loop.ts +0 -119
  300. package/src/manager/resident/outbox.ts +0 -341
  301. package/src/manager/resident/proposal.ts +0 -119
  302. package/src/manager/resident/store.ts +0 -279
  303. package/src/manager/resident/tool-evidence.ts +0 -455
  304. package/src/prompt/resident-learning.ts +0 -152
  305. package/src/prompt/resident-step.ts +0 -182
  306. package/src/tools/resident-history.ts +0 -88
  307. package/src/tools/resident-tool-evidence.ts +0 -72
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Watches the steps of one turn for a run that keeps thinking without
3
+ * changing anything, and the per-step reasoning cap that goes with it.
4
+ *
5
+ * Both judge what the turn did, not what the model says it will do. A step
6
+ * changed something when it made a call that was not read-only and did not
7
+ * fail, and the task list does not count: keeping a list is not progress on
8
+ * the task.
9
+ */
10
+
11
+ import type { ReasoningEffort } from '../../../types/provider/index.js'
12
+
13
+ /** What the model is told when a step spent its whole reasoning allowance before acting. */
14
+ export const REASONING_CAPPED_MESSAGE =
15
+ 'Your thinking for that step reached its limit before you acted, so it was cut short. Act now on what you already know: make the first change, or run the one command that tells you what you still need. Refine as you go; do not plan the whole task again.'
16
+
17
+ /** What the model is told when a reply held thinking but no answer and no action. */
18
+ export const REASONING_ONLY_MESSAGE =
19
+ 'Your last reply held thinking only: no answer and no action. Continue now: give your answer, or make the call that moves the task forward.'
20
+
21
+ /** What the model is told when several steps passed with nothing changed. */
22
+ export const STALL_NUDGE_MESSAGE =
23
+ 'Several steps have passed and nothing has changed yet. Begin changing things now: make the first edit you are sure of, or run the one check that settles what you are unsure of. Reading more or planning again will not move the task.'
24
+
25
+ /**
26
+ * Said again, on one request only, when a step must act but the provider
27
+ * refuses a forced tool choice: the instruction travels in words instead.
28
+ */
29
+ export const FORCED_STEP_MESSAGE =
30
+ 'This reply must be a tool call, not more planning. Make the call that moves the task forward now.'
31
+
32
+ /** The default per-step reasoning allowance, in tokens. */
33
+ export const DEFAULT_MAX_REASONING_TOKENS = 24_000
34
+
35
+ /** Characters a token stands for when counting streamed reasoning. */
36
+ const CHARS_PER_TOKEN = 4
37
+
38
+ /** Efforts at which the per-step reasoning cap applies unless a host says otherwise. */
39
+ const CAPPED_EFFORTS: ReadonlySet<ReasoningEffort> = new Set(['medium', 'high', 'xhigh', 'max'])
40
+
41
+ /** How a turn bounds its reasoning. */
42
+ export interface ReasoningPaceConfig {
43
+ /**
44
+ * The most reasoning one step may stream, in tokens. `0` removes the cap.
45
+ * When absent the cap is {@link DEFAULT_MAX_REASONING_TOKENS} at medium and
46
+ * higher effort and absent at lower effort or none.
47
+ */
48
+ readonly maxTokens?: number
49
+ /** Tunes the stall monitor, or `false` turns it off. */
50
+ readonly stall?: false | StallConfig
51
+ }
52
+
53
+ /** The characters of reasoning a step may stream before it is cut, or `undefined` for no cap. */
54
+ export function reasoningCapChars(
55
+ config: ReasoningPaceConfig | undefined,
56
+ effort: ReasoningEffort | undefined,
57
+ ): number | undefined {
58
+ const configured = config?.maxTokens
59
+ if (configured !== undefined) {
60
+ return configured > 0 ? configured * CHARS_PER_TOKEN : undefined
61
+ }
62
+ if (effort === undefined || !CAPPED_EFFORTS.has(effort)) return undefined
63
+ return DEFAULT_MAX_REASONING_TOKENS * CHARS_PER_TOKEN
64
+ }
65
+
66
+ /** Tools whose calls keep the task list; a turn that only does these has changed nothing. */
67
+ const TASK_LIST_TOOLS = new Set([
68
+ 'task_create',
69
+ 'task_update',
70
+ 'task_list',
71
+ 'task_get',
72
+ 'task_delete',
73
+ 'task_output',
74
+ 'task_stop',
75
+ ])
76
+
77
+ /**
78
+ * Whether a finished call changed something. A failed call changed nothing, a
79
+ * task-list call is not progress, and a call the tool declares read-only only
80
+ * looked. A tool that does not say is taken to have changed things.
81
+ */
82
+ export function callChangedState(
83
+ toolName: string,
84
+ readOnly: boolean | undefined,
85
+ isError: boolean | undefined,
86
+ ): boolean {
87
+ if (isError) return false
88
+ if (TASK_LIST_TOOLS.has(toolName)) return false
89
+ return readOnly !== true
90
+ }
91
+
92
+ /** When the monitor speaks. All counts are of consecutive steps that changed nothing. */
93
+ export interface StallConfig {
94
+ /** Steps without a change after which the model is nudged. Default 12. */
95
+ readonly nudgeAfterSteps?: number
96
+ /** Reasoning characters streamed without a change after which the model is nudged. Default 80,000. */
97
+ readonly nudgeAfterReasoningChars?: number
98
+ /** Further steps without a change after the nudge that make the next step an action step. Default 5. */
99
+ readonly forceAfterSteps?: number
100
+ }
101
+
102
+ /** What the monitor wants done after a step. */
103
+ export type StallDirective =
104
+ | { readonly kind: 'none' }
105
+ | { readonly kind: 'nudge'; readonly text: string }
106
+ | { readonly kind: 'force' }
107
+
108
+ /** One finished step as the monitor sees it. */
109
+ export interface StallObservation {
110
+ readonly reasoningChars: number
111
+ readonly changedState: boolean
112
+ }
113
+
114
+ export class StallMonitor {
115
+ private readonly nudgeAfterSteps: number
116
+ private readonly nudgeAfterChars: number
117
+ private readonly forceAfterSteps: number
118
+ private idleSteps = 0
119
+ private idleChars = 0
120
+ private nudged = false
121
+ private stepsSinceNudge = 0
122
+
123
+ constructor(config?: StallConfig) {
124
+ this.nudgeAfterSteps = config?.nudgeAfterSteps ?? 12
125
+ this.nudgeAfterChars = config?.nudgeAfterReasoningChars ?? 80_000
126
+ this.forceAfterSteps = config?.forceAfterSteps ?? 5
127
+ }
128
+
129
+ observe(step: StallObservation): StallDirective {
130
+ if (step.changedState) {
131
+ this.reset()
132
+ return { kind: 'none' }
133
+ }
134
+ this.idleSteps += 1
135
+ this.idleChars += step.reasoningChars
136
+ if (!this.nudged) {
137
+ if (this.idleSteps >= this.nudgeAfterSteps || this.idleChars >= this.nudgeAfterChars) {
138
+ this.nudged = true
139
+ this.stepsSinceNudge = 0
140
+ return { kind: 'nudge', text: STALL_NUDGE_MESSAGE }
141
+ }
142
+ return { kind: 'none' }
143
+ }
144
+ this.stepsSinceNudge += 1
145
+ if (this.stepsSinceNudge >= this.forceAfterSteps) {
146
+ this.reset()
147
+ return { kind: 'force' }
148
+ }
149
+ return { kind: 'none' }
150
+ }
151
+
152
+ private reset(): void {
153
+ this.idleSteps = 0
154
+ this.idleChars = 0
155
+ this.nudged = false
156
+ this.stepsSinceNudge = 0
157
+ }
158
+ }
@@ -28,7 +28,7 @@ import { uuidv7 } from '../utils/uuidv7.js'
28
28
  * index.sqlite rebuildable index over every log
29
29
  * projects/<slug>/
30
30
  * project.json the project id, minted once
31
- * memory/ residents/ worktrees/<label>/
31
+ * memory/ worktrees/<label>/
32
32
  * <session-id>.jsonl the session log (source of truth)
33
33
  * <session-id>/
34
34
  * subagents/<child-id>.jsonl, <child-id>.meta.json, <child-id>/subagents/…
@@ -96,7 +96,7 @@ export class SessionPathError extends Error {
96
96
  }
97
97
 
98
98
  const SLUG = /^[A-Za-z0-9-]+$/
99
- /** Worktree labels and resident agent keys: one plain segment, never `.` or `..`. */
99
+ /** Worktree labels: one plain segment, never `.` or `..`. */
100
100
  const NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/
101
101
  /** Longest slug before a hash suffix replaces the tail; well under the 255-byte segment limit. */
102
102
  const MAX_SLUG = 200
@@ -301,10 +301,6 @@ export class SessionPaths {
301
301
  return join(this.projectDir(), 'memory')
302
302
  }
303
303
 
304
- residentDir(agentKey: string): string {
305
- return join(this.projectDir(), 'residents', checkName(agentKey, 'resident agent key'))
306
- }
307
-
308
304
  /** The `GitWorktreeManager` default root, or one labelled worktree inside it. */
309
305
  worktrees(label?: string): string {
310
306
  const root = join(this.projectDir(), 'worktrees')
@@ -2,7 +2,7 @@ import { rm } from 'node:fs/promises'
2
2
  import { z } from 'zod'
3
3
  import { isReadOnlyCommandLine } from '../../authorization/readonly-commands.js'
4
4
  import { SANDBOX_KILL_GRACE_MS } from '../../constants/sandbox/index.js'
5
- import { DANGEROUS_PATTERNS } from '../../constants/tools/index.js'
5
+ import { DANGEROUS_PATTERNS, dangerousCommandReason } from '../../constants/tools/index.js'
6
6
  import { killTree } from '../../process/kill-tree.js'
7
7
  import { genericLabel } from '../../registry/tool/presentation.js'
8
8
  import { cancelCauseOf } from '../../types/session/cancel-cause.js'
@@ -33,6 +33,7 @@ import {
33
33
  } from './bash-cwd.js'
34
34
  import { judgeExit } from './exit-semantics.js'
35
35
  import { jsonStringEscapes } from './json-string-hint.js'
36
+ import { creditShellReads } from './shell-read-credit.js'
36
37
  import { leadingLongSleep, longSleepRefusal } from './sleep-guard.js'
37
38
 
38
39
  // Namzu owns its own bash timeout knob — `NAMZU_BASH_TIMEOUT_MS` — and
@@ -517,6 +518,8 @@ export function execHostShell(
517
518
  })
518
519
  }
519
520
 
521
+ export { dangerousCommandReason }
522
+
520
523
  /** The dangerous-pattern check `bash` applies, shared so the `monitor` tool refuses the same lines. */
521
524
  export function isDangerousCommand(command: string): boolean {
522
525
  return DANGEROUS_PATTERNS.some((pattern) => pattern.test(command))
@@ -592,7 +595,7 @@ const BashToolBase = defineTool({
592
595
  return {
593
596
  success: false,
594
597
  output: '',
595
- error: `Dangerous command blocked: "${input.command}"`,
598
+ error: `Dangerous command blocked: "${input.command}". Why: ${dangerousCommandReason(input.command)}. Nothing ran. Use a narrower command that avoids it, or ask the user to run this one.`,
596
599
  facts: { kind: 'failure', reason: 'blocked', subject: 'bash' },
597
600
  }
598
601
  }
@@ -898,6 +901,7 @@ const BashToolBase = defineTool({
898
901
  }
899
902
  }
900
903
  const reset = await settle()
904
+ await creditShellReads(input.command, context, startDirectory)
901
905
  return {
902
906
  success: true,
903
907
  output: withResetNotice(formatShellOutput(stdout, stderr) || '(no output)', reset),
@@ -91,11 +91,72 @@ const MAX_UI_SNAPSHOT_CHARS = 14_000
91
91
  /** The refs of earlier snapshots the tool still resolves for the host. */
92
92
  const MAX_HELD_UI_REFS = 4_000
93
93
 
94
+ /** One control an advisor may be asked to choose among. */
95
+ export interface ComputerUseAdvisorCandidate {
96
+ /** The ref the model was shown for it. */
97
+ readonly ref: string
98
+ /** The control as the model reads it, such as `Button "Save"`. */
99
+ readonly description: string
100
+ }
101
+
102
+ /** What an advisor is asked: which of these controls does this intent name? */
103
+ export interface ComputerUseAdvisorRequest {
104
+ /** What the model wants done, in its own words. */
105
+ readonly intent: string
106
+ readonly candidates: readonly ComputerUseAdvisorCandidate[]
107
+ /**
108
+ * The ref the model itself chose for this intent, when it chose one. Then the
109
+ * advisor is only watching: the answer is not acted on.
110
+ */
111
+ readonly chosen?: string
112
+ }
113
+
114
+ /** An advisor's answer: the candidates in order of fit, best first, each with how sure it is (0 to 1). */
115
+ export interface ComputerUseAdvice {
116
+ readonly ranked: readonly {
117
+ readonly ref: string
118
+ readonly confidence: number
119
+ }[]
120
+ }
121
+
122
+ /**
123
+ * A fast second opinion on which control an intent names, for hosts that run a
124
+ * small decision model beside the main one. The tool never depends on it: an
125
+ * advisor that fails, is slow or is unsure is the same as none.
126
+ */
127
+ export interface ComputerUseAdvisor {
128
+ /**
129
+ * Whether the tool may act on the answer. Then the model can name a control
130
+ * by words (`ui_do`) and the tool chooses; when false the advisor is asked
131
+ * about the controls the model chose by ref (`ui_act` with `intent`) and its
132
+ * answer is not used.
133
+ */
134
+ readonly acts: boolean
135
+ pick(
136
+ request: ComputerUseAdvisorRequest,
137
+ signal?: AbortSignal,
138
+ ): Promise<ComputerUseAdvice | undefined>
139
+ }
140
+
141
+ /** The least confidence in the best control for the tool to act on it. */
142
+ const ADVICE_MIN_CONFIDENCE = 0.85
143
+ /** How far the best control must lead the next one. */
144
+ const ADVICE_MIN_MARGIN = 0.4
145
+ /** The most controls an advisor is asked about at once. */
146
+ const MAX_ADVISED_CANDIDATES = 255
147
+
94
148
  /**
95
149
  * How `createComputerUseTool` sizes screenshots, paces actions and bounds a
96
150
  * batch. Every field is optional.
97
151
  */
98
152
  export interface ComputerUseToolOptions {
153
+ /**
154
+ * A second opinion on which control an intent names; see
155
+ * {@link ComputerUseAdvisor}. With one that `acts`, the tool offers `ui_do`
156
+ * (name a control by words); with any, `ui_act` accepts an `intent`. Absent,
157
+ * neither exists and the tool is exactly what it was.
158
+ */
159
+ readonly advisor?: ComputerUseAdvisor
99
160
  /**
100
161
  * What every screenshot and zoom image is fitted to before the model sees
101
162
  * it. Default {@link STANDARD_SCREENSHOT_LIMITS}, which every current vision
@@ -270,6 +331,16 @@ const uiActSchema = z.object({
270
331
  ref: z.string().min(1),
271
332
  action: z.enum(UI_ACTIONS),
272
333
  value: z.string().optional(),
334
+ /** What this is for, in a few words; only read when the tool has an advisor. */
335
+ intent: z.string().min(1).max(200).optional(),
336
+ })
337
+ const uiDoSchema = z.object({
338
+ type: z.literal('ui_do'),
339
+ /** The control, in words: "the Save button", "the name field". */
340
+ target: z.string().min(1).max(200),
341
+ action: z.enum(UI_ACTIONS),
342
+ value: z.string().optional(),
343
+ window_id: z.string().min(1).optional(),
273
344
  })
274
345
 
275
346
  /** What a batch may carry: everything but the image-returning actions and another batch. */
@@ -285,6 +356,7 @@ const batchItemSchema = z.discriminatedUnion('type', [
285
356
  listWindowsSchema,
286
357
  focusWindowSchema,
287
358
  uiActSchema,
359
+ uiDoSchema,
288
360
  ])
289
361
 
290
362
  const withFrame = { screenshot_id: screenshotIdSchema }
@@ -308,6 +380,7 @@ const actionSchema = z.discriminatedUnion('type', [
308
380
  uiSnapshotSchema,
309
381
  waitForSchema,
310
382
  uiActSchema.extend(withFrame),
383
+ uiDoSchema.extend(withFrame),
311
384
  z.object({
312
385
  type: z.literal('batch'),
313
386
  actions: z.array(batchItemSchema).min(1),
@@ -355,6 +428,7 @@ const ALL_ACTIONS: readonly ToolActionType[] = [
355
428
  'ui_snapshot',
356
429
  'wait_for',
357
430
  'ui_act',
431
+ 'ui_do',
358
432
  'batch',
359
433
  ]
360
434
 
@@ -376,6 +450,7 @@ const DESTRUCTIVE_ACTIONS = new Set<string>([
376
450
  'key',
377
451
  'scroll',
378
452
  'ui_act',
453
+ 'ui_do',
379
454
  ])
380
455
 
381
456
  /** Observations that show the model what is on the screen, on their own. */
@@ -454,6 +529,7 @@ function requiredCapability(type: string): keyof ComputerUseCapabilities | null
454
529
  case 'ui_snapshot':
455
530
  case 'wait_for':
456
531
  case 'ui_act':
532
+ case 'ui_do':
457
533
  return 'uiTree'
458
534
  default:
459
535
  return null
@@ -481,7 +557,11 @@ function windowCaptureAvailable(host: ComputerUseHost, caps: ComputerUseCapabili
481
557
  )
482
558
  }
483
559
 
484
- function availableActions(host: ComputerUseHost, caps: ComputerUseCapabilities): ToolActionType[] {
560
+ function availableActions(
561
+ host: ComputerUseHost,
562
+ caps: ComputerUseCapabilities,
563
+ advisor?: ComputerUseAdvisor,
564
+ ): ToolActionType[] {
485
565
  const displayScreenshot = hostActionAvailable(caps, 'screenshot')
486
566
  const window = windowCaptureAvailable(host, caps)
487
567
  const screenshot = displayScreenshot || window
@@ -516,6 +596,8 @@ function availableActions(host: ComputerUseHost, caps: ComputerUseCapabilities):
516
596
  case 'wait_for':
517
597
  case 'ui_act':
518
598
  return uiTree
599
+ case 'ui_do':
600
+ return uiTree && advisor?.acts === true
519
601
  case 'batch':
520
602
  return false
521
603
  default:
@@ -550,8 +632,9 @@ function buildDescription(
550
632
  host: ComputerUseHost,
551
633
  caps: ComputerUseCapabilities,
552
634
  settings: ResolvedSettings,
635
+ advisor?: ComputerUseAdvisor,
553
636
  ): string {
554
- const available = availableActions(host, caps)
637
+ const available = availableActions(host, caps, advisor)
555
638
  const unavailable = unavailableHostActions(host, caps)
556
639
  const intro = hostActionAvailable(caps, 'screenshot')
557
640
  ? `Controls the user's desktop on a ${caps.displayServer} host: screenshots, mouse and keyboard, for GUI tasks.`
@@ -597,6 +680,10 @@ function buildDescription(
597
680
  lines.push(
598
681
  'To start a Windows program, a shell command (Start-Process <program>, or cmd.exe /c start <program>) is surest when you have a shell tool. The Start menu searches by display names in the system language, and ENTER on a name it does not find opens a web search in the browser.',
599
682
  )
683
+ if (available.includes('ui_do'))
684
+ lines.push(
685
+ 'ui_do {target, action, value, window_id} acts on a control named in words ("the Save button") among those the latest ui_snapshot showed: a second model chooses it and the tool says which control it acted on. When it is not sure it acts on nothing and lists the closest controls: then use ui_act on one of their refs. Use ui_act with a ref whenever you already have one.',
686
+ )
600
687
  if (available.includes('list_windows'))
601
688
  lines.push(
602
689
  'list_windows names the open windows with their ids; focus_window brings one to the front.',
@@ -695,6 +782,7 @@ const ACTION_REQUIREMENTS: Readonly<Record<ToolActionType, string>> = {
695
782
  'ui_snapshot takes an optional window_id, root (a ref to open) and query (text to find)',
696
783
  wait_for: 'wait_for needs until and match; role, window_id and timeout_ms are optional',
697
784
  ui_act: 'ui_act needs ref and action, and value for set_value',
785
+ ui_do: 'ui_do needs target (the control, in words) and action, and value for set_value',
698
786
  batch: 'batch needs actions',
699
787
  }
700
788
 
@@ -712,10 +800,11 @@ function hostModelSchema(
712
800
  host: ComputerUseHost,
713
801
  caps: ComputerUseCapabilities,
714
802
  settings: ResolvedSettings,
803
+ advisor?: ComputerUseAdvisor,
715
804
  ): Record<string, unknown> {
716
805
  // An unavailable host remains a diagnostic tool. Avoid invalid empty enums
717
806
  // on provider wires; every execution is still refused before host access.
718
- const offered = availableActions(host, caps)
807
+ const offered = availableActions(host, caps, advisor)
719
808
  const actions = offered.length > 0 ? offered : ALL_ACTIONS.filter((a) => a !== 'batch')
720
809
  const items = actions.filter((action) => !OUTSIDE_BATCH.has(action))
721
810
  const buttons = new Set<string>()
@@ -841,6 +930,30 @@ function hostModelSchema(
841
930
  type: 'string',
842
931
  description: 'The text set_value puts in the control.',
843
932
  }
933
+ if (advisor)
934
+ fields.intent = {
935
+ type: 'string',
936
+ description:
937
+ 'For ui_act: what this is for, in a few words ("save the file"). Optional; it only helps check the choice.',
938
+ }
939
+ }
940
+ if (present.includes('ui_do')) {
941
+ fields.target = {
942
+ type: 'string',
943
+ description:
944
+ 'For ui_do: the control in words, such as "the Save button" or "the name field". The tool picks the control from the latest ui_snapshot.',
945
+ }
946
+ if (!present.includes('ui_act')) {
947
+ fields.action = {
948
+ type: 'string',
949
+ enum: [...UI_ACTIONS],
950
+ description: 'What ui_do does to the control.',
951
+ }
952
+ fields.value = {
953
+ type: 'string',
954
+ description: 'The text set_value puts in the control.',
955
+ }
956
+ }
844
957
  }
845
958
  if (!forItems && present.includes('zoom'))
846
959
  fields.region = {
@@ -963,6 +1076,33 @@ function uiActLabel(
963
1076
  }
964
1077
  }
965
1078
 
1079
+ /** What a ui_do does, over the words the model named the control with. */
1080
+ function uiDoLabel(input: {
1081
+ readonly target: string
1082
+ readonly action: UiElementAction
1083
+ readonly value?: string
1084
+ }): string {
1085
+ const target = quotedText(input.target)
1086
+ switch (input.action) {
1087
+ case 'invoke':
1088
+ return `Press the control for ${target}`
1089
+ case 'set_value':
1090
+ return `Set the control for ${target} to ${quotedText(input.value ?? '')}`
1091
+ case 'toggle':
1092
+ return `Toggle the control for ${target}`
1093
+ case 'select':
1094
+ return `Select the control for ${target}`
1095
+ case 'expand':
1096
+ return `Expand the control for ${target}`
1097
+ case 'collapse':
1098
+ return `Collapse the control for ${target}`
1099
+ case 'focus':
1100
+ return `Focus the control for ${target}`
1101
+ case 'scroll_into_view':
1102
+ return `Scroll to the control for ${target}`
1103
+ }
1104
+ }
1105
+
966
1106
  /**
967
1107
  * Human activity text for one action; the raw input remains the model-facing
968
1108
  * record. `describe` names a ui_act ref's control when the tool knows it.
@@ -1008,6 +1148,8 @@ function itemLabel(
1008
1148
  return `Wait until ${input.until.replace('_', ' ')} ${quotedText(input.match)}`
1009
1149
  case 'ui_act':
1010
1150
  return uiActLabel(input, describe)
1151
+ case 'ui_do':
1152
+ return uiDoLabel(input)
1011
1153
  case 'batch':
1012
1154
  return batchLabel(input.actions, describe)
1013
1155
  }
@@ -1349,7 +1491,8 @@ export function createComputerUseTool(
1349
1491
  }
1350
1492
  : host.capabilities
1351
1493
  const frames = new ScreenshotFrames()
1352
- const available = new Set<string>(availableActions(host, caps))
1494
+ const available = new Set<string>(availableActions(host, caps, options.advisor))
1495
+ const advisor = options.advisor
1353
1496
 
1354
1497
  // The controls of the latest ui_snapshot, by the ref the model was shown.
1355
1498
  // A ref names a control, not one reading of it: the same host ref keeps the
@@ -1357,6 +1500,8 @@ export function createComputerUseTool(
1357
1500
  // control. Whether the control is still there is the host's answer.
1358
1501
  const uiRefs = new Map<string, UiRef>()
1359
1502
  const uiRefByHost = new Map<string, string>()
1503
+ // The refs the latest reading of each window showed, so an advisor is asked about what is there now.
1504
+ const shownRefs = new Map<string, Set<string>>()
1360
1505
  let uiSnapshots = 0
1361
1506
  let uiRefCount = 0
1362
1507
  let uiSnapshotWindowId: string | undefined
@@ -1371,6 +1516,30 @@ export function createComputerUseTool(
1371
1516
  }
1372
1517
  const label = (input: BatchItem | ActionInput): string => itemLabel(input, describeUiRef)
1373
1518
 
1519
+ /** The controls of a window's latest reading that offer an action (any, when none is named), as an advisor is shown them. */
1520
+ const advisedCandidates = (
1521
+ windowId: string | undefined,
1522
+ action?: UiElementAction,
1523
+ ): ComputerUseAdvisorCandidate[] => {
1524
+ const shown = shownRefs.get(windowId ?? '')
1525
+ const out: ComputerUseAdvisorCandidate[] = []
1526
+ if (!shown) return out
1527
+ for (const ref of shown) {
1528
+ const entry = uiRefs.get(ref)
1529
+ if (!entry) continue
1530
+ const offered = entry.element.actions ?? []
1531
+ if (offered.length === 0) continue
1532
+ if (action !== undefined && !offered.includes(action)) continue
1533
+ const text = uiElementText(entry.element)
1534
+ out.push({
1535
+ ref,
1536
+ description: text.length > 80 ? `${text.slice(0, 79)}…` : text,
1537
+ })
1538
+ if (out.length >= MAX_ADVISED_CANDIDATES) break
1539
+ }
1540
+ return out
1541
+ }
1542
+
1374
1543
  const refusal = (type: string): string | null => {
1375
1544
  if (available.has(type)) return null
1376
1545
  const required = requiredCapability(type)
@@ -1705,6 +1874,14 @@ export function createComputerUseTool(
1705
1874
  if (item.action === 'set_value' && item.value === undefined)
1706
1875
  throw new StepFailure('set_value needs value, the text to put in the control')
1707
1876
  return step(async () => {
1877
+ // A second opinion on the model's own choice: asked beside the action, never waited for.
1878
+ if (advisor && item.intent !== undefined) {
1879
+ const candidates = advisedCandidates(entry.windowId)
1880
+ if (candidates.length >= 2)
1881
+ void advisor
1882
+ .pick({ intent: item.intent, candidates, chosen: item.ref })
1883
+ .catch(() => undefined)
1884
+ }
1708
1885
  let outcome: UiActResult
1709
1886
  try {
1710
1887
  outcome = await (host.uiAct as NonNullable<ComputerUseHost['uiAct']>)(
@@ -1728,6 +1905,62 @@ export function createComputerUseTool(
1728
1905
  return deliveryOutcome(outcome)
1729
1906
  })
1730
1907
  }
1908
+ case 'ui_do': {
1909
+ if (!advisor?.acts)
1910
+ throw new StepFailure('ui_do is not available here; use ui_act with a ref')
1911
+ if (item.action === 'set_value' && item.value === undefined)
1912
+ throw new StepFailure('set_value needs value, the text to put in the control')
1913
+ if (uiSnapshots === 0)
1914
+ throw new StepFailure(
1915
+ 'no ui_snapshot has been taken yet; take one, because ui_do chooses among the controls it showed',
1916
+ )
1917
+ const windowId = item.window_id ?? uiSnapshotWindowId
1918
+ const candidates = advisedCandidates(windowId, item.action)
1919
+ if (candidates.length === 0)
1920
+ throw new StepFailure(
1921
+ `no control in the latest ui_snapshot${windowId ? ` of window ${windowId}` : ''} offers ${item.action}; take a ui_snapshot and act with ui_act on a ref`,
1922
+ )
1923
+ const target = item.target
1924
+ return step(async (signal) => {
1925
+ let advice: ComputerUseAdvice | undefined
1926
+ try {
1927
+ advice = await advisor.pick({ intent: target, candidates }, signal)
1928
+ } catch {
1929
+ if (signal?.aborted) throw new StepFailure('cancelled')
1930
+ }
1931
+ const [best, next] = advice?.ranked ?? []
1932
+ const sure =
1933
+ best !== undefined &&
1934
+ best.confidence >= ADVICE_MIN_CONFIDENCE &&
1935
+ best.confidence - (next?.confidence ?? 0) >= ADVICE_MIN_MARGIN &&
1936
+ uiRefs.has(best.ref)
1937
+ if (!best || !sure) {
1938
+ const close = (advice?.ranked ?? [])
1939
+ .filter((entry) => uiRefs.has(entry.ref))
1940
+ .slice(0, 3)
1941
+ .map((entry) => describeUiRef(entry.ref))
1942
+ throw deliveryFailure({
1943
+ delivery: 'not_delivered',
1944
+ retrySafety: 'safe',
1945
+ detail: `ui_do was not sure which control is ${quotedText(target)}, so it acted on nothing.${close.length > 0 ? ` Closest: ${close.join('; ')}.` : ''} Use ui_act on one of the refs, or ui_snapshot {query: ...} to find it.`,
1946
+ })
1947
+ }
1948
+ const chosen = describeUiRef(best.ref) ?? best.ref
1949
+ const ran = await plan(
1950
+ {
1951
+ type: 'ui_act',
1952
+ ref: best.ref,
1953
+ action: item.action,
1954
+ ...(item.value !== undefined ? { value: item.value } : {}),
1955
+ },
1956
+ undefined,
1957
+ ).run(signal)
1958
+ const prefix = `chose ${chosen} for ${quotedText(target)} (${Math.round(best.confidence * 100)}% sure)`
1959
+ return typeof ran === 'string'
1960
+ ? `${prefix}: ${ran}`
1961
+ : { ...ran, note: `${prefix}: ${ran.note}` }
1962
+ })
1963
+ }
1731
1964
  }
1732
1965
  }
1733
1966
 
@@ -1822,6 +2055,11 @@ export function createComputerUseTool(
1822
2055
  }
1823
2056
  }
1824
2057
  uiSnapshotWindowId = snapshot.windowId ?? requestedWindowId
2058
+ {
2059
+ const key = uiSnapshotWindowId ?? ''
2060
+ const now = new Set(refs.keys())
2061
+ shownRefs.set(key, snapshot.region ? new Set([...(shownRefs.get(key) ?? []), ...now]) : now)
2062
+ }
1825
2063
  if (uiSnapshotWindowId !== undefined) treeUsable.set(uiSnapshotWindowId, refs.size > 0)
1826
2064
  uiSnapshotFrame = frames.latest()
1827
2065
  const window =
@@ -2347,7 +2585,7 @@ export function createComputerUseTool(
2347
2585
  if (step.item.type === 'focus_window' && windowCaptureAvailable(host, caps))
2348
2586
  postWindowId = step.item.window_id
2349
2587
  else if (
2350
- step.item.type === 'ui_act' &&
2588
+ (step.item.type === 'ui_act' || step.item.type === 'ui_do') &&
2351
2589
  uiSnapshotWindowId &&
2352
2590
  !explicitDisplayFrame &&
2353
2591
  windowCaptureAvailable(host, caps)
@@ -2504,9 +2742,9 @@ export function createComputerUseTool(
2504
2742
 
2505
2743
  const tool = defineTool({
2506
2744
  name: COMPUTER_USE_TOOL_NAME,
2507
- description: buildDescription(host, caps, settings),
2745
+ description: buildDescription(host, caps, settings, options.advisor),
2508
2746
  inputSchema: actionSchema,
2509
- modelInputSchema: hostModelSchema(host, caps, settings),
2747
+ modelInputSchema: hostModelSchema(host, caps, settings, options.advisor),
2510
2748
  validationErrorHint: `Action requirements: ${ALL_ACTIONS.map((action) => actionRequirement(action, host, caps)).join('; ')}. A batch is {"type":"batch","actions":[...]} with at most ${settings.maxBatchActions} actions and no screenshot, zoom, ui_snapshot or batch inside.`,
2511
2749
  category: 'custom',
2512
2750
  permissions: [],
@@ -34,6 +34,31 @@ export function unreadFileError(path: string, operation: 'edit' | 'write' = 'edi
34
34
  return `${path} has not been read yet. Read it first, then redo the ${operation}. Nothing was written.`
35
35
  }
36
36
 
37
+ /** The longest file `edit` hands back when it refuses one the session never read, in lines. */
38
+ export const UNREAD_EDIT_SHOWN_MAX_LINES = 600
39
+ /** The longest file `edit` hands back when it refuses one the session never read, in characters. */
40
+ export const UNREAD_EDIT_SHOWN_MAX_CHARS = 24_000
41
+
42
+ /**
43
+ * What `edit` tells the agent when the session never read the file and the
44
+ * file is small enough to hand over: the refusal and the numbered contents.
45
+ * The caller records the read, so the repeated edit is admitted. `undefined`
46
+ * when the file is too large or is not text.
47
+ */
48
+ export function unreadEditShownError(path: string, content: string): string | undefined {
49
+ if (content.length > UNREAD_EDIT_SHOWN_MAX_CHARS || content.includes('\u0000')) return undefined
50
+ const lines = content.split('\n')
51
+ if (lines.length > 1 && lines[lines.length - 1] === '') lines.pop()
52
+ if (lines.length > UNREAD_EDIT_SHOWN_MAX_LINES) return undefined
53
+ const numbered = lines.map((line, i) => `${i + 1}\t${line}`).join('\n')
54
+ return `${path} had not been read in this session, so this edit was not applied. It has now been read for you; its current contents follow. Repeat the edit against them. Nothing was written.\n\n${numbered}`
55
+ }
56
+
57
+ /** What `edit` tells the agent when the file was never read and is too large to hand over. */
58
+ export function unreadEditTooLargeError(path: string): string {
59
+ return `${path} has not been read in this session, so the edit was not applied. Read it with the read tool (one call, no range), then repeat the edit.`
60
+ }
61
+
37
62
  /**
38
63
  * Whether the file at `key` has moved on disk since the session read it.
39
64
  *