aiden-runtime 4.1.4 → 4.5.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 (169) hide show
  1. package/README.md +250 -847
  2. package/dist/api/server.js +32 -5
  3. package/dist/cli/v4/aidenCLI.js +379 -53
  4. package/dist/cli/v4/callbacks.js +248 -0
  5. package/dist/cli/v4/chatSession.js +292 -4
  6. package/dist/cli/v4/commands/_runtimeToggleHelpers.js +92 -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/help.js +7 -0
  12. package/dist/cli/v4/commands/index.js +20 -1
  13. package/dist/cli/v4/commands/runs.js +203 -0
  14. package/dist/cli/v4/commands/sandbox.js +48 -0
  15. package/dist/cli/v4/commands/suggestions.js +68 -0
  16. package/dist/cli/v4/commands/tce.js +41 -0
  17. package/dist/cli/v4/commands/trigger.js +378 -0
  18. package/dist/cli/v4/commands/update.js +95 -3
  19. package/dist/cli/v4/daemonAgentBuilder.js +142 -0
  20. package/dist/cli/v4/defaultSoul.js +75 -3
  21. package/dist/cli/v4/display/capabilityCard.js +26 -0
  22. package/dist/cli/v4/display/progressBar.js +41 -8
  23. package/dist/cli/v4/display.js +258 -15
  24. package/dist/cli/v4/replyRenderer.js +31 -23
  25. package/dist/cli/v4/toolPreview.js +10 -0
  26. package/dist/cli/v4/updateBootPrompt.js +170 -0
  27. package/dist/core/playwrightBridge.js +129 -0
  28. package/dist/core/toolRegistry.js +7 -1
  29. package/dist/core/v4/aidenAgent.js +371 -4
  30. package/dist/core/v4/browserState.js +436 -0
  31. package/dist/core/v4/checkpoint.js +79 -0
  32. package/dist/core/v4/daemon/bootstrap.js +604 -0
  33. package/dist/core/v4/daemon/cleanShutdown.js +154 -0
  34. package/dist/core/v4/daemon/cron/cronBridge.js +126 -0
  35. package/dist/core/v4/daemon/cron/cronEmitter.js +173 -0
  36. package/dist/core/v4/daemon/cron/migration.js +199 -0
  37. package/dist/core/v4/daemon/cron/misfirePolicy.js +115 -0
  38. package/dist/core/v4/daemon/daemonConfig.js +90 -0
  39. package/dist/core/v4/daemon/db/connection.js +106 -0
  40. package/dist/core/v4/daemon/db/migrations.js +296 -0
  41. package/dist/core/v4/daemon/db/schema/v1.spec.js +18 -0
  42. package/dist/core/v4/daemon/dispatcher/agentRunner.js +98 -0
  43. package/dist/core/v4/daemon/dispatcher/budgetGate.js +127 -0
  44. package/dist/core/v4/daemon/dispatcher/daemonApproval.js +113 -0
  45. package/dist/core/v4/daemon/dispatcher/dailyBudgetTracker.js +120 -0
  46. package/dist/core/v4/daemon/dispatcher/dispatcher.js +389 -0
  47. package/dist/core/v4/daemon/dispatcher/fireRateLimiter.js +113 -0
  48. package/dist/core/v4/daemon/dispatcher/index.js +53 -0
  49. package/dist/core/v4/daemon/dispatcher/promptTemplate.js +95 -0
  50. package/dist/core/v4/daemon/dispatcher/realAgentRunner.js +356 -0
  51. package/dist/core/v4/daemon/dispatcher/resolveModel.js +93 -0
  52. package/dist/core/v4/daemon/dispatcher/sessionId.js +93 -0
  53. package/dist/core/v4/daemon/drain.js +156 -0
  54. package/dist/core/v4/daemon/eventLoopLag.js +73 -0
  55. package/dist/core/v4/daemon/health.js +159 -0
  56. package/dist/core/v4/daemon/idempotencyStore.js +204 -0
  57. package/dist/core/v4/daemon/index.js +179 -0
  58. package/dist/core/v4/daemon/instanceTracker.js +99 -0
  59. package/dist/core/v4/daemon/resourceRegistry.js +150 -0
  60. package/dist/core/v4/daemon/restartCode.js +32 -0
  61. package/dist/core/v4/daemon/restartFailureCounter.js +77 -0
  62. package/dist/core/v4/daemon/runStore.js +114 -0
  63. package/dist/core/v4/daemon/runtimeLock.js +167 -0
  64. package/dist/core/v4/daemon/signals.js +50 -0
  65. package/dist/core/v4/daemon/supervisor.js +272 -0
  66. package/dist/core/v4/daemon/triggerBus.js +279 -0
  67. package/dist/core/v4/daemon/triggers/email/allowlist.js +70 -0
  68. package/dist/core/v4/daemon/triggers/email/automatedSender.js +78 -0
  69. package/dist/core/v4/daemon/triggers/email/bodyExtractor.js +0 -0
  70. package/dist/core/v4/daemon/triggers/email/emailSeenStore.js +99 -0
  71. package/dist/core/v4/daemon/triggers/email/emailSpec.js +107 -0
  72. package/dist/core/v4/daemon/triggers/email/imapConnection.js +211 -0
  73. package/dist/core/v4/daemon/triggers/email/index.js +332 -0
  74. package/dist/core/v4/daemon/triggers/email/seenUids.js +60 -0
  75. package/dist/core/v4/daemon/triggers/fileObservationsStore.js +93 -0
  76. package/dist/core/v4/daemon/triggers/fileWatcher.js +253 -0
  77. package/dist/core/v4/daemon/triggers/fileWatcherSpec.js +88 -0
  78. package/dist/core/v4/daemon/triggers/fsIdentity.js +42 -0
  79. package/dist/core/v4/daemon/triggers/globMatcher.js +100 -0
  80. package/dist/core/v4/daemon/triggers/reconcile.js +206 -0
  81. package/dist/core/v4/daemon/triggers/settleStat.js +81 -0
  82. package/dist/core/v4/daemon/triggers/webhook.js +376 -0
  83. package/dist/core/v4/daemon/triggers/webhookDeliveriesStore.js +109 -0
  84. package/dist/core/v4/daemon/triggers/webhookIdempotency.js +72 -0
  85. package/dist/core/v4/daemon/triggers/webhookRateLimit.js +56 -0
  86. package/dist/core/v4/daemon/triggers/webhookSpec.js +76 -0
  87. package/dist/core/v4/daemon/triggers/webhookVerifier.js +128 -0
  88. package/dist/core/v4/daemon/types.js +15 -0
  89. package/dist/core/v4/dockerSession.js +461 -0
  90. package/dist/core/v4/dryRun.js +117 -0
  91. package/dist/core/v4/failureClassifier.js +779 -0
  92. package/dist/core/v4/loopTrace.js +257 -0
  93. package/dist/core/v4/recoveryReport.js +449 -0
  94. package/dist/core/v4/runtimeToggles.js +187 -0
  95. package/dist/core/v4/sandboxConfig.js +285 -0
  96. package/dist/core/v4/sandboxFs.js +316 -0
  97. package/dist/core/v4/suggestionCatalog.js +41 -0
  98. package/dist/core/v4/suggestionEngine.js +210 -0
  99. package/dist/core/v4/toolRegistry.js +18 -0
  100. package/dist/core/v4/turnState.js +587 -0
  101. package/dist/core/v4/update/checkUpdate.js +63 -3
  102. package/dist/core/v4/update/installMethodDetect.js +115 -0
  103. package/dist/core/v4/update/registryClient.js +121 -0
  104. package/dist/core/v4/update/skipState.js +75 -0
  105. package/dist/core/v4/verifier.js +448 -0
  106. package/dist/core/version.js +1 -1
  107. package/dist/core/webSearch.js +64 -24
  108. package/dist/tools/v4/browser/_observer.js +224 -0
  109. package/dist/tools/v4/browser/browserBlocker.js +396 -0
  110. package/dist/tools/v4/browser/browserClick.js +18 -1
  111. package/dist/tools/v4/browser/browserClose.js +18 -1
  112. package/dist/tools/v4/browser/browserExtract.js +5 -1
  113. package/dist/tools/v4/browser/browserFill.js +17 -1
  114. package/dist/tools/v4/browser/browserGetUrl.js +5 -1
  115. package/dist/tools/v4/browser/browserNavigate.js +16 -1
  116. package/dist/tools/v4/browser/browserScreenshot.js +5 -1
  117. package/dist/tools/v4/browser/browserScroll.js +18 -1
  118. package/dist/tools/v4/browser/browserType.js +17 -1
  119. package/dist/tools/v4/browser/captchaCheck.js +5 -1
  120. package/dist/tools/v4/executeCode.js +1 -0
  121. package/dist/tools/v4/files/fileCopy.js +56 -2
  122. package/dist/tools/v4/files/fileDelete.js +38 -1
  123. package/dist/tools/v4/files/fileList.js +12 -1
  124. package/dist/tools/v4/files/fileMove.js +59 -2
  125. package/dist/tools/v4/files/filePatch.js +43 -1
  126. package/dist/tools/v4/files/fileRead.js +12 -1
  127. package/dist/tools/v4/files/fileWrite.js +41 -1
  128. package/dist/tools/v4/index.js +71 -58
  129. package/dist/tools/v4/memory/memoryAdd.js +14 -0
  130. package/dist/tools/v4/memory/memoryRemove.js +14 -0
  131. package/dist/tools/v4/memory/memoryReplace.js +15 -0
  132. package/dist/tools/v4/memory/sessionSummary.js +12 -0
  133. package/dist/tools/v4/process/processKill.js +19 -0
  134. package/dist/tools/v4/process/processList.js +1 -0
  135. package/dist/tools/v4/process/processLogRead.js +1 -0
  136. package/dist/tools/v4/process/processSpawn.js +13 -0
  137. package/dist/tools/v4/process/processWait.js +1 -0
  138. package/dist/tools/v4/sessions/recallSession.js +1 -0
  139. package/dist/tools/v4/sessions/sessionList.js +1 -0
  140. package/dist/tools/v4/sessions/sessionSearch.js +1 -0
  141. package/dist/tools/v4/skills/lookupToolSchema.js +2 -0
  142. package/dist/tools/v4/skills/skillManage.js +13 -0
  143. package/dist/tools/v4/skills/skillView.js +1 -0
  144. package/dist/tools/v4/skills/skillsList.js +1 -0
  145. package/dist/tools/v4/subagent/subagentFanout.js +1 -0
  146. package/dist/tools/v4/system/aidenSelfUpdate.js +16 -0
  147. package/dist/tools/v4/system/appClose.js +13 -0
  148. package/dist/tools/v4/system/appInput.js +13 -0
  149. package/dist/tools/v4/system/appLaunch.js +13 -0
  150. package/dist/tools/v4/system/clipboardRead.js +1 -0
  151. package/dist/tools/v4/system/clipboardWrite.js +14 -0
  152. package/dist/tools/v4/system/mediaKey.js +12 -0
  153. package/dist/tools/v4/system/mediaSessions.js +1 -0
  154. package/dist/tools/v4/system/mediaTransport.js +13 -0
  155. package/dist/tools/v4/system/naturalEvents.js +1 -0
  156. package/dist/tools/v4/system/nowPlaying.js +1 -0
  157. package/dist/tools/v4/system/osProcessList.js +1 -0
  158. package/dist/tools/v4/system/screenshot.js +1 -0
  159. package/dist/tools/v4/system/systemInfo.js +1 -0
  160. package/dist/tools/v4/system/volumeSet.js +17 -0
  161. package/dist/tools/v4/terminal/shellExec.js +81 -9
  162. package/dist/tools/v4/web/deepResearch.js +1 -0
  163. package/dist/tools/v4/web/openUrl.js +1 -0
  164. package/dist/tools/v4/web/webFetch.js +1 -0
  165. package/dist/tools/v4/web/webPage.js +1 -0
  166. package/dist/tools/v4/web/webSearch.js +1 -0
  167. package/dist/tools/v4/web/youtubeSearch.js +1 -0
  168. package/package.json +7 -1
  169. package/plugins/aiden-plugin-cdp-browser/.granted-permissions.json +8 -0
