aiden-runtime 4.0.2 → 4.1.1
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 +19 -11
- package/config/hardware.json +2 -2
- package/dist/api/server.js +50 -52
- package/dist/cli/v4/aidenCLI.js +424 -7
- package/dist/cli/v4/aidenPrompt.js +317 -0
- package/dist/cli/v4/box.js +105 -39
- package/dist/cli/v4/callbacks.js +39 -6
- package/dist/cli/v4/chatSession.js +256 -55
- package/dist/cli/v4/citationFooter.js +97 -0
- package/dist/cli/v4/commands/channel.js +656 -0
- package/dist/cli/v4/commands/clear.js +1 -1
- package/dist/cli/v4/commands/compress.js +1 -1
- package/dist/cli/v4/commands/cron.js +44 -16
- package/dist/cli/v4/commands/fanout.js +236 -0
- package/dist/cli/v4/commands/help.js +15 -4
- package/dist/cli/v4/commands/history.js +84 -0
- package/dist/cli/v4/commands/index.js +16 -1
- package/dist/cli/v4/commands/mcp.js +358 -0
- package/dist/cli/v4/commands/show.js +43 -0
- package/dist/cli/v4/commands/skills.js +169 -4
- package/dist/cli/v4/commands/status.js +84 -0
- package/dist/cli/v4/commands/subagent.js +78 -0
- package/dist/cli/v4/commands/verbose.js +1 -1
- package/dist/cli/v4/commands/voice.js +218 -0
- package/dist/cli/v4/cronCli.js +103 -0
- package/dist/cli/v4/display.js +297 -13
- package/dist/cli/v4/doctor.js +102 -1
- package/dist/cli/v4/doctorLiveness.js +329 -0
- package/dist/cli/v4/envSources.js +105 -0
- package/dist/cli/v4/ghostMatch.js +74 -0
- package/dist/cli/v4/historyStore.js +163 -0
- package/dist/cli/v4/pasteCompression.js +124 -0
- package/dist/cli/v4/pasteIntercept.js +203 -0
- package/dist/cli/v4/replyRenderer.js +209 -0
- package/dist/cli/v4/resizeGuard.js +92 -0
- package/dist/cli/v4/shellInterpolation.js +139 -0
- package/dist/cli/v4/skinEngine.js +21 -1
- package/dist/cli/v4/streamingPrefix.js +121 -0
- package/dist/cli/v4/syntaxHighlight.js +345 -0
- package/dist/cli/v4/table.js +216 -0
- package/dist/cli/v4/themeDetect.js +81 -0
- package/dist/cli/v4/uiBuild.js +74 -0
- package/dist/cli/v4/voiceCli.js +113 -0
- package/dist/cli/v4/voicePromptApi.js +196 -0
- package/dist/core/channels/discord.js +16 -10
- package/dist/core/channels/email.js +13 -9
- package/dist/core/channels/imessage.js +13 -9
- package/dist/core/channels/manager.js +25 -7
- package/dist/core/channels/pdf-extract.js +180 -0
- package/dist/core/channels/photo-vision.js +157 -0
- package/dist/core/channels/signal.js +11 -7
- package/dist/core/channels/slack.js +13 -10
- package/dist/core/channels/telegram-commands.js +154 -0
- package/dist/core/channels/telegram-groups.js +198 -0
- package/dist/core/channels/telegram-rate-limit.js +124 -0
- package/dist/core/channels/telegram.js +1980 -0
- package/dist/core/channels/twilio.js +11 -7
- package/dist/core/channels/webhook.js +9 -5
- package/dist/core/channels/whatsapp.js +15 -11
- package/dist/core/channels/whisper-transcribe.js +163 -0
- package/dist/core/cronManager.js +33 -294
- package/dist/core/gateway.js +29 -8
- package/dist/core/playwrightBridge.js +90 -0
- package/dist/core/v4/aidenAgent.js +35 -0
- package/dist/core/v4/auxiliaryClient.js +2 -2
- package/dist/core/v4/cron/atomicWrite.js +18 -4
- package/dist/core/v4/cron/cronExecute.js +300 -0
- package/dist/core/v4/cron/cronManager.js +502 -0
- package/dist/core/v4/cron/cronState.js +314 -0
- package/dist/core/v4/cron/cronTick.js +90 -0
- package/dist/core/v4/cron/diagnostics.js +104 -0
- package/dist/core/v4/cron/graceWindow.js +79 -0
- package/dist/core/v4/logger/factory.js +110 -0
- package/dist/core/v4/logger/index.js +22 -0
- package/dist/core/v4/logger/logger.js +101 -0
- package/dist/core/v4/logger/sinks/fileSink.js +110 -0
- package/dist/core/v4/logger/sinks/multiSink.js +43 -0
- package/dist/core/v4/logger/sinks/nullSink.js +53 -0
- package/dist/core/v4/logger/sinks/stdSink.js +81 -0
- package/dist/core/v4/mcp/server/diagnostics.js +40 -0
- package/dist/core/v4/mcp/server/skillBridge.js +94 -0
- package/dist/core/v4/mcp/server/stdioServer.js +119 -0
- package/dist/core/v4/mcp/server/toolBridge.js +168 -0
- package/dist/core/v4/platformPaths.js +105 -0
- package/dist/core/v4/providerFallback.js +25 -0
- package/dist/core/v4/skillLoader.js +21 -5
- package/dist/core/v4/skillMining/candidateStore.js +164 -0
- package/dist/core/v4/skillMining/extractorPrompt.js +118 -0
- package/dist/core/v4/skillMining/proposalBuilder.js +140 -0
- package/dist/core/v4/skillMining/skillMiner.js +191 -0
- package/dist/core/v4/skillMining/traceFingerprint.js +51 -0
- package/dist/core/v4/subagent/budget.js +76 -0
- package/dist/core/v4/subagent/diagnostics.js +22 -0
- package/dist/core/v4/subagent/fanout.js +216 -0
- package/dist/core/v4/subagent/merger.js +148 -0
- package/dist/core/v4/subagent/providerRotation.js +54 -0
- package/dist/core/v4/voice/audioStream.js +373 -0
- package/dist/core/v4/voice/cliVoice.js +393 -0
- package/dist/core/v4/voice/diagnostics.js +66 -0
- package/dist/core/v4/voice/ttsStream.js +193 -0
- package/dist/core/version.js +1 -1
- package/dist/core/visionAnalyze.js +291 -90
- package/dist/core/voice/audio.js +61 -5
- package/dist/core/voice/audioBackend.js +134 -0
- package/dist/core/voice/stt.js +61 -6
- package/dist/core/voice/tts.js +19 -3
- package/dist/moat/dangerousPatterns.js +1 -1
- package/dist/providers/v4/codexResponsesAdapter.js +7 -2
- package/dist/providers/v4/errors.js +51 -1
- package/dist/providers/v4/ollamaPromptToolsAdapter.js +9 -2
- package/dist/tools/v4/index.js +32 -1
- package/dist/tools/v4/subagent/subagentFanout.js +190 -0
- package/package.json +11 -2
package/dist/core/voice/stt.js
CHANGED
|
@@ -51,11 +51,23 @@ exports.getSttProviders = getSttProviders;
|
|
|
51
51
|
//
|
|
52
52
|
// If all providers fail: returns { text: '', provider: 'none', error }
|
|
53
53
|
// — never throws; callers check result.text.
|
|
54
|
+
//
|
|
55
|
+
// Phase v4.1-3 surgical edits:
|
|
56
|
+
// - Cloud providers request `verbose_json` so we receive segment-level
|
|
57
|
+
// `avg_logprob`. The mean is exposed on the result as `confidence`
|
|
58
|
+
// (negative; closer to zero is more confident). Channel adapters
|
|
59
|
+
// (e.g. Telegram voice notes) use this to decide whether to echo
|
|
60
|
+
// a low-confidence transcript back to the user before handing it
|
|
61
|
+
// to the agent.
|
|
62
|
+
// - All `console.*` removed in favour of an injectable `Logger` from
|
|
63
|
+
// `core/v4/logger`. Defaults to a noop logger so callers without a
|
|
64
|
+
// wired logger get silence (REPL-safe). v4.1-1.3a contract.
|
|
54
65
|
const fs_1 = __importDefault(require("fs"));
|
|
55
66
|
const path_1 = __importDefault(require("path"));
|
|
56
67
|
const child_process_1 = require("child_process");
|
|
57
68
|
const util_1 = require("util");
|
|
58
69
|
const axios_1 = __importDefault(require("axios"));
|
|
70
|
+
const logger_1 = require("../v4/logger");
|
|
59
71
|
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
60
72
|
// ── Internal helpers ──────────────────────────────────────────────────────────
|
|
61
73
|
const WORKSPACE = path_1.default.join(process.cwd(), 'workspace');
|
|
@@ -75,6 +87,29 @@ function resolveAudioPath(opts) {
|
|
|
75
87
|
}
|
|
76
88
|
throw new Error('SttOptions: provide audioFilePath or audioBuffer');
|
|
77
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Compute mean of `avg_logprob` across Whisper segments — Phase v4.1-3.
|
|
92
|
+
* Returns `undefined` when the field is absent (older response shapes,
|
|
93
|
+
* non-verbose_json fallback, or no segments at all). Callers only use
|
|
94
|
+
* this when the value is finite; preserve that invariant here.
|
|
95
|
+
*/
|
|
96
|
+
function meanAvgLogprob(payload) {
|
|
97
|
+
const segs = payload?.segments;
|
|
98
|
+
if (!Array.isArray(segs) || segs.length === 0)
|
|
99
|
+
return undefined;
|
|
100
|
+
let sum = 0;
|
|
101
|
+
let count = 0;
|
|
102
|
+
for (const s of segs) {
|
|
103
|
+
const v = s?.avg_logprob;
|
|
104
|
+
if (typeof v === 'number' && Number.isFinite(v)) {
|
|
105
|
+
sum += v;
|
|
106
|
+
count += 1;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (count === 0)
|
|
110
|
+
return undefined;
|
|
111
|
+
return sum / count;
|
|
112
|
+
}
|
|
78
113
|
// ── Provider 1 — Groq Whisper ─────────────────────────────────────────────────
|
|
79
114
|
async function transcribeGroq(audioPath, opts) {
|
|
80
115
|
const apiKey = process.env.GROQ_API_KEY;
|
|
@@ -88,15 +123,20 @@ async function transcribeGroq(audioPath, opts) {
|
|
|
88
123
|
form.append('model', 'whisper-large-v3');
|
|
89
124
|
if (opts.language)
|
|
90
125
|
form.append('language', opts.language);
|
|
91
|
-
|
|
126
|
+
// Phase v4.1-3 — verbose_json gives us segment-level `avg_logprob`
|
|
127
|
+
// for confidence scoring on the channel side. Groq mirrors OpenAI's
|
|
128
|
+
// Whisper response shape here.
|
|
129
|
+
form.append('response_format', 'verbose_json');
|
|
92
130
|
const res = await axios_1.default.post('https://api.groq.com/openai/v1/audio/transcriptions', form, {
|
|
93
131
|
headers: { ...form.getHeaders(), Authorization: `Bearer ${apiKey}` },
|
|
94
132
|
timeout,
|
|
95
133
|
});
|
|
134
|
+
const confidence = meanAvgLogprob(res.data);
|
|
96
135
|
return {
|
|
97
136
|
text: (res.data.text ?? '').trim(),
|
|
98
137
|
provider: 'groq',
|
|
99
138
|
durationMs: Date.now() - t0,
|
|
139
|
+
...(typeof confidence === 'number' ? { confidence } : {}),
|
|
100
140
|
};
|
|
101
141
|
}
|
|
102
142
|
// ── Provider 2 — OpenAI Whisper ───────────────────────────────────────────────
|
|
@@ -112,15 +152,18 @@ async function transcribeOpenAI(audioPath, opts) {
|
|
|
112
152
|
form.append('model', 'whisper-1');
|
|
113
153
|
if (opts.language)
|
|
114
154
|
form.append('language', opts.language);
|
|
115
|
-
|
|
155
|
+
// Phase v4.1-3 — same verbose_json switch as Groq for parity.
|
|
156
|
+
form.append('response_format', 'verbose_json');
|
|
116
157
|
const res = await axios_1.default.post('https://api.openai.com/v1/audio/transcriptions', form, {
|
|
117
158
|
headers: { ...form.getHeaders(), Authorization: `Bearer ${apiKey}` },
|
|
118
159
|
timeout,
|
|
119
160
|
});
|
|
161
|
+
const confidence = meanAvgLogprob(res.data);
|
|
120
162
|
return {
|
|
121
163
|
text: (res.data.text ?? '').trim(),
|
|
122
164
|
provider: 'openai',
|
|
123
165
|
durationMs: Date.now() - t0,
|
|
166
|
+
...(typeof confidence === 'number' ? { confidence } : {}),
|
|
124
167
|
};
|
|
125
168
|
}
|
|
126
169
|
// ── Provider 3 — Local Whisper.cpp ────────────────────────────────────────────
|
|
@@ -168,6 +211,7 @@ async function transcribe(options) {
|
|
|
168
211
|
const t0 = Date.now();
|
|
169
212
|
let tmpFile = '';
|
|
170
213
|
const errors = [];
|
|
214
|
+
const log = options.logger ?? (0, logger_1.noopLogger)();
|
|
171
215
|
try {
|
|
172
216
|
const audioPath = resolveAudioPath(options);
|
|
173
217
|
if (!options.audioFilePath && options.audioBuffer)
|
|
@@ -175,7 +219,11 @@ async function transcribe(options) {
|
|
|
175
219
|
// Provider 1 — Groq
|
|
176
220
|
try {
|
|
177
221
|
const r = await transcribeGroq(audioPath, options);
|
|
178
|
-
|
|
222
|
+
log.info(`groq whisper transcribed`, {
|
|
223
|
+
snippet: r.text.slice(0, 60),
|
|
224
|
+
durationMs: r.durationMs,
|
|
225
|
+
confidence: r.confidence,
|
|
226
|
+
});
|
|
179
227
|
return r;
|
|
180
228
|
}
|
|
181
229
|
catch (e) {
|
|
@@ -184,7 +232,11 @@ async function transcribe(options) {
|
|
|
184
232
|
// Provider 2 — OpenAI
|
|
185
233
|
try {
|
|
186
234
|
const r = await transcribeOpenAI(audioPath, options);
|
|
187
|
-
|
|
235
|
+
log.info(`openai whisper transcribed`, {
|
|
236
|
+
snippet: r.text.slice(0, 60),
|
|
237
|
+
durationMs: r.durationMs,
|
|
238
|
+
confidence: r.confidence,
|
|
239
|
+
});
|
|
188
240
|
return r;
|
|
189
241
|
}
|
|
190
242
|
catch (e) {
|
|
@@ -193,7 +245,10 @@ async function transcribe(options) {
|
|
|
193
245
|
// Provider 3 — Local Whisper.cpp
|
|
194
246
|
try {
|
|
195
247
|
const r = await transcribeLocal(audioPath, options);
|
|
196
|
-
|
|
248
|
+
log.info(`local whisper transcribed`, {
|
|
249
|
+
snippet: r.text.slice(0, 60),
|
|
250
|
+
durationMs: r.durationMs,
|
|
251
|
+
});
|
|
197
252
|
return r;
|
|
198
253
|
}
|
|
199
254
|
catch (e) {
|
|
@@ -201,7 +256,7 @@ async function transcribe(options) {
|
|
|
201
256
|
}
|
|
202
257
|
// All failed
|
|
203
258
|
const errorMsg = errors.join(' | ');
|
|
204
|
-
|
|
259
|
+
log.warn(`all providers failed`, { errors: errorMsg });
|
|
205
260
|
return { text: '', provider: 'none', durationMs: Date.now() - t0, error: errorMsg };
|
|
206
261
|
}
|
|
207
262
|
catch (outer) {
|
package/dist/core/voice/tts.js
CHANGED
|
@@ -136,15 +136,27 @@ async function synthesizeEdge(text, opts) {
|
|
|
136
136
|
const voice = opts.voice ?? DEFAULT_VOICE;
|
|
137
137
|
const audioPath = path_1.default.join(WORKSPACE, `tts_edge_${Date.now()}.mp3`);
|
|
138
138
|
const audioFwd = audioPath.replace(/\\/g, '/');
|
|
139
|
-
const escaped = text.replace(/"/g, '\\"').replace(/'/g, "\\'");
|
|
140
139
|
const timeout = opts.timeoutMs ?? 20000;
|
|
140
|
+
// Phase v4.1-voice-cli (Piece 0) — write the user text to a UTF-8
|
|
141
|
+
// file and have Python read it from there. The old inline-escape
|
|
142
|
+
// path (`text.replace(/"/g,'\\"').replace(/'/g,"\\'")`) was
|
|
143
|
+
// brittle for any text containing both quote styles plus
|
|
144
|
+
// backticks / `${...}` (which break the JS template literal that
|
|
145
|
+
// generates the Python script). Reading from a file removes ALL
|
|
146
|
+
// escaping concerns. JSON.stringify on the file paths + voice
|
|
147
|
+
// produces valid Python string literals (JSON ⊂ Python string syntax).
|
|
148
|
+
const textFile = path_1.default.join(WORKSPACE, `tts_edge_text_${Date.now()}.txt`);
|
|
149
|
+
fs_1.default.writeFileSync(textFile, text, 'utf-8');
|
|
150
|
+
const textFileFwd = textFile.replace(/\\/g, '/');
|
|
141
151
|
const script = `
|
|
142
152
|
import asyncio, sys
|
|
143
153
|
sys.stderr = open('nul', 'w')
|
|
144
154
|
import edge_tts
|
|
145
155
|
async def main():
|
|
146
|
-
|
|
147
|
-
|
|
156
|
+
with open(${JSON.stringify(textFileFwd)}, 'r', encoding='utf-8') as f:
|
|
157
|
+
txt = f.read()
|
|
158
|
+
communicate = edge_tts.Communicate(txt, ${JSON.stringify(voice)})
|
|
159
|
+
await communicate.save(${JSON.stringify(audioFwd)})
|
|
148
160
|
asyncio.run(main())
|
|
149
161
|
`.trim();
|
|
150
162
|
const tmpPy = path_1.default.join(WORKSPACE, `tts_edge_gen_${Date.now()}.py`);
|
|
@@ -170,6 +182,10 @@ asyncio.run(main())
|
|
|
170
182
|
fs_1.default.unlinkSync(tmpPy);
|
|
171
183
|
}
|
|
172
184
|
catch { /* ignore */ }
|
|
185
|
+
try {
|
|
186
|
+
fs_1.default.unlinkSync(textFile);
|
|
187
|
+
}
|
|
188
|
+
catch { /* ignore */ }
|
|
173
189
|
}
|
|
174
190
|
}
|
|
175
191
|
// ── Provider 2 — ElevenLabs ───────────────────────────────────────────────────
|
|
@@ -52,7 +52,7 @@ exports.DANGEROUS_PATTERNS = [
|
|
|
52
52
|
{ name: 'kill_all', regex: /\bkill\s+-9\s+-1\b/, tier: 'dangerous', description: 'kill all processes' },
|
|
53
53
|
{ name: 'pkill_force', regex: /\bpkill\s+-9\b/, tier: 'caution', description: 'force kill processes' },
|
|
54
54
|
{ name: 'systemctl_disable', regex: /\bsystemctl\s+(-[^\s]+\s+)*(stop|restart|disable|mask)\b/i, tier: 'caution', description: 'stop/restart system service' },
|
|
55
|
-
{ name: 'pkill_aiden', regex: /\b(pkill|killall)\b.*\b(aiden|gateway
|
|
55
|
+
{ name: 'pkill_aiden', regex: /\b(pkill|killall)\b.*\b(aiden|gateway)\b/i, tier: 'dangerous', description: 'kill aiden/gateway process (self-termination)' },
|
|
56
56
|
// ── Sensitive write targets ───────────────────────────────────
|
|
57
57
|
{ name: 'write_etc', regex: />\s*\/etc\//, tier: 'dangerous', description: 'overwrite system config' },
|
|
58
58
|
{ name: 'tee_etc', regex: /\btee\b.*\/etc\//, tier: 'dangerous', description: 'overwrite system file via tee' },
|
|
@@ -129,14 +129,19 @@ class CodexResponsesAdapter {
|
|
|
129
129
|
const body = {
|
|
130
130
|
model: this.model,
|
|
131
131
|
input: items,
|
|
132
|
-
tool_choice: 'auto',
|
|
133
|
-
parallel_tool_calls: true,
|
|
134
132
|
store: false,
|
|
135
133
|
};
|
|
136
134
|
if (instructions)
|
|
137
135
|
body.instructions = instructions;
|
|
136
|
+
// Phase v4.1.1-oauth-fix Phase 5: `tool_choice` and
|
|
137
|
+
// `parallel_tool_calls` are only meaningful when tools are present.
|
|
138
|
+
// OpenAI Codex returns HTTP 400 (empty body) for `tool_choice: 'auto'`
|
|
139
|
+
// without a `tools` field — surfaced by `aiden doctor --providers`'s
|
|
140
|
+
// no-tools liveness probe.
|
|
138
141
|
if (input.tools && input.tools.length > 0) {
|
|
139
142
|
body.tools = input.tools.map(toWireTool);
|
|
143
|
+
body.tool_choice = 'auto';
|
|
144
|
+
body.parallel_tool_calls = true;
|
|
140
145
|
}
|
|
141
146
|
if (typeof input.temperature === 'number') {
|
|
142
147
|
body.temperature = input.temperature;
|
|
@@ -16,9 +16,59 @@
|
|
|
16
16
|
*/
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
18
|
exports.ProviderRateLimitError = exports.ProviderTimeoutError = exports.ProviderError = void 0;
|
|
19
|
+
exports.formatRawForMessage = formatRawForMessage;
|
|
20
|
+
/**
|
|
21
|
+
* Format a raw response body for inclusion in the user-facing error
|
|
22
|
+
* message. Recognises the OpenAI / Anthropic JSON envelope shape
|
|
23
|
+
* (`{ error: { message: "..." } }`) and falls back to the raw string
|
|
24
|
+
* for plain-text bodies. Returns null when nothing useful is available
|
|
25
|
+
* so callers can omit the ": <detail>" tail entirely.
|
|
26
|
+
*
|
|
27
|
+
* Truncates to 300 chars to keep multi-line responses from blowing
|
|
28
|
+
* up the user's terminal — full body remains on `error.raw` for
|
|
29
|
+
* programmatic consumers / `aiden doctor --providers` deep mode.
|
|
30
|
+
*/
|
|
31
|
+
function formatRawForMessage(raw) {
|
|
32
|
+
if (raw === undefined || raw === null)
|
|
33
|
+
return null;
|
|
34
|
+
// OpenAI / Anthropic JSON envelope: { error: { message: "..." } }
|
|
35
|
+
if (typeof raw === 'object') {
|
|
36
|
+
const err = raw.error;
|
|
37
|
+
if (err && typeof err === 'object') {
|
|
38
|
+
const msg = err.message;
|
|
39
|
+
if (typeof msg === 'string' && msg.length > 0) {
|
|
40
|
+
return msg.length > 300 ? `${msg.slice(0, 300)}…` : msg;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Some providers put the message at the top level.
|
|
44
|
+
const topMsg = raw.message;
|
|
45
|
+
if (typeof topMsg === 'string' && topMsg.length > 0) {
|
|
46
|
+
return topMsg.length > 300 ? `${topMsg.slice(0, 300)}…` : topMsg;
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
// Plain string body.
|
|
51
|
+
if (typeof raw === 'string') {
|
|
52
|
+
const trimmed = raw.trim();
|
|
53
|
+
if (trimmed.length === 0)
|
|
54
|
+
return null;
|
|
55
|
+
return trimmed.length > 300 ? `${trimmed.slice(0, 300)}…` : trimmed;
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Compose the final `Error.message` from the short summary and (when
|
|
61
|
+
* available) the parsed/truncated raw response body. The body remains
|
|
62
|
+
* stashed on `ProviderError.raw` either way — this only enriches what
|
|
63
|
+
* users see when the error is rendered.
|
|
64
|
+
*/
|
|
65
|
+
function composeMessage(message, raw) {
|
|
66
|
+
const tail = formatRawForMessage(raw);
|
|
67
|
+
return tail ? `${message}: ${tail}` : message;
|
|
68
|
+
}
|
|
19
69
|
class ProviderError extends Error {
|
|
20
70
|
constructor(message, providerName, statusCode, raw, retryable = false) {
|
|
21
|
-
super(message);
|
|
71
|
+
super(composeMessage(message, raw));
|
|
22
72
|
this.providerName = providerName;
|
|
23
73
|
this.statusCode = statusCode;
|
|
24
74
|
this.raw = raw;
|
|
@@ -71,7 +71,12 @@ class OllamaPromptToolsAdapter {
|
|
|
71
71
|
const status = response.status;
|
|
72
72
|
const rawText = await this.safeReadText(response);
|
|
73
73
|
const retryable = status >= 500 || status === 429;
|
|
74
|
-
|
|
74
|
+
// Phase v4.1.1-oauth-fix Phase 5: short message only. The raw
|
|
75
|
+
// body flows via the .raw arg and composeMessage in errors.ts
|
|
76
|
+
// appends a truncated summary into the final .message — this
|
|
77
|
+
// file used to inline it, producing duplicated output in
|
|
78
|
+
// `aiden doctor --providers` and anywhere else err.message is logged.
|
|
79
|
+
const err = new errors_1.ProviderError(`Provider ${this.providerName} returned ${status}`, this.providerName, status, rawText, retryable);
|
|
75
80
|
if (!retryable || attempt >= totalAttempts)
|
|
76
81
|
throw err;
|
|
77
82
|
lastError = err;
|
|
@@ -149,7 +154,9 @@ class OllamaPromptToolsAdapter {
|
|
|
149
154
|
clearTimeout(timer);
|
|
150
155
|
const status = response.status;
|
|
151
156
|
const rawText = await this.safeReadText(response);
|
|
152
|
-
|
|
157
|
+
// Phase v4.1.1-oauth-fix Phase 5: composeMessage handles body
|
|
158
|
+
// rendering centrally; inlining it here would duplicate.
|
|
159
|
+
throw new errors_1.ProviderError(`Provider ${this.providerName} returned ${status}`, this.providerName, status, rawText, status >= 500);
|
|
153
160
|
}
|
|
154
161
|
if (!response.body) {
|
|
155
162
|
clearTimeout(timer);
|
package/dist/tools/v4/index.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* Status: PHASE 8.
|
|
22
22
|
*/
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
-
exports.memoryRemoveTool = exports.memoryReplaceTool = exports.memoryAddTool = exports.processWaitTool = exports.processKillTool = exports.processLogReadTool = exports.processListTool = exports.processSpawnTool = exports.executeCodeTool = exports.shellExecTool = exports.naturalEventsTool = exports.nowPlayingTool = exports.systemInfoTool = exports.makeLookupToolSchema = exports.skillManageTool = exports.skillViewTool = exports.skillsListTool = exports.sessionListTool = exports.sessionSearchTool = exports.browserCloseTool = exports.browserScrollTool = exports.browserFillTool = exports.browserTypeTool = exports.browserClickTool = exports.browserNavigateTool = exports.browserGetUrlTool = exports.browserExtractTool = exports.browserScreenshotTool = exports.fileCopyTool = exports.fileMoveTool = exports.fileDeleteTool = exports.filePatchTool = exports.fileWriteTool = exports.fileListTool = exports.fileReadTool = exports.deepResearchTool = exports.webPageTool = exports.webFetchTool = exports.webSearchTool = void 0;
|
|
24
|
+
exports.memoryRemoveTool = exports.memoryReplaceTool = exports.memoryAddTool = exports.processWaitTool = exports.processKillTool = exports.processLogReadTool = exports.processListTool = exports.processSpawnTool = exports.executeCodeTool = exports.shellExecTool = exports.naturalEventsTool = exports.nowPlayingTool = exports.systemInfoTool = exports.makeLookupToolSchema = exports.skillManageTool = exports.skillViewTool = exports.skillsListTool = exports.sessionListTool = exports.sessionSearchTool = exports.browserCloseTool = exports.browserScrollTool = exports.browserFillTool = exports.browserTypeTool = exports.browserClickTool = exports.browserNavigateTool = exports.browserGetUrlTool = exports.browserExtractTool = exports.browserScreenshotTool = exports.fileCopyTool = exports.fileMoveTool = exports.fileDeleteTool = exports.filePatchTool = exports.fileWriteTool = exports.fileListTool = exports.fileReadTool = exports.deepResearchTool = exports.webPageTool = exports.webFetchTool = exports.webSearchTool = exports.makeSubagentFanoutTool = void 0;
|
|
25
25
|
exports.registerReadOnlyTools = registerReadOnlyTools;
|
|
26
26
|
exports.registerWriteTools = registerWriteTools;
|
|
27
27
|
exports.registerAllTools = registerAllTools;
|
|
@@ -66,6 +66,7 @@ const processWait_1 = require("./process/processWait");
|
|
|
66
66
|
const memoryAdd_1 = require("./memory/memoryAdd");
|
|
67
67
|
const memoryReplace_1 = require("./memory/memoryReplace");
|
|
68
68
|
const memoryRemove_1 = require("./memory/memoryRemove");
|
|
69
|
+
const subagentFanout_1 = require("./subagent/subagentFanout");
|
|
69
70
|
/**
|
|
70
71
|
* Register every read-only tool into `registry`. The
|
|
71
72
|
* `lookup_tool_schema` tool needs a registry reference, so it's
|
|
@@ -99,6 +100,34 @@ function registerReadOnlyTools(registry) {
|
|
|
99
100
|
registry.register(nowPlaying_1.nowPlayingTool);
|
|
100
101
|
registry.register(naturalEvents_1.naturalEventsTool);
|
|
101
102
|
registry.register((0, lookupToolSchema_1.makeLookupToolSchema)(registry));
|
|
103
|
+
// Phase v4.1-subagent — register a stub for subagent_fanout so its
|
|
104
|
+
// schema is visible to the agent loop, the MCP server, and the
|
|
105
|
+
// /tools slash command BEFORE the runtime resolves provider /
|
|
106
|
+
// adapter / agent dependencies. The full runtime calls
|
|
107
|
+
// `registry.register(makeSubagentFanoutTool({...real opts}))` to
|
|
108
|
+
// replace this stub once `buildAgentRuntime` has those handles.
|
|
109
|
+
// Until then, calling the stub returns a clear "not wired" error
|
|
110
|
+
// rather than crashing.
|
|
111
|
+
registry.register(makeSubagentFanoutStub());
|
|
112
|
+
}
|
|
113
|
+
/** Stub used until the runtime wires real provider / adapter / agent
|
|
114
|
+
* dependencies. Returns the SAME schema as the real tool so MCP and
|
|
115
|
+
* /tools see a consistent surface. */
|
|
116
|
+
function makeSubagentFanoutStub() {
|
|
117
|
+
return (0, subagentFanout_1.makeSubagentFanoutTool)({
|
|
118
|
+
resolveProviders: () => [],
|
|
119
|
+
resolveActiveModel: () => ({ providerId: 'unset', modelId: 'unset' }),
|
|
120
|
+
aggregatorAdapter: {
|
|
121
|
+
apiMode: 'chat_completions',
|
|
122
|
+
async call() {
|
|
123
|
+
throw new Error('subagent_fanout: tool not wired — runtime did not replace the stub. ' +
|
|
124
|
+
'Call registry.register(makeSubagentFanoutTool({...})) after buildAgentRuntime.');
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
runChild: async () => {
|
|
128
|
+
throw new Error('subagent_fanout: tool not wired — runtime did not replace the stub.');
|
|
129
|
+
},
|
|
130
|
+
});
|
|
102
131
|
}
|
|
103
132
|
/**
|
|
104
133
|
* Register every Phase 8 write/execute tool. Phase 9 will gate
|
|
@@ -138,6 +167,8 @@ function registerAllTools(registry) {
|
|
|
138
167
|
registerReadOnlyTools(registry);
|
|
139
168
|
registerWriteTools(registry);
|
|
140
169
|
}
|
|
170
|
+
var subagentFanout_2 = require("./subagent/subagentFanout");
|
|
171
|
+
Object.defineProperty(exports, "makeSubagentFanoutTool", { enumerable: true, get: function () { return subagentFanout_2.makeSubagentFanoutTool; } });
|
|
141
172
|
var webSearch_2 = require("./web/webSearch");
|
|
142
173
|
Object.defineProperty(exports, "webSearchTool", { enumerable: true, get: function () { return webSearch_2.webSearchTool; } });
|
|
143
174
|
var webFetch_2 = require("./web/webFetch");
|
|
@@ -0,0 +1,190 @@
|
|
|
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/subagent/subagentFanout.ts — `subagent_fanout` wrapper.
|
|
10
|
+
*
|
|
11
|
+
* Phase v4.1-subagent. Spawns N parallel agent instances against the
|
|
12
|
+
* same problem (or a partition of it), then merges results via the
|
|
13
|
+
* chosen strategy. The orchestrator lives at
|
|
14
|
+
* `core/v4/subagent/fanout.ts`; this file is the agent-callable
|
|
15
|
+
* adapter that:
|
|
16
|
+
*
|
|
17
|
+
* 1. Validates the LLM's call args against the schema.
|
|
18
|
+
* 2. Builds a per-child runner (closure over the parent runtime)
|
|
19
|
+
* that wraps an AidenAgent run.
|
|
20
|
+
* 3. Filters mutating tools out of each child's schema array
|
|
21
|
+
* unless `AIDEN_SUBAGENT_ALLOW_DESTRUCTIVE=1`.
|
|
22
|
+
* 4. Returns the merged output + raw N results + diagnostics.
|
|
23
|
+
*
|
|
24
|
+
* The factory pattern (`makeSubagentFanoutTool`) mirrors
|
|
25
|
+
* `lookup_tool_schema` — the runtime constructs it with a closure
|
|
26
|
+
* over registry / providers / paths that the schema can't carry.
|
|
27
|
+
*
|
|
28
|
+
* Tool category is `network` not `write` — the tool itself doesn't
|
|
29
|
+
* touch disk; it only spends LLM tokens. That keeps it default-
|
|
30
|
+
* exposed in MCP under the read-only env (mutates: false).
|
|
31
|
+
*
|
|
32
|
+
* The description bakes a hard-learned lesson from prior multi-agent
|
|
33
|
+
* systems: "Self-reports are not verified facts" — the parent must
|
|
34
|
+
* verify any side-effects children report rather than trust the
|
|
35
|
+
* summary. Children's tool calls are executed in isolated contexts;
|
|
36
|
+
* a child claiming "wrote file X" or "ran command Y" must be
|
|
37
|
+
* verified by the parent before the parent acts on that claim.
|
|
38
|
+
*/
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.makeSubagentFanoutTool = makeSubagentFanoutTool;
|
|
41
|
+
const factory_1 = require("../../../core/v4/logger/factory");
|
|
42
|
+
const fanout_1 = require("../../../core/v4/subagent/fanout");
|
|
43
|
+
const merger_1 = require("../../../core/v4/subagent/merger");
|
|
44
|
+
const SCHEMA_DESC = 'Spawn N parallel agent children against the same problem (ensemble) or a partitioned task list, ' +
|
|
45
|
+
'then merge results via the chosen strategy. Use this for multi-perspective research, ' +
|
|
46
|
+
'provider-diverse fact-checking, or analyzing N independent inputs in parallel. ' +
|
|
47
|
+
'IMPORTANT: self-reports from children are not verified facts — if a child claims it ' +
|
|
48
|
+
'wrote a file, ran a command, or completed a side-effect, you (the parent) MUST verify ' +
|
|
49
|
+
'independently before trusting that claim.';
|
|
50
|
+
function makeSubagentFanoutTool(factory) {
|
|
51
|
+
return {
|
|
52
|
+
schema: {
|
|
53
|
+
name: 'subagent_fanout',
|
|
54
|
+
description: SCHEMA_DESC,
|
|
55
|
+
inputSchema: {
|
|
56
|
+
type: 'object',
|
|
57
|
+
properties: {
|
|
58
|
+
mode: {
|
|
59
|
+
type: 'string',
|
|
60
|
+
description: "'partition' = each child gets a different goal from `tasks`. " +
|
|
61
|
+
"'ensemble' = every child gets the same `query`.",
|
|
62
|
+
enum: ['partition', 'ensemble'],
|
|
63
|
+
},
|
|
64
|
+
n: {
|
|
65
|
+
type: 'number',
|
|
66
|
+
description: 'Number of children to spawn. Default 3, hard cap 5. ' +
|
|
67
|
+
'Higher N hits provider RPM limits and inflates tail latency.',
|
|
68
|
+
},
|
|
69
|
+
query: {
|
|
70
|
+
type: 'string',
|
|
71
|
+
description: 'Same input given to every child (ensemble mode only).',
|
|
72
|
+
},
|
|
73
|
+
tasks: {
|
|
74
|
+
type: 'array',
|
|
75
|
+
description: 'Per-child task list (partition mode only). Length must equal n.',
|
|
76
|
+
// Schema mirrors PartitionTask interface in
|
|
77
|
+
// core/v4/subagent/fanout.ts:70-75. If you change one, change
|
|
78
|
+
// the other. OpenAI Codex backend strictly validates schemas
|
|
79
|
+
// and rejects `type: "array"` declarations missing `items`,
|
|
80
|
+
// so the inner shape must be explicit here.
|
|
81
|
+
items: {
|
|
82
|
+
type: 'object',
|
|
83
|
+
description: 'One unit of work for a partition-mode child.',
|
|
84
|
+
properties: {
|
|
85
|
+
goal: {
|
|
86
|
+
type: 'string',
|
|
87
|
+
description: 'The task this child should accomplish.',
|
|
88
|
+
},
|
|
89
|
+
context: {
|
|
90
|
+
type: 'string',
|
|
91
|
+
description: 'Optional shared context for the child.',
|
|
92
|
+
},
|
|
93
|
+
role: {
|
|
94
|
+
type: 'string',
|
|
95
|
+
description: 'Optional role tag, diagnostic only.',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
required: ['goal'],
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
merge: {
|
|
102
|
+
type: 'string',
|
|
103
|
+
description: "'all' = return raw N results, no aggregator (FREE). " +
|
|
104
|
+
"'vote' = LLM judge picks one verbatim (+1 call). " +
|
|
105
|
+
"'pick-best' = LLM judge picks one with reasoning (+1 call). " +
|
|
106
|
+
"'combine' = LLM synthesizes one unified answer (+1 call).",
|
|
107
|
+
enum: ['all', 'vote', 'pick-best', 'combine'],
|
|
108
|
+
},
|
|
109
|
+
timeoutMs: {
|
|
110
|
+
type: 'number',
|
|
111
|
+
description: 'Per-child wall-clock timeout (ms). Default 90000. ' +
|
|
112
|
+
'Outer wall-clock cap is 5x this value.',
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
required: ['mode'],
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
// mutates: false because the tool itself only spends tokens — disk /
|
|
119
|
+
// process side-effects, if any, happen INSIDE child agents whose
|
|
120
|
+
// toolsets are filtered to read-only by default. This keeps the
|
|
121
|
+
// tool default-exposed in MCP under the read-only env.
|
|
122
|
+
category: 'network',
|
|
123
|
+
mutates: false,
|
|
124
|
+
toolset: 'subagent',
|
|
125
|
+
async execute(args, _ctx) {
|
|
126
|
+
const logger = factory.logger ?? (0, factory_1.noopLogger)();
|
|
127
|
+
// ── Coerce args ────────────────────────────────────────────
|
|
128
|
+
const mode = (args.mode === 'partition' || args.mode === 'ensemble')
|
|
129
|
+
? args.mode
|
|
130
|
+
: null;
|
|
131
|
+
if (!mode) {
|
|
132
|
+
return {
|
|
133
|
+
success: false,
|
|
134
|
+
error: "subagent_fanout: 'mode' must be 'partition' or 'ensemble'",
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
const n = typeof args.n === 'number' && Number.isInteger(args.n)
|
|
138
|
+
? args.n
|
|
139
|
+
: 3;
|
|
140
|
+
const merge = (args.merge === 'all' || args.merge === 'vote'
|
|
141
|
+
|| args.merge === 'pick-best' || args.merge === 'combine')
|
|
142
|
+
? args.merge
|
|
143
|
+
: 'combine';
|
|
144
|
+
const query = typeof args.query === 'string' ? args.query : undefined;
|
|
145
|
+
const tasks = Array.isArray(args.tasks)
|
|
146
|
+
? args.tasks
|
|
147
|
+
: undefined;
|
|
148
|
+
const timeoutMs = typeof args.timeoutMs === 'number'
|
|
149
|
+
&& args.timeoutMs > 0
|
|
150
|
+
? args.timeoutMs
|
|
151
|
+
: undefined;
|
|
152
|
+
// ── Resolve providers + aggregator at call time ───────────
|
|
153
|
+
const providers = factory.resolveProviders();
|
|
154
|
+
if (providers.length === 0) {
|
|
155
|
+
return {
|
|
156
|
+
success: false,
|
|
157
|
+
error: 'subagent_fanout: no providers configured — run `aiden setup` first',
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const aggOverride = (0, merger_1.resolveAggregatorOverride)();
|
|
161
|
+
const aggregatorModel = aggOverride ?? factory.resolveActiveModel();
|
|
162
|
+
const fanoutOpts = {
|
|
163
|
+
mode,
|
|
164
|
+
query,
|
|
165
|
+
tasks,
|
|
166
|
+
n,
|
|
167
|
+
merge,
|
|
168
|
+
providers,
|
|
169
|
+
runChild: factory.runChild,
|
|
170
|
+
aggregatorAdapter: factory.aggregatorAdapter,
|
|
171
|
+
aggregatorModel,
|
|
172
|
+
timeoutMs,
|
|
173
|
+
logger,
|
|
174
|
+
};
|
|
175
|
+
try {
|
|
176
|
+
const result = await (0, fanout_1.runFanout)(fanoutOpts);
|
|
177
|
+
return {
|
|
178
|
+
success: true,
|
|
179
|
+
merged: result.merged,
|
|
180
|
+
results: result.results,
|
|
181
|
+
diagnostics: result.diagnostics,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
186
|
+
return { success: false, error: message };
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aiden-runtime",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -224,7 +224,7 @@
|
|
|
224
224
|
"dependencies": {
|
|
225
225
|
"@inquirer/core": "^11.1.9",
|
|
226
226
|
"@inquirer/prompts": "^8.4.2",
|
|
227
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
227
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
228
228
|
"@slack/bolt": "^4.6.0",
|
|
229
229
|
"@slack/web-api": "^7.15.0",
|
|
230
230
|
"@types/archiver": "^7.0.0",
|
|
@@ -232,6 +232,7 @@
|
|
|
232
232
|
"@types/sql.js": "^1.4.9",
|
|
233
233
|
"@types/twilio": "^3.19.2",
|
|
234
234
|
"@types/ws": "^8.18.1",
|
|
235
|
+
"ansi-escapes": "^4.3.2",
|
|
235
236
|
"archiver": "^7.0.1",
|
|
236
237
|
"axios": "^1.15.2",
|
|
237
238
|
"bcrypt": "^6.0.0",
|
|
@@ -256,15 +257,18 @@
|
|
|
256
257
|
"marked": "^15.0.12",
|
|
257
258
|
"marked-terminal": "^7.3.0",
|
|
258
259
|
"multer": "^2.1.1",
|
|
260
|
+
"node-telegram-bot-api": "0.67.0",
|
|
259
261
|
"nodemailer": "^8.0.3",
|
|
260
262
|
"open": "^11.0.0",
|
|
261
263
|
"ora": "^9.3.0",
|
|
262
264
|
"pdf-parse": "^1.1.1",
|
|
263
265
|
"playwright": "^1.58.2",
|
|
266
|
+
"proper-lockfile": "^4.1.2",
|
|
264
267
|
"puppeteer": "^24.39.1",
|
|
265
268
|
"qrcode-terminal": "^0.12.0",
|
|
266
269
|
"screenshot-desktop": "^1.15.3",
|
|
267
270
|
"sql.js": "^1.14.1",
|
|
271
|
+
"string-width": "^4.2.3",
|
|
268
272
|
"stripe": "^20.4.1",
|
|
269
273
|
"tar-stream": "^3.1.8",
|
|
270
274
|
"twilio": "^5.13.1",
|
|
@@ -272,6 +276,10 @@
|
|
|
272
276
|
"whatsapp-web.js": "^1.26.0",
|
|
273
277
|
"ws": "^8.20.0"
|
|
274
278
|
},
|
|
279
|
+
"optionalDependencies": {
|
|
280
|
+
"decibri": "*",
|
|
281
|
+
"node-record-lpcm16": "*"
|
|
282
|
+
},
|
|
275
283
|
"overrides": {
|
|
276
284
|
"basic-ftp": "^5.3.1",
|
|
277
285
|
"ip-address": "^10.1.1",
|
|
@@ -287,6 +295,7 @@
|
|
|
287
295
|
"@types/js-yaml": "^4.0.9",
|
|
288
296
|
"@types/multer": "^2.0.0",
|
|
289
297
|
"@types/node": "^25.3.0",
|
|
298
|
+
"@types/node-telegram-bot-api": "0.64.14",
|
|
290
299
|
"@types/nodemailer": "^7.0.11",
|
|
291
300
|
"@types/pdf-parse": "^1.1.4",
|
|
292
301
|
"@types/qrcode-terminal": "^0.12.2",
|