@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.
- package/CHANGELOG.md +61 -0
- package/dist/agents/QueryAgent.d.ts.map +1 -1
- package/dist/agents/QueryAgent.js +2 -0
- package/dist/agents/QueryAgent.js.map +1 -1
- package/dist/bridge/acp/permission.d.ts +8 -0
- package/dist/bridge/acp/permission.d.ts.map +1 -1
- package/dist/bridge/acp/permission.js.map +1 -1
- package/dist/bridge/acp/server.d.ts.map +1 -1
- package/dist/bridge/acp/server.js +8 -1
- package/dist/bridge/acp/server.js.map +1 -1
- package/dist/constants/tools/index.d.ts +2 -0
- package/dist/constants/tools/index.d.ts.map +1 -1
- package/dist/constants/tools/index.js +23 -0
- package/dist/constants/tools/index.js.map +1 -1
- package/dist/eval/harness-protection.js +1 -1
- package/dist/eval/harness-protection.js.map +1 -1
- package/dist/learning/cycle.d.ts +5 -3
- package/dist/learning/cycle.d.ts.map +1 -1
- package/dist/learning/cycle.js +11 -13
- package/dist/learning/cycle.js.map +1 -1
- package/dist/learning/guidance.d.ts +74 -0
- package/dist/learning/guidance.d.ts.map +1 -0
- package/dist/learning/guidance.js +259 -0
- package/dist/learning/guidance.js.map +1 -0
- package/dist/learning/skill.d.ts +2 -2
- package/dist/learning/skill.d.ts.map +1 -1
- package/dist/learning/skill.js +6 -6
- package/dist/learning/skill.js.map +1 -1
- package/dist/learning/store.d.ts.map +1 -1
- package/dist/learning/store.js +6 -6
- package/dist/learning/store.js.map +1 -1
- package/dist/pals/communication/ingress-schema.d.ts +8 -8
- package/dist/pals/communication/schema.d.ts +2 -2
- package/dist/peers/client.d.ts +2 -3
- package/dist/peers/client.d.ts.map +1 -1
- package/dist/peers/client.js +2 -3
- package/dist/peers/client.js.map +1 -1
- package/dist/peers/endpoint.d.ts +1 -2
- package/dist/peers/endpoint.d.ts.map +1 -1
- package/dist/peers/endpoint.js +1 -2
- package/dist/peers/endpoint.js.map +1 -1
- package/dist/peers/protocol.d.ts.map +1 -1
- package/dist/peers/protocol.js +1 -0
- package/dist/peers/protocol.js.map +1 -1
- package/dist/prompt/coding-agent-doctrine.d.ts +4 -4
- package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
- package/dist/prompt/coding-agent-doctrine.js +5 -3
- package/dist/prompt/coding-agent-doctrine.js.map +1 -1
- package/dist/prompt/index.d.ts +0 -4
- package/dist/prompt/index.d.ts.map +1 -1
- package/dist/prompt/index.js +0 -2
- package/dist/prompt/index.js.map +1 -1
- package/dist/provider/fallback.d.ts.map +1 -1
- package/dist/provider/fallback.js +3 -0
- package/dist/provider/fallback.js.map +1 -1
- package/dist/provider/idle-timeout.d.ts.map +1 -1
- package/dist/provider/idle-timeout.js +5 -0
- package/dist/provider/idle-timeout.js.map +1 -1
- package/dist/provider/model-window-suffix.d.ts.map +1 -1
- package/dist/provider/model-window-suffix.js +1 -0
- package/dist/provider/model-window-suffix.js.map +1 -1
- package/dist/provider/retry.d.ts.map +1 -1
- package/dist/provider/retry.js +5 -0
- package/dist/provider/retry.js.map +1 -1
- package/dist/provider/step-stopped.d.ts +19 -0
- package/dist/provider/step-stopped.d.ts.map +1 -0
- package/dist/provider/step-stopped.js +25 -0
- package/dist/provider/step-stopped.js.map +1 -0
- package/dist/provider/token-budget.d.ts.map +1 -1
- package/dist/provider/token-budget.js +6 -1
- package/dist/provider/token-budget.js.map +1 -1
- package/dist/public-runtime.d.ts +4 -15
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +3 -14
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-tools.d.ts +0 -2
- package/dist/public-tools.d.ts.map +1 -1
- package/dist/public-tools.js +0 -2
- package/dist/public-tools.js.map +1 -1
- package/dist/public-types.d.ts +3 -21
- package/dist/public-types.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts +17 -0
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +168 -5
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/per-call-review.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/per-call-review.js +4 -5
- package/dist/runtime/query/iteration/phases/per-call-review.js.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.js +6 -3
- package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.d.ts +9 -1
- package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.js +48 -2
- package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
- package/dist/runtime/query/resume-pending.d.ts.map +1 -1
- package/dist/runtime/query/resume-pending.js +7 -4
- package/dist/runtime/query/resume-pending.js.map +1 -1
- package/dist/runtime/query/review-policy.d.ts +40 -8
- package/dist/runtime/query/review-policy.d.ts.map +1 -1
- package/dist/runtime/query/review-policy.js +139 -24
- package/dist/runtime/query/review-policy.js.map +1 -1
- package/dist/runtime/query/turn/completion-audit.d.ts +32 -0
- package/dist/runtime/query/turn/completion-audit.d.ts.map +1 -0
- package/dist/runtime/query/turn/completion-audit.js +56 -0
- package/dist/runtime/query/turn/completion-audit.js.map +1 -0
- package/dist/runtime/query/turn/stall-monitor.d.ts +78 -0
- package/dist/runtime/query/turn/stall-monitor.d.ts.map +1 -0
- package/dist/runtime/query/turn/stall-monitor.js +101 -0
- package/dist/runtime/query/turn/stall-monitor.js.map +1 -0
- package/dist/session/paths.d.ts +1 -2
- package/dist/session/paths.d.ts.map +1 -1
- package/dist/session/paths.js +1 -4
- package/dist/session/paths.js.map +1 -1
- package/dist/tools/builtins/bash.d.ts +2 -1
- package/dist/tools/builtins/bash.d.ts.map +1 -1
- package/dist/tools/builtins/bash.js +5 -2
- package/dist/tools/builtins/bash.js.map +1 -1
- package/dist/tools/builtins/computer-use.d.ts +80 -0
- package/dist/tools/builtins/computer-use.d.ts.map +1 -1
- package/dist/tools/builtins/computer-use.js +172 -9
- package/dist/tools/builtins/computer-use.js.map +1 -1
- package/dist/tools/builtins/content-fingerprint.d.ts +13 -0
- package/dist/tools/builtins/content-fingerprint.d.ts.map +1 -1
- package/dist/tools/builtins/content-fingerprint.js +25 -0
- package/dist/tools/builtins/content-fingerprint.js.map +1 -1
- package/dist/tools/builtins/edit.d.ts.map +1 -1
- package/dist/tools/builtins/edit.js +7 -2
- package/dist/tools/builtins/edit.js.map +1 -1
- package/dist/tools/builtins/shell-read-credit.d.ts +15 -0
- package/dist/tools/builtins/shell-read-credit.d.ts.map +1 -0
- package/dist/tools/builtins/shell-read-credit.js +161 -0
- package/dist/tools/builtins/shell-read-credit.js.map +1 -0
- package/dist/tools/paths.d.ts +15 -2
- package/dist/tools/paths.d.ts.map +1 -1
- package/dist/tools/paths.js +17 -2
- package/dist/tools/paths.js.map +1 -1
- package/dist/turn/evidence-recall.d.ts +1 -1
- package/dist/turn/evidence-recall.d.ts.map +1 -1
- package/dist/turn/evidence-recall.js +5 -14
- package/dist/turn/evidence-recall.js.map +1 -1
- package/dist/types/acp/index.d.ts +6 -0
- package/dist/types/acp/index.d.ts.map +1 -1
- package/dist/types/agent/base.d.ts +4 -0
- package/dist/types/agent/base.d.ts.map +1 -1
- package/dist/types/hitl/index.d.ts +8 -0
- package/dist/types/hitl/index.d.ts.map +1 -1
- package/dist/types/hitl/index.js.map +1 -1
- package/dist/types/ids/index.d.ts +1 -1
- package/dist/types/message/index.d.ts +1 -1
- package/dist/types/message/index.d.ts.map +1 -1
- package/dist/types/message/index.js +1 -0
- package/dist/types/message/index.js.map +1 -1
- package/dist/types/provider/interface.d.ts +12 -0
- package/dist/types/provider/interface.d.ts.map +1 -1
- package/dist/types/session/config.d.ts +25 -0
- package/dist/types/session/config.d.ts.map +1 -1
- package/dist/types/session/records.d.ts.map +1 -1
- package/dist/types/session/records.js +2 -0
- package/dist/types/session/records.js.map +1 -1
- package/dist/types/session/turn.d.ts +1 -1
- package/dist/types/session/turn.d.ts.map +1 -1
- package/dist/types/session/turn.js.map +1 -1
- package/package.json +1 -1
- package/src/agents/QueryAgent.ts +2 -0
- package/src/bridge/acp/permission.ts +8 -0
- package/src/bridge/acp/server.ts +7 -1
- package/src/constants/tools/index.ts +27 -0
- package/src/eval/harness-protection.ts +1 -1
- package/src/learning/cycle.ts +15 -16
- package/src/learning/guidance.ts +339 -0
- package/src/learning/skill.ts +7 -7
- package/src/learning/store.ts +5 -6
- package/src/peers/client.ts +2 -3
- package/src/peers/endpoint.ts +1 -2
- package/src/peers/protocol.ts +1 -0
- package/src/prompt/coding-agent-doctrine.ts +5 -3
- package/src/prompt/index.ts +0 -4
- package/src/provider/fallback.ts +7 -0
- package/src/provider/idle-timeout.ts +8 -0
- package/src/provider/model-window-suffix.ts +1 -0
- package/src/provider/retry.ts +8 -0
- package/src/provider/step-stopped.ts +27 -0
- package/src/provider/token-budget.ts +6 -1
- package/src/public-runtime.ts +10 -28
- package/src/public-tools.ts +0 -3
- package/src/public-types.ts +5 -137
- package/src/runtime/query/iteration/index.ts +207 -5
- package/src/runtime/query/iteration/phases/per-call-review.ts +7 -5
- package/src/runtime/query/iteration/phases/tool-review.ts +5 -3
- package/src/runtime/query/iteration/stream-turn.ts +51 -1
- package/src/runtime/query/resume-pending.ts +11 -4
- package/src/runtime/query/review-policy.ts +157 -24
- package/src/runtime/query/turn/completion-audit.ts +71 -0
- package/src/runtime/query/turn/stall-monitor.ts +158 -0
- package/src/session/paths.ts +2 -6
- package/src/tools/builtins/bash.ts +6 -2
- package/src/tools/builtins/computer-use.ts +245 -7
- package/src/tools/builtins/content-fingerprint.ts +25 -0
- package/src/tools/builtins/edit.ts +11 -2
- package/src/tools/builtins/shell-read-credit.ts +159 -0
- package/src/tools/paths.ts +18 -2
- package/src/turn/evidence-recall.ts +7 -15
- package/src/types/acp/index.ts +6 -0
- package/src/types/agent/base.ts +4 -0
- package/src/types/hitl/index.ts +8 -0
- package/src/types/ids/index.ts +1 -1
- package/src/types/message/index.ts +1 -0
- package/src/types/provider/interface.ts +13 -0
- package/src/types/session/config.ts +27 -0
- package/src/types/session/records.ts +2 -0
- package/src/types/session/turn.ts +2 -1
- package/dist/learning/deprecated.d.ts +0 -86
- package/dist/learning/deprecated.d.ts.map +0 -1
- package/dist/learning/deprecated.js +0 -22
- package/dist/learning/deprecated.js.map +0 -1
- package/dist/manager/resident/activity.d.ts +0 -74
- package/dist/manager/resident/activity.d.ts.map +0 -1
- package/dist/manager/resident/activity.js +0 -125
- package/dist/manager/resident/activity.js.map +0 -1
- package/dist/manager/resident/agenda.d.ts +0 -167
- package/dist/manager/resident/agenda.d.ts.map +0 -1
- package/dist/manager/resident/agenda.js +0 -588
- package/dist/manager/resident/agenda.js.map +0 -1
- package/dist/manager/resident/consumption.d.ts +0 -98
- package/dist/manager/resident/consumption.d.ts.map +0 -1
- package/dist/manager/resident/consumption.js +0 -237
- package/dist/manager/resident/consumption.js.map +0 -1
- package/dist/manager/resident/delivery-window.d.ts +0 -28
- package/dist/manager/resident/delivery-window.d.ts.map +0 -1
- package/dist/manager/resident/delivery-window.js +0 -80
- package/dist/manager/resident/delivery-window.js.map +0 -1
- package/dist/manager/resident/evidence-recall.d.ts +0 -30
- package/dist/manager/resident/evidence-recall.d.ts.map +0 -1
- package/dist/manager/resident/evidence-recall.js +0 -297
- package/dist/manager/resident/evidence-recall.js.map +0 -1
- package/dist/manager/resident/history-disk.d.ts +0 -10
- package/dist/manager/resident/history-disk.d.ts.map +0 -1
- package/dist/manager/resident/history-disk.js +0 -50
- package/dist/manager/resident/history-disk.js.map +0 -1
- package/dist/manager/resident/history.d.ts +0 -115
- package/dist/manager/resident/history.d.ts.map +0 -1
- package/dist/manager/resident/history.js +0 -203
- package/dist/manager/resident/history.js.map +0 -1
- package/dist/manager/resident/host.d.ts +0 -112
- package/dist/manager/resident/host.d.ts.map +0 -1
- package/dist/manager/resident/host.js +0 -254
- package/dist/manager/resident/host.js.map +0 -1
- package/dist/manager/resident/initiative.d.ts +0 -86
- package/dist/manager/resident/initiative.d.ts.map +0 -1
- package/dist/manager/resident/initiative.js +0 -131
- package/dist/manager/resident/initiative.js.map +0 -1
- package/dist/manager/resident/loop.d.ts +0 -49
- package/dist/manager/resident/loop.d.ts.map +0 -1
- package/dist/manager/resident/loop.js +0 -75
- package/dist/manager/resident/loop.js.map +0 -1
- package/dist/manager/resident/outbox.d.ts +0 -124
- package/dist/manager/resident/outbox.d.ts.map +0 -1
- package/dist/manager/resident/outbox.js +0 -216
- package/dist/manager/resident/outbox.js.map +0 -1
- package/dist/manager/resident/proposal.d.ts +0 -65
- package/dist/manager/resident/proposal.d.ts.map +0 -1
- package/dist/manager/resident/proposal.js +0 -77
- package/dist/manager/resident/proposal.js.map +0 -1
- package/dist/manager/resident/store.d.ts +0 -108
- package/dist/manager/resident/store.d.ts.map +0 -1
- package/dist/manager/resident/store.js +0 -205
- package/dist/manager/resident/store.js.map +0 -1
- package/dist/manager/resident/tool-evidence.d.ts +0 -112
- package/dist/manager/resident/tool-evidence.d.ts.map +0 -1
- package/dist/manager/resident/tool-evidence.js +0 -289
- package/dist/manager/resident/tool-evidence.js.map +0 -1
- package/dist/prompt/resident-learning.d.ts +0 -29
- package/dist/prompt/resident-learning.d.ts.map +0 -1
- package/dist/prompt/resident-learning.js +0 -126
- package/dist/prompt/resident-learning.js.map +0 -1
- package/dist/prompt/resident-step.d.ts +0 -42
- package/dist/prompt/resident-step.d.ts.map +0 -1
- package/dist/prompt/resident-step.js +0 -137
- package/dist/prompt/resident-step.js.map +0 -1
- package/dist/tools/resident-history.d.ts +0 -12
- package/dist/tools/resident-history.d.ts.map +0 -1
- package/dist/tools/resident-history.js +0 -82
- package/dist/tools/resident-history.js.map +0 -1
- package/dist/tools/resident-tool-evidence.d.ts +0 -9
- package/dist/tools/resident-tool-evidence.d.ts.map +0 -1
- package/dist/tools/resident-tool-evidence.js +0 -61
- package/dist/tools/resident-tool-evidence.js.map +0 -1
- package/src/learning/deprecated.ts +0 -124
- package/src/manager/resident/activity.ts +0 -211
- package/src/manager/resident/agenda.ts +0 -937
- package/src/manager/resident/consumption.ts +0 -335
- package/src/manager/resident/delivery-window.ts +0 -99
- package/src/manager/resident/evidence-recall.ts +0 -369
- package/src/manager/resident/history-disk.ts +0 -63
- package/src/manager/resident/history.ts +0 -348
- package/src/manager/resident/host.ts +0 -390
- package/src/manager/resident/initiative.ts +0 -231
- package/src/manager/resident/loop.ts +0 -119
- package/src/manager/resident/outbox.ts +0 -341
- package/src/manager/resident/proposal.ts +0 -119
- package/src/manager/resident/store.ts +0 -279
- package/src/manager/resident/tool-evidence.ts +0 -455
- package/src/prompt/resident-learning.ts +0 -152
- package/src/prompt/resident-step.ts +0 -182
- package/src/tools/resident-history.ts +0 -88
- 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
|
+
}
|
package/src/session/paths.ts
CHANGED
|
@@ -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/
|
|
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
|
|
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(
|
|
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
|
*
|