@@ -0,0 +1,448 @@
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
+ * core/v4/verifier.ts — v4.2 Phase 1: Per-tool result verifier.
10
+ *
11
+ * After each tool dispatch, the verifier inspects the result and
12
+ * classifies the outcome:
13
+ *
14
+ * ok — tool produced a usable, non-failed output
15
+ * failed — tool errored, returned `success: false`, or matched
16
+ * a known failure shape
17
+ * no_progress — tool succeeded but produced no useful signal (empty
18
+ * payload, identical hash to a recent call — Phase 3
19
+ * wires the hash repeat detector)
20
+ * low_signal — tool succeeded but with a short / vague response
21
+ * that's informative but probably won't help the
22
+ * model make progress
23
+ * unknown — verifier couldn't classify with confidence
24
+ *
25
+ * Scope (Phase 1):
26
+ * - Pure inspection of `(toolName, args, result)` — NO goal awareness
27
+ * (deferred to Phase 5 / task graph).
28
+ * - Synchronous; runs in the agent's tool-dispatch loop between
29
+ * `onToolCall('after', result)` and `turnState.recordToolCall(...)`.
30
+ * - Default fallback handles ~99% of Aiden tools that return the
31
+ * `{ success: boolean, error?: string, ...payload }` envelope.
32
+ * - Built-in per-tool verifiers for 5 high-signal tools where the
33
+ * default envelope inspection isn't sufficient: `shell_exec`,
34
+ * `web_search`, `file_write`, `file_read`, `web_fetch`.
35
+ * - Behind the same gate as TurnState (default ON; opt-out via
36
+ * `AIDEN_TCE=0`). When disabled, the agent skips verifier
37
+ * classification — the registry is still constructed (cheap) but
38
+ * `resolve()` is never called inside the gated branch.
39
+ *
40
+ * Out of scope (deferred phases):
41
+ * - Phase 2 — typed failure reason taxonomy (timeout / auth /
42
+ * hallucination / network — separate from per-tool verifier).
43
+ * - Phase 3 — RecoveryReport (uses verifier output + Phase 2 classifier).
44
+ * - Phase 4 — checkpoint/restore (uses Phase 3 state shape).
45
+ * - Phase 5 — task-graph sub-step verification (extends VerifierFn
46
+ * signature with optional `subGoal` argument; backward-compatible).
47
+ *
48
+ * The design intentionally mirrors a layered-decision pattern from the
49
+ * reference system's tool-guardrail module: a pure classifier function
50
+ * driving a controller's threshold counters, with per-tool overrides
51
+ * for the small set of tools where heuristic inspection is too coarse.
52
+ */
53
+ Object.defineProperty(exports, "__esModule", { value: true });
54
+ exports.browserInteractiveVerifier = exports.webFetchVerifier = exports.fileReadVerifier = exports.fileWriteVerifier = exports.webSearchVerifier = exports.shellExecVerifier = exports.defaultVerifier = exports.VerifierRegistry = void 0;
55
+ exports.buildDefaultRegistry = buildDefaultRegistry;
56
+ /**
57
+ * Per-tool override registry with a default-fallback resolver. Cheap
58
+ * to construct; safe to keep instantiated even when TCE is disabled
59
+ * because nothing runs unless `resolve(...)` is called by the agent
60
+ * loop (which itself is gated).
61
+ */
62
+ class VerifierRegistry {
63
+ constructor(fallback = exports.defaultVerifier) {
64
+ this.overrides = new Map();
65
+ this.fallback = fallback;
66
+ }
67
+ register(toolName, fn) {
68
+ this.overrides.set(toolName, fn);
69
+ }
70
+ resolve(toolName) {
71
+ return this.overrides.get(toolName) ?? this.fallback;
72
+ }
73
+ /** Direct lookup for tests — returns true when a per-tool override is registered. */
74
+ hasOverride(toolName) {
75
+ return this.overrides.has(toolName);
76
+ }
77
+ }
78
+ exports.VerifierRegistry = VerifierRegistry;
79
+ // ── Default fallback verifier ──────────────────────────────────────────────
80
+ const SHORT_RESPONSE_THRESHOLD = 50; // chars — below this, raw strings are flagged low_signal
81
+ const RAW_STRING_SCAN_WINDOW = 500; // chars — generic error keyword scan only looks at the head
82
+ /**
83
+ * Heuristic default. Handles five result shapes in priority order:
84
+ *
85
+ * 1. Outer envelope error → ToolCallResult.error set → failed (conf 1.0)
86
+ * 2. Inner `success: false` → typed failure (conf 1.0)
87
+ * 3. Inner `success: true` → typed ok (conf 1.0)
88
+ * 4. Raw string < 50 chars → low_signal (conf 0.4, ok: true)
89
+ * 5. Raw string with error keywords in first 500 chars → failed (conf 0.6)
90
+ *
91
+ * Anything else (typed object without `success`, non-empty string
92
+ * without error keywords) is `ok` at conf 0.7 — the verifier doesn't
93
+ * have enough signal to be more precise without a per-tool override.
94
+ */
95
+ const defaultVerifier = (_toolName, _args, result) => {
96
+ // 1. Outer envelope error — executor threw or wrapped a known failure.
97
+ if (typeof result.error === 'string' && result.error.length > 0) {
98
+ return {
99
+ ok: false,
100
+ confidence: 1.0,
101
+ code: 'failed',
102
+ reason: result.error,
103
+ };
104
+ }
105
+ const inner = result.result;
106
+ // 2 + 3. Typed `{ success: boolean }` envelope — the common Aiden shape.
107
+ if (inner !== null && typeof inner === 'object' && !Array.isArray(inner)) {
108
+ const obj = inner;
109
+ if (obj.success === false) {
110
+ const reason = typeof obj.error === 'string' && obj.error.length > 0
111
+ ? obj.error
112
+ : 'tool returned success:false';
113
+ return {
114
+ ok: false,
115
+ confidence: 1.0,
116
+ code: 'failed',
117
+ reason,
118
+ };
119
+ }
120
+ if (obj.success === true) {
121
+ return { ok: true, confidence: 1.0, code: 'ok' };
122
+ }
123
+ // No `success` field — fall through to confidence-0.7 default.
124
+ return { ok: true, confidence: 0.7, code: 'ok' };
125
+ }
126
+ // 4 + 5. Raw string payload (the webSearch / deepResearch / openUrl shape).
127
+ if (typeof inner === 'string') {
128
+ const trimmed = inner.trim();
129
+ if (trimmed.length === 0) {
130
+ return {
131
+ ok: true,
132
+ confidence: 0.4,
133
+ code: 'low_signal',
134
+ reason: 'empty string result',
135
+ };
136
+ }
137
+ if (trimmed.length < SHORT_RESPONSE_THRESHOLD) {
138
+ return {
139
+ ok: true,
140
+ confidence: 0.4,
141
+ code: 'low_signal',
142
+ reason: `short result (${trimmed.length} chars)`,
143
+ };
144
+ }
145
+ const head = trimmed.slice(0, RAW_STRING_SCAN_WINDOW).toLowerCase();
146
+ if (head.startsWith('error') ||
147
+ head.includes('"error"') ||
148
+ head.includes('"failed"')) {
149
+ return {
150
+ ok: false,
151
+ confidence: 0.6,
152
+ code: 'failed',
153
+ reason: 'error keywords detected in raw string head',
154
+ };
155
+ }
156
+ return { ok: true, confidence: 0.7, code: 'ok' };
157
+ }
158
+ // null / undefined / array / number — no clear signal.
159
+ if (inner === null || inner === undefined) {
160
+ return {
161
+ ok: true,
162
+ confidence: 0.5,
163
+ code: 'unknown',
164
+ reason: 'null result',
165
+ };
166
+ }
167
+ return { ok: true, confidence: 0.5, code: 'unknown' };
168
+ };
169
+ exports.defaultVerifier = defaultVerifier;
170
+ // ── Built-in per-tool verifiers ────────────────────────────────────────────
171
+ /**
172
+ * `shell_exec` — inspect `exitCode` directly. A successful exit with
173
+ * empty stdout is suspicious (probe with no output) — surface as
174
+ * `low_signal` rather than ok-with-high-confidence so the loop
175
+ * controller can weight it.
176
+ */
177
+ const shellExecVerifier = (_n, _a, result) => {
178
+ if (typeof result.error === 'string' && result.error.length > 0) {
179
+ return { ok: false, confidence: 1.0, code: 'failed', reason: result.error };
180
+ }
181
+ const inner = result.result;
182
+ if (inner === null || typeof inner !== 'object') {
183
+ return { ok: false, confidence: 0.5, code: 'unknown', reason: 'non-object shell_exec result' };
184
+ }
185
+ // Typed-failure envelope short-circuit — a wrapper returning
186
+ // `{success: false}` without exitCode is still definitively failed.
187
+ if (inner.success === false) {
188
+ return {
189
+ ok: false,
190
+ confidence: 1.0,
191
+ code: 'failed',
192
+ reason: typeof inner.error === 'string' ? inner.error : 'success:false',
193
+ };
194
+ }
195
+ const exitCode = typeof inner.exitCode === 'number' ? inner.exitCode : undefined;
196
+ if (exitCode === undefined) {
197
+ // Some wrappers omit exitCode on a successful run when the
198
+ // underlying command was trivial (e.g. a noop). Trust the typed
199
+ // success flag if present; otherwise we genuinely don't know.
200
+ if (inner.success === true) {
201
+ return { ok: true, confidence: 0.7, code: 'ok' };
202
+ }
203
+ return { ok: false, confidence: 0.5, code: 'unknown', reason: 'missing exitCode' };
204
+ }
205
+ if (exitCode !== 0) {
206
+ return {
207
+ ok: false,
208
+ confidence: 1.0,
209
+ code: 'failed',
210
+ reason: `non-zero exit (${exitCode})`,
211
+ suggestion: 'Inspect stderr and adjust the command — repeating the same invocation will not help.',
212
+ };
213
+ }
214
+ const stdout = typeof inner.stdout === 'string' ? inner.stdout.trim() : '';
215
+ if (stdout.length === 0) {
216
+ return {
217
+ ok: true,
218
+ confidence: 0.4,
219
+ code: 'low_signal',
220
+ reason: 'exit 0 with empty stdout',
221
+ };
222
+ }
223
+ return { ok: true, confidence: 1.0, code: 'ok' };
224
+ };
225
+ exports.shellExecVerifier = shellExecVerifier;
226
+ /**
227
+ * `web_search` — returns a raw string (synthesised answer). Short
228
+ * responses are low-signal, not failures (often "no results found"
229
+ * IS the answer). Generic error-keyword scan applies.
230
+ */
231
+ const webSearchVerifier = (_n, _a, result) => {
232
+ if (typeof result.error === 'string' && result.error.length > 0) {
233
+ return { ok: false, confidence: 1.0, code: 'failed', reason: result.error };
234
+ }
235
+ const inner = result.result;
236
+ if (typeof inner !== 'string') {
237
+ // Some adapters might wrap the string in `{ success, result }`.
238
+ return (0, exports.defaultVerifier)(_n, _a, result);
239
+ }
240
+ const trimmed = inner.trim();
241
+ if (trimmed.length === 0) {
242
+ return {
243
+ ok: true,
244
+ confidence: 0.4,
245
+ code: 'low_signal',
246
+ reason: 'empty web_search result',
247
+ suggestion: 'Try a different query or use web_fetch with a known URL.',
248
+ };
249
+ }
250
+ if (trimmed.length < SHORT_RESPONSE_THRESHOLD) {
251
+ return {
252
+ ok: true,
253
+ confidence: 0.4,
254
+ code: 'low_signal',
255
+ reason: `short web_search result (${trimmed.length} chars)`,
256
+ };
257
+ }
258
+ return { ok: true, confidence: 0.9, code: 'ok' };
259
+ };
260
+ exports.webSearchVerifier = webSearchVerifier;
261
+ /**
262
+ * `file_write` — verify the write actually happened. We trust the
263
+ * tool's `success` flag but additionally require `bytesWritten > 0`
264
+ * when present (catches the "wrote 0 bytes" pathology).
265
+ */
266
+ const fileWriteVerifier = (_n, _a, result) => {
267
+ if (typeof result.error === 'string' && result.error.length > 0) {
268
+ return { ok: false, confidence: 1.0, code: 'failed', reason: result.error };
269
+ }
270
+ const inner = result.result;
271
+ if (inner === null || typeof inner !== 'object') {
272
+ return { ok: false, confidence: 0.5, code: 'unknown', reason: 'non-object file_write result' };
273
+ }
274
+ if (inner.success === false) {
275
+ return {
276
+ ok: false,
277
+ confidence: 1.0,
278
+ code: 'failed',
279
+ reason: typeof inner.error === 'string' ? inner.error : 'success:false',
280
+ };
281
+ }
282
+ if (typeof inner.bytesWritten === 'number' && inner.bytesWritten === 0) {
283
+ return {
284
+ ok: true,
285
+ confidence: 0.4,
286
+ code: 'low_signal',
287
+ reason: 'wrote 0 bytes',
288
+ };
289
+ }
290
+ return { ok: true, confidence: 1.0, code: 'ok' };
291
+ };
292
+ exports.fileWriteVerifier = fileWriteVerifier;
293
+ /**
294
+ * `file_read` — verify content non-empty (a deliberately-empty file
295
+ * is rare; usually means a path mismatch or stale read). Trusts the
296
+ * tool's `success` flag.
297
+ */
298
+ const fileReadVerifier = (_n, _a, result) => {
299
+ if (typeof result.error === 'string' && result.error.length > 0) {
300
+ return { ok: false, confidence: 1.0, code: 'failed', reason: result.error };
301
+ }
302
+ const inner = result.result;
303
+ if (inner === null || typeof inner !== 'object') {
304
+ return { ok: false, confidence: 0.5, code: 'unknown', reason: 'non-object file_read result' };
305
+ }
306
+ if (inner.success === false) {
307
+ return {
308
+ ok: false,
309
+ confidence: 1.0,
310
+ code: 'failed',
311
+ reason: typeof inner.error === 'string' ? inner.error : 'success:false',
312
+ };
313
+ }
314
+ const content = typeof inner.content === 'string' ? inner.content : '';
315
+ if (content.length === 0) {
316
+ return {
317
+ ok: true,
318
+ confidence: 0.4,
319
+ code: 'low_signal',
320
+ reason: 'empty file content',
321
+ };
322
+ }
323
+ return { ok: true, confidence: 1.0, code: 'ok' };
324
+ };
325
+ exports.fileReadVerifier = fileReadVerifier;
326
+ /**
327
+ * `web_fetch` (and aliases) — verify the body is substantive. A
328
+ * < 100 char fetch body is almost certainly a redirect / blank
329
+ * page / soft-block; surface as low_signal.
330
+ */
331
+ const WEB_FETCH_MIN_BODY = 100;
332
+ const webFetchVerifier = (_n, _a, result) => {
333
+ if (typeof result.error === 'string' && result.error.length > 0) {
334
+ return { ok: false, confidence: 1.0, code: 'failed', reason: result.error };
335
+ }
336
+ const inner = result.result;
337
+ // Two shapes: typed `{ success, content/body }` or raw string.
338
+ if (typeof inner === 'string') {
339
+ if (inner.trim().length < WEB_FETCH_MIN_BODY) {
340
+ return {
341
+ ok: true,
342
+ confidence: 0.4,
343
+ code: 'low_signal',
344
+ reason: `short body (${inner.trim().length} chars)`,
345
+ suggestion: 'Try a different URL or check whether the page requires auth.',
346
+ };
347
+ }
348
+ return { ok: true, confidence: 0.9, code: 'ok' };
349
+ }
350
+ if (inner !== null && typeof inner === 'object') {
351
+ const obj = inner;
352
+ if (obj.success === false) {
353
+ return {
354
+ ok: false,
355
+ confidence: 1.0,
356
+ code: 'failed',
357
+ reason: typeof obj.error === 'string' ? obj.error : 'success:false',
358
+ };
359
+ }
360
+ const body = typeof obj.content === 'string' ? obj.content :
361
+ typeof obj.body === 'string' ? obj.body :
362
+ typeof obj.text === 'string' ? obj.text : '';
363
+ if (body.trim().length < WEB_FETCH_MIN_BODY) {
364
+ return {
365
+ ok: true,
366
+ confidence: 0.4,
367
+ code: 'low_signal',
368
+ reason: `short body (${body.trim().length} chars)`,
369
+ };
370
+ }
371
+ return { ok: true, confidence: 1.0, code: 'ok' };
372
+ }
373
+ return (0, exports.defaultVerifier)(_n, _a, result);
374
+ };
375
+ exports.webFetchVerifier = webFetchVerifier;
376
+ /**
377
+ * v4.3 Phase 5 — verifier for the 3 interactive browser tools
378
+ * (`browser_click`, `browser_type`, `browser_fill`) and
379
+ * `browser_navigate`. Extends defaultVerifier with one extra check:
380
+ * when the tool returns `success: true` BUT Phase 1's observer flagged
381
+ * `needs_verifier === true` (page state didn't meaningfully change),
382
+ * demote `ok` to false so the classifier runs and routes to
383
+ * `stale_ref` (page unresponsive) for the right recovery action.
384
+ *
385
+ * Without this demotion, the `needs_verifier` field would be a
386
+ * dormant hint with no behavioral effect. The whole point of Phase 1
387
+ * capturing it was to gate this verifier check.
388
+ *
389
+ * Conservative ordering — only runs the demotion AFTER the default
390
+ * verifier passed. Failed calls still classify via the existing
391
+ * path; success-but-noop is the specific case Phase 5 handles.
392
+ */
393
+ const browserInteractiveVerifier = (toolName, args, result) => {
394
+ const base = (0, exports.defaultVerifier)(toolName, args, result);
395
+ if (!base.ok)
396
+ return base;
397
+ // Read the v4.3 sidecar. Absent when browser depth is opt'd out
398
+ // (AIDEN_BROWSER_DEPTH=0) — in
399
+ // that case the verifier falls back to the default-passing result.
400
+ const inner = result.result;
401
+ if (!inner || typeof inner !== 'object')
402
+ return base;
403
+ const bs = inner.browserState;
404
+ if (!bs)
405
+ return base;
406
+ if (!bs.needs_verifier)
407
+ return base;
408
+ // Demote — the tool returned success but the page didn't change
409
+ // meaningfully. Classifier will route to stale_ref.
410
+ return {
411
+ ok: false,
412
+ confidence: 0.75,
413
+ code: bs.maybe_noop ? 'no_progress' : 'low_signal',
414
+ reason: bs.maybe_noop
415
+ ? 'tool returned success but page state did not change'
416
+ : `low progress (${bs.progress_score.toFixed(2)}) — UI may not have responded`,
417
+ };
418
+ };
419
+ exports.browserInteractiveVerifier = browserInteractiveVerifier;
420
+ // ── Factory ────────────────────────────────────────────────────────────────
421
+ /**
422
+ * Builds a registry pre-wired with the 5 built-in per-tool verifiers.
423
+ * The agent constructs one of these in `runConversation` when TCE is
424
+ * enabled. Plugin authors can register their own via the returned
425
+ * registry instance — Phase 1 doesn't expose a public registration
426
+ * API, but the foundation is here.
427
+ */
428
+ function buildDefaultRegistry() {
429
+ const reg = new VerifierRegistry();
430
+ reg.register('shell_exec', exports.shellExecVerifier);
431
+ reg.register('web_search', exports.webSearchVerifier);
432
+ reg.register('file_write', exports.fileWriteVerifier);
433
+ reg.register('file_read', exports.fileReadVerifier);
434
+ reg.register('web_fetch', exports.webFetchVerifier);
435
+ // Aliases — same verifier handles related shapes.
436
+ reg.register('fetch_page', exports.webFetchVerifier);
437
+ reg.register('web_page', exports.webFetchVerifier);
438
+ // v4.3 Phase 5 — browser interactive verifier reads the Phase 1
439
+ // sidecar (`needs_verifier` / `maybe_noop`) and demotes
440
+ // success-but-no-progress cases so the classifier routes them to
441
+ // `stale_ref` recovery. Falls back to defaultVerifier when sidecar
442
+ // absent (opt-out via AIDEN_BROWSER_DEPTH=0).
443
+ reg.register('browser_click', exports.browserInteractiveVerifier);
444
+ reg.register('browser_type', exports.browserInteractiveVerifier);
445
+ reg.register('browser_fill', exports.browserInteractiveVerifier);
446
+ reg.register('browser_navigate', exports.browserInteractiveVerifier);
447
+ return reg;
448
+ }
@@ -2,4 +2,4 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VERSION = void 0;
4
4
  // AUTO-GENERATED by scripts/inject-version.js — do not edit by hand
