aiden-runtime 4.1.5 → 4.6.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 (181) hide show
  1. package/README.md +265 -847
  2. package/dist/api/server.js +32 -5
  3. package/dist/cli/v4/aidenCLI.js +536 -152
  4. package/dist/cli/v4/callbacks.js +170 -0
  5. package/dist/cli/v4/chatSession.js +245 -3
  6. package/dist/cli/v4/commands/_runtimeToggleHelpers.js +94 -0
  7. package/dist/cli/v4/commands/browserDepth.js +45 -0
  8. package/dist/cli/v4/commands/cron.js +264 -0
  9. package/dist/cli/v4/commands/daemon.js +541 -0
  10. package/dist/cli/v4/commands/daemonStatus.js +253 -0
  11. package/dist/cli/v4/commands/fanout.js +42 -59
  12. package/dist/cli/v4/commands/help.js +13 -0
  13. package/dist/cli/v4/commands/index.js +35 -1
  14. package/dist/cli/v4/commands/mcp.js +80 -54
  15. package/dist/cli/v4/commands/plannerGuard.js +53 -0
  16. package/dist/cli/v4/commands/recovery.js +122 -0
  17. package/dist/cli/v4/commands/runs.js +223 -0
  18. package/dist/cli/v4/commands/sandbox.js +48 -0
  19. package/dist/cli/v4/commands/spawnPause.js +93 -0
  20. package/dist/cli/v4/commands/suggestions.js +68 -0
  21. package/dist/cli/v4/commands/tce.js +41 -0
  22. package/dist/cli/v4/commands/trigger.js +378 -0
  23. package/dist/cli/v4/commands/update.js +95 -3
  24. package/dist/cli/v4/daemonAgentBuilder.js +145 -0
  25. package/dist/cli/v4/defaultSoul.js +1 -1
  26. package/dist/cli/v4/display/capabilityCard.js +26 -0
  27. package/dist/cli/v4/display.js +18 -8
  28. package/dist/cli/v4/replyRenderer.js +31 -23
  29. package/dist/cli/v4/updateBootPrompt.js +170 -0
  30. package/dist/core/playwrightBridge.js +129 -0
  31. package/dist/core/v4/aidenAgent.js +527 -5
  32. package/dist/core/v4/browserState.js +436 -0
  33. package/dist/core/v4/checkpoint.js +79 -0
  34. package/dist/core/v4/daemon/bootstrap.js +651 -0
  35. package/dist/core/v4/daemon/cleanShutdown.js +154 -0
  36. package/dist/core/v4/daemon/cron/cronBridge.js +126 -0
  37. package/dist/core/v4/daemon/cron/cronEmitter.js +173 -0
  38. package/dist/core/v4/daemon/cron/migration.js +199 -0
  39. package/dist/core/v4/daemon/cron/misfirePolicy.js +115 -0
  40. package/dist/core/v4/daemon/daemonConfig.js +90 -0
  41. package/dist/core/v4/daemon/db/connection.js +106 -0
  42. package/dist/core/v4/daemon/db/migrations.js +362 -0
  43. package/dist/core/v4/daemon/db/schema/v1.spec.js +18 -0
  44. package/dist/core/v4/daemon/dispatcher/agentRunner.js +98 -0
  45. package/dist/core/v4/daemon/dispatcher/budgetGate.js +127 -0
  46. package/dist/core/v4/daemon/dispatcher/daemonApproval.js +113 -0
  47. package/dist/core/v4/daemon/dispatcher/dailyBudgetTracker.js +120 -0
  48. package/dist/core/v4/daemon/dispatcher/dispatcher.js +389 -0
  49. package/dist/core/v4/daemon/dispatcher/fireRateLimiter.js +113 -0
  50. package/dist/core/v4/daemon/dispatcher/index.js +53 -0
  51. package/dist/core/v4/daemon/dispatcher/promptTemplate.js +95 -0
  52. package/dist/core/v4/daemon/dispatcher/realAgentRunner.js +356 -0
  53. package/dist/core/v4/daemon/dispatcher/resolveModel.js +93 -0
  54. package/dist/core/v4/daemon/dispatcher/sessionId.js +93 -0
  55. package/dist/core/v4/daemon/drain.js +156 -0
  56. package/dist/core/v4/daemon/eventLoopLag.js +73 -0
  57. package/dist/core/v4/daemon/health.js +159 -0
  58. package/dist/core/v4/daemon/idempotencyStore.js +204 -0
  59. package/dist/core/v4/daemon/index.js +179 -0
  60. package/dist/core/v4/daemon/instanceTracker.js +99 -0
  61. package/dist/core/v4/daemon/resourceRegistry.js +150 -0
  62. package/dist/core/v4/daemon/restartCode.js +32 -0
  63. package/dist/core/v4/daemon/restartFailureCounter.js +77 -0
  64. package/dist/core/v4/daemon/runStore.js +144 -0
  65. package/dist/core/v4/daemon/runtimeLock.js +167 -0
  66. package/dist/core/v4/daemon/signals.js +50 -0
  67. package/dist/core/v4/daemon/supervisor.js +272 -0
  68. package/dist/core/v4/daemon/triggerBus.js +279 -0
  69. package/dist/core/v4/daemon/triggers/email/allowlist.js +70 -0
  70. package/dist/core/v4/daemon/triggers/email/automatedSender.js +78 -0
  71. package/dist/core/v4/daemon/triggers/email/bodyExtractor.js +0 -0
  72. package/dist/core/v4/daemon/triggers/email/emailSeenStore.js +99 -0
  73. package/dist/core/v4/daemon/triggers/email/emailSpec.js +107 -0
  74. package/dist/core/v4/daemon/triggers/email/imapConnection.js +211 -0
  75. package/dist/core/v4/daemon/triggers/email/index.js +332 -0
  76. package/dist/core/v4/daemon/triggers/email/seenUids.js +60 -0
  77. package/dist/core/v4/daemon/triggers/fileObservationsStore.js +93 -0
  78. package/dist/core/v4/daemon/triggers/fileWatcher.js +253 -0
  79. package/dist/core/v4/daemon/triggers/fileWatcherSpec.js +88 -0
  80. package/dist/core/v4/daemon/triggers/fsIdentity.js +42 -0
  81. package/dist/core/v4/daemon/triggers/globMatcher.js +100 -0
  82. package/dist/core/v4/daemon/triggers/reconcile.js +206 -0
  83. package/dist/core/v4/daemon/triggers/settleStat.js +81 -0
  84. package/dist/core/v4/daemon/triggers/webhook.js +376 -0
  85. package/dist/core/v4/daemon/triggers/webhookDeliveriesStore.js +109 -0
  86. package/dist/core/v4/daemon/triggers/webhookIdempotency.js +72 -0
  87. package/dist/core/v4/daemon/triggers/webhookRateLimit.js +56 -0
  88. package/dist/core/v4/daemon/triggers/webhookSpec.js +76 -0
  89. package/dist/core/v4/daemon/triggers/webhookVerifier.js +128 -0
  90. package/dist/core/v4/daemon/types.js +15 -0
  91. package/dist/core/v4/dockerSession.js +461 -0
  92. package/dist/core/v4/dryRun.js +117 -0
  93. package/dist/core/v4/failureClassifier.js +779 -0
  94. package/dist/core/v4/providerFallback.js +35 -2
  95. package/dist/core/v4/recoveryReport.js +449 -0
  96. package/dist/core/v4/runtimeToggles.js +214 -0
  97. package/dist/core/v4/sandboxConfig.js +285 -0
  98. package/dist/core/v4/sandboxFs.js +316 -0
  99. package/dist/core/v4/selfimprovement/recoveryStore.js +307 -0
  100. package/dist/core/v4/selfimprovement/signatureBuilder.js +158 -0
  101. package/dist/core/v4/subagent/childBuilder.js +391 -0
  102. package/dist/core/v4/subagent/fanout.js +75 -51
  103. package/dist/core/v4/subagent/spawnPause.js +191 -0
  104. package/dist/core/v4/subagent/spawnSubAgent.js +310 -0
  105. package/dist/core/v4/suggestionCatalog.js +41 -0
  106. package/dist/core/v4/suggestionEngine.js +210 -0
  107. package/dist/core/v4/toolRegistry.js +37 -3
  108. package/dist/core/v4/turnState.js +587 -0
  109. package/dist/core/v4/update/checkUpdate.js +63 -3
  110. package/dist/core/v4/update/installMethodDetect.js +115 -0
  111. package/dist/core/v4/update/registryClient.js +121 -0
  112. package/dist/core/v4/update/skipState.js +75 -0
  113. package/dist/core/v4/verifier.js +448 -0
  114. package/dist/core/version.js +1 -1
  115. package/dist/moat/plannerGuard.js +29 -0
  116. package/dist/providers/v4/anthropicAdapter.js +31 -3
  117. package/dist/providers/v4/chatCompletionsAdapter.js +26 -3
  118. package/dist/providers/v4/codexResponsesAdapter.js +25 -2
  119. package/dist/providers/v4/ollamaPromptToolsAdapter.js +57 -2
  120. package/dist/tools/v4/browser/_observer.js +224 -0
  121. package/dist/tools/v4/browser/browserBlocker.js +396 -0
  122. package/dist/tools/v4/browser/browserClick.js +18 -1
  123. package/dist/tools/v4/browser/browserClose.js +18 -1
  124. package/dist/tools/v4/browser/browserExtract.js +5 -1
  125. package/dist/tools/v4/browser/browserFill.js +17 -1
  126. package/dist/tools/v4/browser/browserGetUrl.js +5 -1
  127. package/dist/tools/v4/browser/browserNavigate.js +16 -1
  128. package/dist/tools/v4/browser/browserScreenshot.js +5 -1
  129. package/dist/tools/v4/browser/browserScroll.js +18 -1
  130. package/dist/tools/v4/browser/browserType.js +17 -1
  131. package/dist/tools/v4/browser/captchaCheck.js +5 -1
  132. package/dist/tools/v4/executeCode.js +1 -0
  133. package/dist/tools/v4/files/fileCopy.js +56 -2
  134. package/dist/tools/v4/files/fileDelete.js +38 -1
  135. package/dist/tools/v4/files/fileList.js +12 -1
  136. package/dist/tools/v4/files/fileMove.js +59 -2
  137. package/dist/tools/v4/files/filePatch.js +43 -1
  138. package/dist/tools/v4/files/fileRead.js +12 -1
  139. package/dist/tools/v4/files/fileWrite.js +41 -1
  140. package/dist/tools/v4/index.js +88 -61
  141. package/dist/tools/v4/memory/memoryAdd.js +14 -0
  142. package/dist/tools/v4/memory/memoryRemove.js +14 -0
  143. package/dist/tools/v4/memory/memoryReplace.js +15 -0
  144. package/dist/tools/v4/memory/sessionSummary.js +12 -0
  145. package/dist/tools/v4/process/processKill.js +19 -0
  146. package/dist/tools/v4/process/processList.js +1 -0
  147. package/dist/tools/v4/process/processLogRead.js +1 -0
  148. package/dist/tools/v4/process/processSpawn.js +13 -0
  149. package/dist/tools/v4/process/processWait.js +1 -0
  150. package/dist/tools/v4/sessions/recallSession.js +1 -0
  151. package/dist/tools/v4/sessions/sessionList.js +1 -0
  152. package/dist/tools/v4/sessions/sessionSearch.js +1 -0
  153. package/dist/tools/v4/skills/lookupToolSchema.js +7 -0
  154. package/dist/tools/v4/skills/skillManage.js +13 -0
  155. package/dist/tools/v4/skills/skillView.js +1 -0
  156. package/dist/tools/v4/skills/skillsList.js +1 -0
  157. package/dist/tools/v4/subagent/spawnSubAgentTool.js +334 -0
  158. package/dist/tools/v4/subagent/subagentFanout.js +54 -1
  159. package/dist/tools/v4/system/aidenSelfUpdate.js +16 -0
  160. package/dist/tools/v4/system/appClose.js +13 -0
  161. package/dist/tools/v4/system/appInput.js +13 -0
  162. package/dist/tools/v4/system/appLaunch.js +13 -0
  163. package/dist/tools/v4/system/clipboardRead.js +1 -0
  164. package/dist/tools/v4/system/clipboardWrite.js +14 -0
  165. package/dist/tools/v4/system/mediaKey.js +12 -0
  166. package/dist/tools/v4/system/mediaSessions.js +1 -0
  167. package/dist/tools/v4/system/mediaTransport.js +13 -0
  168. package/dist/tools/v4/system/naturalEvents.js +1 -0
  169. package/dist/tools/v4/system/nowPlaying.js +1 -0
  170. package/dist/tools/v4/system/osProcessList.js +1 -0
  171. package/dist/tools/v4/system/screenshot.js +1 -0
  172. package/dist/tools/v4/system/systemInfo.js +1 -0
  173. package/dist/tools/v4/system/volumeSet.js +17 -0
  174. package/dist/tools/v4/terminal/shellExec.js +81 -9
  175. package/dist/tools/v4/web/deepResearch.js +1 -0
  176. package/dist/tools/v4/web/openUrl.js +1 -0
  177. package/dist/tools/v4/web/webFetch.js +1 -0
  178. package/dist/tools/v4/web/webPage.js +1 -0
  179. package/dist/tools/v4/web/webSearch.js +1 -0
  180. package/dist/tools/v4/web/youtubeSearch.js +1 -0
  181. package/package.json +13 -3
