@gajae-code/coding-agent 0.12.21 → 0.13.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 (222) hide show
  1. package/CHANGELOG.md +67 -2
  2. package/dist/types/cli/args.d.ts +5 -2
  3. package/dist/types/cli/root-flags.d.ts +246 -0
  4. package/dist/types/cli.d.ts +11 -19
  5. package/dist/types/commands/daemon.d.ts +0 -30
  6. package/dist/types/commands/launch.d.ts +11 -19
  7. package/dist/types/commands/quick-lane.d.ts +32 -0
  8. package/dist/types/commands/sdk.d.ts +21 -15
  9. package/dist/types/config/file-lock-gc.d.ts +2 -0
  10. package/dist/types/config/model-equivalence.d.ts +20 -0
  11. package/dist/types/config/model-profile-activation.d.ts +62 -17
  12. package/dist/types/config/model-profiles.d.ts +10 -0
  13. package/dist/types/config/model-registry.d.ts +68 -1
  14. package/dist/types/config/model-resolver.d.ts +15 -4
  15. package/dist/types/config/models-config-schema.d.ts +7 -2
  16. package/dist/types/config/provider-selection-policy.d.ts +62 -0
  17. package/dist/types/config/settings-schema.d.ts +36 -78
  18. package/dist/types/config/settings.d.ts +2 -0
  19. package/dist/types/edit/renderer.d.ts +5 -2
  20. package/dist/types/edit/streaming.d.ts +13 -0
  21. package/dist/types/extensibility/custom-tools/types.d.ts +2 -0
  22. package/dist/types/extensibility/extensions/compact-handler.d.ts +2 -1
  23. package/dist/types/extensibility/extensions/types.d.ts +3 -0
  24. package/dist/types/gjc-runtime/gc-runtime.d.ts +19 -5
  25. package/dist/types/internal-urls/local-protocol.d.ts +1 -0
  26. package/dist/types/modes/components/model-selector.d.ts +0 -2
  27. package/dist/types/modes/components/provider-order-context.d.ts +45 -0
  28. package/dist/types/modes/components/provider-order-editor.d.ts +24 -0
  29. package/dist/types/modes/components/settings-defs.d.ts +5 -1
  30. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  31. package/dist/types/quick-lane/classify.d.ts +40 -0
  32. package/dist/types/runtime/stt-settings-setup.d.ts +18 -0
  33. package/dist/types/sdk/broker/broker.d.ts +56 -2
  34. package/dist/types/sdk/broker/operation-receipt-ledger.d.ts +25 -0
  35. package/dist/types/sdk/broker/session-index.d.ts +83 -2
  36. package/dist/types/sdk/broker/state-version.d.ts +1 -1
  37. package/dist/types/sdk/bus/notification-service.d.ts +6 -0
  38. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +8 -1
  39. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +4 -1
  40. package/dist/types/sdk/bus/telegram-daemon.d.ts +26 -0
  41. package/dist/types/sdk/cli/rows.d.ts +98 -0
  42. package/dist/types/sdk/cli/session-cli.d.ts +35 -3
  43. package/dist/types/sdk/elevation/allowlist.d.ts +20 -0
  44. package/dist/types/sdk/elevation/audit.d.ts +93 -0
  45. package/dist/types/sdk/elevation/capability.d.ts +3 -0
  46. package/dist/types/sdk/elevation/digest.d.ts +72 -0
  47. package/dist/types/sdk/elevation/dispatch-receipt.d.ts +85 -0
  48. package/dist/types/sdk/elevation/grant-ledger.d.ts +163 -0
  49. package/dist/types/sdk/elevation/owner.d.ts +48 -0
  50. package/dist/types/sdk/guides/cache.d.ts +98 -0
  51. package/dist/types/sdk/guides/catalog.d.ts +156 -0
  52. package/dist/types/sdk/guides/cli.d.ts +39 -0
  53. package/dist/types/sdk/guides/manifest.d.ts +66 -0
  54. package/dist/types/sdk/guides/verify.d.ts +66 -0
  55. package/dist/types/sdk/host/control/dispatch.d.ts +8 -0
  56. package/dist/types/sdk/host/control/index.d.ts +1 -1
  57. package/dist/types/sdk/host/control/operations.d.ts +3 -2
  58. package/dist/types/sdk/host/query/cursor.d.ts +10 -0
  59. package/dist/types/sdk/host/query/handlers.d.ts +21 -0
  60. package/dist/types/sdk/host/session-runtime.d.ts +9 -0
  61. package/dist/types/sdk/mcp/server.d.ts +2 -2
  62. package/dist/types/sdk/prompt-status.d.ts +11 -0
  63. package/dist/types/sdk/session.d.ts +4 -0
  64. package/dist/types/sdk/transport/index.d.ts +1 -0
  65. package/dist/types/sdk/transport/relay.d.ts +1 -0
  66. package/dist/types/sdk/transport/serve-cli.d.ts +20 -1
  67. package/dist/types/session/agent-session.d.ts +32 -18
  68. package/dist/types/session/blob-store.d.ts +13 -7
  69. package/dist/types/session/fallback-chain-controller.d.ts +15 -0
  70. package/dist/types/session/internal/managed-session-storage.d.ts +25 -1
  71. package/dist/types/session/internal/session-memory-sidecar.d.ts +5 -5
  72. package/dist/types/session/session-manager.d.ts +139 -10
  73. package/dist/types/session/session-storage.d.ts +48 -0
  74. package/dist/types/setup/provider-onboarding.d.ts +7 -1
  75. package/dist/types/slash-commands/builtin-registry.d.ts +14 -0
  76. package/dist/types/stt/setup.d.ts +2 -0
  77. package/dist/types/task/executor.d.ts +10 -2
  78. package/dist/types/tools/browser/runtime-diagnostics.d.ts +65 -0
  79. package/dist/types/tools/browser/tab-protocol.d.ts +4 -0
  80. package/dist/types/tools/browser/tab-supervisor.d.ts +2 -0
  81. package/dist/types/tools/browser/tab-worker.d.ts +2 -0
  82. package/dist/types/tools/browser.d.ts +2 -0
  83. package/dist/types/tools/image-gen.d.ts +14 -18
  84. package/dist/types/tools/index.d.ts +2 -0
  85. package/dist/types/tools/resource-gc.d.ts +2 -0
  86. package/dist/types/utils/pasted-image-path.d.ts +6 -0
  87. package/package.json +9 -9
  88. package/scripts/generate-sdk-operation-inventory.ts +8 -0
  89. package/scripts/run-test-manifest.ts +61 -21
  90. package/scripts/verify-gjc-sdk-canonicalization.ts +9 -4
  91. package/src/cli/args.ts +62 -9
  92. package/src/cli/completion-cli.ts +1 -0
  93. package/src/cli/fast-help.ts +1 -0
  94. package/src/cli/root-flags.ts +100 -0
  95. package/src/cli/setup-cli.ts +6 -2
  96. package/src/cli.ts +42 -65
  97. package/src/commands/acp.ts +1 -1
  98. package/src/commands/daemon.ts +2 -31
  99. package/src/commands/gc.ts +1 -1
  100. package/src/commands/launch.ts +8 -129
  101. package/src/commands/quick-lane.ts +51 -0
  102. package/src/commands/sdk.ts +200 -26
  103. package/src/commands/setup.ts +4 -2
  104. package/src/config/file-lock-gc.ts +4 -1
  105. package/src/config/model-equivalence.ts +76 -7
  106. package/src/config/model-profile-activation.ts +1115 -265
  107. package/src/config/model-profiles.ts +134 -2
  108. package/src/config/model-registry.ts +352 -50
  109. package/src/config/model-resolver.ts +289 -38
  110. package/src/config/models-config-schema.ts +11 -4
  111. package/src/config/provider-selection-policy.ts +146 -0
  112. package/src/config/settings-schema.ts +50 -77
  113. package/src/config/settings.ts +40 -0
  114. package/src/coordinator-mcp/server.ts +32 -2
  115. package/src/edit/renderer.ts +175 -223
  116. package/src/edit/streaming.ts +108 -1
  117. package/src/export/html/index.ts +57 -7
  118. package/src/extensibility/custom-tools/types.ts +2 -0
  119. package/src/extensibility/extensions/compact-handler.ts +3 -2
  120. package/src/extensibility/extensions/runner.ts +6 -0
  121. package/src/extensibility/extensions/types.ts +3 -0
  122. package/src/gjc-runtime/gc-runtime.ts +379 -20
  123. package/src/gjc-runtime/launch-worktree.ts +5 -1
  124. package/src/gjc-runtime/team-runtime.ts +59 -2
  125. package/src/gjc-runtime/tmux-owner-isolation.ts +19 -0
  126. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +16 -1
  127. package/src/internal-urls/docs-index.generated.ts +17 -13
  128. package/src/internal-urls/local-protocol.ts +1 -0
  129. package/src/main.ts +40 -3
  130. package/src/memories/index.ts +4 -2
  131. package/src/modes/acp/acp-agent.ts +235 -91
  132. package/src/modes/components/model-selector.ts +206 -42
  133. package/src/modes/components/provider-order-context.ts +197 -0
  134. package/src/modes/components/provider-order-editor.ts +441 -0
  135. package/src/modes/components/settings-defs.ts +21 -1
  136. package/src/modes/components/settings-selector.ts +45 -2
  137. package/src/modes/components/tool-execution.ts +47 -15
  138. package/src/modes/controllers/command-controller.ts +6 -3
  139. package/src/modes/controllers/extension-ui-controller.ts +2 -0
  140. package/src/modes/controllers/input-controller.ts +73 -2
  141. package/src/modes/controllers/selector-controller.ts +260 -153
  142. package/src/modes/prompt-suggestion-controller.ts +1 -1
  143. package/src/modes/runtime-init.ts +1 -0
  144. package/src/modes/utils/ui-helpers.ts +18 -0
  145. package/src/prompts/tools/browser.md +1 -0
  146. package/src/quick-lane/classify.ts +257 -0
  147. package/src/rlm/index.ts +1 -1
  148. package/src/runtime/stt-settings-setup.ts +57 -0
  149. package/src/sdk/acp/adapter.ts +2 -1
  150. package/src/sdk/broker/broker.ts +780 -12
  151. package/src/sdk/broker/lifecycle.ts +70 -29
  152. package/src/sdk/broker/operation-receipt-ledger.ts +144 -0
  153. package/src/sdk/broker/session-index.ts +463 -48
  154. package/src/sdk/broker/state-version.ts +13 -9
  155. package/src/sdk/broker/transport.ts +14 -1
  156. package/src/sdk/bus/index.ts +34 -13
  157. package/src/sdk/bus/kind-aware-reconciliation.ts +21 -2
  158. package/src/sdk/bus/notification-service.ts +47 -1
  159. package/src/sdk/bus/prompt-reconciliation.ts +22 -3
  160. package/src/sdk/bus/telegram-daemon-contract.ts +4 -1
  161. package/src/sdk/bus/telegram-daemon.ts +54 -3
  162. package/src/sdk/cli/rows.ts +247 -0
  163. package/src/sdk/cli/session-cli.ts +930 -79
  164. package/src/sdk/elevation/allowlist.ts +54 -0
  165. package/src/sdk/elevation/audit.ts +208 -0
  166. package/src/sdk/elevation/capability.ts +41 -0
  167. package/src/sdk/elevation/digest.ts +129 -0
  168. package/src/sdk/elevation/dispatch-receipt.ts +184 -0
  169. package/src/sdk/elevation/grant-ledger.ts +1142 -0
  170. package/src/sdk/elevation/owner.ts +109 -0
  171. package/src/sdk/guides/cache.ts +363 -0
  172. package/src/sdk/guides/catalog.ts +554 -0
  173. package/src/sdk/guides/cli.ts +235 -0
  174. package/src/sdk/guides/manifest.ts +152 -0
  175. package/src/sdk/guides/verify.ts +178 -0
  176. package/src/sdk/host/control/dispatch.ts +49 -1
  177. package/src/sdk/host/control/index.ts +1 -0
  178. package/src/sdk/host/control/operations.ts +8 -1
  179. package/src/sdk/host/query/cursor.ts +47 -3
  180. package/src/sdk/host/query/handlers.ts +155 -2
  181. package/src/sdk/host/sdk-surface-parity.test.ts +1 -0
  182. package/src/sdk/host/session-runtime.test.ts +391 -1
  183. package/src/sdk/host/session-runtime.ts +308 -73
  184. package/src/sdk/host/surface-policy.ts +3 -0
  185. package/src/sdk/host/websocket-transport.ts +7 -1
  186. package/src/sdk/mcp/server.ts +88 -15
  187. package/src/sdk/prompt-status.ts +8 -0
  188. package/src/sdk/protocol/operation-inventory.generated.json +62 -0
  189. package/src/sdk/protocol/operation-registry.ts +3 -2
  190. package/src/sdk/session.ts +87 -39
  191. package/src/sdk/transport/index.ts +1 -0
  192. package/src/sdk/transport/relay.ts +3 -0
  193. package/src/sdk/transport/serve-cli.ts +111 -24
  194. package/src/secrets/obfuscator.ts +92 -12
  195. package/src/session/agent-session.ts +322 -93
  196. package/src/session/blob-store.ts +60 -8
  197. package/src/session/fallback-chain-controller.ts +35 -0
  198. package/src/session/internal/managed-session-scope.ts +25 -3
  199. package/src/session/internal/managed-session-storage.ts +574 -73
  200. package/src/session/internal/session-memory-sidecar.ts +19 -29
  201. package/src/session/session-manager.ts +2166 -743
  202. package/src/session/session-storage.ts +715 -48
  203. package/src/setup/provider-onboarding.ts +19 -3
  204. package/src/setup/provider-presets.json +3 -1
  205. package/src/slash-commands/builtin-registry.ts +85 -3
  206. package/src/stt/downloader.ts +2 -3
  207. package/src/stt/setup.ts +29 -4
  208. package/src/task/executor.ts +22 -18
  209. package/src/task/index.ts +24 -1
  210. package/src/tools/archive-reader.ts +11 -2
  211. package/src/tools/browser/runtime-diagnostics.ts +249 -0
  212. package/src/tools/browser/tab-protocol.ts +4 -0
  213. package/src/tools/browser/tab-supervisor.ts +5 -0
  214. package/src/tools/browser/tab-worker.ts +36 -1
  215. package/src/tools/browser.ts +7 -0
  216. package/src/tools/image-gen.ts +110 -166
  217. package/src/tools/index.ts +2 -0
  218. package/src/tools/read.ts +1 -1
  219. package/src/tools/resource-gc.ts +6 -1
  220. package/src/tools/tool-catalog.generated.ts +5 -1
  221. package/src/utils/pasted-image-path.ts +93 -0
  222. package/src/web/search/index.ts +3 -3