5
- exports.VERSION = '4.1.4';
5
+ exports.VERSION = '4.5.0';
@@ -7,6 +7,46 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.reliableWebSearch = reliableWebSearch;
8
8
  exports.deepResearch = deepResearch;
9
9
  exports.checkSearxNG = checkSearxNG;
10
+ // core/webSearch.ts — Reliable web search with 4-method fallback chain
11
+ //
12
+ // Priority order:
13
+ // 1. SearxNG (self-hosted, unlimited, Docker on port 8888)
14
+ // 2. Brave Search API (if BRAVE_SEARCH_API_KEY env var set)
15
+ // 3. DuckDuckGo Instant Answer API + HTML scrape
16
+ // 4. Wikipedia (always available, good for factual queries)
17
+ //
18
+ // Usage:
19
+ // import { reliableWebSearch, deepResearch } from './webSearch'
20
+ // const result = await reliableWebSearch('query')
21
+ // ── Debug logging (v4.1.5 Issue O) ────────────────────────────
22
+ //
23
+ // All `[webSearch]` / `[deepResearch]` chatter goes through these two
24
+ // helpers, both gated on `process.env.AIDEN_DEBUG_WEB === '1'`. The
25
+ // v4 REPL ran with these blasting unconditionally to stdout/stderr,
26
+ // surfacing 20+ lines of fallback-chain diagnostics between the user
27
+ // prompt and Aiden's reply on any web-search turn — overwhelming the
28
+ // signal users actually wanted (the tool-trail row).
29
+ //
30
+ // Power users debugging a flaky search backend export the env var:
31
+ // AIDEN_DEBUG_WEB=1 aiden
32
+ // Same pattern as `AIDEN_NO_REFORMAT`, `AIDEN_UI_ICONS`. Default off.
33
+ //
34
+ // `core/webSearch.ts` is shared with the legacy v3 path which has no
35
+ // Display dependency, so we cannot route through `display.dim()` /
36
+ // the v4 verbose-mode config. An env var is the lowest-friction
37
+ // transport that works in both paths.
38
+ function debugLog(...args) {
39
+ if (process.env.AIDEN_DEBUG_WEB === '1') {
40
+ // eslint-disable-next-line no-console
41
+ console.log(...args);
42
+ }
43
+ }
44
+ function debugWarn(...args) {
45
+ if (process.env.AIDEN_DEBUG_WEB === '1') {
46
+ // eslint-disable-next-line no-console
47
+ console.warn(...args);
48
+ }
49
+ }
10
50
  // ── Constants ─────────────────────────────────────────────────
