aiden-runtime 4.8.0 → 4.9.0
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 +35 -4
- package/dist/cli/v4/chatSession.js +43 -16
- package/dist/cli/v4/commands/daemon.js +47 -2
- package/dist/cli/v4/commands/daemonDoctor.js +212 -0
- package/dist/cli/v4/commands/daemonStatus.js +1 -1
- package/dist/cli/v4/commands/help.js +2 -0
- package/dist/cli/v4/commands/hooks.js +428 -0
- package/dist/cli/v4/commands/index.js +5 -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 +702 -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/commands/update.js +14 -2
- package/dist/cli/v4/design/tokens.js +52 -4
- package/dist/cli/v4/display.js +102 -46
- package/dist/cli/v4/pasteIntercept.js +214 -70
- package/dist/cli/v4/replyRenderer.js +145 -5
- package/dist/cli/v4/skinEngine.js +67 -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/promptBuilder.js +6 -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/executeInstall.js +10 -6
- package/dist/core/v4/update/installMethodDetect.js +7 -0
- package/dist/core/version.js +67 -2
- 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 -3
- 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,114 @@
|
|
|
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/traceparent.ts — v4.9.0 Slice 7.
|
|
10
|
+
*
|
|
11
|
+
* W3C Trace Context (https://www.w3.org/TR/trace-context/) parse +
|
|
12
|
+
* emit for the `traceparent` HTTP header. We DO NOT swap Aiden's
|
|
13
|
+
* typed-prefix `trc_<uuidv7>` ID; instead, an inbound trace's 32-hex
|
|
14
|
+
* traceId is stored alongside in `spans.external_trace_id` /
|
|
15
|
+
* `runs.external_trace_id` (schema v10). That gives us debuggable
|
|
16
|
+
* typed IDs internally while still letting an external caller
|
|
17
|
+
* correlate via W3C-standard headers.
|
|
18
|
+
*
|
|
19
|
+
* traceparent: 00-<32hex traceId>-<16hex spanId>-<2hex flags>
|
|
20
|
+
*
|
|
21
|
+
* Total length: 4 + 32 + 16 + 2 + 3 dashes = 55 chars exactly.
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.parseTraceparent = parseTraceparent;
|
|
25
|
+
exports.emitTraceparent = emitTraceparent;
|
|
26
|
+
exports.stripPrefix = stripPrefix;
|
|
27
|
+
exports.validateExternalRequestId = validateExternalRequestId;
|
|
28
|
+
const RE_HEX_32 = /^[0-9a-f]{32}$/;
|
|
29
|
+
const RE_HEX_16 = /^[0-9a-f]{16}$/;
|
|
30
|
+
const RE_HEX_2 = /^[0-9a-f]{2}$/;
|
|
31
|
+
const ZERO_TRACE = '00000000000000000000000000000000';
|
|
32
|
+
const ZERO_SPAN = '0000000000000000';
|
|
33
|
+
/**
|
|
34
|
+
* Parse a `traceparent` header. Returns `null` on ANY validation
|
|
35
|
+
* failure — caller decides whether to log + generate fresh or fail.
|
|
36
|
+
*
|
|
37
|
+
* Per spec: a vendor MUST NOT propagate an invalid header. We follow
|
|
38
|
+
* the "ignore + start fresh" recommendation so a malformed upstream
|
|
39
|
+
* doesn't poison Aiden's trace.
|
|
40
|
+
*/
|
|
41
|
+
function parseTraceparent(header) {
|
|
42
|
+
if (!header || typeof header !== 'string')
|
|
43
|
+
return null;
|
|
44
|
+
if (header.length !== 55)
|
|
45
|
+
return null;
|
|
46
|
+
const parts = header.split('-');
|
|
47
|
+
if (parts.length !== 4)
|
|
48
|
+
return null;
|
|
49
|
+
const [version, traceId, parentSpanId, flagsHex] = parts;
|
|
50
|
+
if (version !== '00')
|
|
51
|
+
return null;
|
|
52
|
+
if (!RE_HEX_32.test(traceId))
|
|
53
|
+
return null;
|
|
54
|
+
if (!RE_HEX_16.test(parentSpanId))
|
|
55
|
+
return null;
|
|
56
|
+
if (!RE_HEX_2.test(flagsHex))
|
|
57
|
+
return null;
|
|
58
|
+
if (traceId === ZERO_TRACE)
|
|
59
|
+
return null;
|
|
60
|
+
if (parentSpanId === ZERO_SPAN)
|
|
61
|
+
return null;
|
|
62
|
+
const flags = parseInt(flagsHex, 16);
|
|
63
|
+
if (!Number.isFinite(flags))
|
|
64
|
+
return null;
|
|
65
|
+
return { traceId, parentSpanId, flags };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Emit a `traceparent` header from Aiden ID components. The caller
|
|
69
|
+
* strips the typed prefix (`trc_` / `spn_`) and converts the dashless
|
|
70
|
+
* 32-char compact form to the W3C shape. (Aiden's compact UUIDv7 IS
|
|
71
|
+
* 32 lowercase hex chars, so it's directly W3C-compatible after the
|
|
72
|
+
* prefix is removed.)
|
|
73
|
+
*
|
|
74
|
+
* `sampled` defaults to `true` — we want downstream services to record
|
|
75
|
+
* the trace by default. Set to false for low-priority work.
|
|
76
|
+
*/
|
|
77
|
+
function emitTraceparent(traceIdHex, spanIdHex, sampled = true) {
|
|
78
|
+
const t = stripPrefix(traceIdHex, 'trc_');
|
|
79
|
+
const s = stripPrefix(spanIdHex, 'spn_');
|
|
80
|
+
if (!RE_HEX_32.test(t)) {
|
|
81
|
+
throw new Error(`emitTraceparent: traceId must be 32 hex chars (got ${t.length})`);
|
|
82
|
+
}
|
|
83
|
+
// W3C spans are 16 hex; Aiden's span_id compact form is 32. Take
|
|
84
|
+
// the first 16 — UUIDv7's first 16 chars carry the ms timestamp +
|
|
85
|
+
// version + half of randomness, so collisions across one trace are
|
|
86
|
+
// astronomical.
|
|
87
|
+
const s16 = s.length === 16 ? s : s.slice(0, 16);
|
|
88
|
+
if (!RE_HEX_16.test(s16)) {
|
|
89
|
+
throw new Error(`emitTraceparent: spanId must be 16 hex chars (got ${s16.length})`);
|
|
90
|
+
}
|
|
91
|
+
const flags = sampled ? '01' : '00';
|
|
92
|
+
return `00-${t}-${s16}-${flags}`;
|
|
93
|
+
}
|
|
94
|
+
/** Strip the Aiden typed prefix (`trc_` / `spn_`) if present. */
|
|
95
|
+
function stripPrefix(id, prefix) {
|
|
96
|
+
return id.startsWith(prefix) ? id.slice(prefix.length) : id;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Validate an external `X-Request-Id` style header. Per the project
|
|
100
|
+
* rule "don't poison indexes": max 128 chars, ASCII printable only.
|
|
101
|
+
* Returns the value if safe, `null` if it should be dropped + a
|
|
102
|
+
* fresh id generated.
|
|
103
|
+
*/
|
|
104
|
+
function validateExternalRequestId(raw) {
|
|
105
|
+
if (raw === undefined || raw === null)
|
|
106
|
+
return null;
|
|
107
|
+
if (typeof raw !== 'string')
|
|
108
|
+
return null;
|
|
109
|
+
if (raw.length === 0 || raw.length > 128)
|
|
110
|
+
return null;
|
|
111
|
+
if (!/^[\x20-\x7E]+$/.test(raw))
|
|
112
|
+
return null;
|
|
113
|
+
return raw;
|
|
114
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// core/v4/logger/index.ts — Phase v4.1-1.3a barrel export.
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.MultiSink = exports.MemorySink = exports.NullSink = exports.StdoutJsonSink = exports.StderrSink = exports.FileSink = exports.isReplActive = exports.markReplInactive = exports.markReplActive = exports.noopLogger = exports.createBootLogger = exports.CoreLogger = void 0;
|
|
4
|
+
exports.RedactingSink = exports.MultiSink = exports.MemorySink = exports.NullSink = exports.StdoutJsonSink = exports.StderrSink = exports.FileSink = exports.isReplActive = exports.markReplInactive = exports.markReplActive = exports.noopLogger = exports.createBootLogger = exports.CoreLogger = void 0;
|
|
5
5
|
var logger_1 = require("./logger");
|
|
6
6
|
Object.defineProperty(exports, "CoreLogger", { enumerable: true, get: function () { return logger_1.CoreLogger; } });
|
|
7
7
|
var factory_1 = require("./factory");
|
|
@@ -20,3 +20,5 @@ Object.defineProperty(exports, "NullSink", { enumerable: true, get: function ()
|
|
|
20
20
|
Object.defineProperty(exports, "MemorySink", { enumerable: true, get: function () { return nullSink_1.MemorySink; } });
|
|
21
21
|
var multiSink_1 = require("./sinks/multiSink");
|
|
22
22
|
Object.defineProperty(exports, "MultiSink", { enumerable: true, get: function () { return multiSink_1.MultiSink; } });
|
|
23
|
+
var redact_1 = require("./redact");
|
|
24
|
+
Object.defineProperty(exports, "RedactingSink", { enumerable: true, get: function () { return redact_1.RedactingSink; } });
|
|
@@ -37,11 +37,18 @@ class CoreLogger {
|
|
|
37
37
|
/**
|
|
38
38
|
* Construct a root logger. Use `child(segment)` for sub-loggers.
|
|
39
39
|
* `sinks` may be empty — useful for tests; writes silently drop.
|
|
40
|
+
*
|
|
41
|
+
* v4.9.0 Slice 4 — `getContext` (optional) returns ambient fields
|
|
42
|
+
* (e.g. ExecutionContext + identity holders) merged into every
|
|
43
|
+
* record's `ctx`. Defaults to no-op. The Logger NEVER throws when
|
|
44
|
+
* the provider returns undefined or throws — that would defeat the
|
|
45
|
+
* point of an always-on diagnostic channel.
|
|
40
46
|
*/
|
|
41
47
|
constructor(opts) {
|
|
42
48
|
this.scope = opts.scope ?? '';
|
|
43
49
|
this.sinks = opts.sinks;
|
|
44
50
|
this.level = opts.level ?? 'debug';
|
|
51
|
+
this.getContext = opts.getContext;
|
|
45
52
|
this.sinksOwner = {
|
|
46
53
|
sinks: this.sinks,
|
|
47
54
|
level: this.level,
|
|
@@ -57,6 +64,11 @@ class CoreLogger {
|
|
|
57
64
|
sinks: parent.sinksOwner.sinks,
|
|
58
65
|
level: parent.sinksOwner.level,
|
|
59
66
|
sinksOwner: parent.sinksOwner,
|
|
67
|
+
// v4.9.0 Slice 4 — children inherit the parent's context provider
|
|
68
|
+
// so child-loggers stamped via `parent.child('foo')` carry the
|
|
69
|
+
// same identity fields without each sub-logger needing its own
|
|
70
|
+
// wiring.
|
|
71
|
+
getContext: parent.getContext,
|
|
60
72
|
});
|
|
61
73
|
return c;
|
|
62
74
|
}
|
|
@@ -95,12 +107,27 @@ class CoreLogger {
|
|
|
95
107
|
write(level, msg, ctx) {
|
|
96
108
|
if (exports.LOG_LEVEL_ORDER[level] < exports.LOG_LEVEL_ORDER[this.sinksOwner.level])
|
|
97
109
|
return;
|
|
110
|
+
// v4.9.0 Slice 4 — merge ambient context fields. Caller-supplied
|
|
111
|
+
// `ctx` wins on key collision (callers stamp run-specific data with
|
|
112
|
+
// intent; identity fields are the default backdrop). The provider
|
|
113
|
+
// is wrapped in try/catch — project rule "no log formatter throws
|
|
114
|
+
// because context is missing".
|
|
115
|
+
let merged = ctx;
|
|
116
|
+
if (this.getContext) {
|
|
117
|
+
try {
|
|
118
|
+
const ambient = this.getContext();
|
|
119
|
+
if (ambient && Object.keys(ambient).length > 0) {
|
|
120
|
+
merged = { ...ambient, ...(ctx ?? {}) };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch { /* never let a context provider break logging */ }
|
|
124
|
+
}
|
|
98
125
|
const record = {
|
|
99
126
|
ts: new Date(),
|
|
100
127
|
level,
|
|
101
128
|
scope: this.scope,
|
|
102
129
|
msg,
|
|
103
|
-
ctx,
|
|
130
|
+
ctx: merged,
|
|
104
131
|
};
|
|
105
132
|
// Sinks must not throw — the helpers in ./sinks/* all wrap their
|
|
106
133
|
// I/O in try/catch. Be defensive anyway. Phase v4.1.2-slice3:
|
|
@@ -0,0 +1,149 @@
|
|
|
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/logger/redact.ts — v4.9.0 Slice 3.
|
|
10
|
+
*
|
|
11
|
+
* `RedactingSink` wraps any inner `LoggerSink` and scrubs known secret
|
|
12
|
+
* shapes out of `msg` + `ctx` before the inner sink sees the record.
|
|
13
|
+
* It does NOT touch `ts`, `level`, or `scope` — those are operational
|
|
14
|
+
* metadata, never user-supplied.
|
|
15
|
+
*
|
|
16
|
+
* The patterns are intentionally conservative: false positives (a stray
|
|
17
|
+
* `[REDACTED]` in a debug line) are cheap, false negatives (a real key
|
|
18
|
+
* leaking to disk) are not. When in doubt, add a pattern; when a
|
|
19
|
+
* pattern overlaps with normal log content, narrow it.
|
|
20
|
+
*
|
|
21
|
+
* The decorator pattern composes naturally with the existing sinks
|
|
22
|
+
* (FileSink, StderrSink, StdoutJsonSink, MultiSink): wrap whatever the
|
|
23
|
+
* inner sink is, hand the wrapper to `new CoreLogger({ sinks: [...] })`.
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports._SECRET_PATTERNS_FOR_TESTS = exports.RedactingSink = void 0;
|
|
27
|
+
/**
|
|
28
|
+
* Patterns are conservative: each requires either a recognisable
|
|
29
|
+
* prefix (sk-, AKIA, Bearer …) or a labelled context (api_key="…"),
|
|
30
|
+
* so plain user text like a 32-char hash slug doesn't accidentally
|
|
31
|
+
* trip them.
|
|
32
|
+
*/
|
|
33
|
+
const SECRET_PATTERNS = [
|
|
34
|
+
{ name: 'bearer',
|
|
35
|
+
regex: /Bearer\s+[A-Za-z0-9+/=._-]{20,}/g,
|
|
36
|
+
replacement: 'Bearer [REDACTED]' },
|
|
37
|
+
{ name: 'sk-key',
|
|
38
|
+
regex: /sk-[a-zA-Z0-9_-]{20,}/g,
|
|
39
|
+
replacement: 'sk-[REDACTED]' },
|
|
40
|
+
{ name: 'xoxp-key',
|
|
41
|
+
regex: /xox[pbar]-[a-zA-Z0-9-]{10,}/g,
|
|
42
|
+
replacement: 'xox*-[REDACTED]' },
|
|
43
|
+
{ name: 'aws-akia',
|
|
44
|
+
regex: /AKIA[0-9A-Z]{16}/g,
|
|
45
|
+
replacement: 'AKIA[REDACTED]' },
|
|
46
|
+
{ name: 'aws-asia',
|
|
47
|
+
regex: /ASIA[0-9A-Z]{16}/g,
|
|
48
|
+
replacement: 'ASIA[REDACTED]' },
|
|
49
|
+
{ name: 'gcp-key',
|
|
50
|
+
regex: /AIza[0-9A-Za-z_-]{35}/g,
|
|
51
|
+
replacement: 'AIza[REDACTED]' },
|
|
52
|
+
{ name: 'github',
|
|
53
|
+
regex: /gh[ps]_[A-Za-z0-9]{36,}/g,
|
|
54
|
+
replacement: 'gh*_[REDACTED]' },
|
|
55
|
+
// Generic "api_key = secret" / "token: secret" / "password=secret".
|
|
56
|
+
// The `\b` ensures we don't chew up part of a longer identifier; the
|
|
57
|
+
// 20+ char tail keeps short values (e.g. `password=admin`) out.
|
|
58
|
+
{ name: 'generic-labelled',
|
|
59
|
+
regex: /\b(api[_-]?key|token|password|secret)(["':\s=]+)([A-Za-z0-9_-]{20,})/gi,
|
|
60
|
+
replacement: '$1$2[REDACTED]' },
|
|
61
|
+
];
|
|
62
|
+
/** Apply every pattern to a single string. Cheap; called per record. */
|
|
63
|
+
function scrubString(s) {
|
|
64
|
+
let out = s;
|
|
65
|
+
for (const p of SECRET_PATTERNS) {
|
|
66
|
+
out = out.replace(p.regex, p.replacement);
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Recursively scrub strings inside a structured payload. Numbers,
|
|
72
|
+
* booleans, null, undefined pass through. Cycles collapse to
|
|
73
|
+
* `[circular]` so a misbehaving caller can't OOM the sink.
|
|
74
|
+
*
|
|
75
|
+
* Depth is capped at 8 — deep enough for normal `ctx` shapes (error
|
|
76
|
+
* + stack + nested cause), shallow enough that pathological inputs
|
|
77
|
+
* don't burn CPU.
|
|
78
|
+
*/
|
|
79
|
+
function scrubValue(v, seen, depth) {
|
|
80
|
+
if (depth > 8)
|
|
81
|
+
return '[depth-capped]';
|
|
82
|
+
if (v === null || v === undefined)
|
|
83
|
+
return v;
|
|
84
|
+
if (typeof v === 'string')
|
|
85
|
+
return scrubString(v);
|
|
86
|
+
if (typeof v === 'number' || typeof v === 'boolean' || typeof v === 'bigint')
|
|
87
|
+
return v;
|
|
88
|
+
if (typeof v === 'function' || typeof v === 'symbol')
|
|
89
|
+
return String(v);
|
|
90
|
+
if (v instanceof Error) {
|
|
91
|
+
return {
|
|
92
|
+
type: v.name,
|
|
93
|
+
message: scrubString(v.message),
|
|
94
|
+
stack: v.stack ? scrubString(v.stack) : undefined,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
if (typeof v === 'object') {
|
|
98
|
+
if (seen.has(v))
|
|
99
|
+
return '[circular]';
|
|
100
|
+
seen.add(v);
|
|
101
|
+
if (Array.isArray(v)) {
|
|
102
|
+
return v.map((item) => scrubValue(item, seen, depth + 1));
|
|
103
|
+
}
|
|
104
|
+
const out = {};
|
|
105
|
+
for (const [k, val] of Object.entries(v)) {
|
|
106
|
+
out[k] = scrubValue(val, seen, depth + 1);
|
|
107
|
+
}
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
return v;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* `LoggerSink` decorator. Construct with the inner sink you want to
|
|
114
|
+
* protect; `write()` produces a redacted copy of the record before
|
|
115
|
+
* delegating. Never mutates the input — the original `record.msg` /
|
|
116
|
+
* `record.ctx` are untouched (other sinks attached to the same
|
|
117
|
+
* `CoreLogger` see their own unredacted copies if they're not
|
|
118
|
+
* themselves wrapped).
|
|
119
|
+
*
|
|
120
|
+
* Operational metadata (`ts`, `level`, `scope`) bypasses redaction:
|
|
121
|
+
* those fields can't carry user input, and aggregators key on them.
|
|
122
|
+
*/
|
|
123
|
+
class RedactingSink {
|
|
124
|
+
constructor(inner) {
|
|
125
|
+
this.inner = inner;
|
|
126
|
+
this.name = inner.name ? `redact:${inner.name}` : 'redact:anon';
|
|
127
|
+
}
|
|
128
|
+
write(record) {
|
|
129
|
+
const seen = new WeakSet();
|
|
130
|
+
const scrubbed = {
|
|
131
|
+
ts: record.ts,
|
|
132
|
+
level: record.level,
|
|
133
|
+
scope: record.scope,
|
|
134
|
+
msg: scrubString(record.msg),
|
|
135
|
+
ctx: record.ctx
|
|
136
|
+
? scrubValue(record.ctx, seen, 0)
|
|
137
|
+
: undefined,
|
|
138
|
+
};
|
|
139
|
+
this.inner.write(scrubbed);
|
|
140
|
+
}
|
|
141
|
+
async close() {
|
|
142
|
+
if (typeof this.inner.close === 'function') {
|
|
143
|
+
await this.inner.close();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
exports.RedactingSink = RedactingSink;
|
|
148
|
+
/** Test seam — pattern list shape is exposed for assertions. */
|
|
149
|
+
exports._SECRET_PATTERNS_FOR_TESTS = SECRET_PATTERNS.map((p) => ({ name: p.name }));
|
|
@@ -34,6 +34,8 @@ class FileSink {
|
|
|
34
34
|
this.dirReady = false;
|
|
35
35
|
this.dir = opts.dir;
|
|
36
36
|
this.filePath = node_path_1.default.join(opts.dir, `${opts.name}.log`);
|
|
37
|
+
this.fmt = opts.format ?? 'human';
|
|
38
|
+
this.name = `file:${this.filePath}`;
|
|
37
39
|
}
|
|
38
40
|
write(record) {
|
|
39
41
|
if (!this.ensureDir())
|
|
@@ -88,6 +90,17 @@ class FileSink {
|
|
|
88
90
|
* 2026-05-08T01:32:50.001Z [warn] [channels.telegram] Polling 409 {"streak":1}
|
|
89
91
|
*/
|
|
90
92
|
format(r) {
|
|
93
|
+
if (this.fmt === 'ndjson') {
|
|
94
|
+
const payload = {
|
|
95
|
+
ts: r.ts.toISOString(),
|
|
96
|
+
level: r.level,
|
|
97
|
+
scope: r.scope || undefined,
|
|
98
|
+
msg: r.msg,
|
|
99
|
+
};
|
|
100
|
+
if (r.ctx)
|
|
101
|
+
Object.assign(payload, r.ctx);
|
|
102
|
+
return safeJson(payload) + '\n';
|
|
103
|
+
}
|
|
91
104
|
const scope = r.scope ? ` [${r.scope}]` : '';
|
|
92
105
|
const ctx = r.ctx && Object.keys(r.ctx).length > 0
|
|
93
106
|
? ' ' + safeJson(r.ctx)
|
|
@@ -30,19 +30,37 @@ const LEVEL_THRESHOLD = {
|
|
|
30
30
|
};
|
|
31
31
|
class StderrSink {
|
|
32
32
|
constructor(opts = {}) {
|
|
33
|
+
this.name = 'stderr';
|
|
33
34
|
this.minLevel = LEVEL_THRESHOLD[opts.minLevel ?? 'warn'];
|
|
35
|
+
this.pretty = opts.pretty ?? false;
|
|
34
36
|
}
|
|
35
37
|
write(r) {
|
|
36
38
|
if (LEVEL_THRESHOLD[r.level] < this.minLevel)
|
|
37
39
|
return;
|
|
38
40
|
const scope = r.scope ? ` [${r.scope}]` : '';
|
|
39
41
|
try {
|
|
40
|
-
|
|
42
|
+
if (this.pretty) {
|
|
43
|
+
const ts = formatShortTime(r.ts);
|
|
44
|
+
const runId = typeof r.ctx?.runId === 'string' ? r.ctx.runId : '';
|
|
45
|
+
// Dim ANSI 2 = faint; 22 = reset intensity. Suffix renders as
|
|
46
|
+
// ` (run_…last8)` only when an ambient runId is present.
|
|
47
|
+
const suffix = runId.length >= 8
|
|
48
|
+
? ` \x1b[2m(${runId.slice(-8)})\x1b[22m`
|
|
49
|
+
: '';
|
|
50
|
+
process.stderr.write(`${ts} [${r.level}]${scope} ${r.msg}${suffix}\n`);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
process.stderr.write(`${r.ts.toISOString()} [${r.level}]${scope} ${r.msg}\n`);
|
|
54
|
+
}
|
|
41
55
|
}
|
|
42
56
|
catch { /* dropped */ }
|
|
43
57
|
}
|
|
44
58
|
}
|
|
45
59
|
exports.StderrSink = StderrSink;
|
|
60
|
+
function formatShortTime(d) {
|
|
61
|
+
const pad = (n) => (n < 10 ? `0${n}` : String(n));
|
|
62
|
+
return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
63
|
+
}
|
|
46
64
|
/**
|
|
47
65
|
* NDJSON stdout for `serve` mode. One record per line, structured
|
|
48
66
|
* fields preserved. Aggregators love this; humans don't. Headless
|
|
@@ -0,0 +1,78 @@
|
|
|
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/mcp/install/backup.ts — v4.9.0 Slice 2a.
|
|
10
|
+
*
|
|
11
|
+
* Timestamped backup of a third-party client config before Aiden's
|
|
12
|
+
* `init` / `repair` writes to it. Backups are kept indefinitely so
|
|
13
|
+
* a user can recover if Aiden's entry breaks something downstream.
|
|
14
|
+
* Naming: `<configPath>.aiden-backup-YYYYMMDD-HHMMSS`.
|
|
15
|
+
*/
|
|
16
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
17
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.backupConfig = backupConfig;
|
|
21
|
+
exports.countBackups = countBackups;
|
|
22
|
+
exports.findLatestBackup = findLatestBackup;
|
|
23
|
+
const node_fs_1 = require("node:fs");
|
|
24
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
25
|
+
/** Generate a timestamp suffix matching the YYYYMMDD-HHMMSS pattern. */
|
|
26
|
+
function nowStamp(date = new Date()) {
|
|
27
|
+
const pad = (n, w = 2) => String(n).padStart(w, '0');
|
|
28
|
+
return (`${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}` +
|
|
29
|
+
`-${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Create a backup of `configPath`. Returns the backup path on success,
|
|
33
|
+
* or null if the source doesn't exist (first-time init — nothing to
|
|
34
|
+
* back up). Re-throws fs errors so the caller can abort cleanly.
|
|
35
|
+
*/
|
|
36
|
+
function backupConfig(configPath, now = new Date()) {
|
|
37
|
+
if (!(0, node_fs_1.existsSync)(configPath))
|
|
38
|
+
return null;
|
|
39
|
+
const backupPath = `${configPath}.aiden-backup-${nowStamp(now)}`;
|
|
40
|
+
(0, node_fs_1.copyFileSync)(configPath, backupPath);
|
|
41
|
+
return backupPath;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Count existing backups for a given config path. Used by `doctor`
|
|
45
|
+
* to report backup count + by tests.
|
|
46
|
+
*/
|
|
47
|
+
function countBackups(configPath) {
|
|
48
|
+
const dir = node_path_1.default.dirname(configPath);
|
|
49
|
+
const base = node_path_1.default.basename(configPath);
|
|
50
|
+
if (!(0, node_fs_1.existsSync)(dir))
|
|
51
|
+
return 0;
|
|
52
|
+
try {
|
|
53
|
+
return (0, node_fs_1.readdirSync)(dir).filter((f) => f.startsWith(`${base}.aiden-backup-`)).length;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Find the newest backup matching `<configPath>.aiden-backup-*`.
|
|
61
|
+
* Used by `repair` when restoring from a corrupted edit.
|
|
62
|
+
*/
|
|
63
|
+
function findLatestBackup(configPath) {
|
|
64
|
+
const dir = node_path_1.default.dirname(configPath);
|
|
65
|
+
const base = node_path_1.default.basename(configPath);
|
|
66
|
+
if (!(0, node_fs_1.existsSync)(dir))
|
|
67
|
+
return null;
|
|
68
|
+
try {
|
|
69
|
+
const files = (0, node_fs_1.readdirSync)(dir).filter((f) => f.startsWith(`${base}.aiden-backup-`));
|
|
70
|
+
if (files.length === 0)
|
|
71
|
+
return null;
|
|
72
|
+
files.sort(); // timestamp suffix sorts lexicographically = newest last
|
|
73
|
+
return node_path_1.default.join(dir, files[files.length - 1]);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
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/mcp/install/clientPaths.ts — v4.9.0 Slice 2a.
|
|
10
|
+
*
|
|
11
|
+
* Per-client per-OS config-file path discovery. No hardcoded user
|
|
12
|
+
* paths — every path is built from `os.homedir()` / `process.env`
|
|
13
|
+
* at call time so tests can override via the env.
|
|
14
|
+
*/
|
|
15
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
16
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
17
|
+
};
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.resolveClientPath = resolveClientPath;
|
|
20
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
21
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
22
|
+
/**
|
|
23
|
+
* Resolve the config path for `clientId` on the host platform. Pass a
|
|
24
|
+
* custom env / platform / homedir override for tests.
|
|
25
|
+
*/
|
|
26
|
+
const MCP_SERVERS_SCHEMA = { topKey: 'mcpServers' };
|
|
27
|
+
const VSCODE_SCHEMA = { topKey: 'servers', requiresType: true };
|
|
28
|
+
function resolveClientPath(clientId, opts = {}) {
|
|
29
|
+
const platform = opts.platform ?? process.platform;
|
|
30
|
+
const homedir = opts.homedir ?? node_os_1.default.homedir();
|
|
31
|
+
const env = opts.env ?? process.env;
|
|
32
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
33
|
+
if (clientId === 'vscode') {
|
|
34
|
+
// v4.9.0 Slice 2b — workspace-only config. The user must run
|
|
35
|
+
// `aiden mcp init vscode` from inside a VS Code project (where
|
|
36
|
+
// `.vscode/` is meaningful). We don't fabricate a global path;
|
|
37
|
+
// VS Code doesn't ship a stable global MCP config location.
|
|
38
|
+
const wsDir = node_path_1.default.join(cwd, '.vscode');
|
|
39
|
+
const wsFile = node_path_1.default.join(wsDir, 'mcp.json');
|
|
40
|
+
return {
|
|
41
|
+
configPath: wsFile,
|
|
42
|
+
parentDir: wsDir,
|
|
43
|
+
displayName: 'VS Code (workspace)',
|
|
44
|
+
format: 'jsonc',
|
|
45
|
+
schema: VSCODE_SCHEMA,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (clientId === 'claude') {
|
|
49
|
+
if (platform === 'darwin') {
|
|
50
|
+
const dir = node_path_1.default.join(homedir, 'Library', 'Application Support', 'Claude');
|
|
51
|
+
return {
|
|
52
|
+
configPath: node_path_1.default.join(dir, 'claude_desktop_config.json'),
|
|
53
|
+
parentDir: dir,
|
|
54
|
+
displayName: 'Claude Desktop',
|
|
55
|
+
format: 'json',
|
|
56
|
+
schema: MCP_SERVERS_SCHEMA,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
if (platform === 'win32') {
|
|
60
|
+
const appData = env.APPDATA ?? node_path_1.default.join(homedir, 'AppData', 'Roaming');
|
|
61
|
+
const dir = node_path_1.default.join(appData, 'Claude');
|
|
62
|
+
return {
|
|
63
|
+
configPath: node_path_1.default.join(dir, 'claude_desktop_config.json'),
|
|
64
|
+
parentDir: dir,
|
|
65
|
+
displayName: 'Claude Desktop',
|
|
66
|
+
format: 'json',
|
|
67
|
+
schema: MCP_SERVERS_SCHEMA,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// Linux / others — Claude Desktop has no official Linux build.
|
|
71
|
+
return {
|
|
72
|
+
configPath: node_path_1.default.join(homedir, '.config', 'Claude', 'claude_desktop_config.json'),
|
|
73
|
+
parentDir: node_path_1.default.join(homedir, '.config', 'Claude'),
|
|
74
|
+
displayName: 'Claude Desktop',
|
|
75
|
+
format: 'json',
|
|
76
|
+
schema: MCP_SERVERS_SCHEMA,
|
|
77
|
+
unsupportedOs: true,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// Cursor — same `~/.cursor/mcp.json` layout across all three OSes.
|
|
81
|
+
// Windows uses USERPROFILE if HOME isn't set; we already resolved
|
|
82
|
+
// via homedir which handles both.
|
|
83
|
+
return {
|
|
84
|
+
configPath: node_path_1.default.join(homedir, '.cursor', 'mcp.json'),
|
|
85
|
+
parentDir: node_path_1.default.join(homedir, '.cursor'),
|
|
86
|
+
displayName: 'Cursor',
|
|
87
|
+
format: 'jsonc',
|
|
88
|
+
schema: MCP_SERVERS_SCHEMA,
|
|
89
|
+
};
|
|
90
|
+
}
|