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.
Files changed (100) hide show
  1. package/README.md +88 -1
  2. package/dist/cli/v4/aidenCLI.js +37 -6
  3. package/dist/cli/v4/chatSession.js +53 -13
  4. package/dist/cli/v4/commands/daemon.js +53 -3
  5. package/dist/cli/v4/commands/daemonDoctor.js +212 -0
  6. package/dist/cli/v4/commands/daemonStatus.js +45 -26
  7. package/dist/cli/v4/commands/help.js +5 -0
  8. package/dist/cli/v4/commands/hooks.js +466 -0
  9. package/dist/cli/v4/commands/hooksSlash.js +33 -0
  10. package/dist/cli/v4/commands/index.js +13 -1
  11. package/dist/cli/v4/commands/mcp.js +89 -1
  12. package/dist/cli/v4/commands/mcpClientInstall.js +359 -0
  13. package/dist/cli/v4/commands/memory.js +707 -0
  14. package/dist/cli/v4/commands/memorySlash.js +38 -0
  15. package/dist/cli/v4/commands/recovery.js +1 -1
  16. package/dist/cli/v4/commands/skin.js +7 -0
  17. package/dist/cli/v4/commands/theme.js +217 -0
  18. package/dist/cli/v4/commands/trigger.js +1 -1
  19. package/dist/cli/v4/design/tokens.js +52 -4
  20. package/dist/cli/v4/display.js +39 -26
  21. package/dist/cli/v4/replyRenderer.js +6 -5
  22. package/dist/cli/v4/skinEngine.js +67 -0
  23. package/dist/cli/v4/ui/progressBar.js +179 -0
  24. package/dist/cli/v4/util/closestAction.js +48 -0
  25. package/dist/core/v4/aidenAgent.js +45 -2
  26. package/dist/core/v4/daemon/api/runs.js +131 -0
  27. package/dist/core/v4/daemon/bootstrap.js +368 -13
  28. package/dist/core/v4/daemon/db/migrations.js +169 -0
  29. package/dist/core/v4/daemon/idempotency/runIdempotencyStore.js +128 -0
  30. package/dist/core/v4/daemon/incarnationStore.js +47 -0
  31. package/dist/core/v4/daemon/runs/attemptStore.js +67 -0
  32. package/dist/core/v4/daemon/runs/reclaim.js +88 -0
  33. package/dist/core/v4/daemon/runs/retryPolicy.js +73 -0
  34. package/dist/core/v4/daemon/runs/runWithRetry.js +80 -0
  35. package/dist/core/v4/daemon/runs/stuckAttemptWatchdog.js +72 -0
  36. package/dist/core/v4/daemon/spans/spanHelpers.js +272 -0
  37. package/dist/core/v4/daemon/spans/spanStore.js +113 -0
  38. package/dist/core/v4/daemon/triggerBus.js +50 -19
  39. package/dist/core/v4/hooks/auditQuery.js +67 -0
  40. package/dist/core/v4/hooks/dispatcher.js +286 -0
  41. package/dist/core/v4/hooks/index.js +46 -0
  42. package/dist/core/v4/hooks/lifecycle.js +27 -0
  43. package/dist/core/v4/hooks/manifest.js +142 -0
  44. package/dist/core/v4/hooks/registry.js +149 -0
  45. package/dist/core/v4/hooks/runtime/subprocessRunner.js +188 -0
  46. package/dist/core/v4/hooks/toolHookGate.js +76 -0
  47. package/dist/core/v4/hooks/trust.js +14 -0
  48. package/dist/core/v4/identity/contextManager.js +83 -0
  49. package/dist/core/v4/identity/daemonId.js +85 -0
  50. package/dist/core/v4/identity/enforcement.js +103 -0
  51. package/dist/core/v4/identity/executionContext.js +153 -0
  52. package/dist/core/v4/identity/hookExecution.js +62 -0
  53. package/dist/core/v4/identity/httpContext.js +68 -0
  54. package/dist/core/v4/identity/ids.js +185 -0
  55. package/dist/core/v4/identity/index.js +60 -0
  56. package/dist/core/v4/identity/subprocessContext.js +98 -0
  57. package/dist/core/v4/identity/traceparent.js +114 -0
  58. package/dist/core/v4/logger/index.js +3 -1
  59. package/dist/core/v4/logger/logger.js +28 -1
  60. package/dist/core/v4/logger/redact.js +149 -0
  61. package/dist/core/v4/logger/sinks/fileSink.js +13 -0
  62. package/dist/core/v4/logger/sinks/stdSink.js +19 -1
  63. package/dist/core/v4/mcp/install/backup.js +78 -0
  64. package/dist/core/v4/mcp/install/clientPaths.js +90 -0
  65. package/dist/core/v4/mcp/install/clients.js +203 -0
  66. package/dist/core/v4/mcp/install/healthCheck.js +83 -0
  67. package/dist/core/v4/mcp/install/jsoncMerge.js +174 -0
  68. package/dist/core/v4/mcp/install/profiles.js +109 -0
  69. package/dist/core/v4/mcp/install/wslDetect.js +62 -0
  70. package/dist/core/v4/memory/namespaceRegistry.js +117 -0
  71. package/dist/core/v4/memory/projectRoot.js +76 -0
  72. package/dist/core/v4/memory/reviewer/index.js +162 -0
  73. package/dist/core/v4/memory/reviewer/pendingStore.js +136 -0
  74. package/dist/core/v4/memory/reviewer/prompt.js +105 -0
  75. package/dist/core/v4/memory/reviewer/skipRules.js +92 -0
  76. package/dist/core/v4/memoryManager.js +57 -10
  77. package/dist/core/v4/paths.js +2 -0
  78. package/dist/core/v4/subagent/spawnSubAgent.js +20 -7
  79. package/dist/core/v4/theme/bundledThemes.js +106 -0
  80. package/dist/core/v4/theme/themeLoader.js +160 -0
  81. package/dist/core/v4/theme/themeRegistry.js +97 -0
  82. package/dist/core/v4/theme/themeWatcher.js +95 -0
  83. package/dist/core/v4/toolRegistry.js +71 -8
  84. package/dist/core/v4/update/depWarningFilter.js +76 -0
  85. package/dist/core/v4/update/executeInstall.js +41 -35
  86. package/dist/core/v4/update/platformInstructions.js +128 -0
  87. package/dist/moat/approvalEngine.js +4 -0
  88. package/dist/moat/memoryGuard.js +8 -1
  89. package/dist/providers/v4/anthropicAdapter.js +10 -4
  90. package/dist/tools/v4/backends/local.js +19 -2
  91. package/dist/tools/v4/sessions/recallSession.js +6 -1
  92. package/package.json +3 -1
  93. package/plugins/aiden-plugin-chatgpt-plus/index.js +10 -1
  94. package/themes/default.yaml +52 -0
  95. package/themes/dracula.yaml +32 -0
  96. package/themes/light.yaml +32 -0
  97. package/themes/monochrome.yaml +31 -0
  98. package/themes/tokyo-night.yaml +32 -0
  99. package/dist/core/pluginSystem.js +0 -121
  100. 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
- output = await this.callProvider(messages, effectiveTools, runOptions);
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
+ }