11
51
  const SEARXNG_URL = process.env.SEARXNG_URL || 'http://localhost:8888';
12
52
  const BRAVE_API_KEY = process.env.BRAVE_SEARCH_API_KEY || '';
@@ -21,7 +61,7 @@ async function searchViaSearxNG(query) {
21
61
  signal: AbortSignal.timeout(SEARCH_TIMEOUT),
22
62
  });
23
63
  if (!res.ok) {
24
- console.warn(`[webSearch] SearxNG returned ${res.status}`);
64
+ debugWarn(`[webSearch] SearxNG returned ${res.status}`);
25
65
  return null;
26
66
  }
27
67
  const data = await res.json();
@@ -35,11 +75,11 @@ async function searchViaSearxNG(query) {
35
75
  return null;
36
76
  const lines = results.map(r => `**${r.title}**\n${r.snippet}\n${r.url}`);
37
77
  const output = `[SearxNG Results for "${query}"]\n\n${lines.join('\n\n')}`;
38
- console.log(`[webSearch] SearxNG: ${results.length} results`);
78
+ debugLog(`[webSearch] SearxNG: ${results.length} results`);
39
79
  return { success: true, output, method: 'searxng', results };
40
80
  }
41
81
  catch (e) {
42
- console.warn(`[webSearch] SearxNG failed: ${e.message}`);
82
+ debugWarn(`[webSearch] SearxNG failed: ${e.message}`);
43
83
  return null;
44
84
  }
