cli-jaw 2.17.41 → 2.17.42

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 (167) hide show
  1. package/README.md +3 -3
  2. package/dist/bin/commands/browser.js +52 -5
  3. package/dist/bin/commands/browser.js.map +1 -1
  4. package/dist/src/agent/args.js +69 -19
  5. package/dist/src/agent/args.js.map +1 -1
  6. package/dist/src/agent/codex-app-events.js +31 -1
  7. package/dist/src/agent/codex-app-events.js.map +1 -1
  8. package/dist/src/agent/cursor-model-inventory.js +140 -0
  9. package/dist/src/agent/cursor-model-inventory.js.map +1 -0
  10. package/dist/src/agent/grok-models.js +80 -0
  11. package/dist/src/agent/grok-models.js.map +1 -0
  12. package/dist/src/browser/actions.js +383 -20
  13. package/dist/src/browser/actions.js.map +1 -1
  14. package/dist/src/browser/grounding-candidate.js +272 -0
  15. package/dist/src/browser/grounding-candidate.js.map +1 -0
  16. package/dist/src/browser/grounding-eval.js +247 -0
  17. package/dist/src/browser/grounding-eval.js.map +1 -0
  18. package/dist/src/browser/image-size.js +63 -0
  19. package/dist/src/browser/image-size.js.map +1 -0
  20. package/dist/src/browser/index.js +1 -0
  21. package/dist/src/browser/index.js.map +1 -1
  22. package/dist/src/browser/occlusion.js +148 -0
  23. package/dist/src/browser/occlusion.js.map +1 -0
  24. package/dist/src/browser/verify-candidate.js +162 -0
  25. package/dist/src/browser/verify-candidate.js.map +1 -0
  26. package/dist/src/browser/vision-input.js +74 -0
  27. package/dist/src/browser/vision-input.js.map +1 -0
  28. package/dist/src/browser/vision-provider.js +75 -0
  29. package/dist/src/browser/vision-provider.js.map +1 -0
  30. package/dist/src/browser/vision.js +360 -47
  31. package/dist/src/browser/vision.js.map +1 -1
  32. package/dist/src/browser/web-ai/candidate-reconcile.js +14 -5
  33. package/dist/src/browser/web-ai/candidate-reconcile.js.map +1 -1
  34. package/dist/src/cli/claude-model-discovery.js +175 -0
  35. package/dist/src/cli/claude-model-discovery.js.map +1 -0
  36. package/dist/src/cli/claude-models.js +46 -0
  37. package/dist/src/cli/claude-models.js.map +1 -1
  38. package/dist/src/cli/registry-live.js +104 -1
  39. package/dist/src/cli/registry-live.js.map +1 -1
  40. package/dist/src/cli/registry.js +28 -5
  41. package/dist/src/cli/registry.js.map +1 -1
  42. package/dist/src/code-mode/host.js +6 -0
  43. package/dist/src/code-mode/host.js.map +1 -1
  44. package/dist/src/code-mode/manager.js +37 -8
  45. package/dist/src/code-mode/manager.js.map +1 -1
  46. package/dist/src/code-mode/providers/catalog.js +53 -1
  47. package/dist/src/code-mode/providers/catalog.js.map +1 -1
  48. package/dist/src/code-mode/providers/codex-app.js +6 -0
  49. package/dist/src/code-mode/providers/codex-app.js.map +1 -1
  50. package/dist/src/code-mode/providers/live-models.js +85 -0
  51. package/dist/src/code-mode/providers/live-models.js.map +1 -0
  52. package/dist/src/code-mode/providers/provider-live-models.js +120 -0
  53. package/dist/src/code-mode/providers/provider-live-models.js.map +1 -0
  54. package/dist/src/code-mode/session.js +37 -0
  55. package/dist/src/code-mode/session.js.map +1 -1
  56. package/dist/src/prompt/templates/a1-system.md +24 -14
  57. package/dist/src/prompt/templates/control-system.md +6 -11
  58. package/dist/src/prompt/templates/employee.md +5 -5
  59. package/dist/src/routes/browser.js +17 -5
  60. package/dist/src/routes/browser.js.map +1 -1
  61. package/package.json +1 -1
  62. package/public/css/activity.css +20 -1
  63. package/public/css/layout.css +7 -3
  64. package/public/dist/.vite/manifest.json +104 -105
  65. package/public/dist/assets/Agent-CQ4EBgY8.js +1 -0
  66. package/public/dist/assets/CodeCanvas-DWsx6sv5.js +2 -0
  67. package/public/dist/assets/DashboardMeta-Dh-tasbY.js +1 -0
  68. package/public/dist/assets/Display-NrqY_IaN.js +1 -0
  69. package/public/dist/assets/{Permissions-BzbnqenZ.js → Permissions-Bh947tUG.js} +1 -1
  70. package/public/dist/assets/Permissions-DzNJCt8K.js +1 -0
  71. package/public/dist/assets/app-BCBnxiGr.css +1 -0
  72. package/public/dist/assets/{app-x5Kray4W.js → app-Q0prKOFx.js} +4 -4
  73. package/public/dist/assets/{bgtask-badge-W9djRQVu.js → bgtask-badge-DAcWaUfS.js} +1 -1
  74. package/public/dist/assets/bgtask-badge-DHA2OQm8.js +1 -0
  75. package/public/dist/assets/{employees--_dmDw_a.js → employees-CWS3Juca.js} +1 -1
  76. package/public/dist/assets/{iframe-renderer-D4BmLtAq.js → iframe-renderer-DCDKBMb7.js} +2 -2
  77. package/public/dist/assets/iframe-renderer-RpvBly2x.js +1 -0
  78. package/public/dist/assets/manager-Nk7-RYPs.css +1 -0
  79. package/public/dist/assets/manager-XkR-DRaQ.js +5 -0
  80. package/public/dist/assets/manager-shortcuts-jsiX-zV6.js +1 -0
  81. package/public/dist/assets/memory-4YH8sz0L.js +1 -0
  82. package/public/dist/assets/{memory-DENQWzM8.js → memory-DhNdKZLO.js} +1 -1
  83. package/public/dist/assets/{message-history-BsrLcGJj.js → message-history-DbRTl1P0.js} +1 -1
  84. package/public/dist/assets/message-history-lVcLpAw8.js +1 -0
  85. package/public/dist/assets/{render-CF6Q1oIh.js → render-DP8SJtdZ.js} +1 -1
  86. package/public/dist/assets/settings--EGy3_B9.js +1 -0
  87. package/public/dist/assets/settings-85n9NjvX.js +1 -0
  88. package/public/dist/assets/{settings-B-C_jdZR.js → settings-C9_D-pn0.js} +32 -32
  89. package/public/dist/assets/settings-core-8wBl1mg-.js +1 -0
  90. package/public/dist/assets/{settings-core-BzAJLsKN.js → settings-core-BcVD2FgV.js} +1 -1
  91. package/public/dist/assets/{settings-embedding-dEMyByVi.js → settings-embedding-BSpq785l.js} +1 -1
  92. package/public/dist/assets/{settings-embedding-BSA42oKM.css → settings-embedding-LIMq5ebM.css} +1 -1
  93. package/public/dist/assets/{sidebar-fFEOpvJl.js → sidebar-BVhLb5Ft.js} +3 -3
  94. package/public/dist/assets/skills-6GFkHTlQ.js +1 -0
  95. package/public/dist/assets/{skills-Da_b06ci.js → skills-X7qxmHka.js} +1 -1
  96. package/public/dist/assets/{slash-commands-BbPUHHF4.js → slash-commands-BnDH4IU_.js} +1 -1
  97. package/public/dist/assets/slash-commands-qu8fPAZ3.js +1 -0
  98. package/public/dist/assets/{trace-drawer-B-QmzUfP.js → trace-drawer-DasaS1z4.js} +1 -1
  99. package/public/dist/assets/{ui-CkRUrIfp.js → ui-BtiBTXng.js} +1 -1
  100. package/public/dist/assets/ui-BucJ1zMA.js +1 -0
  101. package/public/dist/index.html +2 -2
  102. package/public/dist/manager/index.html +3 -3
  103. package/public/dist/settings/index.html +2 -2
  104. package/public/js/features/settings.ts +16 -0
  105. package/public/manager/src/App.tsx +10 -12
  106. package/public/manager/src/AppChrome.tsx +2 -3
  107. package/public/manager/src/InstancePreview.tsx +47 -0
  108. package/public/manager/src/SidebarRailRouter.tsx +34 -30
  109. package/public/manager/src/code/CodeComposer.tsx +31 -6
  110. package/public/manager/src/code/CodeSessionList.tsx +25 -7
  111. package/public/manager/src/code/CodeToastHost.tsx +49 -0
  112. package/public/manager/src/code/CodeTranscript.tsx +136 -26
  113. package/public/manager/src/code/CodeWorkbench.tsx +16 -2
  114. package/public/manager/src/code/ComposerFooter.tsx +130 -46
  115. package/public/manager/src/code/ContextUsageMeter.tsx +72 -0
  116. package/public/manager/src/code/ProviderGlyph.tsx +58 -0
  117. package/public/manager/src/code/code-composer.css +159 -175
  118. package/public/manager/src/code/code-controller-runtime.ts +3 -1
  119. package/public/manager/src/code/code-session-state.ts +7 -1
  120. package/public/manager/src/code/code-toasts.ts +73 -0
  121. package/public/manager/src/code/code-workbench.css +42 -1
  122. package/public/manager/src/code/code.css +130 -15
  123. package/public/manager/src/code/context-usage.ts +74 -0
  124. package/public/manager/src/code/pending-user-item.ts +46 -0
  125. package/public/manager/src/code/session-order.ts +70 -0
  126. package/public/manager/src/code/tool-summary.ts +101 -0
  127. package/public/manager/src/code/use-dictation.ts +130 -0
  128. package/public/manager/src/components/CommandCenter.tsx +4 -8
  129. package/public/manager/src/components/InstanceDetailPanel.tsx +0 -18
  130. package/public/manager/src/components/Workbench.tsx +6 -10
  131. package/public/manager/src/components/WorkbenchHeader.tsx +0 -11
  132. package/public/manager/src/dashboard-settings/dashboard-settings-ui.ts +3 -1
  133. package/public/manager/src/hooks/useDashboardView.ts +38 -38
  134. package/public/manager/src/manager-components.css +0 -8
  135. package/public/manager/src/manager-shortcut-runner.ts +0 -1
  136. package/public/manager/src/manager-shortcuts.ts +0 -2
  137. package/public/manager/src/settings/pages/Agent.tsx +5 -2
  138. package/public/manager/src/settings/pages/DashboardMeta.tsx +20 -1
  139. package/public/manager/src/settings/pages/Permissions.tsx +23 -4
  140. package/public/manager/src/settings/pages/components/agent/PermissionQuickSection.tsx +6 -2
  141. package/public/manager/src/settings/pages/components/agent/agent-save.ts +24 -1
  142. package/public/manager/src/settings/settings-icons.ts +3 -1
  143. package/public/manager/src/settings/settings-registry.ts +3 -3
  144. package/public/manager/src/settings-controls.css +0 -1
  145. package/public/manager/src/settings-standalone.tsx +8 -8
  146. package/public/manager/src/types.ts +0 -1
  147. package/scripts/check-private-boundary.mjs +101 -0
  148. package/scripts/grounding-eval.mjs +223 -0
  149. package/public/dist/assets/Agent-B8ZEWJ6B.js +0 -1
  150. package/public/dist/assets/CodeCanvas-Bj3lTSu6.js +0 -2
  151. package/public/dist/assets/DashboardMeta-CmZavI4H.js +0 -1
  152. package/public/dist/assets/Display-DFkjKcMk.js +0 -1
  153. package/public/dist/assets/Permissions-CJftCO6p.js +0 -1
  154. package/public/dist/assets/app-DVbrIl8f.css +0 -1
  155. package/public/dist/assets/bgtask-badge-CuAc8bAl.js +0 -1
  156. package/public/dist/assets/iframe-renderer-By2WO5bd.js +0 -1
  157. package/public/dist/assets/manager-CxLXWCGi.css +0 -1
  158. package/public/dist/assets/manager-DA3dF3eN.js +0 -5
  159. package/public/dist/assets/manager-shortcuts-CzGkE0ZG.js +0 -1
  160. package/public/dist/assets/memory-thjr2apY.js +0 -1
  161. package/public/dist/assets/message-history-CfU7_P5t.js +0 -1
  162. package/public/dist/assets/settings-DXA5L4DR.js +0 -1
  163. package/public/dist/assets/settings-Dy_IaNVH.js +0 -1
  164. package/public/dist/assets/settings-core-D7sAQqhC.js +0 -1
  165. package/public/dist/assets/skills-BcIievZp.js +0 -1
  166. package/public/dist/assets/slash-commands-B-5Shayl.js +0 -1
  167. package/public/dist/assets/ui-CJLdMfRx.js +0 -1
