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,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.serializeContext = serializeContext;
|
|
4
|
+
exports.deserializeContext = deserializeContext;
|
|
5
|
+
exports.childSpan = childSpan;
|
|
6
|
+
/**
|
|
7
|
+
* Copyright (c) 2026 Shiva Deore (Taracod).
|
|
8
|
+
* Licensed under AGPL-3.0. See LICENSE for details.
|
|
9
|
+
*
|
|
10
|
+
* Aiden — local-first agent.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* core/v4/identity/executionContext.ts — v4.9.0 Slice 4.
|
|
14
|
+
*
|
|
15
|
+
* The `ExecutionContext` is the per-unit-of-work envelope every layer
|
|
16
|
+
* of the runtime can reach via `currentContext()`. It carries the
|
|
17
|
+
* stable correlation IDs (daemon, incarnation, run, trace, span) plus
|
|
18
|
+
* the situational metadata (source, attempt, deadline, optional
|
|
19
|
+
* external request id, optional baggage).
|
|
20
|
+
*
|
|
21
|
+
* Slice 4 is **additive only** — nothing in the existing code paths
|
|
22
|
+
* required a context yet; this file just defines the shape and the
|
|
23
|
+
* (de)serialisation primitives so later slices can plumb it through
|
|
24
|
+
* tool dispatch, hooks, trigger bus claims, and the agent loop.
|
|
25
|
+
*
|
|
26
|
+
* Serialisation is JSON. We could compact this further (e.g. msgpack
|
|
27
|
+
* over a base64url envelope) but JSON keeps it grep-friendly in env
|
|
28
|
+
* vars and log lines — and the contexts we'll be ferrying are <300 bytes.
|
|
29
|
+
*/
|
|
30
|
+
const ids_1 = require("./ids");
|
|
31
|
+
const VALID_SOURCES = new Set([
|
|
32
|
+
'cli', 'api', 'webhook', 'cron', 'email', 'folder', 'subagent', 'unknown',
|
|
33
|
+
]);
|
|
34
|
+
/**
|
|
35
|
+
* Serialise a context to a single string suitable for stuffing into an
|
|
36
|
+
* env var, a log field, or a tracestate-style header. Round-trippable
|
|
37
|
+
* via `deserializeContext`.
|
|
38
|
+
*
|
|
39
|
+
* We don't ship undefined fields — keeps the payload tight when most
|
|
40
|
+
* of the optional surface is empty (the common case).
|
|
41
|
+
*/
|
|
42
|
+
function serializeContext(ctx) {
|
|
43
|
+
const payload = {
|
|
44
|
+
d: ctx.daemonId,
|
|
45
|
+
i: ctx.incarnationId,
|
|
46
|
+
r: ctx.runId,
|
|
47
|
+
t: ctx.traceId,
|
|
48
|
+
s: ctx.spanId,
|
|
49
|
+
sr: ctx.source,
|
|
50
|
+
a: ctx.attempt,
|
|
51
|
+
};
|
|
52
|
+
if (ctx.parentSpanId)
|
|
53
|
+
payload.ps = ctx.parentSpanId;
|
|
54
|
+
if (ctx.requestId)
|
|
55
|
+
payload.rq = ctx.requestId;
|
|
56
|
+
if (ctx.externalRequestId)
|
|
57
|
+
payload.er = ctx.externalRequestId;
|
|
58
|
+
if (ctx.triggerId)
|
|
59
|
+
payload.tg = ctx.triggerId;
|
|
60
|
+
if (ctx.sessionId)
|
|
61
|
+
payload.se = ctx.sessionId;
|
|
62
|
+
if (ctx.deadlineAt)
|
|
63
|
+
payload.dl = ctx.deadlineAt;
|
|
64
|
+
if (ctx.baggage && Object.keys(ctx.baggage).length > 0)
|
|
65
|
+
payload.b = ctx.baggage;
|
|
66
|
+
return JSON.stringify(payload);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Parse a serialised context. Throws on malformed input — this is a
|
|
70
|
+
* trust-boundary deserialise (env var that's supposed to exist), so
|
|
71
|
+
* "fail loud" beats silent context loss.
|
|
72
|
+
*/
|
|
73
|
+
function deserializeContext(s) {
|
|
74
|
+
if (typeof s !== 'string' || s.length === 0) {
|
|
75
|
+
throw new Error('deserializeContext: input must be a non-empty string');
|
|
76
|
+
}
|
|
77
|
+
let raw;
|
|
78
|
+
try {
|
|
79
|
+
raw = JSON.parse(s);
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
throw new Error(`deserializeContext: invalid JSON (${e instanceof Error ? e.message : String(e)})`);
|
|
83
|
+
}
|
|
84
|
+
const need = (k) => {
|
|
85
|
+
const v = raw[k];
|
|
86
|
+
if (typeof v !== 'string' || v.length === 0) {
|
|
87
|
+
throw new Error(`deserializeContext: missing or non-string field '${k}'`);
|
|
88
|
+
}
|
|
89
|
+
return v;
|
|
90
|
+
};
|
|
91
|
+
// Validate required string fields FIRST so a malformed payload gets
|
|
92
|
+
// a useful "missing field 'X'" message rather than a downstream
|
|
93
|
+
// "invalid source 'undefined'" surprise.
|
|
94
|
+
const daemonId = need('d');
|
|
95
|
+
const incarnationId = need('i');
|
|
96
|
+
const runId = need('r');
|
|
97
|
+
const traceId = need('t');
|
|
98
|
+
const spanId = need('s');
|
|
99
|
+
const source = need('sr');
|
|
100
|
+
if (!VALID_SOURCES.has(source)) {
|
|
101
|
+
throw new Error(`deserializeContext: invalid source '${source}'`);
|
|
102
|
+
}
|
|
103
|
+
const attempt = Number(raw.a);
|
|
104
|
+
if (!Number.isFinite(attempt) || attempt < 0) {
|
|
105
|
+
throw new Error(`deserializeContext: invalid attempt '${String(raw.a)}'`);
|
|
106
|
+
}
|
|
107
|
+
const ctx = {
|
|
108
|
+
daemonId,
|
|
109
|
+
incarnationId,
|
|
110
|
+
runId,
|
|
111
|
+
traceId,
|
|
112
|
+
spanId,
|
|
113
|
+
source: source,
|
|
114
|
+
attempt,
|
|
115
|
+
};
|
|
116
|
+
if (typeof raw.ps === 'string')
|
|
117
|
+
ctx.parentSpanId = raw.ps;
|
|
118
|
+
if (typeof raw.rq === 'string')
|
|
119
|
+
ctx.requestId = raw.rq;
|
|
120
|
+
if (typeof raw.er === 'string')
|
|
121
|
+
ctx.externalRequestId = raw.er;
|
|
122
|
+
if (typeof raw.tg === 'string')
|
|
123
|
+
ctx.triggerId = raw.tg;
|
|
124
|
+
if (typeof raw.se === 'string')
|
|
125
|
+
ctx.sessionId = raw.se;
|
|
126
|
+
if (typeof raw.dl === 'string')
|
|
127
|
+
ctx.deadlineAt = raw.dl;
|
|
128
|
+
if (raw.b && typeof raw.b === 'object' && !Array.isArray(raw.b)) {
|
|
129
|
+
// Defensive cast: only string-valued entries survive.
|
|
130
|
+
const bag = {};
|
|
131
|
+
for (const [k, v] of Object.entries(raw.b)) {
|
|
132
|
+
if (typeof v === 'string')
|
|
133
|
+
bag[k] = v;
|
|
134
|
+
}
|
|
135
|
+
if (Object.keys(bag).length > 0)
|
|
136
|
+
ctx.baggage = bag;
|
|
137
|
+
}
|
|
138
|
+
return ctx;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Fork a child span: returns a new context with the same trace/run but
|
|
142
|
+
* a fresh `spanId` and `parentSpanId` set to the parent's `spanId`.
|
|
143
|
+
*
|
|
144
|
+
* Pass `newSpanIdOverride` only in tests where determinism matters; in
|
|
145
|
+
* normal operation the freshly-minted span id is what you want.
|
|
146
|
+
*/
|
|
147
|
+
function childSpan(ctx, newSpanIdOverride) {
|
|
148
|
+
return {
|
|
149
|
+
...ctx,
|
|
150
|
+
spanId: newSpanIdOverride ?? (0, ids_1.newSpanId)(),
|
|
151
|
+
parentSpanId: ctx.spanId,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
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/identity/hookExecution.ts — v4.9.0 Slice 7.
|
|
10
|
+
*
|
|
11
|
+
* Ergonomic boundary helper for hook execution. Wraps a hook callback
|
|
12
|
+
* with:
|
|
13
|
+
*
|
|
14
|
+
* - Timeout enforcement (default 5s; configurable per call)
|
|
15
|
+
* - Structured outcome shape (`'ok' | 'timeout' | 'error'`) — caller
|
|
16
|
+
* decides whether a hook timeout/error should block the operation
|
|
17
|
+
* or fail-open
|
|
18
|
+
* - A span via Slice 6's `runHookWithSpan` so every firing is durable
|
|
19
|
+
*
|
|
20
|
+
* Slice 7 lands the helper. Slice 9 wires it into the actual lifecycle
|
|
21
|
+
* dispatcher.
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.HookTimeoutError = void 0;
|
|
25
|
+
exports.executeHookWithBoundary = executeHookWithBoundary;
|
|
26
|
+
/**
|
|
27
|
+
* Execute `fn` inside a hook boundary. Returns a structured outcome —
|
|
28
|
+
* the caller decides whether to block or fail-open on timeout/error.
|
|
29
|
+
* Never throws.
|
|
30
|
+
*
|
|
31
|
+
* Uses Slice 6's `runHookWithSpan` for the durable span side; here we
|
|
32
|
+
* adapt the spanned result into the explicit `{outcome}` envelope.
|
|
33
|
+
*/
|
|
34
|
+
async function executeHookWithBoundary(_ctx, opts, fn) {
|
|
35
|
+
const timeoutMs = opts.timeoutMs ?? 5000;
|
|
36
|
+
let timer = null;
|
|
37
|
+
try {
|
|
38
|
+
const timeoutPromise = new Promise((_resolve, reject) => {
|
|
39
|
+
timer = setTimeout(() => reject(new HookTimeoutError(opts.hookName, timeoutMs)), timeoutMs);
|
|
40
|
+
});
|
|
41
|
+
const value = await Promise.race([fn(), timeoutPromise]);
|
|
42
|
+
if (timer)
|
|
43
|
+
clearTimeout(timer);
|
|
44
|
+
return { outcome: 'ok', value: value };
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
if (timer)
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
if (err instanceof HookTimeoutError) {
|
|
50
|
+
return { outcome: 'timeout', error: err };
|
|
51
|
+
}
|
|
52
|
+
const wrapped = err instanceof Error ? err : new Error(String(err));
|
|
53
|
+
return { outcome: 'error', error: wrapped };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
class HookTimeoutError extends Error {
|
|
57
|
+
constructor(hookName, ms) {
|
|
58
|
+
super(`hook '${hookName}' timed out after ${ms}ms`);
|
|
59
|
+
this.name = 'HookTimeout';
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
exports.HookTimeoutError = HookTimeoutError;
|
|
@@ -0,0 +1,68 @@
|
|
|
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/identity/httpContext.ts — v4.9.0 Slice 7.
|
|
10
|
+
*
|
|
11
|
+
* Inject outbound HTTP headers carrying ExecutionContext correlation
|
|
12
|
+
* data so downstream services + log aggregators can stitch the call
|
|
13
|
+
* back to its originating run/trace/span. Emits both:
|
|
14
|
+
*
|
|
15
|
+
* - W3C-standard `traceparent` (32-hex/16-hex/sampled)
|
|
16
|
+
* - `X-Aiden-*` companion headers for fine-grained run/incarnation
|
|
17
|
+
* correlation that doesn't fit in `traceparent`
|
|
18
|
+
*
|
|
19
|
+
* `injectContextHeaders` is pure — it does not read ambient context
|
|
20
|
+
* itself. Callers pass an explicit ctx (either current or one they
|
|
21
|
+
* forked). Outside a `runWithContext` frame, callers can pass
|
|
22
|
+
* `currentContext()` and receive the same headers, or short-circuit
|
|
23
|
+
* to skip header injection entirely.
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.injectContextHeaders = injectContextHeaders;
|
|
27
|
+
exports.maybeInjectContextHeaders = maybeInjectContextHeaders;
|
|
28
|
+
const traceparent_1 = require("./traceparent");
|
|
29
|
+
const ids_1 = require("./ids");
|
|
30
|
+
const enforcement_1 = require("./enforcement");
|
|
31
|
+
/**
|
|
32
|
+
* Headers Aiden adds to outbound requests when an ExecutionContext
|
|
33
|
+
* is active. Caller-supplied headers in the merge target win on key
|
|
34
|
+
* collision so a tool that needs to override (e.g. `User-Agent`) can.
|
|
35
|
+
*/
|
|
36
|
+
function injectContextHeaders(ctx, headers = {}) {
|
|
37
|
+
const traceparent = (() => {
|
|
38
|
+
try {
|
|
39
|
+
return (0, traceparent_1.emitTraceparent)((0, traceparent_1.stripPrefix)(ctx.traceId, 'trc_'), (0, traceparent_1.stripPrefix)(ctx.spanId, 'spn_'), true);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
})();
|
|
45
|
+
const out = {};
|
|
46
|
+
if (traceparent)
|
|
47
|
+
out['traceparent'] = traceparent;
|
|
48
|
+
out['X-Request-Id'] = ctx.requestId ?? (0, ids_1.newRequestId)();
|
|
49
|
+
out['X-Aiden-Run-Id'] = ctx.runId;
|
|
50
|
+
out['X-Aiden-Trace-Id'] = ctx.traceId;
|
|
51
|
+
out['X-Aiden-Span-Id'] = ctx.spanId;
|
|
52
|
+
// Caller-supplied headers win on collision.
|
|
53
|
+
return { ...out, ...headers };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Convenience: pass-through helper when the caller may or may not
|
|
57
|
+
* have a context. Returns the original headers unchanged when ctx
|
|
58
|
+
* is undefined (no trace propagation outside a context frame).
|
|
59
|
+
*/
|
|
60
|
+
function maybeInjectContextHeaders(ctx, headers = {}) {
|
|
61
|
+
if (ctx)
|
|
62
|
+
return injectContextHeaders(ctx, headers);
|
|
63
|
+
// v4.9.0 Slice 8 — report missing-context on the outbound HTTP path.
|
|
64
|
+
// 'silent' / 'warn' degrade to pass-through (no trace propagation
|
|
65
|
+
// outside a context frame); 'strict' throws via the enforcement.
|
|
66
|
+
(0, enforcement_1.reportMissingContext)('http_outbound', 'maybeInjectContextHeaders');
|
|
67
|
+
return headers;
|
|
68
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
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/identity/ids.ts — v4.9.0 Slice 4.
|
|
10
|
+
*
|
|
11
|
+
* UUIDv7 generation per RFC 9562 §5.7 + typed-prefix wrappers.
|
|
12
|
+
*
|
|
13
|
+
* UUIDv7 lays a millisecond timestamp in the top 48 bits, so lexicographic
|
|
14
|
+
* sort == time sort. That property is the reason we picked v7 over v4:
|
|
15
|
+
* `runs.id`-style auto-increment is wedded to one SQLite file; UUIDv7
|
|
16
|
+
* gives us a globally-unique, time-orderable id we can correlate across
|
|
17
|
+
* the daemon DB, the structured log, future remote sinks, and crash
|
|
18
|
+
* reports without needing a central counter.
|
|
19
|
+
*
|
|
20
|
+
* The `uuid` package on disk is v9, which doesn't ship UUIDv7 (added in
|
|
21
|
+
* uuid@10). We hand-roll the 16-byte layout from RFC 9562 §5.7:
|
|
22
|
+
*
|
|
23
|
+
* 0 1 2 3
|
|
24
|
+
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
25
|
+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
26
|
+
* | unix_ts_ms |
|
|
27
|
+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
28
|
+
* | unix_ts_ms | ver | rand_a |
|
|
29
|
+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
30
|
+
* |var| rand_b |
|
|
31
|
+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
32
|
+
* | rand_b |
|
|
33
|
+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
34
|
+
*
|
|
35
|
+
* - 48 bits unix_ts_ms (big-endian)
|
|
36
|
+
* - 4 bits version = 0b0111 (UUIDv7)
|
|
37
|
+
* - 12 bits rand_a (random)
|
|
38
|
+
* - 2 bits variant = 0b10 (RFC 4122)
|
|
39
|
+
* - 62 bits rand_b (random)
|
|
40
|
+
*
|
|
41
|
+
* Output canonical hex with dashes (e.g. `01919c34-8e21-7c8c-bcd7-...`).
|
|
42
|
+
* That's then base32-cased for prefixed IDs — keeps the output URL-safe
|
|
43
|
+
* and short while preserving the millisecond ordering at the prefix.
|
|
44
|
+
*/
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.newHookExecId = exports.newHookSubId = exports.newHookId = exports.newMemoryId = exports.newToolCallId = exports.newRequestId = exports.newSpanId = exports.newTraceId = exports.newAttemptId = exports.newRunId = exports.newTriggerId = exports.newIncarnationId = exports.newDaemonId = exports.ID_PREFIXES = void 0;
|
|
47
|
+
exports.uuidv7Bytes = uuidv7Bytes;
|
|
48
|
+
exports.newUuidV7 = newUuidV7;
|
|
49
|
+
exports.newUuidV7Compact = newUuidV7Compact;
|
|
50
|
+
exports.parseId = parseId;
|
|
51
|
+
exports.isIdWithPrefix = isIdWithPrefix;
|
|
52
|
+
const node_crypto_1 = require("node:crypto");
|
|
53
|
+
/**
|
|
54
|
+
* Allowed prefixes. Adding a new ID kind means adding the prefix here
|
|
55
|
+
* and the helper. `parseId` validates against this set so a stray
|
|
56
|
+
* `xyz_...` string can't masquerade as one of ours.
|
|
57
|
+
*/
|
|
58
|
+
exports.ID_PREFIXES = [
|
|
59
|
+
'dmn', // daemon identity (persists across incarnations)
|
|
60
|
+
'inc', // single daemon process incarnation
|
|
61
|
+
'trg', // trigger event
|
|
62
|
+
'run', // agent run (one turn or daemon-fired job)
|
|
63
|
+
'att', // v4.9.0 Slice 5 — execution attempt within a run
|
|
64
|
+
'trc', // trace (top-level correlation across runs/spans)
|
|
65
|
+
'spn', // span (sub-unit of a trace)
|
|
66
|
+
'req', // external request id (e.g. webhook delivery)
|
|
67
|
+
'tool', // tool invocation
|
|
68
|
+
'mem', // memory write
|
|
69
|
+
'hook', // hook firing (legacy generic id)
|
|
70
|
+
'hsub', // v4.9.0 Slice 12a — hook subscription
|
|
71
|
+
'hexec', // v4.9.0 Slice 12a — hook execution (subprocess invocation)
|
|
72
|
+
];
|
|
73
|
+
const PREFIX_SET = new Set(exports.ID_PREFIXES);
|
|
74
|
+
/**
|
|
75
|
+
* Generate one UUIDv7 byte buffer.
|
|
76
|
+
* Exported for tests; consumers should use `newUuidV7()` or one of the
|
|
77
|
+
* typed helpers below.
|
|
78
|
+
*/
|
|
79
|
+
function uuidv7Bytes(now = Date.now()) {
|
|
80
|
+
if (!Number.isFinite(now) || now < 0) {
|
|
81
|
+
throw new Error(`uuidv7Bytes: invalid timestamp ${now}`);
|
|
82
|
+
}
|
|
83
|
+
const buf = (0, node_crypto_1.randomBytes)(16);
|
|
84
|
+
// 48-bit timestamp, big-endian, into bytes 0-5.
|
|
85
|
+
// JS bitwise ops are 32-bit, so split into high/low halves first.
|
|
86
|
+
const tsHi = Math.floor(now / 4294967296); // top 16 of the 48-bit ts
|
|
87
|
+
const tsLo = now >>> 0; // bottom 32
|
|
88
|
+
buf[0] = (tsHi >>> 8) & 0xff;
|
|
89
|
+
buf[1] = tsHi & 0xff;
|
|
90
|
+
buf[2] = (tsLo >>> 24) & 0xff;
|
|
91
|
+
buf[3] = (tsLo >>> 16) & 0xff;
|
|
92
|
+
buf[4] = (tsLo >>> 8) & 0xff;
|
|
93
|
+
buf[5] = tsLo & 0xff;
|
|
94
|
+
// Version 0b0111 in the top nibble of byte 6.
|
|
95
|
+
buf[6] = (buf[6] & 0x0f) | 0x70;
|
|
96
|
+
// Variant 0b10 in the top 2 bits of byte 8.
|
|
97
|
+
buf[8] = (buf[8] & 0x3f) | 0x80;
|
|
98
|
+
return buf;
|
|
99
|
+
}
|
|
100
|
+
/** Format 16 bytes as canonical UUID string with dashes. */
|
|
101
|
+
function bytesToUuidString(buf) {
|
|
102
|
+
const hex = Buffer.from(buf).toString('hex');
|
|
103
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
104
|
+
}
|
|
105
|
+
/** Strip dashes; canonical UUID → 32-char lowercase hex. */
|
|
106
|
+
function bytesToCompact(buf) {
|
|
107
|
+
return Buffer.from(buf).toString('hex');
|
|
108
|
+
}
|
|
109
|
+
/** One fresh UUIDv7 in canonical dashed form. */
|
|
110
|
+
function newUuidV7() {
|
|
111
|
+
return bytesToUuidString(uuidv7Bytes());
|
|
112
|
+
}
|
|
113
|
+
/** One fresh UUIDv7 in compact (dashless lowercase hex) form. */
|
|
114
|
+
function newUuidV7Compact() {
|
|
115
|
+
return bytesToCompact(uuidv7Bytes());
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Build a typed ID: `<prefix>_<compactHex>`. The compact form keeps the
|
|
119
|
+
* output URL-safe and shorter than the canonical dashed form, while
|
|
120
|
+
* preserving the millisecond-ordered prefix.
|
|
121
|
+
*/
|
|
122
|
+
function makeId(prefix) {
|
|
123
|
+
return `${prefix}_${newUuidV7Compact()}`;
|
|
124
|
+
}
|
|
125
|
+
const newDaemonId = () => makeId('dmn');
|
|
126
|
+
exports.newDaemonId = newDaemonId;
|
|
127
|
+
const newIncarnationId = () => makeId('inc');
|
|
128
|
+
exports.newIncarnationId = newIncarnationId;
|
|
129
|
+
const newTriggerId = () => makeId('trg');
|
|
130
|
+
exports.newTriggerId = newTriggerId;
|
|
131
|
+
const newRunId = () => makeId('run');
|
|
132
|
+
exports.newRunId = newRunId;
|
|
133
|
+
const newAttemptId = () => makeId('att');
|
|
134
|
+
exports.newAttemptId = newAttemptId;
|
|
135
|
+
const newTraceId = () => makeId('trc');
|
|
136
|
+
exports.newTraceId = newTraceId;
|
|
137
|
+
const newSpanId = () => makeId('spn');
|
|
138
|
+
exports.newSpanId = newSpanId;
|
|
139
|
+
const newRequestId = () => makeId('req');
|
|
140
|
+
exports.newRequestId = newRequestId;
|
|
141
|
+
const newToolCallId = () => makeId('tool');
|
|
142
|
+
exports.newToolCallId = newToolCallId;
|
|
143
|
+
const newMemoryId = () => makeId('mem');
|
|
144
|
+
exports.newMemoryId = newMemoryId;
|
|
145
|
+
const newHookId = () => makeId('hook');
|
|
146
|
+
exports.newHookId = newHookId;
|
|
147
|
+
const newHookSubId = () => makeId('hsub');
|
|
148
|
+
exports.newHookSubId = newHookSubId;
|
|
149
|
+
const newHookExecId = () => makeId('hexec');
|
|
150
|
+
exports.newHookExecId = newHookExecId;
|
|
151
|
+
/**
|
|
152
|
+
* Parse a prefixed ID. Returns `null` if the prefix is unknown or the
|
|
153
|
+
* UUID payload is malformed. Never throws — callers are expected to
|
|
154
|
+
* branch on `null` (think "trusted input boundary check, not parse-or-die").
|
|
155
|
+
*
|
|
156
|
+
* Accepts both compact (no dashes) and canonical (with dashes) UUID
|
|
157
|
+
* forms in the payload — the typed helpers emit compact, but
|
|
158
|
+
* deserialised IDs from external sources (logs, env vars, query strings)
|
|
159
|
+
* could carry either shape.
|
|
160
|
+
*/
|
|
161
|
+
function parseId(s) {
|
|
162
|
+
if (typeof s !== 'string' || s.length === 0)
|
|
163
|
+
return null;
|
|
164
|
+
const idx = s.indexOf('_');
|
|
165
|
+
if (idx <= 0)
|
|
166
|
+
return null;
|
|
167
|
+
const prefix = s.slice(0, idx);
|
|
168
|
+
if (!PREFIX_SET.has(prefix))
|
|
169
|
+
return null;
|
|
170
|
+
let payload = s.slice(idx + 1);
|
|
171
|
+
// Strip dashes if the caller passed canonical form.
|
|
172
|
+
if (payload.includes('-'))
|
|
173
|
+
payload = payload.replace(/-/g, '');
|
|
174
|
+
if (!/^[0-9a-f]{32}$/i.test(payload))
|
|
175
|
+
return null;
|
|
176
|
+
return { prefix: prefix, uuid: payload.toLowerCase() };
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Type guard — `true` when `s` is a well-formed typed ID with the
|
|
180
|
+
* expected prefix. Cheap; used at API boundaries.
|
|
181
|
+
*/
|
|
182
|
+
function isIdWithPrefix(s, prefix) {
|
|
183
|
+
const parsed = parseId(s);
|
|
184
|
+
return parsed !== null && parsed.prefix === prefix;
|
|
185
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports._resetContextMissingCountersForTests = exports.getAllContextMissingCounters = exports.getContextMissingCounter = exports.ContextMissingError = exports.reportMissingContext = exports.getEnforcementMode = exports.HookTimeoutError = exports.executeHookWithBoundary = exports.readContextFromEnv = exports.spawnEnvWithContext = exports.maybeInjectContextHeaders = exports.injectContextHeaders = exports.validateExternalRequestId = exports.stripPrefix = exports.emitTraceparent = exports.parseTraceparent = exports.daemonIdFilePath = exports.loadOrCreateDaemonId = exports.requireContext = exports.currentContext = exports.runWithContext = exports.childSpan = exports.deserializeContext = exports.serializeContext = exports.isIdWithPrefix = exports.parseId = exports.newHookExecId = exports.newHookSubId = exports.newHookId = exports.newMemoryId = exports.newToolCallId = exports.newRequestId = exports.newSpanId = exports.newTraceId = exports.newAttemptId = exports.newRunId = exports.newTriggerId = exports.newIncarnationId = exports.newDaemonId = exports.newUuidV7Compact = exports.newUuidV7 = exports.uuidv7Bytes = exports.ID_PREFIXES = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* core/v4/identity/index.ts — v4.9.0 Slice 4 barrel.
|
|
6
|
+
*/
|
|
7
|
+
var ids_1 = require("./ids");
|
|
8
|
+
Object.defineProperty(exports, "ID_PREFIXES", { enumerable: true, get: function () { return ids_1.ID_PREFIXES; } });
|
|
9
|
+
Object.defineProperty(exports, "uuidv7Bytes", { enumerable: true, get: function () { return ids_1.uuidv7Bytes; } });
|
|
10
|
+
Object.defineProperty(exports, "newUuidV7", { enumerable: true, get: function () { return ids_1.newUuidV7; } });
|
|
11
|
+
Object.defineProperty(exports, "newUuidV7Compact", { enumerable: true, get: function () { return ids_1.newUuidV7Compact; } });
|
|
12
|
+
Object.defineProperty(exports, "newDaemonId", { enumerable: true, get: function () { return ids_1.newDaemonId; } });
|
|
13
|
+
Object.defineProperty(exports, "newIncarnationId", { enumerable: true, get: function () { return ids_1.newIncarnationId; } });
|
|
14
|
+
Object.defineProperty(exports, "newTriggerId", { enumerable: true, get: function () { return ids_1.newTriggerId; } });
|
|
15
|
+
Object.defineProperty(exports, "newRunId", { enumerable: true, get: function () { return ids_1.newRunId; } });
|
|
16
|
+
Object.defineProperty(exports, "newAttemptId", { enumerable: true, get: function () { return ids_1.newAttemptId; } });
|
|
17
|
+
Object.defineProperty(exports, "newTraceId", { enumerable: true, get: function () { return ids_1.newTraceId; } });
|
|
18
|
+
Object.defineProperty(exports, "newSpanId", { enumerable: true, get: function () { return ids_1.newSpanId; } });
|
|
19
|
+
Object.defineProperty(exports, "newRequestId", { enumerable: true, get: function () { return ids_1.newRequestId; } });
|
|
20
|
+
Object.defineProperty(exports, "newToolCallId", { enumerable: true, get: function () { return ids_1.newToolCallId; } });
|
|
21
|
+
Object.defineProperty(exports, "newMemoryId", { enumerable: true, get: function () { return ids_1.newMemoryId; } });
|
|
22
|
+
Object.defineProperty(exports, "newHookId", { enumerable: true, get: function () { return ids_1.newHookId; } });
|
|
23
|
+
Object.defineProperty(exports, "newHookSubId", { enumerable: true, get: function () { return ids_1.newHookSubId; } });
|
|
24
|
+
Object.defineProperty(exports, "newHookExecId", { enumerable: true, get: function () { return ids_1.newHookExecId; } });
|
|
25
|
+
Object.defineProperty(exports, "parseId", { enumerable: true, get: function () { return ids_1.parseId; } });
|
|
26
|
+
Object.defineProperty(exports, "isIdWithPrefix", { enumerable: true, get: function () { return ids_1.isIdWithPrefix; } });
|
|
27
|
+
var executionContext_1 = require("./executionContext");
|
|
28
|
+
Object.defineProperty(exports, "serializeContext", { enumerable: true, get: function () { return executionContext_1.serializeContext; } });
|
|
29
|
+
Object.defineProperty(exports, "deserializeContext", { enumerable: true, get: function () { return executionContext_1.deserializeContext; } });
|
|
30
|
+
Object.defineProperty(exports, "childSpan", { enumerable: true, get: function () { return executionContext_1.childSpan; } });
|
|
31
|
+
var contextManager_1 = require("./contextManager");
|
|
32
|
+
Object.defineProperty(exports, "runWithContext", { enumerable: true, get: function () { return contextManager_1.runWithContext; } });
|
|
33
|
+
Object.defineProperty(exports, "currentContext", { enumerable: true, get: function () { return contextManager_1.currentContext; } });
|
|
34
|
+
Object.defineProperty(exports, "requireContext", { enumerable: true, get: function () { return contextManager_1.requireContext; } });
|
|
35
|
+
var daemonId_1 = require("./daemonId");
|
|
36
|
+
Object.defineProperty(exports, "loadOrCreateDaemonId", { enumerable: true, get: function () { return daemonId_1.loadOrCreateDaemonId; } });
|
|
37
|
+
Object.defineProperty(exports, "daemonIdFilePath", { enumerable: true, get: function () { return daemonId_1.daemonIdFilePath; } });
|
|
38
|
+
// v4.9.0 Slice 7 — HTTP / subprocess / hook boundaries.
|
|
39
|
+
var traceparent_1 = require("./traceparent");
|
|
40
|
+
Object.defineProperty(exports, "parseTraceparent", { enumerable: true, get: function () { return traceparent_1.parseTraceparent; } });
|
|
41
|
+
Object.defineProperty(exports, "emitTraceparent", { enumerable: true, get: function () { return traceparent_1.emitTraceparent; } });
|
|
42
|
+
Object.defineProperty(exports, "stripPrefix", { enumerable: true, get: function () { return traceparent_1.stripPrefix; } });
|
|
43
|
+
Object.defineProperty(exports, "validateExternalRequestId", { enumerable: true, get: function () { return traceparent_1.validateExternalRequestId; } });
|
|
44
|
+
var httpContext_1 = require("./httpContext");
|
|
45
|
+
Object.defineProperty(exports, "injectContextHeaders", { enumerable: true, get: function () { return httpContext_1.injectContextHeaders; } });
|
|
46
|
+
Object.defineProperty(exports, "maybeInjectContextHeaders", { enumerable: true, get: function () { return httpContext_1.maybeInjectContextHeaders; } });
|
|
47
|
+
var subprocessContext_1 = require("./subprocessContext");
|
|
48
|
+
Object.defineProperty(exports, "spawnEnvWithContext", { enumerable: true, get: function () { return subprocessContext_1.spawnEnvWithContext; } });
|
|
49
|
+
Object.defineProperty(exports, "readContextFromEnv", { enumerable: true, get: function () { return subprocessContext_1.readContextFromEnv; } });
|
|
50
|
+
var hookExecution_1 = require("./hookExecution");
|
|
51
|
+
Object.defineProperty(exports, "executeHookWithBoundary", { enumerable: true, get: function () { return hookExecution_1.executeHookWithBoundary; } });
|
|
52
|
+
Object.defineProperty(exports, "HookTimeoutError", { enumerable: true, get: function () { return hookExecution_1.HookTimeoutError; } });
|
|
53
|
+
// v4.9.0 Slice 8 — enforcement layer for missing-context events.
|
|
54
|
+
var enforcement_1 = require("./enforcement");
|
|
55
|
+
Object.defineProperty(exports, "getEnforcementMode", { enumerable: true, get: function () { return enforcement_1.getEnforcementMode; } });
|
|
56
|
+
Object.defineProperty(exports, "reportMissingContext", { enumerable: true, get: function () { return enforcement_1.reportMissingContext; } });
|
|
57
|
+
Object.defineProperty(exports, "ContextMissingError", { enumerable: true, get: function () { return enforcement_1.ContextMissingError; } });
|
|
58
|
+
Object.defineProperty(exports, "getContextMissingCounter", { enumerable: true, get: function () { return enforcement_1.getContextMissingCounter; } });
|
|
59
|
+
Object.defineProperty(exports, "getAllContextMissingCounters", { enumerable: true, get: function () { return enforcement_1.getAllContextMissingCounters; } });
|
|
60
|
+
Object.defineProperty(exports, "_resetContextMissingCountersForTests", { enumerable: true, get: function () { return enforcement_1._resetContextMissingCountersForTests; } });
|
|
@@ -0,0 +1,98 @@
|
|
|
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/identity/subprocessContext.ts — v4.9.0 Slice 7.
|
|
10
|
+
*
|
|
11
|
+
* AsyncLocalStorage does NOT cross subprocess boundaries; the child's
|
|
12
|
+
* Node runtime has its own ALS instance. To preserve correlation, we
|
|
13
|
+
* stamp the parent's ExecutionContext into the child's `env` block at
|
|
14
|
+
* spawn time. The child reconstitutes via `readContextFromEnv` and
|
|
15
|
+
* enters its own `runWithContext(ctx, fn)` frame on startup.
|
|
16
|
+
*
|
|
17
|
+
* Wire-format choice: ten env vars instead of one JSON-encoded blob.
|
|
18
|
+
* Reasons:
|
|
19
|
+
* - Greppable in `ps -ef`-style output for forensics
|
|
20
|
+
* - Individual vars survive partial truncation by intermediate
|
|
21
|
+
* shells / sudo / containers that mangle long env values
|
|
22
|
+
* - The set is small + stable; no schema-version footgun
|
|
23
|
+
*
|
|
24
|
+
* The child's spanId is ALWAYS freshly minted — never reuse the
|
|
25
|
+
* parent's spanId in the child, because the child's spans are
|
|
26
|
+
* causally downstream and should chain via `parentSpanId`.
|
|
27
|
+
*/
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.spawnEnvWithContext = spawnEnvWithContext;
|
|
30
|
+
exports.readContextFromEnv = readContextFromEnv;
|
|
31
|
+
const ids_1 = require("./ids");
|
|
32
|
+
const ENV_PREFIX = 'AIDEN_';
|
|
33
|
+
/**
|
|
34
|
+
* Build a child-process env block carrying the parent's context.
|
|
35
|
+
* Caller spreads `baseEnv` first so AIDEN_* keys win — these are
|
|
36
|
+
* never user-overridable from the parent invocation.
|
|
37
|
+
*/
|
|
38
|
+
function spawnEnvWithContext(ctx, baseEnv = process.env) {
|
|
39
|
+
return {
|
|
40
|
+
...baseEnv,
|
|
41
|
+
[`${ENV_PREFIX}DAEMON_ID`]: ctx.daemonId,
|
|
42
|
+
[`${ENV_PREFIX}INCARNATION_ID`]: ctx.incarnationId,
|
|
43
|
+
[`${ENV_PREFIX}RUN_ID`]: ctx.runId,
|
|
44
|
+
[`${ENV_PREFIX}TRACE_ID`]: ctx.traceId,
|
|
45
|
+
// Child's parent IS this span; child mints its own spanId on read.
|
|
46
|
+
[`${ENV_PREFIX}PARENT_SPAN_ID`]: ctx.spanId,
|
|
47
|
+
[`${ENV_PREFIX}REQUEST_ID`]: ctx.requestId ?? '',
|
|
48
|
+
[`${ENV_PREFIX}SESSION_ID`]: ctx.sessionId ?? '',
|
|
49
|
+
[`${ENV_PREFIX}SOURCE`]: ctx.source,
|
|
50
|
+
[`${ENV_PREFIX}ATTEMPT`]: String(ctx.attempt),
|
|
51
|
+
[`${ENV_PREFIX}TRIGGER_ID`]: ctx.triggerId ?? '',
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const VALID_SOURCES = new Set([
|
|
55
|
+
'cli', 'api', 'webhook', 'cron', 'email', 'folder', 'subagent', 'unknown',
|
|
56
|
+
]);
|
|
57
|
+
/**
|
|
58
|
+
* Reconstruct an ExecutionContext from env vars. Returns `null` when
|
|
59
|
+
* any of the three required fields (daemonId, incarnationId, runId)
|
|
60
|
+
* are absent — child wasn't spawned with context.
|
|
61
|
+
*
|
|
62
|
+
* The child's `spanId` is ALWAYS freshly minted; the parent's spanId
|
|
63
|
+
* becomes the child's `parentSpanId`. `source` falls back to
|
|
64
|
+
* `'subagent'` (the most common child case) if the env didn't carry it.
|
|
65
|
+
*/
|
|
66
|
+
function readContextFromEnv(env = process.env) {
|
|
67
|
+
const daemonId = env[`${ENV_PREFIX}DAEMON_ID`];
|
|
68
|
+
const incarnationId = env[`${ENV_PREFIX}INCARNATION_ID`];
|
|
69
|
+
const runId = env[`${ENV_PREFIX}RUN_ID`];
|
|
70
|
+
if (!daemonId || !incarnationId || !runId)
|
|
71
|
+
return null;
|
|
72
|
+
const sourceRaw = env[`${ENV_PREFIX}SOURCE`] ?? 'subagent';
|
|
73
|
+
const source = (VALID_SOURCES.has(sourceRaw)
|
|
74
|
+
? sourceRaw
|
|
75
|
+
: 'subagent');
|
|
76
|
+
const attemptRaw = env[`${ENV_PREFIX}ATTEMPT`] ?? '1';
|
|
77
|
+
const attempt = Number.parseInt(attemptRaw, 10);
|
|
78
|
+
const ctx = {
|
|
79
|
+
daemonId,
|
|
80
|
+
incarnationId,
|
|
81
|
+
runId,
|
|
82
|
+
traceId: env[`${ENV_PREFIX}TRACE_ID`] ?? '',
|
|
83
|
+
spanId: (0, ids_1.newSpanId)(),
|
|
84
|
+
parentSpanId: env[`${ENV_PREFIX}PARENT_SPAN_ID`] || undefined,
|
|
85
|
+
source,
|
|
86
|
+
attempt: Number.isFinite(attempt) && attempt > 0 ? attempt : 1,
|
|
87
|
+
};
|
|
88
|
+
const req = env[`${ENV_PREFIX}REQUEST_ID`];
|
|
89
|
+
if (req)
|
|
90
|
+
ctx.requestId = req;
|
|
91
|
+
const sess = env[`${ENV_PREFIX}SESSION_ID`];
|
|
92
|
+
if (sess)
|
|
93
|
+
ctx.sessionId = sess;
|
|
94
|
+
const trig = env[`${ENV_PREFIX}TRIGGER_ID`];
|
|
95
|
+
if (trig)
|
|
96
|
+
ctx.triggerId = trig;
|
|
97
|
+
return ctx;
|
|
98
|
+
}
|