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.
- package/README.md +265 -847
- package/dist/api/server.js +32 -5
- package/dist/cli/v4/aidenCLI.js +536 -152
- package/dist/cli/v4/callbacks.js +170 -0
- package/dist/cli/v4/chatSession.js +245 -3
- package/dist/cli/v4/commands/_runtimeToggleHelpers.js +94 -0
- package/dist/cli/v4/commands/browserDepth.js +45 -0
- package/dist/cli/v4/commands/cron.js +264 -0
- package/dist/cli/v4/commands/daemon.js +541 -0
- package/dist/cli/v4/commands/daemonStatus.js +253 -0
- package/dist/cli/v4/commands/fanout.js +42 -59
- package/dist/cli/v4/commands/help.js +13 -0
- package/dist/cli/v4/commands/index.js +35 -1
- package/dist/cli/v4/commands/mcp.js +80 -54
- package/dist/cli/v4/commands/plannerGuard.js +53 -0
- package/dist/cli/v4/commands/recovery.js +122 -0
- package/dist/cli/v4/commands/runs.js +223 -0
- package/dist/cli/v4/commands/sandbox.js +48 -0
- package/dist/cli/v4/commands/spawnPause.js +93 -0
- package/dist/cli/v4/commands/suggestions.js +68 -0
- package/dist/cli/v4/commands/tce.js +41 -0
- package/dist/cli/v4/commands/trigger.js +378 -0
- package/dist/cli/v4/commands/update.js +95 -3
- package/dist/cli/v4/daemonAgentBuilder.js +145 -0
- package/dist/cli/v4/defaultSoul.js +1 -1
- package/dist/cli/v4/display/capabilityCard.js +26 -0
- package/dist/cli/v4/display.js +18 -8
- package/dist/cli/v4/replyRenderer.js +31 -23
- package/dist/cli/v4/updateBootPrompt.js +170 -0
- package/dist/core/playwrightBridge.js +129 -0
- package/dist/core/v4/aidenAgent.js +527 -5
- package/dist/core/v4/browserState.js +436 -0
- package/dist/core/v4/checkpoint.js +79 -0
- package/dist/core/v4/daemon/bootstrap.js +651 -0
- package/dist/core/v4/daemon/cleanShutdown.js +154 -0
- package/dist/core/v4/daemon/cron/cronBridge.js +126 -0
- package/dist/core/v4/daemon/cron/cronEmitter.js +173 -0
- package/dist/core/v4/daemon/cron/migration.js +199 -0
- package/dist/core/v4/daemon/cron/misfirePolicy.js +115 -0
- package/dist/core/v4/daemon/daemonConfig.js +90 -0
- package/dist/core/v4/daemon/db/connection.js +106 -0
- package/dist/core/v4/daemon/db/migrations.js +362 -0
- package/dist/core/v4/daemon/db/schema/v1.spec.js +18 -0
- package/dist/core/v4/daemon/dispatcher/agentRunner.js +98 -0
- package/dist/core/v4/daemon/dispatcher/budgetGate.js +127 -0
- package/dist/core/v4/daemon/dispatcher/daemonApproval.js +113 -0
- package/dist/core/v4/daemon/dispatcher/dailyBudgetTracker.js +120 -0
- package/dist/core/v4/daemon/dispatcher/dispatcher.js +389 -0
- package/dist/core/v4/daemon/dispatcher/fireRateLimiter.js +113 -0
- package/dist/core/v4/daemon/dispatcher/index.js +53 -0
- package/dist/core/v4/daemon/dispatcher/promptTemplate.js +95 -0
- package/dist/core/v4/daemon/dispatcher/realAgentRunner.js +356 -0
- package/dist/core/v4/daemon/dispatcher/resolveModel.js +93 -0
- package/dist/core/v4/daemon/dispatcher/sessionId.js +93 -0
- package/dist/core/v4/daemon/drain.js +156 -0
- package/dist/core/v4/daemon/eventLoopLag.js +73 -0
- package/dist/core/v4/daemon/health.js +159 -0
- package/dist/core/v4/daemon/idempotencyStore.js +204 -0
- package/dist/core/v4/daemon/index.js +179 -0
- package/dist/core/v4/daemon/instanceTracker.js +99 -0
- package/dist/core/v4/daemon/resourceRegistry.js +150 -0
- package/dist/core/v4/daemon/restartCode.js +32 -0
- package/dist/core/v4/daemon/restartFailureCounter.js +77 -0
- package/dist/core/v4/daemon/runStore.js +144 -0
- package/dist/core/v4/daemon/runtimeLock.js +167 -0
- package/dist/core/v4/daemon/signals.js +50 -0
- package/dist/core/v4/daemon/supervisor.js +272 -0
- package/dist/core/v4/daemon/triggerBus.js +279 -0
- package/dist/core/v4/daemon/triggers/email/allowlist.js +70 -0
- package/dist/core/v4/daemon/triggers/email/automatedSender.js +78 -0
- package/dist/core/v4/daemon/triggers/email/bodyExtractor.js +0 -0
- package/dist/core/v4/daemon/triggers/email/emailSeenStore.js +99 -0
- package/dist/core/v4/daemon/triggers/email/emailSpec.js +107 -0
- package/dist/core/v4/daemon/triggers/email/imapConnection.js +211 -0
- package/dist/core/v4/daemon/triggers/email/index.js +332 -0
- package/dist/core/v4/daemon/triggers/email/seenUids.js +60 -0
- package/dist/core/v4/daemon/triggers/fileObservationsStore.js +93 -0
- package/dist/core/v4/daemon/triggers/fileWatcher.js +253 -0
- package/dist/core/v4/daemon/triggers/fileWatcherSpec.js +88 -0
- package/dist/core/v4/daemon/triggers/fsIdentity.js +42 -0
- package/dist/core/v4/daemon/triggers/globMatcher.js +100 -0
- package/dist/core/v4/daemon/triggers/reconcile.js +206 -0
- package/dist/core/v4/daemon/triggers/settleStat.js +81 -0
- package/dist/core/v4/daemon/triggers/webhook.js +376 -0
- package/dist/core/v4/daemon/triggers/webhookDeliveriesStore.js +109 -0
- package/dist/core/v4/daemon/triggers/webhookIdempotency.js +72 -0
- package/dist/core/v4/daemon/triggers/webhookRateLimit.js +56 -0
- package/dist/core/v4/daemon/triggers/webhookSpec.js +76 -0
- package/dist/core/v4/daemon/triggers/webhookVerifier.js +128 -0
- package/dist/core/v4/daemon/types.js +15 -0
- package/dist/core/v4/dockerSession.js +461 -0
- package/dist/core/v4/dryRun.js +117 -0
- package/dist/core/v4/failureClassifier.js +779 -0
- package/dist/core/v4/providerFallback.js +35 -2
- package/dist/core/v4/recoveryReport.js +449 -0
- package/dist/core/v4/runtimeToggles.js +214 -0
- package/dist/core/v4/sandboxConfig.js +285 -0
- package/dist/core/v4/sandboxFs.js +316 -0
- package/dist/core/v4/selfimprovement/recoveryStore.js +307 -0
- package/dist/core/v4/selfimprovement/signatureBuilder.js +158 -0
- package/dist/core/v4/subagent/childBuilder.js +391 -0
- package/dist/core/v4/subagent/fanout.js +75 -51
- package/dist/core/v4/subagent/spawnPause.js +191 -0
- package/dist/core/v4/subagent/spawnSubAgent.js +310 -0
- package/dist/core/v4/suggestionCatalog.js +41 -0
- package/dist/core/v4/suggestionEngine.js +210 -0
- package/dist/core/v4/toolRegistry.js +37 -3
- package/dist/core/v4/turnState.js +587 -0
- package/dist/core/v4/update/checkUpdate.js +63 -3
- package/dist/core/v4/update/installMethodDetect.js +115 -0
- package/dist/core/v4/update/registryClient.js +121 -0
- package/dist/core/v4/update/skipState.js +75 -0
- package/dist/core/v4/verifier.js +448 -0
- package/dist/core/version.js +1 -1
- package/dist/moat/plannerGuard.js +29 -0
- package/dist/providers/v4/anthropicAdapter.js +31 -3
- package/dist/providers/v4/chatCompletionsAdapter.js +26 -3
- package/dist/providers/v4/codexResponsesAdapter.js +25 -2
- package/dist/providers/v4/ollamaPromptToolsAdapter.js +57 -2
- package/dist/tools/v4/browser/_observer.js +224 -0
- package/dist/tools/v4/browser/browserBlocker.js +396 -0
- package/dist/tools/v4/browser/browserClick.js +18 -1
- package/dist/tools/v4/browser/browserClose.js +18 -1
- package/dist/tools/v4/browser/browserExtract.js +5 -1
- package/dist/tools/v4/browser/browserFill.js +17 -1
- package/dist/tools/v4/browser/browserGetUrl.js +5 -1
- package/dist/tools/v4/browser/browserNavigate.js +16 -1
- package/dist/tools/v4/browser/browserScreenshot.js +5 -1
- package/dist/tools/v4/browser/browserScroll.js +18 -1
- package/dist/tools/v4/browser/browserType.js +17 -1
- package/dist/tools/v4/browser/captchaCheck.js +5 -1
- package/dist/tools/v4/executeCode.js +1 -0
- package/dist/tools/v4/files/fileCopy.js +56 -2
- package/dist/tools/v4/files/fileDelete.js +38 -1
- package/dist/tools/v4/files/fileList.js +12 -1
- package/dist/tools/v4/files/fileMove.js +59 -2
- package/dist/tools/v4/files/filePatch.js +43 -1
- package/dist/tools/v4/files/fileRead.js +12 -1
- package/dist/tools/v4/files/fileWrite.js +41 -1
- package/dist/tools/v4/index.js +88 -61
- package/dist/tools/v4/memory/memoryAdd.js +14 -0
- package/dist/tools/v4/memory/memoryRemove.js +14 -0
- package/dist/tools/v4/memory/memoryReplace.js +15 -0
- package/dist/tools/v4/memory/sessionSummary.js +12 -0
- package/dist/tools/v4/process/processKill.js +19 -0
- package/dist/tools/v4/process/processList.js +1 -0
- package/dist/tools/v4/process/processLogRead.js +1 -0
- package/dist/tools/v4/process/processSpawn.js +13 -0
- package/dist/tools/v4/process/processWait.js +1 -0
- package/dist/tools/v4/sessions/recallSession.js +1 -0
- package/dist/tools/v4/sessions/sessionList.js +1 -0
- package/dist/tools/v4/sessions/sessionSearch.js +1 -0
- package/dist/tools/v4/skills/lookupToolSchema.js +7 -0
- package/dist/tools/v4/skills/skillManage.js +13 -0
- package/dist/tools/v4/skills/skillView.js +1 -0
- package/dist/tools/v4/skills/skillsList.js +1 -0
- package/dist/tools/v4/subagent/spawnSubAgentTool.js +334 -0
- package/dist/tools/v4/subagent/subagentFanout.js +54 -1
- package/dist/tools/v4/system/aidenSelfUpdate.js +16 -0
- package/dist/tools/v4/system/appClose.js +13 -0
- package/dist/tools/v4/system/appInput.js +13 -0
- package/dist/tools/v4/system/appLaunch.js +13 -0
- package/dist/tools/v4/system/clipboardRead.js +1 -0
- package/dist/tools/v4/system/clipboardWrite.js +14 -0
- package/dist/tools/v4/system/mediaKey.js +12 -0
- package/dist/tools/v4/system/mediaSessions.js +1 -0
- package/dist/tools/v4/system/mediaTransport.js +13 -0
- package/dist/tools/v4/system/naturalEvents.js +1 -0
- package/dist/tools/v4/system/nowPlaying.js +1 -0
- package/dist/tools/v4/system/osProcessList.js +1 -0
- package/dist/tools/v4/system/screenshot.js +1 -0
- package/dist/tools/v4/system/systemInfo.js +1 -0
- package/dist/tools/v4/system/volumeSet.js +17 -0
- package/dist/tools/v4/terminal/shellExec.js +81 -9
- package/dist/tools/v4/web/deepResearch.js +1 -0
- package/dist/tools/v4/web/openUrl.js +1 -0
- package/dist/tools/v4/web/webFetch.js +1 -0
- package/dist/tools/v4/web/webPage.js +1 -0
- package/dist/tools/v4/web/webSearch.js +1 -0
- package/dist/tools/v4/web/youtubeSearch.js +1 -0
- 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
|
+
}
|