@@ -0,0 +1,148 @@
1
+ /**
2
+ * src/browser/occlusion.ts — what will actually receive a click at a point.
3
+ *
4
+ * A coordinate click dispatches wherever the point lands. If a cookie banner,
5
+ * modal or sticky header covers the target, that element receives the click
6
+ * and the call still reports success — the most common silent failure in
7
+ * coordinate-based automation, because nothing in the response says the wrong
8
+ * thing was clicked.
9
+ *
10
+ * Ported from BrowserOS's hit test. Relatedness is decided IN THE PAGE against
11
+ * the actual node, because that is the only place element identity is
12
+ * knowable: comparing a reconciled ref's ARIA role against a DOM tag name is a
13
+ * category error, since a `link` is an `a`, a `textbox` is an `input`, and a
14
+ * `button` is often a `div`.
15
+ */
16
+ /**
17
+ * Decide whether a hit means the click would go somewhere unintended.
18
+ *
19
+ * With no marked target — a pure coordinate click into canvas — there is
20
+ * nothing to contradict, so the verdict is `unknown` rather than `clear`.
21
+ * No evidence of a problem is not evidence of no problem, and collapsing the
22
+ * two would let an unchecked click report as a verified one.
23
+ */
24
+ export function judgeHit(hit) {
25
+ // The hit test could not run — an unreadable cross-origin frame, a page
26
+ // that navigated mid-check. Fail OPEN: an infrastructure failure must not
27
+ // block a legitimate click, and pretending to know is worse than saying
28
+ // we do not.
29
+ if (!hit)
30
+ return { blocked: false, reason: 'unknown' };
31
+ // The walk stopped at a frame it could not see into. Whatever is behind it
32
+ // is unknowable, and a target inside that frame would look identical to a
33
+ // cover. Refusing on that evidence would block legitimate clicks into
34
+ // every cross-origin embed.
35
+ if (hit.opaqueFrame)
36
+ return { blocked: false, reason: 'unknown' };
37
+ if (hit.relatesToTarget === undefined)
38
+ return { blocked: false, reason: 'unknown' };
39
+ if (hit.relatesToTarget)
40
+ return { blocked: false, reason: 'clear' };
41
+ return {
42
+ blocked: true,
43
+ blocker: hit.descriptor,
44
+ reason: hit.crossedFrame
45
+ ? `the point resolves into an iframe, to ${hit.descriptor}, not the intended target`
46
+ : `${hit.descriptor} covers the target at this point`,
47
+ };
48
+ }
49
+ /**
50
+ * The page-side hit test. **A real function, deliberately not a source string.**
51
+ *
52
+ * Playwright sets `isFunction: typeof pageFunction === "function"`. Pass a
53
+ * string and the page evaluates the expression, obtains a function object, and
54
+ * never calls it — the result serializes to `undefined`. An earlier version of
55
+ * this shipped as a string, which made the entire check a permanent no-op. It
56
+ * was invisible precisely because the guard fails open: `undefined` became
57
+ * `null` became "unknown", indistinguishable from the intended safe path.
58
+ *
59
+ * Everything below runs in the page, so it may only use DOM globals.
60
+ */
61
+ export function hitTestInPage(arg) {
62
+ const describe = (el) => {
63
+ if (!el || !el.tagName)
64
+ return 'element';
65
+ const tag = String(el.tagName).toLowerCase();
66
+ if (el.id)
67
+ return tag + '#' + el.id;
68
+ if (typeof el.className === 'string' && el.className.trim()) {
69
+ return tag + '.' + el.className.trim().split(/\s+/)[0];
70
+ }
71
+ return tag;
72
+ };
73
+ const up = (node) => {
74
+ if (!node)
75
+ return null;
76
+ return node.parentNode || node.host || (node.getRootNode ? node.getRootNode().host ?? null : null) || null;
77
+ };
78
+ const reaches = (from, to) => {
79
+ for (let node = from, guard = 0; node && guard < 200; node = up(node), guard++) {
80
+ if (node === to)
81
+ return true;
82
+ }
83
+ return false;
84
+ };
85
+ let target = null;
86
+ if (arg.targetPoint) {
87
+ target = document.elementFromPoint(arg.targetPoint.x, arg.targetPoint.y);
88
+ }
89
+ let doc = document;
90
+ let x = arg.x;
91
+ let y = arg.y;
92
+ let crossedFrame = false;
93
+ let opaqueFrame = false;
94
+ let hit = doc.elementFromPoint(x, y);
95
+ // Bounded: a frame chain should be shallow, and a cycle must not hang the page.
96
+ for (let depth = 0; depth < 16; depth++) {
97
+ if (!hit || (hit.tagName !== 'IFRAME' && hit.tagName !== 'FRAME'))
98
+ break;
99
+ if (target && (hit === target || reaches(target, hit)))
100
+ break;
101
+ let childDoc = null;
102
+ try {
103
+ childDoc = hit.contentDocument ?? null;
104
+ }
105
+ catch {
106
+ childDoc = null;
107
+ }
108
+ if (!childDoc) {
109
+ opaqueFrame = true;
110
+ break;
111
+ }
112
+ const rect = hit.getBoundingClientRect();
113
+ // elementFromPoint in the child is relative to the child's own
114
+ // viewport, so subtract the frame's position and its border.
115
+ x -= rect.left + hit.clientLeft;
116
+ y -= rect.top + hit.clientTop;
117
+ doc = childDoc;
118
+ crossedFrame = true;
119
+ const next = doc.elementFromPoint(x, y);
120
+ if (!next)
121
+ break;
122
+ hit = next;
123
+ }
124
+ if (!hit)
125
+ return null;
126
+ const ancestry = [];
127
+ for (let node = up(hit), guard = 0; node && guard < 24; node = up(node), guard++) {
128
+ if (node.tagName)
129
+ ancestry.push(describe(node));
130
+ }
131
+ const result = { descriptor: describe(hit), ancestry, crossedFrame };
132
+ if (opaqueFrame)
133
+ result.opaqueFrame = true;
134
+ if (target) {
135
+ // The hit is the target, inside it, or contains it. Clicking a
136
+ // button's inner span is clicking the button. A label forwarding to
137
+ // this control counts too.
138
+ let related = hit === target || reaches(hit, target) || reaches(target, hit);
139
+ if (!related && hit.closest) {
140
+ const lbl = hit.closest('label');
141
+ if (lbl && (lbl.control === target || (lbl.contains ? lbl.contains(target) : false)))
142
+ related = true;
143
+ }
144
+ result.relatesToTarget = related;
145
+ }
146
+ return result;
147
+ }
148
+ //# sourceMappingURL=occlusion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"occlusion.js","sourceRoot":"","sources":["../../../src/browser/occlusion.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,162 @@
1
+ /**
2
+ * src/browser/verify-candidate.ts — a second look that can actually disagree.
3
+ *
4
+ * The option this replaces re-ran the identical query against the identical
5
+ * screenshot and then discarded the coordinates, keeping only whether the
6
+ * second answer still found something. Same input, same question: the only way
7
+ * it could fail was model non-determinism, and the only thing it reliably
8
+ * bought was a second round-trip. Its test asserted the string
9
+ * `verifyBeforeClick` appeared in the source, so a guard that guarded nothing
10
+ * passed a suite that checked nothing.
11
+ *
12
+ * A verification that can disagree has to change the input. Cropping tightly
13
+ * around the candidate and asking again does that: the target now dominates
14
+ * the frame, so a model that was guessing has to guess differently, and an
15
+ * answer landing far from the original candidate is evidence it was wrong.
16
+ *
17
+ * Geometry only. The capture and the model call belong to the caller.
18
+ */
19
+ /** Default crop around a candidate, in CSS pixels. */
20
+ export const VERIFY_CROP = { width: 280, height: 200 };
21
+ /**
22
+ * How far from the original candidate a second answer may land, as a fraction
23
+ * of the crop. Beyond this the two answers point at different things, and the
24
+ * disagreement matters more than either one.
25
+ *
26
+ * It is a fraction per axis, so on the default 280x200 crop it permits 126px
27
+ * horizontally and 90px vertically — one number, two distances.
28
+ */
29
+ export const VERIFY_DRIFT_LIMIT = 0.45;
30
+ /**
31
+ * A crop centred on the candidate, kept inside the viewport.
32
+ *
33
+ * Being outside the viewport is fixed by SHIFTING the rectangle, so a
34
+ * candidate near an edge still gets a full-size crop and the drift threshold
35
+ * means the same thing everywhere. A viewport smaller than the crop is the one
36
+ * case that genuinely shrinks it, since there is nothing else to do.
37
+ */
38
+ export function cropAroundPoint(point, viewport, size = VERIFY_CROP) {
39
+ const width = Math.min(size.width, viewport.width);
40
+ const height = Math.min(size.height, viewport.height);
41
+ const x = Math.max(0, Math.min(Math.round(point.x - width / 2), viewport.width - width));
42
+ const y = Math.max(0, Math.min(Math.round(point.y - height / 2), viewport.height - height));
43
+ return { x, y, width, height };
44
+ }
45
+ /**
46
+ * How long an observation may be trusted before the click it informs.
47
+ *
48
+ * Verification adds a second model round-trip, which roughly doubles the
49
+ * interval between seeing the page and acting on it. The freshness guard on
50
+ * the reconciliation path catches navigation, but nothing catches scroll,
51
+ * reflow or animation — so a long enough gap means the coordinate describes a
52
+ * page that no longer exists.
53
+ *
54
+ * This does not detect movement. It bounds how long we are willing to assume
55
+ * there was none, which is a weaker claim and the honest one.
56
+ */
57
+ export const OBSERVATION_MAX_AGE_MS = 30_000;
58
+ export function isObservationStale(observedAt, now, maxAgeMs = OBSERVATION_MAX_AGE_MS) {
59
+ // A clock that ran backwards tells us nothing, so treat it as unusable
60
+ // rather than as freshness.
61
+ if (!Number.isFinite(observedAt) || !Number.isFinite(now))
62
+ return true;
63
+ return now - observedAt > maxAgeMs || now < observedAt;
64
+ }
65
+ /**
66
+ * How much of the freshness budget is left, in milliseconds.
67
+ *
68
+ * A provider child was given a flat 60s while the observation it works from
69
+ * expired at 30s, so two round-trips could spend two minutes producing an
70
+ * answer the very next line was guaranteed to refuse. Deriving the child's
71
+ * timeout from what remains kills it at the moment its answer stops being
72
+ * usable instead of at an unrelated constant.
73
+ *
74
+ * Never negative, and never above `cap`: the existing 60s ceiling still
75
+ * applies when the budget is larger than it.
76
+ */
77
+ export function remainingObservationBudget(observedAt, now, cap, maxAgeMs = OBSERVATION_MAX_AGE_MS) {
78
+ if (!Number.isFinite(observedAt) || !Number.isFinite(now) || !Number.isFinite(cap))
79
+ return 0;
80
+ const spent = now - observedAt;
81
+ if (spent < 0)
82
+ return 0;
83
+ return Math.max(0, Math.min(cap, maxAgeMs - spent));
84
+ }
85
+ /**
86
+ * Fit a requested capture rectangle inside the viewport.
87
+ *
88
+ * Playwright trims a clip to the viewport before capturing, so a rectangle
89
+ * extending past the edge yields a smaller image than asked for — and one
90
+ * lying entirely outside fails with an opaque assertion about the clipped area
91
+ * being empty. Neither says which rectangle was wrong or what the viewport was.
92
+ *
93
+ * Clamping here means the caller learns the capture was truncated, and a
94
+ * genuinely impossible rectangle gets a named error instead.
95
+ */
96
+ export function clampClipToViewport(clip, viewport) {
97
+ if (clip.x >= viewport.width || clip.y >= viewport.height) {
98
+ throw new Error(`clip (${clip.x}, ${clip.y}) starts outside the ${viewport.width}x${viewport.height} viewport`);
99
+ }
100
+ const width = Math.min(clip.width, viewport.width - clip.x);
101
+ const height = Math.min(clip.height, viewport.height - clip.y);
102
+ if (width <= 0 || height <= 0) {
103
+ throw new Error(`clip ${clip.width}x${clip.height} at (${clip.x}, ${clip.y}) has no area inside the ${viewport.width}x${viewport.height} viewport`);
104
+ }
105
+ return {
106
+ clip: { x: clip.x, y: clip.y, width, height },
107
+ truncated: width !== clip.width || height !== clip.height,
108
+ };
109
+ }
110
+ /**
111
+ * Judge a second answer taken inside `crop`.
112
+ *
113
+ * `localPoint` is where the re-run landed within the crop, in CSS pixels
114
+ * relative to the crop's own origin. The returned point is back in page
115
+ * coordinates, so a caller that agrees can use it directly — the second look
116
+ * replaces the first estimate rather than merely blessing it.
117
+ *
118
+ * Drift is measured from the ORIGINAL CANDIDATE, not from the crop centre.
119
+ * Those coincide in the middle of a viewport but not near an edge, where the
120
+ * crop clamps: a candidate 5px from the left sits 5px into its crop while the
121
+ * crop centre is 140px in. Measuring from the centre there would refuse a
122
+ * second answer that re-found the exact same point — the one case that should
123
+ * agree most strongly.
124
+ */
125
+ export function judgeVerification(localPoint, crop, candidate, driftLimit = VERIFY_DRIFT_LIMIT) {
126
+ // The target is not in a crop centred on where we thought it was. That is
127
+ // the first answer being wrong, which is exactly what this is for.
128
+ if (!localPoint)
129
+ return { agreed: false, reason: 'the target was not in the verification crop' };
130
+ // The model answered about an image of exactly this size, so a point
131
+ // outside it is impossible by construction and means the scaling was
132
+ // wrong. Without this, a candidate near a shifted crop's edge can drift
133
+ // the permitted fraction and land off the far side — and off the viewport,
134
+ // since the returned point is clicked directly.
135
+ if (localPoint.x < 0 || localPoint.y < 0
136
+ || localPoint.x > crop.width || localPoint.y > crop.height) {
137
+ return {
138
+ agreed: false,
139
+ reason: `the second answer (${Math.round(localPoint.x)}, ${Math.round(localPoint.y)}) is outside the ${crop.width}x${crop.height} verification crop`,
140
+ };
141
+ }
142
+ // Fall back to the crop centre only when no candidate was supplied, which
143
+ // is the same point whenever the crop was not clamped.
144
+ const cx = candidate ? candidate.x - crop.x : crop.width / 2;
145
+ const cy = candidate ? candidate.y - crop.y : crop.height / 2;
146
+ const dx = Math.abs(localPoint.x - cx) / crop.width;
147
+ const dy = Math.abs(localPoint.y - cy) / crop.height;
148
+ const drift = Math.max(dx, dy);
149
+ if (drift > driftLimit) {
150
+ return {
151
+ agreed: false,
152
+ reason: `the second answer drifted ${Math.round(drift * 100)}% from the original candidate`,
153
+ drift,
154
+ };
155
+ }
156
+ return {
157
+ agreed: true,
158
+ point: { x: crop.x + localPoint.x, y: crop.y + localPoint.y },
159
+ drift,
160
+ };
161
+ }
162
+ //# sourceMappingURL=verify-candidate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify-candidate.js","sourceRoot":"","sources":["../../../src/browser/verify-candidate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * src/browser/vision-input.ts — untrusted-input handling for the vision path.
3
+ *
4
+ * Kept separate from vision.ts so it can be tested without pulling in the CDP
5
+ * action layer: these are pure functions over strings, and they guard the one
6
+ * place where caller-supplied text reaches a child process.
7
+ */
8
+ /**
9
+ * The target description reaches the vision path from the HTTP body of
10
+ * `POST /api/browser/vision-click` and is interpolated into a prompt for a
11
+ * child process. Treat it as untrusted text, not as instructions.
12
+ *
13
+ * Be clear about what this is worth. The prompt's delimited span is a
14
+ * model-behavior hint, not a security boundary; the instructions that follow
15
+ * the span are what actually constrain the answer. This function's job is
16
+ * narrower: keep the target on one line, stop it closing its own delimiter,
17
+ * and bound its size. It does not, and cannot, stop the text from *reading*
18
+ * like an instruction.
19
+ */
20
+ export const MAX_TARGET_LENGTH = 200;
21
+ /**
22
+ * Collapse every run of two or more double quotes to a single one.
23
+ *
24
+ * A single `replace(/"""/g, '"')` is not enough: `String.replace` scans left
25
+ * to right without rescanning its own output, so `n` quotes become
26
+ * `floor(n/3) + n%3`, and any `n >= 7` still leaves three in a row. Matching
27
+ * runs of length two or more removes the whole class in one pass, so there is
28
+ * no arity that survives.
29
+ */
30
+ function collapseQuoteRuns(value) {
31
+ return value.replace(/"{2,}/g, '"');
32
+ }
33
+ /** Drop a trailing high surrogate so a truncated target stays well-formed. */
34
+ function trimLoneSurrogate(value) {
35
+ return /[\uD800-\uDBFF]$/.test(value) ? value.slice(0, -1) : value;
36
+ }
37
+ export function sanitizeTarget(target) {
38
+ if (typeof target !== 'string')
39
+ throw new Error('vision-click target must be a string');
40
+ const cleaned = collapseQuoteRuns(target
41
+ // C0 and C1 controls, plus the Unicode line and paragraph
42
+ // separators, which JS \s also matches but which are line breaks
43
+ // rather than ordinary whitespace and belong in this class.
44
+ .replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]+/g, ' ')
45
+ // Zero-width characters can split a token invisibly and pad length.
46
+ .replace(/[\u200b-\u200f\u2060\ufeff]+/g, '')
47
+ // Bidirectional overrides can reorder the value where it is echoed
48
+ // back to a terminal.
49
+ .replace(/[\u202a-\u202e\u2066-\u2069]+/g, ''))
50
+ .replace(/\s+/g, ' ')
51
+ .trim();
52
+ if (!cleaned)
53
+ throw new Error('vision-click target is empty after sanitization');
54
+ return cleaned.length > MAX_TARGET_LENGTH
55
+ ? trimLoneSurrogate(cleaned.slice(0, MAX_TARGET_LENGTH))
56
+ : cleaned;
57
+ }
58
+ /**
59
+ * An agentic `codex exec` can emit command output, so its stdout is not
60
+ * bounded by the answer we asked for. Keep the newest units: the coordinate
61
+ * JSON arrives in a late event and the scan reads from the end, so a truncated
62
+ * head line is expected garbage and the parser already skips it.
63
+ *
64
+ * This counts UTF-16 code units, not bytes — non-ASCII output can occupy up to
65
+ * four bytes per unit, so treat it as an order-of-magnitude bound.
66
+ */
67
+ export const MAX_CODEX_STDOUT_UNITS = 1024 * 1024;
68
+ /** @deprecated Misnamed: this bounds code units. Use MAX_CODEX_STDOUT_UNITS. */
69
+ export const MAX_CODEX_STDOUT_BYTES = MAX_CODEX_STDOUT_UNITS;
70
+ export function appendBounded(buffer, chunk, limit = MAX_CODEX_STDOUT_UNITS) {
71
+ const next = buffer + chunk;
72
+ return next.length > limit ? next.slice(next.length - limit) : next;
73
+ }
74
+ //# sourceMappingURL=vision-input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vision-input.js","sourceRoot":"","sources":["../../../src/browser/vision-input.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAErC;;;;;;;;GAQG;AACH,SAAS,iBAAiB,CAAC,KAAa;IACpC,OAAO,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;AACxC,CAAC;AAED,8EAA8E;AAC9E,SAAS,iBAAiB,CAAC,KAAa;IACpC,OAAO,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AACvE,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAe;IAC1C,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IACxF,MAAM,OAAO,GAAG,iBAAiB,CAC7B,MAAM;QACF,0DAA0D;QAC1D,iEAAiE;QACjE,4DAA4D;SAC3D,OAAO,CAAC,4CAA4C,EAAE,GAAG,CAAC;QAC3D,oEAAoE;SACnE,OAAO,CAAC,+BAA+B,EAAE,EAAE,CAAC;QAC7C,mEAAmE;QACnE,sBAAsB;SACrB,OAAO,CAAC,gCAAgC,EAAE,EAAE,CAAC,CACrD;SACI,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,IAAI,EAAE,CAAC;IACZ,IAAI,CAAC,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;IACjF,OAAO,OAAO,CAAC,MAAM,GAAG,iBAAiB;QACrC,CAAC,CAAC,iBAAiB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,iBAAiB,CAAC,CAAC;QACxD,CAAC,CAAC,OAAO,CAAC;AAClB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,GAAG,IAAI,CAAC;AAElD,gFAAgF;AAChF,MAAM,CAAC,MAAM,sBAAsB,GAAG,sBAAsB,CAAC;AAE7D,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,KAAa,EAAE,KAAK,GAAG,sBAAsB;IACvF,MAAM,IAAI,GAAG,MAAM,GAAG,KAAK,CAAC;IAC5B,OAAO,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACxE,CAAC"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * src/browser/vision-provider.ts — how the vision provider is invoked.
3
+ *
4
+ * The invocation used to be a literal array inside the pipeline, which is why
5
+ * it drifted from the documented safety position without anyone noticing:
6
+ * `--dangerously-bypass-approvals-and-sandbox` was passed unconditionally for
7
+ * what is a pure image-classification call, while the skill docs stated that
8
+ * cli-jaw never adds it automatically. Describing the invocation here makes
9
+ * that disagreement visible and testable instead of buried in a spawn call.
10
+ *
11
+ * The flag cannot simply be deleted. It is documented as the only known
12
+ * workaround for the Windows sandbox killing `codex exec` children with exit
13
+ * `-1073741502` and an empty stderr. So it becomes an explicit opt-in that is
14
+ * off by default, and the Windows consequence is stated rather than implied.
15
+ *
16
+ * Note the contrast with `agent/args.ts`, which does add the flag when the
17
+ * user selected the Auto (YOLO) permission policy. That is not the same case:
18
+ * an agent turn exists to run commands, so choosing Auto is the user saying
19
+ * they accept that. A vision lookup runs none, so it starts from off.
20
+ */
21
+ /** Disables the codexclaw plugin for the duration of the lookup. */
22
+ export const CODEXCLAW_PLUGIN_DISABLE_CONFIG = 'plugins."codexclaw@personal".enabled=false';
23
+ /**
24
+ * Build the argument list for a vision lookup.
25
+ *
26
+ * Pure, so the exact invocation can be asserted without spawning anything —
27
+ * which matters here, because the defect this replaces was a single wrong
28
+ * argument that no test could see.
29
+ */
30
+ export function buildVisionInvocation(opts) {
31
+ const bypassedSandbox = opts.bypassSandbox === true;
32
+ const args = [
33
+ 'exec', '-i', opts.screenshotPath, '--json',
34
+ // Do not persist a thread for a one-shot lookup.
35
+ '--ephemeral',
36
+ '-c', CODEXCLAW_PLUGIN_DISABLE_CONFIG,
37
+ '--skip-git-repo-check',
38
+ ];
39
+ if (bypassedSandbox)
40
+ args.push('--dangerously-bypass-approvals-and-sandbox');
41
+ args.push(opts.prompt);
42
+ return { command: 'codex', args, bypassedSandbox };
43
+ }
44
+ /**
45
+ * Explain a spawn failure in terms of the sandbox, when that is what it was.
46
+ *
47
+ * Exit `-1073741502` with no stderr is the Windows sandbox terminating the
48
+ * child. Without this the caller sees an empty error and reasonably concludes
49
+ * the model failed, which sends them looking in the wrong place entirely.
50
+ */
51
+ export const WINDOWS_SANDBOX_KILL_CODE = -1073741502;
52
+ /**
53
+ * The same status as an unsigned 32-bit value (`0xC0000142`).
54
+ *
55
+ * Node normally surfaces a Windows exit status as a signed integer, but the
56
+ * value travels through enough layers — shells, wrappers, JSON round-trips —
57
+ * that the unsigned form does turn up. Matching only one representation would
58
+ * mean the explanation silently never fires on exactly the platform it exists
59
+ * for, which is the failure mode this whole phase is about.
60
+ */
61
+ export const WINDOWS_SANDBOX_KILL_CODE_UNSIGNED = WINDOWS_SANDBOX_KILL_CODE >>> 0;
62
+ function isWindowsSandboxKill(code) {
63
+ return code === WINDOWS_SANDBOX_KILL_CODE || code === WINDOWS_SANDBOX_KILL_CODE_UNSIGNED;
64
+ }
65
+ export function explainExit(code, stderr, bypassedSandbox) {
66
+ const detail = stderr.trim().slice(0, 200);
67
+ if (isWindowsSandboxKill(code) && !detail) {
68
+ return bypassedSandbox
69
+ ? `codex exec was terminated (exit ${code}) even with the sandbox bypass enabled`
70
+ : `codex exec was terminated by the Windows sandbox (exit ${code}, no stderr). `
71
+ + 'Retry with the sandbox bypass only if you accept that it disables both approvals and the sandbox.';
72
+ }
73
+ return `codex exec failed (code ${code})${detail ? ': ' + detail : ''}`;
74
+ }
75
+ //# sourceMappingURL=vision-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vision-provider.js","sourceRoot":"","sources":["../../../src/browser/vision-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,oEAAoE;AACpE,MAAM,CAAC,MAAM,+BAA+B,GAAG,4CAA4C,CAAC;AAwB5F;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAA6B;IAC/D,MAAM,eAAe,GAAG,IAAI,CAAC,aAAa,KAAK,IAAI,CAAC;IACpD,MAAM,IAAI,GAAG;QACT,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,cAAc,EAAE,QAAQ;QAC3C,iDAAiD;QACjD,aAAa;QACb,IAAI,EAAE,+BAA+B;QACrC,uBAAuB;KAC1B,CAAC;IACF,IAAI,eAAe;QAAE,IAAI,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;IAC7E,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACvB,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC;AACvD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,UAAU,CAAC;AAErD;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,kCAAkC,GAAG,yBAAyB,KAAK,CAAC,CAAC;AAElF,SAAS,oBAAoB,CAAC,IAAmB;IAC7C,OAAO,IAAI,KAAK,yBAAyB,IAAI,IAAI,KAAK,kCAAkC,CAAC;AAC7F,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAmB,EAAE,MAAc,EAAE,eAAwB;IACrF,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC3C,IAAI,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;QACxC,OAAO,eAAe;YAClB,CAAC,CAAC,mCAAmC,IAAI,wCAAwC;YACjF,CAAC,CAAC,0DAA0D,IAAI,gBAAgB;kBAC5E,mGAAmG,CAAC;IAChH,CAAC;IACD,OAAO,2BAA2B,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;AAC5E,CAAC"}