@@ -0,0 +1,249 @@
1
+ import { createHash } from "node:crypto";
2
+ import type { CDPSession, Page } from "puppeteer-core";
3
+
4
+ const MAX_RUNTIME_DIAGNOSTICS = 20;
5
+
6
+ /** Hard cap on the `url` field (chars). Truncation is visible: the value ends in `…`. */
7
+ const MAX_RUNTIME_DIAGNOSTIC_URL_CHARS = 256;
8
+
9
+ /** Hard cap on the serialized diagnostics block (UTF-8 bytes) emitted into tool results. */
10
+ const MAX_RUNTIME_DIAGNOSTICS_BLOCK_BYTES = 4 * 1024;
11
+
12
+ /**
13
+ * Fixed allowlist of built-in error class names. Page-controlled class names (anything
14
+ * defined by page scripts, e.g. `CustomerAlice123`) are never echoed: a diagnostic's
15
+ * `class` field is emitted only when it matches one of these fixed names.
16
+ */
17
+ const ERROR_CLASS_ALLOWLIST = new Set([
18
+ "Error",
19
+ "EvalError",
20
+ "RangeError",
21
+ "ReferenceError",
22
+ "SyntaxError",
23
+ "TypeError",
24
+ "URIError",
25
+ "AggregateError",
26
+ "InternalError",
27
+ "DOMException",
28
+ "AbortError",
29
+ "CompileError",
30
+ "DataError",
31
+ "EncodingError",
32
+ "HierarchyRequestError",
33
+ "IndexSizeError",
34
+ "InvalidAccessError",
35
+ "InvalidCharacterError",
36
+ "InvalidModificationError",
37
+ "InvalidNodeTypeError",
38
+ "InvalidStateError",
39
+ "LinkError",
40
+ "NamespaceError",
41
+ "NetworkError",
42
+ "NoModificationAllowedError",
43
+ "NotFoundError",
44
+ "NotReadableError",
45
+ "NotSupportedError",
46
+ "OperationError",
47
+ "QuotaExceededError",
48
+ "ReadOnlyError",
49
+ "RuntimeError",
50
+ "SecurityError",
51
+ "TimeoutError",
52
+ "TransactionInactiveError",
53
+ "VersionError",
54
+ "WrongDocumentError",
55
+ ]);
56
+
57
+ /**
58
+ * Fixed, page-uncontrollable script-URL literals that V8 reports in stack frames and
59
+ * exception details. These are safe to surface verbatim; anything else that is not a
60
+ * parseable URL is emitted as an irreversible hash so a raw path/token cannot leak.
61
+ */
62
+ const SAFE_NON_URL_LITERALS = new Set([
63
+ "",
64
+ "eval",
65
+ "anonymous",
66
+ "script",
67
+ "[native code]",
68
+ "__puppeteer_evaluation_script__",
69
+ ]);
70
+
71
+ /** `about:` paths are page-navigable, so keep only browser-fixed safe names. */
72
+ const SAFE_ABOUT_PATH = /^[A-Za-z0-9._-]{1,32}$/;
73
+
74
+ export interface BrowserRuntimeDiagnostic {
75
+ kind: "pageerror" | "console-error";
76
+ at: string;
77
+ url: string;
78
+ line?: number;
79
+ column?: number;
80
+ class?: string;
81
+ }
82
+
83
+ interface ExceptionThrownEvent {
84
+ exceptionDetails?: {
85
+ url?: string;
86
+ lineNumber?: number;
87
+ columnNumber?: number;
88
+ exception?: { className?: string };
89
+ };
90
+ }
91
+
92
+ interface ConsoleCalledEvent {
93
+ type?: string;
94
+ stackTrace?: { callFrames?: Array<{ url?: string; lineNumber?: number; columnNumber?: number }> };
95
+ }
96
+
97
+ function sha256Hex(value: string): string {
98
+ return createHash("sha256").update(value).digest("hex");
99
+ }
100
+
101
+ /**
102
+ * Reduce a runtime-event URL to a persisted-safe location.
103
+ *
104
+ * http(s) URLs are emitted origin-only: path segments routinely carry tokens (signed
105
+ * URLs, invite/reset links, per-tenant identifiers), and diagnostics end up in tool
106
+ * results that are persisted and shareable. Non-parseable values are never echoed
107
+ * verbatim — known engine literals pass through, everything else becomes an
108
+ * irreversible bounded hash so entries stay correlatable without leaking.
109
+ */
110
+ export function maskBrowserRuntimeUrl(value: string): string {
111
+ let url: URL | undefined;
112
+ try {
113
+ url = new URL(value);
114
+ } catch {
115
+ url = undefined;
116
+ }
117
+ if (url) {
118
+ if (url.protocol === "http:" || url.protocol === "https:") {
119
+ return boundUrlField(url.origin);
120
+ }
121
+ if (url.protocol === "about:") {
122
+ const path = url.pathname && SAFE_ABOUT_PATH.test(url.pathname) ? `about:${url.pathname}` : "about:…";
123
+ return boundUrlField(path);
124
+ }
125
+ return boundUrlField(`${url.protocol}…`);
126
+ }
127
+ if (SAFE_NON_URL_LITERALS.has(value)) return value;
128
+ return `[non-url:${sha256Hex(value).slice(0, 8)}]`;
129
+ }
130
+
131
+ function finite(value: unknown): number | undefined {
132
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
133
+ }
134
+
135
+ export function pageErrorDiagnostic(event: ExceptionThrownEvent, fallbackUrl: string): BrowserRuntimeDiagnostic {
136
+ const details = event.exceptionDetails;
137
+ const className = details?.exception?.className;
138
+ return {
139
+ kind: "pageerror",
140
+ at: new Date().toISOString(),
141
+ url: maskBrowserRuntimeUrl(details?.url || fallbackUrl),
142
+ ...(finite(details?.lineNumber) === undefined ? {} : { line: details?.lineNumber }),
143
+ ...(finite(details?.columnNumber) === undefined ? {} : { column: details?.columnNumber }),
144
+ ...(typeof className === "string" && ERROR_CLASS_ALLOWLIST.has(className) ? { class: className } : {}),
145
+ };
146
+ }
147
+
148
+ export function consoleErrorDiagnostic(
149
+ event: ConsoleCalledEvent,
150
+ fallbackUrl: string,
151
+ ): BrowserRuntimeDiagnostic | undefined {
152
+ if (event.type !== "error") return undefined;
153
+ const frame = event.stackTrace?.callFrames?.[0];
154
+ return {
155
+ kind: "console-error",
156
+ at: new Date().toISOString(),
157
+ url: maskBrowserRuntimeUrl(frame?.url || fallbackUrl),
158
+ ...(finite(frame?.lineNumber) === undefined ? {} : { line: frame?.lineNumber }),
159
+ ...(finite(frame?.columnNumber) === undefined ? {} : { column: frame?.columnNumber }),
160
+ };
161
+ }
162
+
163
+ function boundUrlField(url: string): string {
164
+ if (url.length <= MAX_RUNTIME_DIAGNOSTIC_URL_CHARS) return url;
165
+ return `${url.slice(0, MAX_RUNTIME_DIAGNOSTIC_URL_CHARS - 1)}…`;
166
+ }
167
+
168
+ /**
169
+ * Serialize a drained mailbox into the tool-result text block.
170
+ *
171
+ * - Per-field: `url` is capped with a visible `…` marker; every other field is
172
+ * structurally bounded (ISO timestamp, finite integers, allowlisted class).
173
+ * - Total: the serialized block is capped at `MAX_RUNTIME_DIAGNOSTICS_BLOCK_BYTES`.
174
+ * When the budget is tight the oldest entries are shed (newest survive) and the
175
+ * block carries an explicit `runtimeDiagnosticsTruncated: true` — truncation is
176
+ * never silent.
177
+ */
178
+ export function serializeRuntimeDiagnostics(drained: {
179
+ runtimeDiagnostics: BrowserRuntimeDiagnostic[];
180
+ runtimeDiagnosticsDropped: number;
181
+ }): string {
182
+ const { runtimeDiagnostics, runtimeDiagnosticsDropped } = drained;
183
+ const bounded = runtimeDiagnostics.map(entry => ({ ...entry, url: boundUrlField(entry.url) }));
184
+ const encoder = new TextEncoder();
185
+ const render = (entries: BrowserRuntimeDiagnostic[], truncated: boolean): string =>
186
+ JSON.stringify(
187
+ {
188
+ runtimeDiagnostics: entries,
189
+ runtimeDiagnosticsDropped,
190
+ ...(truncated ? { runtimeDiagnosticsTruncated: true } : {}),
191
+ },
192
+ null,
193
+ 2,
194
+ );
195
+ let kept = bounded;
196
+ let text = render(kept, false);
197
+ while (encoder.encode(text).byteLength > MAX_RUNTIME_DIAGNOSTICS_BLOCK_BYTES && kept.length > 1) {
198
+ kept = kept.slice(1);
199
+ text = render(kept, true);
200
+ }
201
+ // A single entry can exceed the budget only on type-violating input (every field is
202
+ // structurally bounded); the block still surfaces the entry, but truncation is
203
+ // never silent — flag the over-budget block explicitly.
204
+ if (encoder.encode(text).byteLength > MAX_RUNTIME_DIAGNOSTICS_BLOCK_BYTES) {
205
+ text = render(kept, true);
206
+ }
207
+ return text;
208
+ }
209
+
210
+ export class BrowserRuntimeDiagnosticsMailbox {
211
+ #entries: BrowserRuntimeDiagnostic[] = [];
212
+ #dropped = 0;
213
+
214
+ push(entry: BrowserRuntimeDiagnostic): void {
215
+ if (this.#entries.length === MAX_RUNTIME_DIAGNOSTICS) {
216
+ this.#entries.shift();
217
+ this.#dropped += 1;
218
+ }
219
+ this.#entries.push(entry);
220
+ }
221
+
222
+ drain(): { runtimeDiagnostics: BrowserRuntimeDiagnostic[]; runtimeDiagnosticsDropped: number } {
223
+ const runtimeDiagnostics = this.#entries.splice(0);
224
+ const runtimeDiagnosticsDropped = this.#dropped;
225
+ this.#dropped = 0;
226
+ return { runtimeDiagnostics, runtimeDiagnosticsDropped };
227
+ }
228
+ }
229
+
230
+ export async function instrumentBrowserRuntimeDiagnostics(
231
+ page: Page,
232
+ mailbox: BrowserRuntimeDiagnosticsMailbox,
233
+ ): Promise<CDPSession> {
234
+ const session = await page.target().createCDPSession();
235
+ session.on("Runtime.exceptionThrown", event => {
236
+ mailbox.push(pageErrorDiagnostic(event, page.url()));
237
+ });
238
+ session.on("Runtime.consoleAPICalled", event => {
239
+ const diagnostic = consoleErrorDiagnostic(event, page.url());
240
+ if (diagnostic) mailbox.push(diagnostic);
241
+ });
242
+ try {
243
+ await session.send("Runtime.enable");
244
+ return session;
245
+ } catch (error) {
246
+ await session.detach().catch(() => undefined);
247
+ throw error;
248
+ }
249
+ }
@@ -51,6 +51,8 @@ export type WorkerInitPayload =
51
51
  url?: string;