45
85
  }
@@ -58,7 +98,7 @@ async function searchViaBrave(query) {
58
98
  signal: AbortSignal.timeout(SEARCH_TIMEOUT),
59
99
  });
60
100
  if (!res.ok) {
61
- console.warn(`[webSearch] Brave API returned ${res.status}`);
101
+ debugWarn(`[webSearch] Brave API returned ${res.status}`);
62
102
  return null;
63
103
  }
64
104
  const data = await res.json();
@@ -73,11 +113,11 @@ async function searchViaBrave(query) {
73
113
  }));
74
114
  const lines = results.map(r => `**${r.title}**\n${r.snippet}\n${r.url}`);
75
115
  const output = `[Brave Search Results for "${query}"]\n\n${lines.join('\n\n')}`;
76
- console.log(`[webSearch] Brave: ${results.length} results`);
116
+ debugLog(`[webSearch] Brave: ${results.length} results`);
77
117
  return { success: true, output, method: 'brave', results };
78
118
  }
79
119
  catch (e) {
80
- console.warn(`[webSearch] Brave failed: ${e.message}`);
120
+ debugWarn(`[webSearch] Brave failed: ${e.message}`);
81
121
  return null;
82
122
  }
83
123
  }
@@ -108,7 +148,7 @@ async function searchViaDDG(query) {
108
148
  }
