aiden-runtime 4.8.1 → 4.9.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 +88 -1
- package/dist/cli/v4/aidenCLI.js +37 -6
- package/dist/cli/v4/chatSession.js +53 -13
- package/dist/cli/v4/commands/daemon.js +53 -3
- package/dist/cli/v4/commands/daemonDoctor.js +212 -0
- package/dist/cli/v4/commands/daemonStatus.js +45 -26
- package/dist/cli/v4/commands/help.js +5 -0
- package/dist/cli/v4/commands/hooks.js +466 -0
- package/dist/cli/v4/commands/hooksSlash.js +33 -0
- package/dist/cli/v4/commands/index.js +13 -1
- package/dist/cli/v4/commands/mcp.js +89 -1
- package/dist/cli/v4/commands/mcpClientInstall.js +359 -0
- package/dist/cli/v4/commands/memory.js +707 -0
- package/dist/cli/v4/commands/memorySlash.js +38 -0
- package/dist/cli/v4/commands/recovery.js +1 -1
- package/dist/cli/v4/commands/skin.js +7 -0
- package/dist/cli/v4/commands/theme.js +217 -0
- package/dist/cli/v4/commands/trigger.js +1 -1
- package/dist/cli/v4/design/tokens.js +52 -4
- package/dist/cli/v4/display.js +39 -26
- package/dist/cli/v4/replyRenderer.js +6 -5
- package/dist/cli/v4/skinEngine.js +67 -0
- package/dist/cli/v4/ui/progressBar.js +179 -0
- package/dist/cli/v4/util/closestAction.js +48 -0
- package/dist/core/v4/aidenAgent.js +45 -2
- package/dist/core/v4/daemon/api/runs.js +131 -0
- package/dist/core/v4/daemon/bootstrap.js +368 -13
- package/dist/core/v4/daemon/db/migrations.js +169 -0
- package/dist/core/v4/daemon/idempotency/runIdempotencyStore.js +128 -0
- package/dist/core/v4/daemon/incarnationStore.js +47 -0
- package/dist/core/v4/daemon/runs/attemptStore.js +67 -0
- package/dist/core/v4/daemon/runs/reclaim.js +88 -0
- package/dist/core/v4/daemon/runs/retryPolicy.js +73 -0
- package/dist/core/v4/daemon/runs/runWithRetry.js +80 -0
- package/dist/core/v4/daemon/runs/stuckAttemptWatchdog.js +72 -0
- package/dist/core/v4/daemon/spans/spanHelpers.js +272 -0
- package/dist/core/v4/daemon/spans/spanStore.js +113 -0
- package/dist/core/v4/daemon/triggerBus.js +50 -19
- package/dist/core/v4/hooks/auditQuery.js +67 -0
- package/dist/core/v4/hooks/dispatcher.js +286 -0
- package/dist/core/v4/hooks/index.js +46 -0
- package/dist/core/v4/hooks/lifecycle.js +27 -0
- package/dist/core/v4/hooks/manifest.js +142 -0
- package/dist/core/v4/hooks/registry.js +149 -0
- package/dist/core/v4/hooks/runtime/subprocessRunner.js +188 -0
- package/dist/core/v4/hooks/toolHookGate.js +76 -0
- package/dist/core/v4/hooks/trust.js +14 -0
- package/dist/core/v4/identity/contextManager.js +83 -0
- package/dist/core/v4/identity/daemonId.js +85 -0
- package/dist/core/v4/identity/enforcement.js +103 -0
- package/dist/core/v4/identity/executionContext.js +153 -0
- package/dist/core/v4/identity/hookExecution.js +62 -0
- package/dist/core/v4/identity/httpContext.js +68 -0
- package/dist/core/v4/identity/ids.js +185 -0
- package/dist/core/v4/identity/index.js +60 -0
- package/dist/core/v4/identity/subprocessContext.js +98 -0
- package/dist/core/v4/identity/traceparent.js +114 -0
- package/dist/core/v4/logger/index.js +3 -1
- package/dist/core/v4/logger/logger.js +28 -1
- package/dist/core/v4/logger/redact.js +149 -0
- package/dist/core/v4/logger/sinks/fileSink.js +13 -0
- package/dist/core/v4/logger/sinks/stdSink.js +19 -1
- package/dist/core/v4/mcp/install/backup.js +78 -0
- package/dist/core/v4/mcp/install/clientPaths.js +90 -0
- package/dist/core/v4/mcp/install/clients.js +203 -0
- package/dist/core/v4/mcp/install/healthCheck.js +83 -0
- package/dist/core/v4/mcp/install/jsoncMerge.js +174 -0
- package/dist/core/v4/mcp/install/profiles.js +109 -0
- package/dist/core/v4/mcp/install/wslDetect.js +62 -0
- package/dist/core/v4/memory/namespaceRegistry.js +117 -0
- package/dist/core/v4/memory/projectRoot.js +76 -0
- package/dist/core/v4/memory/reviewer/index.js +162 -0
- package/dist/core/v4/memory/reviewer/pendingStore.js +136 -0
- package/dist/core/v4/memory/reviewer/prompt.js +105 -0
- package/dist/core/v4/memory/reviewer/skipRules.js +92 -0
- package/dist/core/v4/memoryManager.js +57 -10
- package/dist/core/v4/paths.js +2 -0
- package/dist/core/v4/subagent/spawnSubAgent.js +20 -7
- package/dist/core/v4/theme/bundledThemes.js +106 -0
- package/dist/core/v4/theme/themeLoader.js +160 -0
- package/dist/core/v4/theme/themeRegistry.js +97 -0
- package/dist/core/v4/theme/themeWatcher.js +95 -0
- package/dist/core/v4/toolRegistry.js +71 -8
- package/dist/core/v4/update/depWarningFilter.js +76 -0
- package/dist/core/v4/update/executeInstall.js +41 -35
- package/dist/core/v4/update/platformInstructions.js +128 -0
- package/dist/moat/approvalEngine.js +4 -0
- package/dist/moat/memoryGuard.js +8 -1
- package/dist/providers/v4/anthropicAdapter.js +10 -4
- package/dist/tools/v4/backends/local.js +19 -2
- package/dist/tools/v4/sessions/recallSession.js +6 -1
- package/package.json +3 -1
- package/plugins/aiden-plugin-chatgpt-plus/index.js +10 -1
- package/themes/default.yaml +52 -0
- package/themes/dracula.yaml +32 -0
- package/themes/light.yaml +32 -0
- package/themes/monochrome.yaml +31 -0
- package/themes/tokyo-night.yaml +32 -0
- package/dist/core/pluginSystem.js +0 -121
- package/dist/tools/v4/ui/_uiSmokeTool.js +0 -60
|
@@ -0,0 +1,179 @@
|
|
|
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
|
+
* cli/v4/ui/progressBar.ts — v4.9.1 reusable progress animation.
|
|
10
|
+
* Auto-detects TTY / NO_COLOR / TERM=dumb / CI to pick render mode
|
|
11
|
+
* (block glyphs vs `#-`, color vs plain, animated vs once-per-second
|
|
12
|
+
* non-TTY lines). Cursor hidden during animation, restored on exit.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.detectRenderMode = detectRenderMode;
|
|
16
|
+
exports.renderLine = renderLine;
|
|
17
|
+
exports.startProgressBar = startProgressBar;
|
|
18
|
+
exports.npmInstallPhasePercent = npmInstallPhasePercent;
|
|
19
|
+
exports.detectNpmPhase = detectNpmPhase;
|
|
20
|
+
const DEFAULT_WIDTH = 28;
|
|
21
|
+
const DEFAULT_TICK_MS = 100;
|
|
22
|
+
/** Minimum elapsed before we paint anything — avoids flicker on sub-300ms ops. */
|
|
23
|
+
const PAINT_AFTER_MS = 300;
|
|
24
|
+
const ANSI_HIDE_CURSOR = '\x1b[?25l';
|
|
25
|
+
const ANSI_SHOW_CURSOR = '\x1b[?25h';
|
|
26
|
+
const ANSI_CLEAR_LINE = '\r\x1b[2K';
|
|
27
|
+
const ANSI_BRAND = '\x1b[38;2;255;107;53m'; // RGB 255,107,53 (Aiden orange)
|
|
28
|
+
const ANSI_MUTED = '\x1b[38;2;106;106;106m';
|
|
29
|
+
const ANSI_SUCCESS = '\x1b[38;2;127;194;139m';
|
|
30
|
+
const ANSI_ERROR = '\x1b[38;2;224;90;90m';
|
|
31
|
+
const ANSI_RESET = '\x1b[0m';
|
|
32
|
+
/** Detect the right render mode from TTY + env. Pure function. */
|
|
33
|
+
function detectRenderMode(isTTY, env = process.env) {
|
|
34
|
+
if (!isTTY)
|
|
35
|
+
return { color: false, blocks: false, animated: false };
|
|
36
|
+
const noColor = env.NO_COLOR !== undefined && env.NO_COLOR !== '';
|
|
37
|
+
const dumb = env.TERM === 'dumb' || env.CI === 'true' || env.CI === '1';
|
|
38
|
+
return {
|
|
39
|
+
color: !noColor && !dumb,
|
|
40
|
+
blocks: !dumb,
|
|
41
|
+
animated: true,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Build the rendered line (without trailing newline). Pure so tests
|
|
46
|
+
* can assert byte-for-byte without timing.
|
|
47
|
+
*/
|
|
48
|
+
function renderLine(opts) {
|
|
49
|
+
const pct = Math.max(0, Math.min(100, Math.round(opts.percent)));
|
|
50
|
+
const filled = Math.round((pct / 100) * opts.width);
|
|
51
|
+
const empty = opts.width - filled;
|
|
52
|
+
const full = opts.mode.blocks ? '█' : '#';
|
|
53
|
+
const blank = opts.mode.blocks ? '░' : '-';
|
|
54
|
+
const elapsed = `${(opts.elapsedMs / 1000).toFixed(1)}s`;
|
|
55
|
+
const bar = full.repeat(filled) + blank.repeat(empty);
|
|
56
|
+
if (opts.mode.color) {
|
|
57
|
+
return `${ANSI_BRAND}[${bar}]${ANSI_RESET} ${pct}% ${ANSI_MUTED}${opts.phase}${ANSI_RESET} ${ANSI_MUTED}${elapsed}${ANSI_RESET}`;
|
|
58
|
+
}
|
|
59
|
+
return `[${bar}] ${pct}% ${opts.phase} ${elapsed}`;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Start a progress bar. Returns a controller object. Never throws —
|
|
63
|
+
* any I/O failure on output degrades the bar to a silent no-op while
|
|
64
|
+
* still honoring `complete` / `fail` semantics for the caller.
|
|
65
|
+
*/
|
|
66
|
+
function startProgressBar(opts) {
|
|
67
|
+
const width = opts.width ?? DEFAULT_WIDTH;
|
|
68
|
+
const out = opts.out ?? process.stdout;
|
|
69
|
+
const env = opts.env ?? process.env;
|
|
70
|
+
const tickMs = opts.tickMs ?? DEFAULT_TICK_MS;
|
|
71
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
72
|
+
const isTTY = opts.isTTY ?? Boolean(out.isTTY);
|
|
73
|
+
const mode = detectRenderMode(isTTY, env);
|
|
74
|
+
const startedAt = Date.now();
|
|
75
|
+
let phase = opts.phases[0] ?? '';
|
|
76
|
+
let percent = 0;
|
|
77
|
+
let painted = false;
|
|
78
|
+
let closed = false;
|
|
79
|
+
const write = (s) => {
|
|
80
|
+
try {
|
|
81
|
+
out.write(s);
|
|
82
|
+
}
|
|
83
|
+
catch { /* swallow — never break caller */ }
|
|
84
|
+
};
|
|
85
|
+
// SIGINT: restore cursor + clear the partial line before bubbling.
|
|
86
|
+
const onSigint = () => {
|
|
87
|
+
try {
|
|
88
|
+
write(ANSI_CLEAR_LINE + ANSI_SHOW_CURSOR);
|
|
89
|
+
}
|
|
90
|
+
catch { /* noop */ }
|
|
91
|
+
};
|
|
92
|
+
if (mode.animated) {
|
|
93
|
+
try {
|
|
94
|
+
process.once('SIGINT', onSigint);
|
|
95
|
+
}
|
|
96
|
+
catch { /* noop */ }
|
|
97
|
+
}
|
|
98
|
+
// Label line paints once, immediately.
|
|
99
|
+
write(`${mode.color ? ANSI_MUTED : ''}${opts.label}${mode.color ? ANSI_RESET : ''}\n`);
|
|
100
|
+
const paint = () => {
|
|
101
|
+
if (closed)
|
|
102
|
+
return;
|
|
103
|
+
const elapsedMs = Date.now() - startedAt;
|
|
104
|
+
if (elapsedMs < PAINT_AFTER_MS)
|
|
105
|
+
return;
|
|
106
|
+
const line = renderLine({ width, percent, phase, elapsedMs, mode });
|
|
107
|
+
if (mode.animated) {
|
|
108
|
+
if (!painted) {
|
|
109
|
+
write(ANSI_HIDE_CURSOR);
|
|
110
|
+
painted = true;
|
|
111
|
+
}
|
|
112
|
+
write(ANSI_CLEAR_LINE + line);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
write(line + '\n');
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
let timer = null;
|
|
119
|
+
if (mode.animated) {
|
|
120
|
+
timer = setInterval(paint, tickMs);
|
|
121
|
+
if (typeof timer.unref === 'function')
|
|
122
|
+
timer.unref();
|
|
123
|
+
}
|
|
124
|
+
const close = (icon, color, message) => {
|
|
125
|
+
if (closed)
|
|
126
|
+
return;
|
|
127
|
+
closed = true;
|
|
128
|
+
if (timer)
|
|
129
|
+
clearInterval(timer);
|
|
130
|
+
try {
|
|
131
|
+
process.removeListener('SIGINT', onSigint);
|
|
132
|
+
}
|
|
133
|
+
catch { /* noop */ }
|
|
134
|
+
const finalLine = mode.color
|
|
135
|
+
? `${color}${icon}${ANSI_RESET} ${message}`
|
|
136
|
+
: `${icon} ${message}`;
|
|
137
|
+
if (mode.animated && painted)
|
|
138
|
+
write(ANSI_CLEAR_LINE);
|
|
139
|
+
write(finalLine + '\n');
|
|
140
|
+
if (mode.animated)
|
|
141
|
+
write(ANSI_SHOW_CURSOR);
|
|
142
|
+
};
|
|
143
|
+
return {
|
|
144
|
+
setPhase(name) { phase = name; if (!mode.animated)
|
|
145
|
+
paint(); },
|
|
146
|
+
setPercent(p) { percent = p; if (!mode.animated)
|
|
147
|
+
paint(); },
|
|
148
|
+
complete(message) { close('✓', ANSI_SUCCESS, message); },
|
|
149
|
+
fail(message) { close('✗', ANSI_ERROR, message); },
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/** npm install phase → default percent. Best-effort bar shaping. */
|
|
153
|
+
function npmInstallPhasePercent(phase) {
|
|
154
|
+
switch (phase) {
|
|
155
|
+
case 'spawning': return 3;
|
|
156
|
+
case 'resolving': return 15;
|
|
157
|
+
case 'downloading': return 50;
|
|
158
|
+
case 'extracting': return 85;
|
|
159
|
+
case 'verifying': return 97;
|
|
160
|
+
case 'installed': return 100;
|
|
161
|
+
case 'failed': return 100;
|
|
162
|
+
default: return 0;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/** Detect npm phase from a stdout/stderr line. Checks ordered for npm 9/10/11. */
|
|
166
|
+
function detectNpmPhase(line) {
|
|
167
|
+
const l = line.toLowerCase();
|
|
168
|
+
if (l.includes('added ') && l.includes('package'))
|
|
169
|
+
return 'verifying';
|
|
170
|
+
if (l.includes('http fetch'))
|
|
171
|
+
return 'downloading';
|
|
172
|
+
if (l.includes('extracting') || l.includes('extract'))
|
|
173
|
+
return 'extracting';
|
|
174
|
+
if (l.includes('reify:'))
|
|
175
|
+
return 'downloading';
|
|
176
|
+
if (l.includes('resolved') || l.includes('audit'))
|
|
177
|
+
return 'resolving';
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.closestAction = closestAction;
|
|
4
|
+
/**
|
|
5
|
+
* Copyright (c) 2026 Shiva Deore (Taracod).
|
|
6
|
+
* Licensed under AGPL-3.0. See LICENSE for details.
|
|
7
|
+
*
|
|
8
|
+
* Aiden — local-first agent.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* cli/v4/util/closestAction.ts — v4.9.1 amendment.
|
|
12
|
+
* Suggest the closest known action when the user mis-types a subcommand.
|
|
13
|
+
* Matches if input is a substring of a known action OR Levenshtein
|
|
14
|
+
* distance ≤ 2. Returns null when nothing is reasonably close.
|
|
15
|
+
*/
|
|
16
|
+
function lev(a, b) {
|
|
17
|
+
const m = a.length, n = b.length;
|
|
18
|
+
if (m === 0)
|
|
19
|
+
return n;
|
|
20
|
+
if (n === 0)
|
|
21
|
+
return m;
|
|
22
|
+
const row = Array.from({ length: n + 1 }, (_, i) => i);
|
|
23
|
+
for (let i = 1; i <= m; i++) {
|
|
24
|
+
let prev = i - 1;
|
|
25
|
+
row[0] = i;
|
|
26
|
+
for (let j = 1; j <= n; j++) {
|
|
27
|
+
const cur = row[j];
|
|
28
|
+
row[j] = a[i - 1] === b[j - 1] ? prev : Math.min(prev, row[j - 1], row[j]) + 1;
|
|
29
|
+
prev = cur;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return row[n];
|
|
33
|
+
}
|
|
34
|
+
function closestAction(input, known) {
|
|
35
|
+
if (!input)
|
|
36
|
+
return null;
|
|
37
|
+
const lo = input.toLowerCase();
|
|
38
|
+
let best = null;
|
|
39
|
+
for (const k of known) {
|
|
40
|
+
const kl = k.toLowerCase();
|
|
41
|
+
if (kl.includes(lo) || lo.includes(kl))
|
|
42
|
+
return k;
|
|
43
|
+
const d = lev(lo, kl);
|
|
44
|
+
if (d <= 2 && (!best || d < best.d))
|
|
45
|
+
best = { name: k, d };
|
|
46
|
+
}
|
|
47
|
+
return best?.name ?? null;
|
|
48
|
+
}
|
|
@@ -698,7 +698,28 @@ class AidenAgent {
|
|
|
698
698
|
}
|
|
699
699
|
let output;
|
|
700
700
|
try {
|
|
701
|
-
|
|
701
|
+
// v4.9.0 Slice 6 — wrap the provider call in an LLM span when
|
|
702
|
+
// the daemon foundation is up AND a runWithContext frame is
|
|
703
|
+
// active. NOOP otherwise. patchAttrs back-fills tokens +
|
|
704
|
+
// finish_reason from the response after the call returns.
|
|
705
|
+
const shim = llmSpanShim();
|
|
706
|
+
if (shim && shim.db && shim.hasContext()) {
|
|
707
|
+
output = await shim.withLlmSpan(shim.db, { model: this.modelId ?? 'unknown', provider: this.providerId ?? 'unknown' }, async (_ctx, patchAttrs) => {
|
|
708
|
+
const out = await this.callProvider(messages, effectiveTools, runOptions);
|
|
709
|
+
patchAttrs({
|
|
710
|
+
input_tokens: out.usage?.inputTokens ?? 0,
|
|
711
|
+
output_tokens: out.usage?.outputTokens ?? 0,
|
|
712
|
+
total_tokens: (out.usage?.inputTokens ?? 0) + (out.usage?.outputTokens ?? 0),
|
|
713
|
+
cache_read_tokens: out.usage?.cacheReadTokens ?? 0,
|
|
714
|
+
cache_write_tokens: out.usage?.cacheWriteTokens ?? 0,
|
|
715
|
+
finish_reason: out.finishReason,
|
|
716
|
+
});
|
|
717
|
+
return out;
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
else {
|
|
721
|
+
output = await this.callProvider(messages, effectiveTools, runOptions);
|
|
722
|
+
}
|
|
702
723
|
}
|
|
703
724
|
catch (err) {
|
|
704
725
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
@@ -1203,10 +1224,16 @@ class AidenAgent {
|
|
|
1203
1224
|
this.onProviderRequestStart?.(this.providerId);
|
|
1204
1225
|
}
|
|
1205
1226
|
catch { /* defensive */ }
|
|
1227
|
+
// v4.9.0 Slice 7 — when an ExecutionContext is active, emit
|
|
1228
|
+
// outbound correlation headers (`traceparent`, `X-Aiden-*`). The
|
|
1229
|
+
// adapter merges them under its own auth headers so they can't
|
|
1230
|
+
// override security-relevant fields. No-context: headers omitted.
|
|
1231
|
+
const ambient = (0, identity_1.currentContext)();
|
|
1232
|
+
const outboundHeaders = ambient ? (0, identity_2.injectContextHeaders)(ambient) : undefined;
|
|
1206
1233
|
if (!wantStream) {
|
|
1207
1234
|
// v4.6 prep — forward the abort signal into the provider call so
|
|
1208
1235
|
// an in-flight HTTP request can be cancelled mid-flight.
|
|
1209
|
-
return this.provider.call({ messages, tools, signal: runOptions.signal });
|
|
1236
|
+
return this.provider.call({ messages, tools, signal: runOptions.signal, headers: outboundHeaders });
|
|
1210
1237
|
}
|
|
1211
1238
|
let firstDeltaFired = false;
|
|
1212
1239
|
let finalOutput = null;
|
|
@@ -1217,6 +1244,7 @@ class AidenAgent {
|
|
|
1217
1244
|
// v4.6 prep — also forward to streaming adapters; mid-stream
|
|
1218
1245
|
// aborts cancel the underlying SSE read via the same signal.
|
|
1219
1246
|
signal: runOptions.signal,
|
|
1247
|
+
headers: outboundHeaders,
|
|
1220
1248
|
});
|
|
1221
1249
|
for await (const evt of stream) {
|
|
1222
1250
|
if (evt.type === 'delta') {
|
|
@@ -1304,3 +1332,18 @@ function stringifyToolResult(result) {
|
|
|
1304
1332
|
return String(result);
|
|
1305
1333
|
}
|
|
1306
1334
|
}
|
|
1335
|
+
// v4.9.0 Slice 6 — static imports for the LLM span bridge. Same
|
|
1336
|
+
// reasoning as toolRegistry: vite-node doesn't intercept CJS require
|
|
1337
|
+
// for `.ts` modules, so the lazy `require()` form returned null in
|
|
1338
|
+
// tests. Static ESM imports work everywhere.
|
|
1339
|
+
const bootstrap_1 = require("./daemon/bootstrap");
|
|
1340
|
+
const spanHelpers_1 = require("./daemon/spans/spanHelpers");
|
|
1341
|
+
const identity_1 = require("./identity");
|
|
1342
|
+
// v4.9.0 Slice 7 — outbound trace propagation.
|
|
1343
|
+
const identity_2 = require("./identity");
|
|
1344
|
+
const _llmSpanShim = {
|
|
1345
|
+
get db() { return (0, bootstrap_1.getCurrentDaemonDb)(); },
|
|
1346
|
+
hasContext: () => (0, identity_1.currentContext)() !== undefined,
|
|
1347
|
+
withLlmSpan: spanHelpers_1.withLlmSpan,
|
|
1348
|
+
};
|
|
1349
|
+
function llmSpanShim() { return _llmSpanShim; }
|
|
@@ -0,0 +1,131 @@
|
|
|
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/daemon/api/runs.ts — v4.9.0 Slice 5.
|
|
10
|
+
*
|
|
11
|
+
* `POST /api/runs` — durable run-acceptance ingress. The handler:
|
|
12
|
+
*
|
|
13
|
+
* 1. Validates the body (must contain at least `args` or `prompt`).
|
|
14
|
+
* 2. Computes a fingerprint from a canonical JSON of the body.
|
|
15
|
+
* 3. Honours a caller-supplied `Idempotency-Key` header (Stripe/RFC
|
|
16
|
+
* pattern). If absent, falls back to the body fingerprint itself.
|
|
17
|
+
* 4. Calls `triggerBus.insert({source:'api',...})`, which (with
|
|
18
|
+
* `enableRunIdempotency:true`) atomically writes both the
|
|
19
|
+
* `trigger_events` row AND the `run_idempotency_keys` anchor.
|
|
20
|
+
* 5. Returns `202` with the persisted trigger_event id — the
|
|
21
|
+
* dispatcher picks the row up off the queue and creates the
|
|
22
|
+
* `runs` row downstream. This is the "202 only after durable
|
|
23
|
+
* insert" guarantee.
|
|
24
|
+
*
|
|
25
|
+
* AUTH: the existing bind-safety check covers non-loopback binds; this
|
|
26
|
+
* endpoint inherits the same `AIDEN_API_KEY` requirement when the
|
|
27
|
+
* daemon binds beyond 127.0.0.1. Loopback-only callers (the common
|
|
28
|
+
* case) authenticate by being on-host.
|
|
29
|
+
*/
|
|
30
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
31
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
32
|
+
};
|
|
33
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
34
|
+
exports.mountRunsRoutes = mountRunsRoutes;
|
|
35
|
+
const express_1 = __importDefault(require("express"));
|
|
36
|
+
const runIdempotencyStore_1 = require("../idempotency/runIdempotencyStore");
|
|
37
|
+
// v4.9.0 Slice 7 — inbound trace adoption.
|
|
38
|
+
const identity_1 = require("../../identity");
|
|
39
|
+
const bootstrap_1 = require("../bootstrap");
|
|
40
|
+
function mountRunsRoutes(opts) {
|
|
41
|
+
const PATH = '/api/runs';
|
|
42
|
+
opts.app.post(PATH, express_1.default.json({ limit: '1mb' }), (req, res, _next) => {
|
|
43
|
+
// Optional shared-secret auth.
|
|
44
|
+
if (opts.apiKeyRequired) {
|
|
45
|
+
const expected = process.env.AIDEN_API_KEY ?? '';
|
|
46
|
+
const auth = req.header('authorization') ?? '';
|
|
47
|
+
const tokenMatch = /^Bearer\s+(\S+)/i.exec(auth);
|
|
48
|
+
const provided = tokenMatch ? tokenMatch[1] : '';
|
|
49
|
+
if (!expected || expected !== provided) {
|
|
50
|
+
res.status(401).json({ error: 'unauthorized' });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const body = (req.body ?? {});
|
|
55
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) {
|
|
56
|
+
res.status(400).json({ error: 'body must be a JSON object' });
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (!body.args && !body.prompt) {
|
|
60
|
+
res.status(400).json({ error: 'body requires `args` or `prompt`' });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const fingerprint = (0, runIdempotencyStore_1.fingerprintCanonical)(body);
|
|
64
|
+
const headerKey = (req.header('idempotency-key') ?? '').trim();
|
|
65
|
+
const idempotencyKey = headerKey.length > 0 ? headerKey : fingerprint;
|
|
66
|
+
const sourceKey = (typeof body.client_id === 'string' && body.client_id.length > 0)
|
|
67
|
+
? body.client_id
|
|
68
|
+
: 'default';
|
|
69
|
+
// v4.9.0 Slice 7 — inbound trace adoption.
|
|
70
|
+
const incomingTp = (0, identity_1.parseTraceparent)(req.header('traceparent'));
|
|
71
|
+
const rawIncomingTp = req.header('traceparent');
|
|
72
|
+
if (rawIncomingTp && !incomingTp) {
|
|
73
|
+
opts.log('warn', `[api/runs] dropped malformed traceparent header (length=${rawIncomingTp.length})`);
|
|
74
|
+
}
|
|
75
|
+
const rawExternalReqId = req.header('x-request-id');
|
|
76
|
+
const externalReqId = (0, identity_1.validateExternalRequestId)(rawExternalReqId);
|
|
77
|
+
if (rawExternalReqId && externalReqId === null) {
|
|
78
|
+
opts.log('warn', `[api/runs] dropped invalid X-Request-Id header (length=${rawExternalReqId.length})`);
|
|
79
|
+
}
|
|
80
|
+
const ctx = {
|
|
81
|
+
daemonId: (0, bootstrap_1.getCurrentDaemonId)() ?? '',
|
|
82
|
+
incarnationId: (0, bootstrap_1.getCurrentIncarnationId)() ?? '',
|
|
83
|
+
runId: (0, identity_1.newRunId)(), // pre-claim run id (dispatcher assigns final numeric id)
|
|
84
|
+
traceId: incomingTp ? `trc_${incomingTp.traceId}` : (0, identity_1.newTraceId)(),
|
|
85
|
+
spanId: (0, identity_1.newSpanId)(),
|
|
86
|
+
parentSpanId: incomingTp?.parentSpanId ?? undefined,
|
|
87
|
+
requestId: (0, identity_1.newRequestId)(),
|
|
88
|
+
externalRequestId: externalReqId ?? undefined,
|
|
89
|
+
source: 'api',
|
|
90
|
+
attempt: 1,
|
|
91
|
+
};
|
|
92
|
+
void (0, identity_1.runWithContext)(ctx, () => {
|
|
93
|
+
try {
|
|
94
|
+
const result = opts.triggerBus.insert({
|
|
95
|
+
source: 'manual',
|
|
96
|
+
sourceKey,
|
|
97
|
+
idempotencyKey,
|
|
98
|
+
payload: {
|
|
99
|
+
body, fingerprint, headerKey,
|
|
100
|
+
external_trace_id: incomingTp?.traceId ?? null,
|
|
101
|
+
external_request_id: externalReqId ?? null,
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
// Persist `external_trace_id` on the trigger payload so the
|
|
105
|
+
// dispatcher can copy it onto the `runs` row when it
|
|
106
|
+
// creates one. We can't write to `runs` here (no run row
|
|
107
|
+
// yet), but the payload carries the value.
|
|
108
|
+
opts.log('info', `[api/runs] accepted trigger_event_id=${result.id} ` +
|
|
109
|
+
`${incomingTp ? `external_trace_id=${incomingTp.traceId} ` : ''}` +
|
|
110
|
+
`${externalReqId ? `external_request_id=${externalReqId}` : ''}`);
|
|
111
|
+
res.status(202).json({
|
|
112
|
+
accepted: true,
|
|
113
|
+
duplicate: !result.inserted,
|
|
114
|
+
trigger_event_id: result.id,
|
|
115
|
+
idempotency_key: idempotencyKey,
|
|
116
|
+
run_id: ctx.runId,
|
|
117
|
+
trace_id: ctx.traceId,
|
|
118
|
+
external_trace_id: incomingTp?.traceId ?? null,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
catch (e) {
|
|
122
|
+
opts.log('error', `[api/runs] insert failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
123
|
+
res.status(500).json({ error: 'internal_error' });
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
// Quiet unused-warning on the db handle; future Slice 8 uses it
|
|
127
|
+
// to back-fill the `runs.external_trace_id` column on creation.
|
|
128
|
+
void bootstrap_1.getCurrentDaemonDb;
|
|
129
|
+
});
|
|
130
|
+
return { path: PATH };
|
|
131
|
+
}
|