52
52
  waitUntil?: "load" | "domcontentloaded" | "networkidle0" | "networkidle2";
53
53
  timeoutMs: number;
54
+ /** Opt-in bounded page runtime diagnostics (extra CDP session + Runtime events). */
55
+ runtimeDiagnostics?: boolean;
54
56
  }
55
57
  | {
56
58
  mode: "attach";
@@ -58,6 +60,8 @@ export type WorkerInitPayload =
58
60
  safeDir: string;
59
61
  targetId: string;
60
62
  dialogs?: "accept" | "dismiss";
63
+ /** Opt-in bounded page runtime diagnostics (extra CDP session + Runtime events). */
64
+ runtimeDiagnostics?: boolean;
61
65
  };
62
66
 
63
67
  export type ToolReply = { ok: true; value: unknown } | { ok: false; error: RunErrorPayload };
@@ -81,6 +81,8 @@ export interface AcquireTabOptions {
81
81
  dialogs?: DialogPolicy;
82
82
  /** Owning session id so dispose can release only this session's tabs (F13). */
83
83
  ownerId?: string;
84
+ /** Opt-in bounded page runtime diagnostics (extra CDP session + Runtime events per tab). */
85
+ runtimeDiagnostics?: boolean;
84
86
  /** Internal recovery-only target id; makes a headless replacement attach, not open a page. */
85
87
  recoveryTargetId?: string;
86
88
  /** Recovery fence: replacement must not overwrite a tab installed after dead teardown. */
@@ -666,6 +668,7 @@ async function buildInitPayload(browser: BrowserHandle, opts: AcquireTabOptions)
666
668
  safeDir,
667
669
  targetId: opts.recoveryTargetId,
668
670
  dialogs: opts.dialogs,
671
+ ...(opts.runtimeDiagnostics ? { runtimeDiagnostics: true } : {}),
669
672
  };
