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,95 @@
|
|
|
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/theme/themeWatcher.ts — v4.9.0 Slice 1a.
|
|
10
|
+
*
|
|
11
|
+
* chokidar-backed file watcher for `~/.aiden/theme.yaml`. On change
|
|
12
|
+
* (debounced 200ms to avoid mid-save partial reads), re-parses the
|
|
13
|
+
* file and applies it via the ThemeRegistry. Self-contained — caller
|
|
14
|
+
* just calls `startThemeWatcher(path)` once at REPL start and
|
|
15
|
+
* `stopThemeWatcher()` on quit.
|
|
16
|
+
*/
|
|
17
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
18
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
19
|
+
};
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.startThemeWatcher = startThemeWatcher;
|
|
22
|
+
exports.stopThemeWatcher = stopThemeWatcher;
|
|
23
|
+
exports._isWatcherActive = _isWatcherActive;
|
|
24
|
+
const chokidar_1 = __importDefault(require("chokidar"));
|
|
25
|
+
const node_fs_1 = require("node:fs");
|
|
26
|
+
const themeLoader_1 = require("./themeLoader");
|
|
27
|
+
const themeRegistry_1 = require("./themeRegistry");
|
|
28
|
+
const DEBOUNCE_MS = 200;
|
|
29
|
+
let active = null;
|
|
30
|
+
/**
|
|
31
|
+
* Start watching the theme file. If the file already exists at start,
|
|
32
|
+
* applies it immediately (so the first paint reflects the user's
|
|
33
|
+
* choice). Idempotent — calling twice replaces the previous watcher.
|
|
34
|
+
*/
|
|
35
|
+
function startThemeWatcher(themePath, opts = {}) {
|
|
36
|
+
stopThemeWatcher();
|
|
37
|
+
const debounceMs = opts.debounceMs ?? DEBOUNCE_MS;
|
|
38
|
+
const onWarn = opts.onWarn ?? ((m) => console.warn(`[theme] ${m}`));
|
|
39
|
+
// Apply on first-existence so the REPL boots into the user's theme.
|
|
40
|
+
if ((0, node_fs_1.existsSync)(themePath)) {
|
|
41
|
+
const { parsed, warnings } = (0, themeLoader_1.loadThemeFile)(themePath);
|
|
42
|
+
for (const w of warnings)
|
|
43
|
+
onWarn(w);
|
|
44
|
+
if (parsed)
|
|
45
|
+
(0, themeRegistry_1.applyTheme)(parsed, themePath);
|
|
46
|
+
}
|
|
47
|
+
const watcher = chokidar_1.default.watch(themePath, {
|
|
48
|
+
persistent: true,
|
|
49
|
+
awaitWriteFinish: { stabilityThreshold: debounceMs, pollInterval: 50 },
|
|
50
|
+
ignoreInitial: true,
|
|
51
|
+
});
|
|
52
|
+
const state = {
|
|
53
|
+
watcher,
|
|
54
|
+
debounceTimer: null,
|
|
55
|
+
themePath,
|
|
56
|
+
onWarn,
|
|
57
|
+
};
|
|
58
|
+
const reload = () => {
|
|
59
|
+
if (state.debounceTimer)
|
|
60
|
+
clearTimeout(state.debounceTimer);
|
|
61
|
+
state.debounceTimer = setTimeout(() => {
|
|
62
|
+
state.debounceTimer = null;
|
|
63
|
+
if (!(0, node_fs_1.existsSync)(themePath)) {
|
|
64
|
+
(0, themeRegistry_1.resetToDefault)();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const { parsed, warnings } = (0, themeLoader_1.loadThemeFile)(themePath);
|
|
68
|
+
for (const w of warnings)
|
|
69
|
+
onWarn(w);
|
|
70
|
+
if (parsed)
|
|
71
|
+
(0, themeRegistry_1.applyTheme)(parsed, themePath);
|
|
72
|
+
}, debounceMs);
|
|
73
|
+
};
|
|
74
|
+
watcher.on('add', reload);
|
|
75
|
+
watcher.on('change', reload);
|
|
76
|
+
watcher.on('unlink', () => {
|
|
77
|
+
if (state.debounceTimer)
|
|
78
|
+
clearTimeout(state.debounceTimer);
|
|
79
|
+
state.debounceTimer = null;
|
|
80
|
+
(0, themeRegistry_1.resetToDefault)();
|
|
81
|
+
});
|
|
82
|
+
watcher.on('error', (err) => onWarn(`watcher error: ${err.message}`));
|
|
83
|
+
active = state;
|
|
84
|
+
}
|
|
85
|
+
/** Stop the active watcher. No-op when none is running. */
|
|
86
|
+
function stopThemeWatcher() {
|
|
87
|
+
if (!active)
|
|
88
|
+
return;
|
|
89
|
+
if (active.debounceTimer)
|
|
90
|
+
clearTimeout(active.debounceTimer);
|
|
91
|
+
void active.watcher.close();
|
|
92
|
+
active = null;
|
|
93
|
+
}
|
|
94
|
+
/** Test helper. */
|
|
95
|
+
function _isWatcherActive() { return active !== null; }
|
|
@@ -186,15 +186,40 @@ class ToolRegistry {
|
|
|
186
186
|
};
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
|
+
// v4.9.0 Slice 6 — wrap the handler call in a tool span when the
|
|
190
|
+
// daemon foundation is up AND an ExecutionContext is active. NOOP
|
|
191
|
+
// outside daemon mode or outside a runWithContext frame. Lazy
|
|
192
|
+
// require avoids pulling daemon code into the v4 core import
|
|
193
|
+
// graph at module load (would break headless / cli-test imports
|
|
194
|
+
// that don't open a DB).
|
|
195
|
+
//
|
|
196
|
+
// v4.9.0 Slice 12a Phase 3 — inside the tool span, fire
|
|
197
|
+
// `tool.call.pre` + `tool.call.post` hooks via `runToolWithHooks`.
|
|
198
|
+
// Mandatory pre-hook blocks surface as HookBlockedError, caught
|
|
199
|
+
// by the outer try/catch and mapped to a structured error result.
|
|
200
|
+
const dispatch = async (a) => handler.execute(a, context);
|
|
201
|
+
let result;
|
|
189
202
|
try {
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
203
|
+
const sliced = sliceSpanShim();
|
|
204
|
+
if (sliced && sliced.db && sliced.hasContext()) {
|
|
205
|
+
const sideEffect = sliced.classifySideEffect(handler);
|
|
206
|
+
const inputFp = sliced.fingerprint(args);
|
|
207
|
+
result = await sliced.withToolSpan(sliced.db, { toolName: call.name, inputFingerprint: inputFp, sideEffectClass: sideEffect }, async (childCtx) => sliced.runToolWithHooks({
|
|
208
|
+
db: sliced.db,
|
|
209
|
+
toolName: call.name,
|
|
210
|
+
toolCallId: call.id,
|
|
211
|
+
args,
|
|
212
|
+
ctx: {
|
|
213
|
+
runId: childCtx.runId,
|
|
214
|
+
traceId: childCtx.traceId,
|
|
215
|
+
spanId: childCtx.spanId,
|
|
216
|
+
parentSpanId: childCtx.parentSpanId,
|
|
217
|
+
},
|
|
218
|
+
}, dispatch));
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
result = await dispatch(args);
|
|
222
|
+
}
|
|
198
223
|
const inner = result;
|
|
199
224
|
const out = { id: call.id, name: call.name, result };
|
|
200
225
|
if (typeof inner?.degraded === 'boolean' && inner.degraded) {
|
|
@@ -206,6 +231,17 @@ class ToolRegistry {
|
|
|
206
231
|
return out;
|
|
207
232
|
}
|
|
208
233
|
catch (err) {
|
|
234
|
+
// v4.9.0 Slice 12a — hook blocks surface as a structured
|
|
235
|
+
// rejection so the model gets the hook's `reason` / `model_message`
|
|
236
|
+
// verbatim instead of a bare exception string.
|
|
237
|
+
if (err instanceof toolHookGate_1.HookBlockedError) {
|
|
238
|
+
return {
|
|
239
|
+
id: call.id,
|
|
240
|
+
name: call.name,
|
|
241
|
+
result: null,
|
|
242
|
+
error: err.modelMessage ?? err.message,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
209
245
|
const message = err instanceof Error ? err.message : String(err);
|
|
210
246
|
return { id: call.id, name: call.name, result: null, error: message };
|
|
211
247
|
}
|
|
@@ -213,3 +249,30 @@ class ToolRegistry {
|
|
|
213
249
|
}
|
|
214
250
|
}
|
|
215
251
|
exports.ToolRegistry = ToolRegistry;
|
|
252
|
+
// v4.9.0 Slice 6 — static imports for the span-shim bridge. Earlier
|
|
253
|
+
// attempts used lazy `require()` to keep daemon code out of the import
|
|
254
|
+
// graph when the test harness doesn't compile it; that path broke
|
|
255
|
+
// under vite-node which doesn't intercept CJS require for `.ts`
|
|
256
|
+
// targets. Static ESM imports work in both vitest + production builds.
|
|
257
|
+
const bootstrap_1 = require("./daemon/bootstrap");
|
|
258
|
+
const spanHelpers_1 = require("./daemon/spans/spanHelpers");
|
|
259
|
+
const identity_1 = require("./identity");
|
|
260
|
+
const toolHookGate_1 = require("./hooks/toolHookGate");
|
|
261
|
+
function classifySideEffectForHandler(h) {
|
|
262
|
+
if (h.riskTier === 'dangerous')
|
|
263
|
+
return 'destructive';
|
|
264
|
+
if (h.mutates === false)
|
|
265
|
+
return 'read';
|
|
266
|
+
if (h.mutates === true)
|
|
267
|
+
return 'mutating';
|
|
268
|
+
return 'read';
|
|
269
|
+
}
|
|
270
|
+
const _toolSpanShim = {
|
|
271
|
+
get db() { return (0, bootstrap_1.getCurrentDaemonDb)(); },
|
|
272
|
+
hasContext: () => (0, identity_1.currentContext)() !== undefined,
|
|
273
|
+
classifySideEffect: classifySideEffectForHandler,
|
|
274
|
+
fingerprint: spanHelpers_1.shortInputFingerprint,
|
|
275
|
+
withToolSpan: spanHelpers_1.withToolSpan,
|
|
276
|
+
runToolWithHooks: toolHookGate_1.runToolWithHooks,
|
|
277
|
+
};
|
|
278
|
+
function sliceSpanShim() { return _toolSpanShim; }
|
|
@@ -0,0 +1,76 @@
|
|
|
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/update/depWarningFilter.ts — v4.9.1.
|
|
10
|
+
*
|
|
11
|
+
* Strip Node `DeprecationWarning` noise (DEP0190 and friends) from
|
|
12
|
+
* npm install stderr before it reaches the user. Filtered lines are
|
|
13
|
+
* preserved in `~/.aiden/logs/update.log` so diagnostics aren't lost.
|
|
14
|
+
*
|
|
15
|
+
* Conservative match — only filters lines that BOTH look like a Node
|
|
16
|
+
* deprecation header AND name a DEP code or the trace-deprecation hint.
|
|
17
|
+
* Legitimate npm errors (EACCES, ENOTFOUND, ENOENT, etc.) pass through.
|
|
18
|
+
*/
|
|
19
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
20
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
21
|
+
};
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.isDeprecationLine = isDeprecationLine;
|
|
24
|
+
exports.splitStderr = splitStderr;
|
|
25
|
+
exports.logFilteredWarnings = logFilteredWarnings;
|
|
26
|
+
const node_fs_1 = require("node:fs");
|
|
27
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
28
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
29
|
+
/** True iff the line is Node-deprecation chatter we should hide. */
|
|
30
|
+
function isDeprecationLine(line) {
|
|
31
|
+
// Node's deprecation banner header: "(node:NNNN) [DEP0190] DeprecationWarning: ..."
|
|
32
|
+
if (/^\s*\(node:\d+\)\s*(?:\[DEP\d+\]\s*)?DeprecationWarning:/.test(line))
|
|
33
|
+
return true;
|
|
34
|
+
// The follow-up hint Node emits underneath the header.
|
|
35
|
+
if (/Use `node --trace-deprecation/.test(line))
|
|
36
|
+
return true;
|
|
37
|
+
// Bare DEP code lines (some Node versions emit these stand-alone).
|
|
38
|
+
if (/^\s*\[DEP\d+\]/.test(line))
|
|
39
|
+
return true;
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Split an stderr blob into `kept` (user-visible) and `filtered`
|
|
44
|
+
* (deprecation noise, routed to the diagnostic log).
|
|
45
|
+
*/
|
|
46
|
+
function splitStderr(blob) {
|
|
47
|
+
if (!blob)
|
|
48
|
+
return { kept: '', filtered: '' };
|
|
49
|
+
const lines = blob.split(/\r?\n/);
|
|
50
|
+
const kept = [];
|
|
51
|
+
const filtered = [];
|
|
52
|
+
for (const ln of lines) {
|
|
53
|
+
if (isDeprecationLine(ln))
|
|
54
|
+
filtered.push(ln);
|
|
55
|
+
else
|
|
56
|
+
kept.push(ln);
|
|
57
|
+
}
|
|
58
|
+
return { kept: kept.join('\n'), filtered: filtered.join('\n') };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Append filtered lines to `~/.aiden/logs/update.log` with an ISO
|
|
62
|
+
* timestamp header. Fail-open: a log-write failure must NEVER crash
|
|
63
|
+
* the install path.
|
|
64
|
+
*/
|
|
65
|
+
async function logFilteredWarnings(filtered, opts = {}) {
|
|
66
|
+
if (!filtered || !filtered.trim())
|
|
67
|
+
return;
|
|
68
|
+
try {
|
|
69
|
+
const root = opts.aidenRoot ?? node_path_1.default.join(node_os_1.default.homedir(), '.aiden');
|
|
70
|
+
const logDir = node_path_1.default.join(root, 'logs');
|
|
71
|
+
await node_fs_1.promises.mkdir(logDir, { recursive: true });
|
|
72
|
+
const entry = `[${new Date().toISOString()}] update.npm-deprecation:\n${filtered}\n\n`;
|
|
73
|
+
await node_fs_1.promises.appendFile(node_path_1.default.join(logDir, 'update.log'), entry, 'utf8');
|
|
74
|
+
}
|
|
75
|
+
catch { /* fail-open */ }
|
|
76
|
+
}
|
|
@@ -42,11 +42,18 @@
|
|
|
42
42
|
* - No registry probe — call `checkForUpdate` first if you need to
|
|
43
43
|
* know whether an install is warranted.
|
|
44
44
|
*/
|
|
45
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
46
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
47
|
+
};
|
|
45
48
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
49
|
exports.INSTALL_TIMEOUT_MS = void 0;
|
|
47
50
|
exports.executeInstall = executeInstall;
|
|
48
51
|
exports.parseInstalledVersion = parseInstalledVersion;
|
|
49
52
|
const node_child_process_1 = require("node:child_process");
|
|
53
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
54
|
+
const depWarningFilter_1 = require("./depWarningFilter");
|
|
55
|
+
const platformInstructions_1 = require("./platformInstructions");
|
|
56
|
+
const progressBar_1 = require("../../../cli/v4/ui/progressBar");
|
|
50
57
|
/** 90 s wall-clock cap. Generous on cold caches / slow networks. */
|
|
51
58
|
exports.INSTALL_TIMEOUT_MS = 90000;
|
|
52
59
|
const DEFAULT_PACKAGE_SPEC = 'aiden-runtime@latest';
|
|
@@ -64,6 +71,9 @@ async function executeInstall(opts = {}) {
|
|
|
64
71
|
const timeoutMs = opts.timeoutMs ?? exports.INSTALL_TIMEOUT_MS;
|
|
65
72
|
const packageSpec = opts.packageSpec ?? DEFAULT_PACKAGE_SPEC;
|
|
66
73
|
const platform = opts.platform ?? process.platform;
|
|
74
|
+
const home = opts.home ?? node_os_1.default.homedir();
|
|
75
|
+
const env = opts.env ?? process.env;
|
|
76
|
+
const onPhase = opts.onPhase ?? ((_p) => { });
|
|
67
77
|
return new Promise((resolve) => {
|
|
68
78
|
const args = ['install', '-g', packageSpec];
|
|
69
79
|
// v4.8.1 Slice 2 — drop `shell: true`. Node 20+ emits
|
|
@@ -78,6 +88,7 @@ async function executeInstall(opts = {}) {
|
|
|
78
88
|
const spawnOpts = {
|
|
79
89
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
80
90
|
};
|
|
91
|
+
onPhase('spawning');
|
|
81
92
|
let child;
|
|
82
93
|
try {
|
|
83
94
|
child = spawn(cmd, args, spawnOpts);
|
|
@@ -92,11 +103,23 @@ async function executeInstall(opts = {}) {
|
|
|
92
103
|
}
|
|
93
104
|
let stdoutBuf = '';
|
|
94
105
|
let stderrBuf = '';
|
|
106
|
+
// v4.9.1 — parse phase signal off each chunk.
|
|
107
|
+
const tryEmitPhase = (chunk) => {
|
|
108
|
+
for (const ln of chunk.split(/\r?\n/)) {
|
|
109
|
+
const p = (0, progressBar_1.detectNpmPhase)(ln);
|
|
110
|
+
if (p)
|
|
111
|
+
onPhase(p);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
95
114
|
child.stdout?.on('data', (chunk) => {
|
|
96
|
-
|
|
115
|
+
const s = chunk.toString();
|
|
116
|
+
stdoutBuf += s;
|
|
117
|
+
tryEmitPhase(s);
|
|
97
118
|
});
|
|
98
119
|
child.stderr?.on('data', (chunk) => {
|
|
99
|
-
|
|
120
|
+
const s = chunk.toString();
|
|
121
|
+
stderrBuf += s;
|
|
122
|
+
tryEmitPhase(s);
|
|
100
123
|
});
|
|
101
124
|
// Timeout — kill the child + resolve as a failure with the captured
|
|
102
125
|
// output so the user sees what npm was doing.
|
|
@@ -121,7 +144,11 @@ async function executeInstall(opts = {}) {
|
|
|
121
144
|
child.on('close', (code) => {
|
|
122
145
|
clearTimeout(timer);
|
|
123
146
|
const stdout = stdoutBuf;
|
|
124
|
-
|
|
147
|
+
// v4.9.1 — strip Node DEP* noise from stderr before any surfacing
|
|
148
|
+
// to the user. Filtered lines land in ~/.aiden/logs/update.log.
|
|
149
|
+
const { kept: stderr, filtered } = (0, depWarningFilter_1.splitStderr)(stderrBuf);
|
|
150
|
+
if (filtered)
|
|
151
|
+
void (0, depWarningFilter_1.logFilteredWarnings)(filtered);
|
|
125
152
|
const exitCode = code ?? -1;
|
|
126
153
|
if (timedOut) {
|
|
127
154
|
resolve({
|
|
@@ -134,14 +161,16 @@ async function executeInstall(opts = {}) {
|
|
|
134
161
|
}
|
|
135
162
|
// Permission-denied: surface platform-specific remediations.
|
|
136
163
|
if (isPermissionDenied(stdout, stderr, exitCode)) {
|
|
164
|
+
onPhase('failed');
|
|
137
165
|
resolve({
|
|
138
166
|
success: false,
|
|
139
|
-
error: permissionDeniedMessage(platform),
|
|
167
|
+
error: permissionDeniedMessage(platform, home, env),
|
|
140
168
|
stdout, stderr, exitCode,
|
|
141
169
|
});
|
|
142
170
|
return;
|
|
143
171
|
}
|
|
144
172
|
if (exitCode !== 0) {
|
|
173
|
+
onPhase('failed');
|
|
145
174
|
resolve({
|
|
146
175
|
success: false,
|
|
147
176
|
error: `Install failed (npm exit ${exitCode}). ` +
|
|
@@ -153,6 +182,7 @@ async function executeInstall(opts = {}) {
|
|
|
153
182
|
}
|
|
154
183
|
// Success — parse installed version from npm output. Pattern:
|
|
155
184
|
// "+ aiden-runtime@4.1.3" or "added 1 package ... aiden-runtime@4.1.3"
|
|
185
|
+
onPhase('installed');
|
|
156
186
|
const installedVersion = parseInstalledVersion(stdout) ?? parseInstalledVersion(stderr) ?? undefined;
|
|
157
187
|
resolve({
|
|
158
188
|
success: true,
|
|
@@ -190,38 +220,14 @@ function isPermissionDenied(stdout, stderr, exitCode) {
|
|
|
190
220
|
return false;
|
|
191
221
|
}
|
|
192
222
|
/**
|
|
193
|
-
* Build the platform-specific copy-paste remediation.
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
* from inside the running REPL.
|
|
223
|
+
* v4.9.1 — Build the platform-specific copy-paste remediation. Delegates
|
|
224
|
+
* to `platformInstructions.ts` for the heavy lifting so the same builder
|
|
225
|
+
* powers both EPERM remediation + stale-prefix warnings + the future
|
|
226
|
+
* `aiden update --setup-user-prefix` helper.
|
|
198
227
|
*/
|
|
199
|
-
function permissionDeniedMessage(platform) {
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
' export PATH=~/.npm-global/bin:$PATH # add to your shell profile\n' +
|
|
203
|
-
' npm install -g aiden-runtime@latest';
|
|
204
|
-
if (platform === 'win32') {
|
|
205
|
-
return [
|
|
206
|
-
'Install failed: permission denied (npm needs Administrator for global install).',
|
|
207
|
-
'',
|
|
208
|
-
'To update manually:',
|
|
209
|
-
' Windows: Open PowerShell as Administrator, then:',
|
|
210
|
-
' npm install -g aiden-runtime@latest',
|
|
211
|
-
'',
|
|
212
|
-
userLocal,
|
|
213
|
-
].join('\n');
|
|
214
|
-
}
|
|
215
|
-
// darwin / linux / others — sudo path.
|
|
216
|
-
return [
|
|
217
|
-
'Install failed: permission denied (npm needs sudo for global install).',
|
|
218
|
-
'',
|
|
219
|
-
'To update manually:',
|
|
220
|
-
' macOS / Linux:',
|
|
221
|
-
' sudo npm install -g aiden-runtime@latest',
|
|
222
|
-
'',
|
|
223
|
-
userLocal,
|
|
224
|
-
].join('\n');
|
|
228
|
+
function permissionDeniedMessage(platform, home, env) {
|
|
229
|
+
const instr = (0, platformInstructions_1.permissionDeniedInstructions)({ platform, home, env });
|
|
230
|
+
return [instr.headline, '', ...instr.steps].join('\n');
|
|
225
231
|
}
|
|
226
232
|
/**
|
|
227
233
|
* Find the installed version in npm output. Two common patterns:
|
|
@@ -0,0 +1,128 @@
|
|
|
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/update/platformInstructions.ts — v4.9.1.
|
|
10
|
+
*
|
|
11
|
+
* Per-platform copy-paste remediation text for:
|
|
12
|
+
* - EPERM / EACCES during global npm install
|
|
13
|
+
* - Stale / risky npm prefix detection
|
|
14
|
+
*
|
|
15
|
+
* Branches purely on `process.platform` (and `$SHELL` for unix shell-
|
|
16
|
+
* rc-file recommendations). Shell syntax MUST be correct per-platform:
|
|
17
|
+
* PowerShell on Windows, bash/zsh on Unix. Cross-contamination is a
|
|
18
|
+
* regression (the v4.9.0 bug we're hot-fixing).
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.detectShell = detectShell;
|
|
22
|
+
exports.permissionDeniedInstructions = permissionDeniedInstructions;
|
|
23
|
+
exports.detectStalePrefix = detectStalePrefix;
|
|
24
|
+
/**
|
|
25
|
+
* Detect the user's interactive shell on POSIX. Returns the basename
|
|
26
|
+
* (`zsh` / `bash` / `sh`) or null when undetectable. Pure — env is
|
|
27
|
+
* injected so tests pin a value.
|
|
28
|
+
*/
|
|
29
|
+
function detectShell(env = process.env) {
|
|
30
|
+
const sh = env.SHELL;
|
|
31
|
+
if (!sh)
|
|
32
|
+
return null;
|
|
33
|
+
const last = sh.split(/[\\/]/).pop() || '';
|
|
34
|
+
return last.toLowerCase() || null;
|
|
35
|
+
}
|
|
36
|
+
/** rc-file path the user should edit, per shell. POSIX only. */
|
|
37
|
+
function rcFileFor(shell, home) {
|
|
38
|
+
if (shell === 'zsh')
|
|
39
|
+
return `${home}/.zshrc`;
|
|
40
|
+
if (shell === 'bash')
|
|
41
|
+
return `${home}/.bashrc (or ~/.bash_profile on macOS)`;
|
|
42
|
+
return `${home}/.profile`;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Build the EPERM/EACCES remediation. Two options per platform —
|
|
46
|
+
* elevation (one-time) and user-local prefix (permanent, no privs).
|
|
47
|
+
*/
|
|
48
|
+
function permissionDeniedInstructions(opts) {
|
|
49
|
+
const env = opts.env ?? process.env;
|
|
50
|
+
if (opts.platform === 'win32') {
|
|
51
|
+
return {
|
|
52
|
+
headline: 'Install failed: permission denied. npm needs Administrator for a global install on Windows.',
|
|
53
|
+
shell: 'powershell',
|
|
54
|
+
steps: [
|
|
55
|
+
'Option 1 — Run once with elevated privileges:',
|
|
56
|
+
' • Open PowerShell as Administrator (right-click → "Run as administrator")',
|
|
57
|
+
' • npm install -g aiden-runtime@latest',
|
|
58
|
+
'',
|
|
59
|
+
'Option 2 — Permanent: switch npm to a user-local prefix (no admin needed ever again):',
|
|
60
|
+
' • npm config set prefix "$env:USERPROFILE\\AppData\\Roaming\\npm"',
|
|
61
|
+
' • [Environment]::SetEnvironmentVariable("Path", "$env:USERPROFILE\\AppData\\Roaming\\npm;" + [Environment]::GetEnvironmentVariable("Path", "User"), "User")',
|
|
62
|
+
' • Close + reopen PowerShell, then: npm install -g aiden-runtime@latest',
|
|
63
|
+
],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// POSIX: darwin / linux / *bsd / etc.
|
|
67
|
+
const shell = detectShell(env);
|
|
68
|
+
const rcFile = rcFileFor(shell, opts.home);
|
|
69
|
+
return {
|
|
70
|
+
headline: `Install failed: permission denied. npm needs sudo for a global install on ${opts.platform}.`,
|
|
71
|
+
shell: shell ?? undefined,
|
|
72
|
+
rcFile,
|
|
73
|
+
steps: [
|
|
74
|
+
'Option 1 — Run once with elevated privileges:',
|
|
75
|
+
' • sudo npm install -g aiden-runtime@latest',
|
|
76
|
+
'',
|
|
77
|
+
'Option 2 — Permanent: switch npm to a user-local prefix (no sudo needed ever again):',
|
|
78
|
+
` • npm config set prefix "${opts.home}/.npm-global"`,
|
|
79
|
+
` • echo 'export PATH="${opts.home}/.npm-global/bin:$PATH"' >> ${rcFile}`,
|
|
80
|
+
` • source ${rcFile}`,
|
|
81
|
+
' • npm install -g aiden-runtime@latest',
|
|
82
|
+
],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Stale / risky prefix detection. Returns a warning when the npm
|
|
87
|
+
* `prefix` config points at a location that needs elevation OR is
|
|
88
|
+
* known to cause permission churn. `null` when the prefix is safe.
|
|
89
|
+
*
|
|
90
|
+
* `writable` is the result of a `fs.access` check the caller does
|
|
91
|
+
* before invoking us (we don't want to do filesystem I/O in a pure
|
|
92
|
+
* builder — caller controls the side effect).
|
|
93
|
+
*/
|
|
94
|
+
function detectStalePrefix(opts) {
|
|
95
|
+
const env = opts.env ?? process.env;
|
|
96
|
+
const p = opts.prefix;
|
|
97
|
+
// Windows risk: Program Files.
|
|
98
|
+
if (opts.platform === 'win32') {
|
|
99
|
+
if (/^[a-zA-Z]:\\Program Files/i.test(p)) {
|
|
100
|
+
return {
|
|
101
|
+
warning: `npm prefix is "${p}" — global installs here require Administrator every time.`,
|
|
102
|
+
switchSteps: [
|
|
103
|
+
'Switch to a user-local prefix to avoid the prompt forever:',
|
|
104
|
+
' • npm config set prefix "$env:USERPROFILE\\AppData\\Roaming\\npm"',
|
|
105
|
+
' • [Environment]::SetEnvironmentVariable("Path", "$env:USERPROFILE\\AppData\\Roaming\\npm;" + [Environment]::GetEnvironmentVariable("Path", "User"), "User")',
|
|
106
|
+
' • Close + reopen PowerShell.',
|
|
107
|
+
],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
// POSIX risk: /usr or /usr/local without write access.
|
|
113
|
+
const risky = p === '/usr' || p === '/usr/local' || p.startsWith('/usr/');
|
|
114
|
+
if (risky && !opts.writable) {
|
|
115
|
+
const shell = detectShell(env);
|
|
116
|
+
const rcFile = rcFileFor(shell, opts.home);
|
|
117
|
+
return {
|
|
118
|
+
warning: `npm prefix is "${p}" — global installs here require sudo every time.`,
|
|
119
|
+
switchSteps: [
|
|
120
|
+
'Switch to a user-local prefix to avoid sudo forever:',
|
|
121
|
+
` • npm config set prefix "${opts.home}/.npm-global"`,
|
|
122
|
+
` • echo 'export PATH="${opts.home}/.npm-global/bin:$PATH"' >> ${rcFile}`,
|
|
123
|
+
` • source ${rcFile}`,
|
|
124
|
+
],
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
@@ -180,6 +180,10 @@ class ApprovalEngine {
|
|
|
180
180
|
* categories are always allowed without consulting any callback.
|
|
181
181
|
*/
|
|
182
182
|
async checkApproval(req) {
|
|
183
|
+
// v4.9.0 Slice 12b — fire `approval.requested` for any gated call.
|
|
184
|
+
// Read-category short-circuits below; we still fire so observers
|
|
185
|
+
// see ALL approval requests (not just the ones that prompt).
|
|
186
|
+
this.callbacks.onRequested?.(req);
|
|
183
187
|
if (req.category === 'read') {
|
|
184
188
|
this.callbacks.onDecision?.(req, 'allow');
|
|
185
189
|
return true;
|
package/dist/moat/memoryGuard.js
CHANGED
|
@@ -212,7 +212,14 @@ class MemoryGuard {
|
|
|
212
212
|
}
|
|
213
213
|
exports.MemoryGuard = MemoryGuard;
|
|
214
214
|
function pickFile(snap, file) {
|
|
215
|
-
|
|
215
|
+
// v4.9.0 Slice 11 — legacy back-compat for 'memory' / 'user' direct
|
|
216
|
+
// reads (Phase 9 + Honesty Enforcement still consult these); new
|
|
217
|
+
// namespaces flow through the generalized `files` map.
|
|
218
|
+
if (file === 'user' && snap.userMd !== undefined)
|
|
219
|
+
return snap.userMd;
|
|
220
|
+
if (file === 'memory' && snap.memoryMd !== undefined)
|
|
221
|
+
return snap.memoryMd;
|
|
222
|
+
return snap.files?.[file]?.content ?? '';
|
|
216
223
|
}
|
|
217
224
|
/**
|
|
218
225
|
* Phase v4.1.2-bug-X: section-aware containment check.
|
|
@@ -92,14 +92,14 @@ class AnthropicAdapter {
|
|
|
92
92
|
// ── Public: non-streaming ────────────────────────────────────────────────
|
|
93
93
|
async call(input) {
|
|
94
94
|
const body = this.buildBody(input, /* streaming */ false);
|
|
95
|
-
const reply = await this.dispatch(body, /* streaming */ false, input.signal);
|
|
95
|
+
const reply = await this.dispatch(body, /* streaming */ false, input.signal, input.headers);
|
|
96
96
|
const json = (await reply.json());
|
|
97
97
|
return decodeResponse(json);
|
|
98
98
|
}
|
|
99
99
|
// ── Public: streaming ────────────────────────────────────────────────────
|
|
100
100
|
async *callStream(input) {
|
|
101
101
|
const body = this.buildBody(input, /* streaming */ true);
|
|
102
|
-
const reply = await this.dispatch(body, /* streaming */ true, input.signal);
|
|
102
|
+
const reply = await this.dispatch(body, /* streaming */ true, input.signal, input.headers);
|
|
103
103
|
if (!reply.body) {
|
|
104
104
|
// Server promised SSE but gave us nothing — fall through to a synthetic
|
|
105
105
|
// empty done event so the agent loop terminates rather than hangs.
|
|
@@ -163,11 +163,17 @@ class AnthropicAdapter {
|
|
|
163
163
|
// beta flags, or per-deployment routing tags without forking the adapter.
|
|
164
164
|
return { ...headers, ...this.extraHeaders };
|
|
165
165
|
}
|
|
166
|
-
async dispatch(body, streaming, externalSignal) {
|
|
166
|
+
async dispatch(body, streaming, externalSignal, outboundHeaders) {
|
|
167
167
|
// Resolved once per process via the userAgent module's cache, so paying
|
|
168
168
|
// for the version detection here is cheap on every retry/turn.
|
|
169
169
|
const userAgent = await (0, userAgent_1.getClaudeCliUserAgent)();
|
|
170
|
-
|
|
170
|
+
// v4.9.0 Slice 7 — merge caller-supplied outbound headers (e.g.
|
|
171
|
+
// `traceparent`, `X-Aiden-Run-Id`) below the adapter's defaults so
|
|
172
|
+
// they can't override `Authorization` / `anthropic-version` etc.,
|
|
173
|
+
// but above `extraHeaders` so a deliberate per-deployment override
|
|
174
|
+
// still wins.
|
|
175
|
+
const base = this.buildHeaders(streaming, userAgent);
|
|
176
|
+
const headers = outboundHeaders ? { ...outboundHeaders, ...base } : base;
|
|
171
177
|
const serialised = JSON.stringify(body);
|
|
172
178
|
const totalTries = this.maxRetries + 1;
|
|
173
179
|
let lastErr = null;
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
21
|
exports.localBackendExecute = localBackendExecute;
|
|
22
22
|
const node_child_process_1 = require("node:child_process");
|
|
23
|
+
// v4.9.0 Slice 7 — propagate ExecutionContext into the child via env.
|
|
24
|
+
const identity_1 = require("../../../core/v4/identity");
|
|
23
25
|
const DEFAULT_TIMEOUT = 30000;
|
|
24
26
|
async function localBackendExecute(args, cb = {}) {
|
|
25
27
|
const command = args.command.trim();
|
|
@@ -38,14 +40,29 @@ async function localBackendExecute(args, cb = {}) {
|
|
|
38
40
|
const timeoutMs = args.timeoutMs ?? DEFAULT_TIMEOUT;
|
|
39
41
|
const capture = args.captureOutput ?? true;
|
|
40
42
|
return new Promise((resolve) => {
|
|
43
|
+
// v4.9.0 Slice 7 — when running inside a `runWithContext` frame,
|
|
44
|
+
// stamp AIDEN_* env vars so the child process can reconstitute
|
|
45
|
+
// the same daemon/incarnation/run/trace correlation chain via
|
|
46
|
+
// `readContextFromEnv(process.env)`. Outside a context frame, the
|
|
47
|
+
// env spread is unchanged.
|
|
48
|
+
const ambient = (0, identity_1.currentContext)();
|
|
49
|
+
let baseEnv;
|
|
50
|
+
if (ambient) {
|
|
51
|
+
baseEnv = (0, identity_1.spawnEnvWithContext)(ambient, process.env);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
// v4.9.0 Slice 8 — report through the enforcement layer.
|
|
55
|
+
(0, identity_1.reportMissingContext)('subprocess', 'shellExec');
|
|
56
|
+
baseEnv = process.env;
|
|
57
|
+
}
|
|
41
58
|
const child = isWin
|
|
42
59
|
? (0, node_child_process_1.spawn)('powershell.exe', ['-NoProfile', '-Command', command], {
|
|
43
60
|
cwd: args.cwd,
|
|
44
|
-
env: { ...
|
|
61
|
+
env: { ...baseEnv, ...(args.env ?? {}) },
|
|
45
62
|
})
|
|
46
63
|
: (0, node_child_process_1.spawn)('bash', ['-lc', command], {
|
|
47
64
|
cwd: args.cwd,
|
|
48
|
-
env: { ...
|
|
65
|
+
env: { ...baseEnv, ...(args.env ?? {}) },
|
|
49
66
|
});
|
|
50
67
|
let stdout = '';
|
|
51
68
|
let stderr = '';
|