aiden-runtime 4.1.3 → 4.1.5
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/dist/cli/v4/aidenCLI.js +28 -0
- package/dist/cli/v4/callbacks.js +148 -13
- package/dist/cli/v4/chatSession.js +283 -22
- package/dist/cli/v4/defaultSoul.js +143 -4
- package/dist/cli/v4/display/frame.js +234 -0
- package/dist/cli/v4/display/progressBar.js +170 -0
- package/dist/cli/v4/display.js +663 -24
- package/dist/cli/v4/replyRenderer.js +196 -26
- package/dist/cli/v4/skinEngine.js +15 -4
- package/dist/cli/v4/toolPreview.js +78 -19
- package/dist/core/toolRegistry.js +7 -1
- package/dist/core/v4/aidenAgent.js +72 -0
- package/dist/core/v4/loopTrace.js +257 -0
- package/dist/core/v4/promptBuilder.js +2 -1
- package/dist/core/version.js +1 -1
- package/dist/core/webSearch.js +64 -24
- package/dist/providers/v4/anthropicAdapter.js +25 -2
- package/package.json +2 -1
- package/plugins/aiden-plugin-cdp-browser/.granted-permissions.json +8 -0
|
@@ -0,0 +1,257 @@
|
|
|
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/loopTrace.ts — Phase v4.1.5+ Path A.
|
|
10
|
+
*
|
|
11
|
+
* Env-var-gated per-turn audit log for diagnosing tool-call loops
|
|
12
|
+
* (the user-reported "30+ skill_view calls in 0ms each" failure mode
|
|
13
|
+
* from v4.1.5 visual smoke). Default OFF — adds zero overhead when
|
|
14
|
+
* `AIDEN_DEBUG_LOOP !== '1'`.
|
|
15
|
+
*
|
|
16
|
+
* When enabled, captures:
|
|
17
|
+
* - Full tool-call sequence (name, args, timing) for the turn
|
|
18
|
+
* - Assembled system prompt at turn start
|
|
19
|
+
* - MEMORY.md + USER.md content hashes (sha256, first 12 hex)
|
|
20
|
+
* - Recent skills list (last 10 `skill_view` calls)
|
|
21
|
+
* - Conversation history snapshot (last 5 turns)
|
|
22
|
+
*
|
|
23
|
+
* Auto-writes to `<paths.logsDir>/loop-trace-{ISO-timestamp}.json` at
|
|
24
|
+
* turn end IF the turn triggered loop detection (10+ tool calls OR
|
|
25
|
+
* 5+ consecutive same-name). Quiet otherwise — non-loop turns don't
|
|
26
|
+
* spam the logs directory.
|
|
27
|
+
*
|
|
28
|
+
* A/B harness reproduction lesson: the original 30+ loop the user
|
|
29
|
+
* observed could not be reproduced with a fresh `[system, user]`
|
|
30
|
+
* history (see `scripts/smoke-prompt-bias-ab.ts` results). The loop
|
|
31
|
+
* is either stochastic gpt-5.5 behaviour, history-poisoning from
|
|
32
|
+
* prior turns, or MEMORY/USER context-specific. This logger captures
|
|
33
|
+
* the EXACT context next time the loop happens in live use so we
|
|
34
|
+
* can A/B against the real failure case.
|
|
35
|
+
*
|
|
36
|
+
* Pure module — no Display dependency, no event-emitter, no side
|
|
37
|
+
* effects beyond the gated file write. Safe to import from anywhere.
|
|
38
|
+
*/
|
|
39
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
40
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
41
|
+
};
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.LoopTracer = void 0;
|
|
44
|
+
const node_fs_1 = require("node:fs");
|
|
45
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
46
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
47
|
+
// ── Tracer ──────────────────────────────────────────────────────────────────
|
|
48
|
+
const ARGS_CAP = 200;
|
|
49
|
+
const HISTORY_TAIL_DEPTH = 5;
|
|
50
|
+
const RECENT_SKILLS_MAX = 10;
|
|
51
|
+
/**
|
|
52
|
+
* Tracks tool calls for one turn. Construct fresh per turn (the
|
|
53
|
+
* counters and recent-skills list are turn-scoped). Idempotent
|
|
54
|
+
* `finalize()` — multiple calls produce one file at most.
|
|
55
|
+
*/
|
|
56
|
+
class LoopTracer {
|
|
57
|
+
constructor(rawOpts) {
|
|
58
|
+
this.systemPrompt = '';
|
|
59
|
+
this.history = [];
|
|
60
|
+
this.toolStart = new Map();
|
|
61
|
+
this.sequence = [];
|
|
62
|
+
this.lastName = null;
|
|
63
|
+
this.consecSame = 0;
|
|
64
|
+
this.maxConsec = 0;
|
|
65
|
+
this.warnFired = false;
|
|
66
|
+
this.finalized = false;
|
|
67
|
+
this.recentSkills = [];
|
|
68
|
+
this.enabled = rawOpts.enabled ?? (process.env.AIDEN_DEBUG_LOOP === '1');
|
|
69
|
+
this.opts = {
|
|
70
|
+
paths: rawOpts.paths,
|
|
71
|
+
providerId: rawOpts.providerId,
|
|
72
|
+
modelId: rawOpts.modelId,
|
|
73
|
+
enabled: this.enabled,
|
|
74
|
+
toolCountThreshold: rawOpts.toolCountThreshold ?? 10,
|
|
75
|
+
consecSameThreshold: rawOpts.consecSameThreshold ?? 5,
|
|
76
|
+
warnConsecThreshold: rawOpts.warnConsecThreshold ?? 8,
|
|
77
|
+
onLoopWarning: rawOpts.onLoopWarning ?? (() => { }),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
isEnabled() { return this.enabled; }
|
|
81
|
+
/**
|
|
82
|
+
* Set the assembled system prompt for this turn. Called once at turn
|
|
83
|
+
* start (before any tool calls). Stored verbatim; no truncation.
|
|
84
|
+
*/
|
|
85
|
+
setSystemPrompt(prompt) {
|
|
86
|
+
if (!this.enabled)
|
|
87
|
+
return;
|
|
88
|
+
this.systemPrompt = prompt;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Set the turn's conversation history snapshot. The tracer keeps
|
|
92
|
+
* only the last `HISTORY_TAIL_DEPTH` messages on finalize.
|
|
93
|
+
*/
|
|
94
|
+
setHistory(messages) {
|
|
95
|
+
if (!this.enabled)
|
|
96
|
+
return;
|
|
97
|
+
this.history = messages;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Record the start of a tool call. Pair with `endTool(id, name)` to
|
|
101
|
+
* capture the duration. Caller must pass a stable `id` (the tool
|
|
102
|
+
* call request id) so before/after pairs find each other.
|
|
103
|
+
*/
|
|
104
|
+
startTool(id, _name) {
|
|
105
|
+
if (!this.enabled)
|
|
106
|
+
return;
|
|
107
|
+
this.toolStart.set(id, Date.now());
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Record the end of a tool call. Computes duration from the matching
|
|
111
|
+
* `startTool` call. Updates consec-same counter; fires the live
|
|
112
|
+
* warning when threshold crosses.
|
|
113
|
+
*/
|
|
114
|
+
endTool(id, name, args) {
|
|
115
|
+
if (!this.enabled)
|
|
116
|
+
return;
|
|
117
|
+
const start = this.toolStart.get(id);
|
|
118
|
+
this.toolStart.delete(id);
|
|
119
|
+
const durationMs = start === undefined ? 0 : (Date.now() - start);
|
|
120
|
+
let argsPreview;
|
|
121
|
+
try {
|
|
122
|
+
argsPreview = JSON.stringify(args ?? {});
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
argsPreview = String(args);
|
|
126
|
+
}
|
|
127
|
+
if (argsPreview.length > ARGS_CAP)
|
|
128
|
+
argsPreview = `${argsPreview.slice(0, ARGS_CAP - 1)}…`;
|
|
129
|
+
this.sequence.push({
|
|
130
|
+
name, argsPreview, durationMs,
|
|
131
|
+
ts: new Date().toISOString(),
|
|
132
|
+
});
|
|
133
|
+
// Consec-same accounting.
|
|
134
|
+
if (name === this.lastName) {
|
|
135
|
+
this.consecSame += 1;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
this.consecSame = 1;
|
|
139
|
+
this.lastName = name;
|
|
140
|
+
}
|
|
141
|
+
if (this.consecSame > this.maxConsec)
|
|
142
|
+
this.maxConsec = this.consecSame;
|
|
143
|
+
// Skill-view tracking (separate from sequence for quick reference).
|
|
144
|
+
if (name === 'skill_view' || name === 'lookup_tool_schema') {
|
|
145
|
+
const tk = args;
|
|
146
|
+
const target = tk?.name ?? tk?.toolName ?? '(unknown)';
|
|
147
|
+
this.recentSkills.push(target);
|
|
148
|
+
if (this.recentSkills.length > RECENT_SKILLS_MAX) {
|
|
149
|
+
this.recentSkills = this.recentSkills.slice(-RECENT_SKILLS_MAX);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// Live warning at the loud threshold.
|
|
153
|
+
if (!this.warnFired && this.consecSame >= this.opts.warnConsecThreshold) {
|
|
154
|
+
this.warnFired = true;
|
|
155
|
+
try {
|
|
156
|
+
this.opts.onLoopWarning(`[loop] same tool '${name}' called ${this.consecSame}× — Ctrl+C to interrupt`);
|
|
157
|
+
}
|
|
158
|
+
catch { /* defensive */ }
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Compute whether a snapshot would be written if `finalize()` ran
|
|
163
|
+
* now. Useful for tests + the live warning path. Pure read.
|
|
164
|
+
*/
|
|
165
|
+
shouldEmit() {
|
|
166
|
+
if (!this.enabled)
|
|
167
|
+
return false;
|
|
168
|
+
return (this.sequence.length >= this.opts.toolCountThreshold ||
|
|
169
|
+
this.maxConsec >= this.opts.consecSameThreshold);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Write the trace to disk if thresholds tripped. Idempotent —
|
|
173
|
+
* subsequent calls are no-ops. Returns the snapshot path or `null`
|
|
174
|
+
* if nothing was written.
|
|
175
|
+
*/
|
|
176
|
+
async finalize() {
|
|
177
|
+
if (this.finalized)
|
|
178
|
+
return null;
|
|
179
|
+
this.finalized = true;
|
|
180
|
+
if (!this.enabled)
|
|
181
|
+
return null;
|
|
182
|
+
if (!this.shouldEmit())
|
|
183
|
+
return null;
|
|
184
|
+
const snapshot = await this.buildSnapshot();
|
|
185
|
+
const ts = snapshot.capturedAt.replace(/[:.]/g, '-');
|
|
186
|
+
const filename = `loop-trace-${ts}.json`;
|
|
187
|
+
const filePath = node_path_1.default.join(this.opts.paths.logsDir, filename);
|
|
188
|
+
try {
|
|
189
|
+
await node_fs_1.promises.mkdir(this.opts.paths.logsDir, { recursive: true });
|
|
190
|
+
await node_fs_1.promises.writeFile(filePath, JSON.stringify(snapshot, null, 2), 'utf8');
|
|
191
|
+
return filePath;
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
// Diagnostic logging must never break the turn. Silently swallow
|
|
195
|
+
// write failures — power user with AIDEN_DEBUG_LOOP=1 will notice
|
|
196
|
+
// missing files and can re-run with stricter perms if needed.
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/** Test-accessor: synchronous read of the current consec-same state. */
|
|
201
|
+
getMaxConsecutive() { return this.maxConsec; }
|
|
202
|
+
/** Test-accessor: tool call count so far. */
|
|
203
|
+
getToolCount() { return this.sequence.length; }
|
|
204
|
+
// ── Internals ────────────────────────────────────────────────────────────
|
|
205
|
+
async buildSnapshot() {
|
|
206
|
+
const reason = this.maxConsec >= this.opts.consecSameThreshold
|
|
207
|
+
? (this.sequence.length >= this.opts.toolCountThreshold
|
|
208
|
+
? 'consecutive_same'
|
|
209
|
+
: 'consecutive_same')
|
|
210
|
+
: 'tool_count';
|
|
211
|
+
const memoryMdHash = await hashFileFirst12(this.opts.paths.memoryMd);
|
|
212
|
+
const userMdHash = await hashFileFirst12(this.opts.paths.userMd);
|
|
213
|
+
const historyTail = this.history.slice(-HISTORY_TAIL_DEPTH).map((m) => ({
|
|
214
|
+
role: m.role,
|
|
215
|
+
contentPreview: trunc(typeof m.content === 'string' ? m.content : JSON.stringify(m.content), 300),
|
|
216
|
+
}));
|
|
217
|
+
return {
|
|
218
|
+
schemaVersion: 1,
|
|
219
|
+
capturedAt: new Date().toISOString(),
|
|
220
|
+
reason,
|
|
221
|
+
toolCallCount: this.sequence.length,
|
|
222
|
+
maxConsecSame: this.maxConsec,
|
|
223
|
+
consecSameName: this.maxConsec >= 2 ? this.lastName : null,
|
|
224
|
+
toolSequence: this.sequence,
|
|
225
|
+
systemPrompt: this.systemPrompt,
|
|
226
|
+
memoryMdHash,
|
|
227
|
+
userMdHash,
|
|
228
|
+
recentSkills: this.recentSkills,
|
|
229
|
+
historyTail,
|
|
230
|
+
envHints: {
|
|
231
|
+
provider: this.opts.providerId,
|
|
232
|
+
model: this.opts.modelId,
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
exports.LoopTracer = LoopTracer;
|
|
238
|
+
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
239
|
+
/**
|
|
240
|
+
* Read a file and return the sha256 hash, first 12 hex chars.
|
|
241
|
+
* Returns `null` if the file is missing or unreadable. Pure-async.
|
|
242
|
+
* 12 hex is enough collision resistance for context-fingerprinting
|
|
243
|
+
* without bloating the trace file with full 64-char hashes.
|
|
244
|
+
*/
|
|
245
|
+
async function hashFileFirst12(filePath) {
|
|
246
|
+
try {
|
|
247
|
+
const content = await node_fs_1.promises.readFile(filePath, 'utf8');
|
|
248
|
+
const hash = node_crypto_1.default.createHash('sha256').update(content).digest('hex');
|
|
249
|
+
return hash.slice(0, 12);
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function trunc(s, n) {
|
|
256
|
+
return s.length <= n ? s : `${s.slice(0, n - 1)}…`;
|
|
257
|
+
}
|
|
@@ -128,7 +128,8 @@ const EXECUTION_DISCIPLINE_PROSE = [
|
|
|
128
128
|
'file"), you MUST immediately make the corresponding tool call in the same response.',
|
|
129
129
|
'Never end your turn with a promise of future action — execute it now. Every',
|
|
130
130
|
'response should either contain tool calls that make progress, or deliver a final',
|
|
131
|
-
'result.
|
|
131
|
+
'result. When the user requests an action, take it. When the user requests',
|
|
132
|
+
'discussion, discuss.',
|
|
132
133
|
].join('\n');
|
|
133
134
|
/**
|
|
134
135
|
* Llama-3.3-specific tool-call format guard. Adapter-side recovery picks
|
package/dist/core/version.js
CHANGED
package/dist/core/webSearch.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
78
|
+
debugLog(`[webSearch] SearxNG: ${results.length} results`);
|
|
39
79
|
return { success: true, output, method: 'searxng', results };
|
|
40
80
|
}
|
|
41
81
|
catch (e) {
|
|
42
|
-
|
|
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
|
-
|
|
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
|
-
|
|
116
|
+
debugLog(`[webSearch] Brave: ${results.length} results`);
|
|
77
117
|
return { success: true, output, method: 'brave', results };
|
|
78
118
|
}
|
|
79
119
|
catch (e) {
|
|
80
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
312
|
+
debugLog(`[webSearch] ✓ Wikipedia fallback`);
|
|
273
313
|
return { success: true, output: wikiResult.output };
|
|
274
314
|
}
|
|
275
|
-
|
|
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
|
-
|
|
326
|
+
debugLog(`[deepResearch] Topic: "${topic}"`);
|
|
287
327
|
const parts = [];
|
|
288
328
|
// Pass 1: Broad
|
|
289
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ──────────────────────────────────────
|
|
@@ -114,7 +114,7 @@ class AnthropicAdapter {
|
|
|
114
114
|
};
|
|
115
115
|
return;
|
|
116
116
|
}
|
|
117
|
-
yield* decodeStream(reply.body);
|
|
117
|
+
yield* decodeStream(reply.body, input.maxTokens ?? DEFAULT_MAX_TOKENS);
|
|
118
118
|
}
|
|
119
119
|
// ── Request body assembly ────────────────────────────────────────────────
|
|
120
120
|
buildBody(input, streaming) {
|
|
@@ -404,13 +404,20 @@ function decodeUsage(u) {
|
|
|
404
404
|
}
|
|
405
405
|
return out;
|
|
406
406
|
}
|
|
407
|
-
async function* decodeStream(body) {
|
|
407
|
+
async function* decodeStream(body, maxTokens) {
|
|
408
408
|
const blocks = new Map();
|
|
409
409
|
const toolCalls = [];
|
|
410
410
|
let stopReason;
|
|
411
411
|
let usage = undefined;
|
|
412
412
|
// Stable text emission order: walk content blocks by index at end-of-stream.
|
|
413
413
|
const textOrder = [];
|
|
414
|
+
// v4.1.4 Part 1.6: track the last-emitted output-token count so we
|
|
415
|
+
// only yield a `progress` event when the counter actually advances.
|
|
416
|
+
// Anthropic emits `message_delta.usage.output_tokens` as a running
|
|
417
|
+
// total — multiple deltas may carry the same value if no new tokens
|
|
418
|
+
// were produced between them. Deduping keeps the event stream
|
|
419
|
+
// proportional to real progress.
|
|
420
|
+
let lastProgressEmitted = -1;
|
|
414
421
|
for await (const payload of (0, chatCompletionsAdapter_1.parseSseStream)(body)) {
|
|
415
422
|
if (!payload || payload === '[DONE]')
|
|
416
423
|
continue;
|
|
@@ -490,6 +497,22 @@ async function* decodeStream(body) {
|
|
|
490
497
|
stopReason = evt.delta.stop_reason;
|
|
491
498
|
if (evt.usage) {
|
|
492
499
|
usage = { ...(usage ?? {}), ...evt.usage };
|
|
500
|
+
// v4.1.4 Part 1.6 — emit a `progress` event when the running
|
|
501
|
+
// output-token counter advances. The display layer uses these
|
|
502
|
+
// for the ▰▱ progress bar. Deduped via `lastProgressEmitted`
|
|
503
|
+
// so a stream of message_delta events with no real progress
|
|
504
|
+
// doesn't flood the consumer.
|
|
505
|
+
const outputTokens = typeof evt.usage.output_tokens === 'number'
|
|
506
|
+
? evt.usage.output_tokens
|
|
507
|
+
: -1;
|
|
508
|
+
if (outputTokens > lastProgressEmitted) {
|
|
509
|
+
lastProgressEmitted = outputTokens;
|
|
510
|
+
yield {
|
|
511
|
+
type: 'progress',
|
|
512
|
+
outputTokens,
|
|
513
|
+
maxTokens,
|
|
514
|
+
};
|
|
515
|
+
}
|
|
493
516
|
}
|
|
494
517
|
break;
|
|
495
518
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aiden-runtime",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.5",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -277,6 +277,7 @@
|
|
|
277
277
|
"twilio": "^5.13.1",
|
|
278
278
|
"uuid": "^9.0.0",
|
|
279
279
|
"whatsapp-web.js": "^1.26.0",
|
|
280
|
+
"wrap-ansi": "^9.0.2",
|
|
280
281
|
"ws": "^8.20.0"
|
|
281
282
|
},
|
|
282
283
|
"optionalDependencies": {
|