@@ -104,7 +104,7 @@ class CodexResponsesAdapter {
104
104
  // ── Public: non-streaming entry ─────────────────────────────────────
105
105
  async call(input) {
106
106
  const body = this.buildBody(input);
107
- const reply = await this.dispatch(body);
107
+ const reply = await this.dispatch(body, input.signal);
108
108
  // Codex backend always streams; aggregate the SSE frames into the
109
109
  // same shape the JSON path returns. Plain api.openai.com path returns
110
110
  // JSON directly.
@@ -175,7 +175,7 @@ class CodexResponsesAdapter {
175
175
  }
176
176
  return { ...headers, ...this.extraHeaders };
177
177
  }
178
- async dispatch(body) {
178
+ async dispatch(body, externalSignal) {
179
179
  const headers = this.buildHeaders();
180
180
  const serialised = JSON.stringify(body);
181
181
  const totalTries = this.maxRetries + 1;
@@ -183,6 +183,19 @@ class CodexResponsesAdapter {
183
183
  for (let attempt = 0; attempt < totalTries; attempt++) {
184
184
  const controller = new AbortController();
185
185
  const timer = setTimeout(() => controller.abort(), this.timeoutMs);
186
+ // v4.6 prep — forward external abort into the internal controller.
187
+ // External aborts surface as raw AbortError so AidenAgent routes
188
+ // them as 'interrupted' rather than retrying as ProviderTimeoutError.
189
+ let externalAbortHandler = null;
190
+ if (externalSignal) {
191
+ if (externalSignal.aborted) {
192
+ controller.abort();
193
+ }
194
+ else {
195
+ externalAbortHandler = () => controller.abort();
196
+ externalSignal.addEventListener('abort', externalAbortHandler, { once: true });
197
+ }
198
+ }
186
199
  let response;
187
200
  try {
188
201
  response = await fetch(this.endpoint, {
@@ -194,7 +207,14 @@ class CodexResponsesAdapter {
194
207
  }
195
208
  catch (err) {
196
209
  clearTimeout(timer);
210
+ if (externalAbortHandler && externalSignal) {
211
+ externalSignal.removeEventListener('abort', externalAbortHandler);
212
+ }
197
213
  if (err?.name === 'AbortError') {
214
+ // v4.6 prep — external abort takes priority over internal timeout.
215
+ if (externalSignal?.aborted) {
216
+ throw err;
217
+ }
198
218
  lastErr = new errors_1.ProviderTimeoutError(this.providerName, this.timeoutMs);
199
219
  }
200
220
  else {
@@ -207,6 +227,9 @@ class CodexResponsesAdapter {
207
227
  throw lastErr;
208
228
  }
209
229
  clearTimeout(timer);
230
+ if (externalAbortHandler && externalSignal) {
231
+ externalSignal.removeEventListener('abort', externalAbortHandler);
232
+ }
210
233
  if (response.ok)
211
234
  return response;
212
235
  const status = response.status;
@@ -63,7 +63,7 @@ class OllamaPromptToolsAdapter {
63
63
  let lastError = null;
64
64
  for (let attempt = 1; attempt <= totalAttempts; attempt += 1) {
65
65
  try {
66
- const response = await this.fetchWithTimeout(url, headers, body);
66
+ const response = await this.fetchWithTimeout(url, headers, body, input.signal);
67
67
  if (response.ok) {
68
68
  const json = (await response.json());
69
69
  return this.parseResponse(json);
@@ -134,6 +134,17 @@ class OllamaPromptToolsAdapter {
134
134
  const headers = { 'Content-Type': 'application/json' };
135
135
  const controller = new AbortController();
136
136
  const timer = setTimeout(() => controller.abort(), this.timeoutMs);
137
+ // v4.6 prep — forward external abort into the internal controller.
138
+ let externalAbortHandler = null;
139
+ if (input.signal) {
140
+ if (input.signal.aborted) {
141
+ controller.abort();
142
+ }
143
+ else {
144
+ externalAbortHandler = () => controller.abort();
145
+ input.signal.addEventListener('abort', externalAbortHandler, { once: true });
146
+ }
147
+ }
137
148
  let response;
138
149
  try {
139
150
  response = await fetch(url, {
@@ -145,13 +156,23 @@ class OllamaPromptToolsAdapter {
145
156
  }
146
157
  catch (err) {
147
158
  clearTimeout(timer);
159
+ if (externalAbortHandler && input.signal) {
160
+ input.signal.removeEventListener('abort', externalAbortHandler);
161
+ }
148
162
  if (err instanceof Error && err.name === 'AbortError') {
163
+ // v4.6 prep — external abort takes priority over internal timeout.
164
+ if (input.signal?.aborted) {
165
+ throw err;
166
+ }
149
167
  throw new errors_1.ProviderTimeoutError(this.providerName, this.timeoutMs);
150
168
  }
151
169
  throw new errors_1.ProviderError(`Ollama not reachable at ${this.baseUrl}: ${err instanceof Error ? err.message : String(err)}`, this.providerName, undefined, err, true);
152
170
  }
153
171
  if (!response.ok) {
154
172
  clearTimeout(timer);
173
+ if (externalAbortHandler && input.signal) {
174
+ input.signal.removeEventListener('abort', externalAbortHandler);
175
+ }
155
176
  const status = response.status;
156
177
  const rawText = await this.safeReadText(response);
157
178
  // Phase v4.1.1-oauth-fix Phase 5: composeMessage handles body
@@ -160,8 +181,15 @@ class OllamaPromptToolsAdapter {
160
181
  }
161
182
  if (!response.body) {
162
183
  clearTimeout(timer);
184
+ if (externalAbortHandler && input.signal) {
185
+ input.signal.removeEventListener('abort', externalAbortHandler);
186
+ }
163
187
  throw new errors_1.ProviderError(`Provider ${this.providerName} returned an empty stream body`, this.providerName);
164
188
  }
189
+ // Response is good; the stream consumer will run for a while. The
190
+ // controller stays armed (with `externalSignal` still listening) so
191
+ // that mid-stream aborts cancel reader.read() via fetch's signal.
192
+ // Listener cleanup happens in the stream-consumer try/finally below.
165
193
  const reader = response.body.getReader();
166
194
  const decoder = new TextDecoder('utf-8');
167
195
  let lineBuffer = '';
@@ -223,10 +251,18 @@ class OllamaPromptToolsAdapter {
223
251
  }
224
252
  catch (err) {
225
253
  clearTimeout(timer);
254
+ // v4.6 prep — external abort during mid-stream read surfaces as
255
+ // AbortError; re-throw so AidenAgent routes it as 'interrupted'.
256
+ if (err instanceof Error && err.name === 'AbortError' && input.signal?.aborted) {
257
+ throw err;
258
+ }
226
259
  throw new errors_1.ProviderError(`Provider ${this.providerName} stream interrupted: ${err instanceof Error ? err.message : String(err)}`, this.providerName, undefined, err, true);
227
260
  }
228
261
  finally {
229
262
  clearTimeout(timer);
263
+ if (externalAbortHandler && input.signal) {
264
+ input.signal.removeEventListener('abort', externalAbortHandler);
265
+ }
230
266
  try {
231
267
  reader.releaseLock();
232
268
  }
@@ -422,9 +458,20 @@ class OllamaPromptToolsAdapter {
422
458
  const textBefore = firstTagIdx >= 0 ? text.slice(0, firstTagIdx).trim() : text;
423
459
  return { textBefore, toolCalls };
424
460
  }
425
- async fetchWithTimeout(url, headers, body) {
461
+ async fetchWithTimeout(url, headers, body, externalSignal) {
426
462
  const controller = new AbortController();
427
463
  const timer = setTimeout(() => controller.abort(), this.timeoutMs);
464
+ // v4.6 prep — forward external abort into the internal controller.
465
+ let externalAbortHandler = null;
466
+ if (externalSignal) {
467
+ if (externalSignal.aborted) {
468
+ controller.abort();
469
+ }
470
+ else {
471
+ externalAbortHandler = () => controller.abort();
472
+ externalSignal.addEventListener('abort', externalAbortHandler, { once: true });
473
+ }
474
+ }
428
475
  try {
429
476
  return await fetch(url, {
430
477
  method: 'POST',
@@ -435,12 +482,20 @@ class OllamaPromptToolsAdapter {
435
482
  }
436
483
  catch (err) {
437
484
  if (err instanceof Error && err.name === 'AbortError') {
485
+ // v4.6 prep — external abort takes priority over internal timeout.
486
+ // Surface the raw AbortError so AidenAgent routes it as 'interrupted'.
487
+ if (externalSignal?.aborted) {
488
+ throw err;
489
+ }
438
490
  throw new errors_1.ProviderTimeoutError(this.providerName, this.timeoutMs);
439
491
  }
440
492
  throw err;
441
493
  }
442
494
  finally {
443
495
  clearTimeout(timer);
496
+ if (externalAbortHandler && externalSignal) {
497
+ externalSignal.removeEventListener('abort', externalAbortHandler);
498
+ }
444
499
  }
445
500
  }
446
501
  async safeReadText(response) {
@@ -0,0 +1,224 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) 2026 Shiva Deore (Taracod).
4
+ * Licensed under AGPL-3.0. See LICENSE for details.
5
+ *
6
+ * Aiden — local-first agent.
7
+ */
8
+ /**
9
+ * tools/v4/browser/_observer.ts — v4.3 Phase 1 + 2: shared BrowserState
10
+ * observer + stale-ref retry HOC for browser ToolHandlers.
11
+ *
12
+ * One BrowserState lives per server process (lifecycle matches the
13
+ * persistent playwrightBridge context). Every browser tool wraps its
14
+ * ToolHandler in `withBrowserState(...)` so the observer's pre/post
15
+ * snapshot capture happens automatically.
16
+ *
17
+ * Phase 1 — observer captures pre/post snapshots and embeds them as
18
+ * a `browserState` sidecar on the tool result when
19
+ * browser depth is enabled (default ON; opt-out via
20
+ * AIDEN_BROWSER_DEPTH=0). No-op when disabled.
21
+ *
22
+ * Phase 2 — stale-ref recovery. When an interactive browser tool
23
+ * (browser_click / browser_type / browser_fill) returns a
24
+ * resolution-class failure (`element not found`, `not visible`,
25
+ * `not attached`, `timeout`, `target closed`), the HOC resnapshots
26
+ * and retries the inner execute ONCE with the same args. The retry
27
+ * logic is reactive only — no preflight tax on success paths. The
28
+ * retry attempt + outcome lands on `ActionResult.staleRefRetry`
29
+ * for Phase 5's classifier to consume.
30
+ *
31
+ * The one-retry hard cap is the consult-derived non-negotiable: a
32
+ * second retry doesn't help (the cause isn't transient) and starts
33
+ * looking like agent thrashing. If the retry fails, the original
34
+ * failure result is preserved — same error message, but with the
35
+ * `staleRefRetry: { attempted: true, succeeded: false, ... }`
36
+ * sidecar so the classifier can recognise the pattern.
37
+ */
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.STALE_REF_PATTERNS = exports.STALE_REF_RETRYABLE = exports.browserState = void 0;
40
+ exports.detectStaleRefError = detectStaleRefError;
41
+ exports.withBrowserState = withBrowserState;
42
+ const browserState_1 = require("../../../core/v4/browserState");
43
+ const browserBlocker_1 = require("./browserBlocker");
44
+ const playwrightBridge_1 = require("../../../core/playwrightBridge");
45
+ /**
46
+ * Shared observer — one instance per server process. The HOC closes
47
+ * over this reference so all 9 browser tools share the same snapshot
48
+ * counter and gating decision.
49
+ *
50
+ * Tests can construct their own BrowserState with a stubbed bridge
51
+ * loader and call `withBrowserState(handler, customState)` directly.
52
+ */
53
+ exports.browserState = (0, browserState_1.createBrowserState)();
54
+ // ── Phase 2 — stale-ref retry primitives ─────────────────────────────
55
+ /**
56
+ * Interactive browser tools that operate on a selector. Stale-ref
57
+ * retry only fires for these — other tools either don't take a
58
+ * selector (browser_navigate, browser_close, browser_get_url) or
59
+ * are read-only (browser_extract, browser_screenshot, browser_scroll).
60
+ */
61
+ exports.STALE_REF_RETRYABLE = new Set([
62
+ 'browser_click',
63
+ 'browser_type',
64
+ 'browser_fill',
65
+ ]);
66
+ /**
67
+ * Error-message patterns that indicate a resolution-class failure
68
+ * (DOM lookup failed BEFORE any side-effect-producing action fired).
69
+ * Phase 2 retries only on these — never on action-failure messages
70
+ * (network errors, permission denials, etc.).
71
+ *
72
+ * The patterns are case-insensitive substrings; one match is enough.
73
+ * False positives are tolerable — retry-once costs ~200ms and produces
74
+ * the same result on the second attempt. False negatives miss the
75
+ * common transient-race case, so bias toward sensitivity.
76
+ */
77
+ exports.STALE_REF_PATTERNS = [
78
+ /element not found/i,
79
+ /not visible/i,
80
+ /not attached/i,
81
+ /detached from the DOM/i,
82
+ /target closed/i,
83
+ /timeout \d+ms exceeded/i,
84
+ ];
85
+ /**
86
+ * Check if a tool result represents a resolution-class failure.
87
+ * Returns the matched pattern (as a short string) when stale, null
88
+ * otherwise. Pure helper, exported for tests.
89
+ */
90
+ function detectStaleRefError(result) {
91
+ if (result === null || result === undefined || typeof result !== 'object')
92
+ return null;
93
+ const r = result;
94
+ if (r.success !== false)
95
+ return null;
96
+ if (typeof r.error !== 'string' || r.error.length === 0)
97
+ return null;
98
+ for (const pattern of exports.STALE_REF_PATTERNS) {
99
+ if (pattern.test(r.error)) {
100
+ return pattern.source;
101
+ }
102
+ }
103
+ return null;
104
+ }
105
+ /**
106
+ * Test whether a tool result represents success. Used by the HOC to
107
+ * decide whether the retry "succeeded" and should become canonical.
108
+ */
109
+ function isSuccessResult(result) {
110
+ if (result === null || result === undefined || typeof result !== 'object')
111
+ return false;
112
+ return result.success === true;
113
+ }
114
+ const defaultPageTextFetcher = () => (0, playwrightBridge_1.pwSnapshot)();
115
+ function withBrowserState(handler, state = exports.browserState,
116
+ /**
117
+ * Optional page-text fetcher. Production code uses pwSnapshot;
118
+ * tests inject a stub returning canned text for the blocker
119
+ * detection tier. The fetcher is called ONCE per action when
120
+ * browser depth is enabled — disabled path skips entirely.
121
+ */
122
+ pageTextFetcher = defaultPageTextFetcher) {
123
+ return {
124
+ ...handler,
125
+ async execute(args, ctx) {
126
+ if (!state.isEnabled()) {
127
+ return handler.execute(args, ctx);
128
+ }
129
+ const pre = await state.captureState();
130
+ let result = await handler.execute(args, ctx);
131
+ // v4.3 Phase 3 — manual-blocker detection. Runs on every
132
+ // browser-tool result when enabled. Uses the configured
133
+ // page-text fetcher (pwSnapshot in production). Detection
134
+ // never breaks the inner tool — pwSnapshot is wrapped in
135
+ // try/catch via the fetcher itself; failures produce no
136
+ // blocker and no observer sidecar field.
137
+ //
138
+ // The detected blocker is BOTH embedded on the result sidecar
139
+ // (Phase 5 + chat layer consumers) AND used to suppress
140
+ // Phase 2's stale-ref retry below. Pause-and-surface contract
141
+ // (Q-CDP5) — never auto-action a blocker.
142
+ let blocker;
143
+ try {
144
+ const snap = await pageTextFetcher();
145
+ if (snap.ok && snap.text) {
146
+ const url = result?.url ?? '';
147
+ const detected = (0, browserBlocker_1.detectBlocker)({ text: snap.text, url });
148
+ if (detected)
149
+ blocker = detected;
150
+ }
151
+ }
152
+ catch { /* detection never breaks the inner tool */ }
153
+ // v4.3 Phase 4 — propagate blocker (or its absence) to the
154
+ // active tab's metadata in BrowserState. Cross-tab queries can
155
+ // then ask "is there a pending blocker on any tab" without
156
+ // re-running detection. No-op when state is disabled or when
157
+ // the tabs map has no active entry (the reconciliation in
158
+ // captureState above sets activeTabId).
159
+ try {
160
+ state.updateActiveTabBlocker(blocker
161
+ ? {
162
+ kind: blocker.kind,
163
+ subtype: blocker.subtype,
164
+ url: blocker.url,
165
+ confidence: blocker.confidence,
166
+ }
167
+ : null);
168
+ }
169
+ catch { /* defensive — tab updates never break the inner tool */ }
170
+ // v4.3 Phase 2 — stale-ref retry. Reactive: fires only after a
171
+ // resolution-class failure on an interactive tool. One retry
172
+ // hard cap. Safe because the resolution-class errors fire
173
+ // BEFORE any DOM event is dispatched, so retry can't double-act.
174
+ //
175
+ // v4.3 Phase 3 suppression: skip the retry when a manual
176
+ // blocker is present (`!blocker` gate). A blocker means the
177
+ // page is asking for human action — retrying the same tool
178
+ // call against a sign-in wall or 2FA prompt won't help and
179
+ // looks like agent thrashing.
180
+ let staleRefRetry;
181
+ if (pre && !blocker &&
182
+ exports.STALE_REF_RETRYABLE.has(handler.schema.name)) {
183
+ const staleReason = detectStaleRefError(result);
184
+ if (staleReason !== null) {
185
+ // Resnapshot — the "between" state. We use it for the
186
+ // diagnostic state_delta. The retry fires unconditionally
187
+ // (per Q-P2-3 single-signal rule): even when DOM hash
188
+ // hasn't changed, a transient race condition (element
189
+ // attached one tick after the original timeout) is the
190
+ // common case the retry catches.
191
+ const between = await state.captureState();
192
+ const state_delta = state.computeStateDelta(pre, between);
193
+ const retryResult = await handler.execute(args, ctx);
194
+ const retryOk = isSuccessResult(retryResult);
195
+ staleRefRetry = {
196
+ attempted: true,
197
+ succeeded: retryOk,
198
+ reason: staleReason,
199
+ state_delta,
200
+ };
201
+ // If retry succeeded, the retry result becomes canonical.
202
+ // If retry failed, keep the original failure — its error
203
+ // context is what the model needs to see, and a same-error
204
+ // retry would just look like duplicated chrome.
205
+ if (retryOk)
206
+ result = retryResult;
207
+ }
208
+ }
209
+ const post = await state.captureState();
210
+ const observerMeta = state.buildActionResult({ pre, post });
211
+ if (observerMeta &&
212
+ result !== null && result !== undefined &&
213
+ typeof result === 'object' && !Array.isArray(result)) {
214
+ const sidecar = {
215
+ ...observerMeta,
216
+ ...(staleRefRetry && { staleRefRetry }),
217
+ ...(blocker && { blocker }),
218
+ };
219
+ return { ...result, browserState: sidecar };
220
+ }
221
+ return result;
222
+ },
223
+ };
224
+ }