109
149
  }
110
150
  catch (e) {
111
- console.warn(`[webSearch] DDG Instant failed: ${e.message}`);
151
+ debugWarn(`[webSearch] DDG Instant failed: ${e.message}`);
112
152
  }
113
153
  // DDG HTML scrape — get snippet text + page content
114
154
  try {
@@ -164,12 +204,12 @@ async function searchViaDDG(query) {
164
204
  parts.push(...validPages);
165
205
  }
166
206
  catch (e) {
167
- console.warn(`[webSearch] DDG HTML scrape failed: ${e.message}`);
207
+ debugWarn(`[webSearch] DDG HTML scrape failed: ${e.message}`);
168
208
  }
169
209
  if (parts.length === 0)
170
210
  return null;
171
211
  const output = `[DuckDuckGo Results for "${query}"]\n\n${parts.join('\n\n')}`;
172
- console.log(`[webSearch] DDG: ${parts.length} sections`);
212
+ debugLog(`[webSearch] DDG: ${parts.length} sections`);
173
213
  return { success: true, output, method: 'ddg' };
174
214
  }
175
215
  // ── METHOD 4: Wikipedia ───────────────────────────────────────
@@ -193,11 +233,11 @@ async function searchViaWikipedia(query) {
193
233
  .filter(s => s.length > 20);
194
234
  const extra = snippets.length > 0 ? `\n\nRelated: ${snippets.join(' | ')}` : '';
195
235
  const output = `[Wikipedia: ${wiki.title}]\n${wiki.extract.slice(0, 1500)}${extra}`;
196
- console.log(`[webSearch] Wikipedia: ${wiki.extract.length} chars for "${wiki.title}"`);
236
+ debugLog(`[webSearch] Wikipedia: ${wiki.extract.length} chars for "${wiki.title}"`);
197
237
  return { success: true, output, method: 'wikipedia' };
198
238
  }