670
673
  }
671
674
  if (browser.kind.kind === "headless") {
@@ -679,6 +682,7 @@ async function buildInitPayload(browser: BrowserHandle, opts: AcquireTabOptions)
679
682
  url: opts.url,
680
683
  waitUntil: opts.waitUntil,
681
684
  timeoutMs: opts.timeoutMs,
685
+ ...(opts.runtimeDiagnostics ? { runtimeDiagnostics: true } : {}),
682
686
  };
683
687
  }
684
688
  const page = await pickElectronTarget(browser.browser, opts.target);
@@ -689,6 +693,7 @@ async function buildInitPayload(browser: BrowserHandle, opts: AcquireTabOptions)
689
693
  safeDir,
690
694
  targetId,
691
695
  dialogs: opts.dialogs,
696
+ ...(opts.runtimeDiagnostics ? { runtimeDiagnostics: true } : {}),
692
697
  };
693
698
  }
694
699
  export async function buildInitPayloadForTest(
@@ -7,6 +7,7 @@ import { Snowflake } from "@gajae-code/utils/snowflake";
7
7
  import type { HTMLElement } from "linkedom";
8
8
  import type {
9
9
  Browser,
10
+ CDPSession,
10
11
  Dialog,
11
12
  ElementHandle,
12
13
  HTTPResponse,
@@ -27,6 +28,11 @@ import {
27
28
  loadPuppeteerInWorker,
28
29
  } from "./launch";
29
30
  import { extractReadableFromHtml, type ReadableFormat, type ReadableResult } from "./readable";
31
+ import {
32
+ BrowserRuntimeDiagnosticsMailbox,
33
+ instrumentBrowserRuntimeDiagnostics,
34
+ serializeRuntimeDiagnostics,
35
+ } from "./runtime-diagnostics";
30
36
  import { formatScreenshot } from "./screenshot-format";
31
37
  import type {
32
38
  Observation,
@@ -75,6 +81,16 @@ const INTERACTIVE_AX_ROLES = new Set([
75
81
 
76
82
  const LEGACY_SELECTOR_PREFIXES = ["p-aria/", "p-text/", "p-xpath/", "p-pierce/"] as const;
77
83
 
84
+ /**
85
+ * Test seam: override the worker's puppeteer loader so WorkerCore-level tests can
86
+ * inject a fake browser/page without a live Chromium. Mirrors the supervisor's
87
+ * `__setAcquireTabWorkerDepsForTest` pattern.
88
+ */
89
+ let loadPuppeteerInWorkerForTest: typeof loadPuppeteerInWorker | undefined;
90
+ export function __setLoadPuppeteerInWorkerForTest(loader: typeof loadPuppeteerInWorker | undefined): void {
91
+ loadPuppeteerInWorkerForTest = loader;
92
+ }
93
+
78
94
  type DialogPolicy = "accept" | "dismiss";
79
95
  type DragTarget = string | { readonly x: number; readonly y: number };
80
96
  type ActionabilityResult = { ok: true; x: number; y: number } | { ok: false; reason: string };
@@ -398,6 +414,8 @@ export class WorkerCore {
398
414
  #transport: Transport;
399
415
  #browser?: Browser;
400
416
  #page?: Page;
417
+ #runtimeDiagnostics = new BrowserRuntimeDiagnosticsMailbox();
418
+ #runtimeDiagnosticsSession?: CDPSession;
401
419
  #targetId?: string;
402
420
  #elementCache = new Map<number, ElementHandle>();
403
421
  #elementCounter = 0;
@@ -447,7 +465,7 @@ export class WorkerCore {
447
465
  async #init(payload: WorkerInitPayload): Promise<void> {
448
466
  try {
449
467
  this.#mode = payload.mode;
450
- const puppeteer = await loadPuppeteerInWorker(payload.safeDir);
468
+ const puppeteer = await (loadPuppeteerInWorkerForTest ?? loadPuppeteerInWorker)(payload.safeDir);
451
469
  this.#browser = await puppeteer.connect({
452
470
  browserWSEndpoint: payload.browserWSEndpoint,
453
471
  defaultViewport: null,
@@ -457,6 +475,7 @@ export class WorkerCore {
457
475
  this.#page = await this.#browser.newPage();
458
476
  await applyStealthPatches(this.#browser, this.#page, { browserSession: null, override: null }, payload.geo);
459
477
  await applyViewport(this.#page, payload.viewport);
478
+ if (payload.runtimeDiagnostics) await this.#instrumentRuntimeDiagnostics(this.#page);
460
479
  if (payload.dialogs) this.#applyDialogPolicy(payload.dialogs);
461
480
  if (payload.url) {
462
481
  await this.#page.goto(payload.url, {
@@ -467,6 +486,7 @@ export class WorkerCore {
467
486
  }
468
487
  } else {
469
488
  this.#page = await this.#findAttachedPage(payload.targetId);
489
+ if (payload.runtimeDiagnostics) await this.#instrumentRuntimeDiagnostics(this.#page);
470
490
  if (payload.dialogs) this.#applyDialogPolicy(payload.dialogs);
471
491
  }
472
492
  this.#targetId = await targetIdForPage(this.#page);
@@ -517,6 +537,16 @@ export class WorkerCore {
517
537
  this.#dialogHandler = handler;
518
538
  }
519
539
 
540
+ async #instrumentRuntimeDiagnostics(page: Page): Promise<void> {
541
+ try {
542
+ this.#runtimeDiagnosticsSession = await instrumentBrowserRuntimeDiagnostics(page, this.#runtimeDiagnostics);
543
+ } catch (error) {
544
+ this.#log("debug", "Failed to instrument browser runtime diagnostics", {
545
+ error: error instanceof Error ? error.message : String(error),
546
+ });
547
+ }
548
+ }
549
+
520
550
  async #postReadyInfo(): Promise<void> {
521
551
  try {
522
552
  this.#transport.send({ type: "ready", info: await this.#currentReadyInfo() });
@@ -581,6 +611,10 @@ export class WorkerCore {
581
611
  cancelRejection,
582
612
  ]);
583
613
  await this.#postReadyInfo();
614
+ const drained = this.#runtimeDiagnostics.drain();
615
+ if (drained.runtimeDiagnostics.length || drained.runtimeDiagnosticsDropped) {
616
+ displays.push({ type: "text", text: serializeRuntimeDiagnostics(drained) });
617
+ }
584
618
  this.#transport.send({
585
619
  type: "result",
586
620
  id: msg.id,
@@ -1034,6 +1068,7 @@ export class WorkerCore {
1034
1068
  this.#clearElementCache();
1035
1069
  const page = this.#page;
1036
1070
  if (this.#dialogHandler && page && !page.isClosed()) page.off("dialog", this.#dialogHandler);
1071
+ await this.#runtimeDiagnosticsSession?.detach().catch(() => undefined);
1037
1072
  if (this.#mode === "headless" && page && !page.isClosed()) await page.close().catch(() => undefined);
1038
1073
  if (this.#browser?.connected) this.#browser.disconnect();
1039
1074
  this.#transport.send({ type: "closed" });
@@ -87,6 +87,12 @@ const browserSchema = z.object({
87
87
  .enum(["accept", "dismiss"] as const)
88
88
  .describe("auto-handle dialogs")
89
89
  .optional(),
90
+ diagnostics: z
91
+ .boolean()
92
+ .describe(
93
+ "opt-in: capture bounded page runtime diagnostics (page exceptions and console.error metadata) in the next successful run/act response",
94
+ )
95
+ .optional(),
90
96
  code: z.string().describe("js body to run in tab").optional(),
91
97
  actions: z.array(actionStepSchema).describe("structured action steps for action 'act'").optional(),
92
98
  timeout: z.number().default(30).describe("timeout in seconds (default 30, max 300)").optional(),
@@ -258,6 +264,7 @@ export class BrowserTool implements AgentTool<typeof browserSchema, BrowserToolD
258
264
  target: params.app?.target,
259
265
  timeoutMs,
260
266
  dialogs: params.dialogs,
267
+ runtimeDiagnostics: params.diagnostics === true,
261
268
  signal,
262
269
  }),
263
270
  );