199
239
  catch (e) {
200
- console.warn(`[webSearch] Wikipedia failed: ${e.message}`);
240
+ debugWarn(`[webSearch] Wikipedia failed: ${e.message}`);
201
241
  return null;
202
242
  }
203
243
  }
@@ -229,11 +269,11 @@ async function fetchWeather(query) {
229
269
  out += ` ${day.date}: High ${day.maxtempC}°C / Low ${day.mintempC}°C${mid ? ' — ' + mid : ''}\n`;
230
270
  }
231
271
  }
232
- console.log(`[webSearch] Weather: retrieved for "${city}"`);
272
+ debugLog(`[webSearch] Weather: retrieved for "${city}"`);
233
273
  return { success: true, output: out.trim(), method: 'wttr.in' };
234
274
  }
235
275
  catch (e) {
236
- console.warn(`[webSearch] Weather failed: ${e.message}`);
276
+ debugWarn(`[webSearch] Weather failed: ${e.message}`);
237
277
  return null;
238
278
  }
239
279
  }
@@ -241,7 +281,7 @@ async function fetchWeather(query) {
241
281
  async function reliableWebSearch(query) {
242
282
  if (!query?.trim())
243
283
  return { success: false, output: '', error: 'No query provided' };
244
- console.log(`[webSearch] Query: "${query}"`);
284
+ debugLog(`[webSearch] Query: "${query}"`);
245
285
  // Weather shortcut
246
286
  if (/weather|temperature|forecast|rain|snow|sunny|cloudy|humidity|wind/i.test(query)) {
247
287
  const weather = await fetchWeather(query);
@@ -251,28 +291,28 @@ async function reliableWebSearch(query) {
251
291
  // Method 1 — SearxNG
252
292
  const searxResult = await searchViaSearxNG(query);
253
293
  if (searxResult) {
254
- console.log(`[webSearch] ✓ SearxNG succeeded`);
294
+ debugLog(`[webSearch] ✓ SearxNG succeeded`);
255
295
  return { success: true, output: searxResult.output.slice(0, 10000) };
256
296
  }
257
297
  // Method 2 — Brave
258
298
  const braveResult = await searchViaBrave(query);
259
299
  if (braveResult) {
260
- console.log(`[webSearch] ✓ Brave succeeded`);
300
+ debugLog(`[webSearch] ✓ Brave succeeded`);
261
301
  return { success: true, output: braveResult.output.slice(0, 10000) };
262
302
  }
263
303
  // Method 3 — DDG
264
304
  const ddgResult = await searchViaDDG(query);
265
305
  if (ddgResult) {
266
- console.log(`[webSearch] ✓ DDG succeeded`);
306
+ debugLog(`[webSearch] ✓ DDG succeeded`);
267
307
  return { success: true, output: ddgResult.output.slice(0, 10000) };
268
308
  }
269
309
  // Method 4 — Wikipedia
270
310
  const wikiResult = await searchViaWikipedia(query);
271
311
  if (wikiResult) {
272
- console.log(`[webSearch] ✓ Wikipedia fallback`);
312
+ debugLog(`[webSearch] ✓ Wikipedia fallback`);
273
313
  return { success: true, output: wikiResult.output };
274
314
  }
275
- console.warn(`[webSearch] All methods failed for: "${query}"`);
315
+ debugWarn(`[webSearch] All methods failed for: "${query}"`);
276
316
  return {
277
317
  success: false,
278
318
  output: '',
@@ -283,24 +323,24 @@ async function reliableWebSearch(query) {
283
323
  async function deepResearch(topic) {
284
324
  if (!topic?.trim())
285
325
  return { success: false, output: '', error: 'No topic provided' };
286
- console.log(`[deepResearch] Topic: "${topic}"`);
326
+ debugLog(`[deepResearch] Topic: "${topic}"`);
287
327
  const parts = [];
288
328
  // Pass 1: Broad
289
- console.log(`[deepResearch] Pass 1: broad`);
329
+ debugLog(`[deepResearch] Pass 1: broad`);
290
330
  const broad = await reliableWebSearch(topic);
291
331
  if (broad.success && broad.output.length > 100) {
292
332
  parts.push(`=== PASS 1: BROAD RESEARCH ===\n${broad.output}`);
293
333
  }
294
334
  // Pass 2: Latest 2026
295
335
  const latestQ = `${topic} 2026 latest`;
296
- console.log(`[deepResearch] Pass 2: latest — "${latestQ}"`);
336
+ debugLog(`[deepResearch] Pass 2: latest — "${latestQ}"`);
297
337
  const latest = await reliableWebSearch(latestQ);
298
338
  if (latest.success && latest.output.length > 100) {
299
339
  parts.push(`=== PASS 2: LATEST (2026) ===\n${latest.output}`);
300
340
  }
301
341
  // Pass 3: Comparison / review
302
342
  const compareQ = `best top ${topic} comparison review`;
303
- console.log(`[deepResearch] Pass 3: comparison — "${compareQ}"`);
343
+ debugLog(`[deepResearch] Pass 3: comparison — "${compareQ}"`);
304
344
  const compare = await reliableWebSearch(compareQ);
305
345
  if (compare.success && compare.output.length > 100) {
306
346
  parts.push(`=== PASS 3: COMPARISON & REVIEWS ===\n${compare.output}`);
@@ -309,7 +349,7 @@ async function deepResearch(topic) {
309
349
  return { success: false, output: '', error: `No research results found for: ${topic}` };
310
350
  }
311
351
  const combined = parts.join('\n\n');
312
- console.log(`[deepResearch] Complete: ${combined.length} chars across ${parts.length} passes`);
352
+ debugLog(`[deepResearch] Complete: ${combined.length} chars across ${parts.length} passes`);
313
353
  return { success: true, output: combined.slice(0, 15000) };
314
354
  }
315
355
  // ── SearxNG health check ──────────────────────────────────────