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.
Files changed (99) hide show
  1. package/README.md +88 -1
  2. package/dist/cli/v4/aidenCLI.js +35 -4
  3. package/dist/cli/v4/chatSession.js +43 -16
  4. package/dist/cli/v4/commands/daemon.js +47 -2
  5. package/dist/cli/v4/commands/daemonDoctor.js +212 -0
  6. package/dist/cli/v4/commands/daemonStatus.js +1 -1
  7. package/dist/cli/v4/commands/help.js +2 -0
  8. package/dist/cli/v4/commands/hooks.js +428 -0
  9. package/dist/cli/v4/commands/index.js +5 -1
  10. package/dist/cli/v4/commands/mcp.js +89 -1
  11. package/dist/cli/v4/commands/mcpClientInstall.js +359 -0
  12. package/dist/cli/v4/commands/memory.js +702 -0
  13. package/dist/cli/v4/commands/recovery.js +1 -1
  14. package/dist/cli/v4/commands/skin.js +7 -0
  15. package/dist/cli/v4/commands/theme.js +217 -0
  16. package/dist/cli/v4/commands/trigger.js +1 -1
  17. package/dist/cli/v4/commands/update.js +14 -2
  18. package/dist/cli/v4/design/tokens.js +52 -4
  19. package/dist/cli/v4/display.js +102 -46
  20. package/dist/cli/v4/pasteIntercept.js +214 -70
  21. package/dist/cli/v4/replyRenderer.js +145 -5
  22. package/dist/cli/v4/skinEngine.js +67 -0
  23. package/dist/core/v4/aidenAgent.js +45 -2
  24. package/dist/core/v4/daemon/api/runs.js +131 -0
  25. package/dist/core/v4/daemon/bootstrap.js +368 -13
  26. package/dist/core/v4/daemon/db/migrations.js +169 -0
  27. package/dist/core/v4/daemon/idempotency/runIdempotencyStore.js +128 -0
  28. package/dist/core/v4/daemon/incarnationStore.js +47 -0
  29. package/dist/core/v4/daemon/runs/attemptStore.js +67 -0
  30. package/dist/core/v4/daemon/runs/reclaim.js +88 -0
  31. package/dist/core/v4/daemon/runs/retryPolicy.js +73 -0
  32. package/dist/core/v4/daemon/runs/runWithRetry.js +80 -0
  33. package/dist/core/v4/daemon/runs/stuckAttemptWatchdog.js +72 -0
  34. package/dist/core/v4/daemon/spans/spanHelpers.js +272 -0
  35. package/dist/core/v4/daemon/spans/spanStore.js +113 -0
  36. package/dist/core/v4/daemon/triggerBus.js +50 -19
  37. package/dist/core/v4/hooks/auditQuery.js +67 -0
  38. package/dist/core/v4/hooks/dispatcher.js +286 -0
  39. package/dist/core/v4/hooks/index.js +46 -0
  40. package/dist/core/v4/hooks/lifecycle.js +27 -0
  41. package/dist/core/v4/hooks/manifest.js +142 -0
  42. package/dist/core/v4/hooks/registry.js +149 -0
  43. package/dist/core/v4/hooks/runtime/subprocessRunner.js +188 -0
  44. package/dist/core/v4/hooks/toolHookGate.js +76 -0
  45. package/dist/core/v4/hooks/trust.js +14 -0
  46. package/dist/core/v4/identity/contextManager.js +83 -0
  47. package/dist/core/v4/identity/daemonId.js +85 -0
  48. package/dist/core/v4/identity/enforcement.js +103 -0
  49. package/dist/core/v4/identity/executionContext.js +153 -0
  50. package/dist/core/v4/identity/hookExecution.js +62 -0
  51. package/dist/core/v4/identity/httpContext.js +68 -0
  52. package/dist/core/v4/identity/ids.js +185 -0
  53. package/dist/core/v4/identity/index.js +60 -0
  54. package/dist/core/v4/identity/subprocessContext.js +98 -0
  55. package/dist/core/v4/identity/traceparent.js +114 -0
  56. package/dist/core/v4/logger/index.js +3 -1
  57. package/dist/core/v4/logger/logger.js +28 -1
  58. package/dist/core/v4/logger/redact.js +149 -0
  59. package/dist/core/v4/logger/sinks/fileSink.js +13 -0
  60. package/dist/core/v4/logger/sinks/stdSink.js +19 -1
  61. package/dist/core/v4/mcp/install/backup.js +78 -0
  62. package/dist/core/v4/mcp/install/clientPaths.js +90 -0
  63. package/dist/core/v4/mcp/install/clients.js +203 -0
  64. package/dist/core/v4/mcp/install/healthCheck.js +83 -0
  65. package/dist/core/v4/mcp/install/jsoncMerge.js +174 -0
  66. package/dist/core/v4/mcp/install/profiles.js +109 -0
  67. package/dist/core/v4/mcp/install/wslDetect.js +62 -0
  68. package/dist/core/v4/memory/namespaceRegistry.js +117 -0
  69. package/dist/core/v4/memory/projectRoot.js +76 -0
  70. package/dist/core/v4/memory/reviewer/index.js +162 -0
  71. package/dist/core/v4/memory/reviewer/pendingStore.js +136 -0
  72. package/dist/core/v4/memory/reviewer/prompt.js +105 -0
  73. package/dist/core/v4/memory/reviewer/skipRules.js +92 -0
  74. package/dist/core/v4/memoryManager.js +57 -10
  75. package/dist/core/v4/paths.js +2 -0
  76. package/dist/core/v4/promptBuilder.js +6 -0
  77. package/dist/core/v4/subagent/spawnSubAgent.js +20 -7
  78. package/dist/core/v4/theme/bundledThemes.js +106 -0
  79. package/dist/core/v4/theme/themeLoader.js +160 -0
  80. package/dist/core/v4/theme/themeRegistry.js +97 -0
  81. package/dist/core/v4/theme/themeWatcher.js +95 -0
  82. package/dist/core/v4/toolRegistry.js +71 -8
  83. package/dist/core/v4/update/executeInstall.js +10 -6
  84. package/dist/core/v4/update/installMethodDetect.js +7 -0
  85. package/dist/core/version.js +67 -2
  86. package/dist/moat/approvalEngine.js +4 -0
  87. package/dist/moat/memoryGuard.js +8 -1
  88. package/dist/providers/v4/anthropicAdapter.js +10 -4
  89. package/dist/tools/v4/backends/local.js +19 -2
  90. package/dist/tools/v4/sessions/recallSession.js +6 -1
  91. package/package.json +3 -3
  92. package/plugins/aiden-plugin-chatgpt-plus/index.js +10 -1
  93. package/themes/default.yaml +52 -0
  94. package/themes/dracula.yaml +32 -0
  95. package/themes/light.yaml +32 -0
  96. package/themes/monochrome.yaml +31 -0
  97. package/themes/tokyo-night.yaml +32 -0
  98. package/dist/core/pluginSystem.js +0 -121
  99. 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 result = await handler.execute(args, context);
191
- // v4.1.3-repl-polish: lift `degraded` + `degradedReason` from the
192
- // handler's inner result to the outer ToolCallResult so the CLI
193
- // trail row can render the partial-yellow state. Tools opt in by
194
- // setting these on the object they return; without this lift the
195
- // flags would sit on `out.result.degraded` where callbacks.ts
196
- // can't see them. Strict typeof checks avoid promoting truthy-
197
- // but-wrong-shape junk (numbers, strings, nested objects).
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; }
@@ -66,17 +66,21 @@ async function executeInstall(opts = {}) {
66
66
  const platform = opts.platform ?? process.platform;
67
67
  return new Promise((resolve) => {
68
68
  const args = ['install', '-g', packageSpec];
69
- // shell: true on Windows so npm.cmd is found via PATHEXT; on
70
- // POSIX we spawn npm directly. Either way the args are validated
71
- // (only npm + install + a hardcoded spec by default) — no user
72
- // input flows into argv.
69
+ // v4.8.1 Slice 2 drop `shell: true`. Node 20+ emits
70
+ // `DeprecationWarning: Passing args to a child process with shell
71
+ // option true can lead to security vulnerabilities` whenever
72
+ // shell:true is paired with an args array. We don't need the
73
+ // shell either — on Windows we spawn `npm.cmd` explicitly (the
74
+ // shim that PATHEXT would otherwise resolve to); on POSIX we
75
+ // spawn `npm` directly. No user input flows into argv on either
76
+ // path so the prior shell-resolution wasn't load-bearing.
77
+ const cmd = platform === 'win32' ? 'npm.cmd' : 'npm';
73
78
  const spawnOpts = {
74
- shell: platform === 'win32',
75
79
  stdio: ['ignore', 'pipe', 'pipe'],
76
80
  };
77
81
  let child;
78
82
  try {
79
- child = spawn('npm', args, spawnOpts);
83
+ child = spawn(cmd, args, spawnOpts);
80
84
  }
81
85
  catch (err) {
82
86
  resolve({
@@ -37,6 +37,13 @@ const NPM_GLOBAL_HINTS = [
37
37
  /[/\\]npm-global[/\\]/,
38
38
  /[/\\]\.nvm[/\\]versions[/\\]node[/\\][^/\\]+[/\\]lib[/\\]node_modules\b/,
39
39
  /Program Files[/\\]nodejs[/\\]node_modules[/\\]aiden-runtime\b/i,
40
+ // v4.8.1 Slice 2 — Windows user-mode `npm install -g` lands in
41
+ // `C:\Users\<u>\AppData\Roaming\npm\node_modules\aiden-runtime\`.
42
+ // The leading `[/\\]npm[/\\]node_modules` hint above usually catches
43
+ // it, but tests on a non-default `npm config prefix` setup
44
+ // (Cmder, Scoop, etc.) can land outside the canonical path. The
45
+ // extra hint here is a belt-and-suspenders explicit AppData match.
46
+ /[/\\]AppData[/\\]Roaming[/\\]npm[/\\]/i,
40
47
  ];
41
48
  function inferDirs(input) {
42
49
  return {
@@ -1,5 +1,70 @@
1
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/version.ts — runtime version reader.
10
+ *
11
+ * v4.8.1 Slice 2 — switched from build-time injection to a runtime
12
+ * `package.json` walk. The previous design relied on
13
+ * `scripts/inject-version.js` (a `prebuild:cli` / `prebuild:api` hook)
14
+ * to write a hardcoded VERSION constant into this file. That design
15
+ * had a subtle ordering bug:
16
+ *
17
+ * `npm run build` ran `tsc --outDir dist` BEFORE `inject-version.js`.
18
+ * tsc compiled `core/version.ts` (still at the previously-committed
19
+ * value) into `dist/core/version.js`. Inject then mutated the
20
+ * source, but only the esbuild bundle (`dist-bundle/cli.js`) picked
21
+ * up the fresh value. The `bin` entry uses the tsc tree
22
+ * (`dist/cli/v4/aidenCLI.js`), so the globally-installed CLI
23
+ * reported the stale version.
24
+ *
25
+ * Fix: read the version at module-load time by walking up from
26
+ * `__dirname` and parsing the first `package.json` we find whose
27
+ * `name` is `aiden-runtime`. This works for:
28
+ *
29
+ * - the tsc tree (`dist/core/version.js` → walk to `<install>/package.json`)
30
+ * - the esbuild bundle (`dist-bundle/cli.js` → walk to root)
31
+ * - source / tsx dev runs (`core/version.ts` → walk to repo root)
32
+ * - tests (any `__dirname` inside the repo lands on the right pkg)
33
+ *
34
+ * Failure mode: returns `'0.0.0-unknown'` if no aiden-runtime
35
+ * package.json is found within 6 parent directories. End-user
36
+ * deployments always have one within 3 levels; the 6-level budget
37
+ * keeps the function defensive without scanning the whole filesystem.
38
+ */
2
39
  Object.defineProperty(exports, "__esModule", { value: true });
3
40
  exports.VERSION = void 0;
4
- // AUTO-GENERATED by scripts/inject-version.js — do not edit by hand
5
- exports.VERSION = '4.8.0';
41
+ const node_fs_1 = require("node:fs");
42
+ const node_path_1 = require("node:path");
43
+ function readVersion() {
44
+ let dir = __dirname;
45
+ for (let i = 0; i < 6; i += 1) {
46
+ const candidate = (0, node_path_1.join)(dir, 'package.json');
47
+ if ((0, node_fs_1.existsSync)(candidate)) {
48
+ try {
49
+ const pkg = JSON.parse((0, node_fs_1.readFileSync)(candidate, 'utf8'));
50
+ if (pkg.name === 'aiden-runtime' && typeof pkg.version === 'string') {
51
+ return pkg.version;
52
+ }
53
+ }
54
+ catch {
55
+ /* unreadable / non-JSON → keep walking */
56
+ }
57
+ }
58
+ const parent = (0, node_path_1.dirname)(dir);
59
+ if (parent === dir)
60
+ break;
61
+ dir = parent;
62
+ }
63
+ return '0.0.0-unknown';
64
+ }
65
+ /**
66
+ * Resolved at module-load time. Idempotent — multiple imports share
67
+ * the cached value. Re-reading on every access would be wasteful;
68
+ * the package.json version doesn't change during a process lifetime.
69
+ */
70
+ exports.VERSION = readVersion();
@@ -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;
@@ -212,7 +212,14 @@ class MemoryGuard {
212
212
  }
213
213
  exports.MemoryGuard = MemoryGuard;
214
214
  function pickFile(snap, file) {
215
- return file === 'user' ? snap.userMd : snap.memoryMd;
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
- const headers = this.buildHeaders(streaming, userAgent);
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: { ...process.env, ...(args.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: { ...process.env, ...(args.env ?? {}) },
65
+ env: { ...baseEnv, ...(args.env ?? {}) },
49
66
  });
50
67
  let stdout = '';
51
68
  let stderr = '';
@@ -86,7 +86,10 @@ exports.recallSessionTool = {
86
86
  error: 'recall_session requires resolved aiden paths',
87
87
  };
88
88
  }
89
- const dir = node_path_1.default.join(ctx.paths.root, 'distillations');
89
+ // v4.9.0 Slice 9 — paths hygiene: use centralised distillationsDir
90
+ // when present (added in Slice 9); fall back to the legacy ad-hoc
91
+ // reconstruction so externally-mocked `ctx.paths` objects keep working.
92
+ const dir = ctx.paths.distillationsDir ?? node_path_1.default.join(ctx.paths.root, 'distillations');
90
93
  // Read everything off disk first. Each failure (malformed JSON,
91
94
  // EACCES on individual files) is skipped silently; the diagnostic
92
95
  // for "files exist but couldn't be read" is the gap between
@@ -169,6 +172,8 @@ exports.recallSessionTool = {
169
172
  // register recall_session with the slice3 SubsystemHealthRegistry
170
173
  // pass this helper to the tracker so they get health snapshots without
171
174
  // hard-coding the path in two places.
175
+ // v4.9.0 Slice 9 — kept for back-compat; mirrors `paths.distillationsDir`
176
+ // without requiring the full AidenPaths object.
172
177
  function getDistillationsDir(rootDir) {
173
178
  return node_path_1.default.join(rootDir, 'distillations');
174
179
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiden-runtime",
3
- "version": "4.8.0",
3
+ "version": "4.9.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -43,6 +43,7 @@
43
43
  "dist/",
44
44
  "config/",
45
45
  "skills/",
46
+ "themes/",
46
47
  "plugins/aiden-plugin-cdp-browser/",
47
48
  "plugins/aiden-plugin-claude-pro/",
48
49
  "plugins/aiden-plugin-chatgpt-plus/",
@@ -60,9 +61,7 @@
60
61
  "scripts": {
61
62
  "dev": "electron electron/main.js",
62
63
  "build": "tsc --outDir dist && npm run build:cli && npm run build:api",
63
- "prebuild:cli": "node scripts/inject-version.js",
64
64
  "build:cli": "esbuild cli/aiden.ts --bundle --platform=node --target=node18 --outfile=dist-bundle/cli.js --external:electron --external:cpu-features --external:ssh2 --external:bcrypt --external:playwright --external:playwright-core --external:@aws-sdk/client-s3",
65
- "prebuild:api": "node scripts/inject-version.js",
66
65
  "build:api": "esbuild api/entry.ts --bundle --platform=node --target=node18 --outfile=dist-bundle/index.js --external:electron --external:cpu-features --external:ssh2 --external:bcrypt --external:playwright --external:playwright-core --external:@aws-sdk/client-s3",
67
66
  "prepublishOnly": "npm run typecheck && npm run build",
68
67
  "typecheck": "tsc --noEmit",
@@ -258,6 +257,7 @@
258
257
  "imap-simple": "^5.1.0",
259
258
  "js-tiktoken": "^1.0.21",
260
259
  "js-yaml": "^4.1.1",
260
+ "jsonc-parser": "^3.3.1",
261
261
  "kleur": "^4.1.5",
262
262
  "lru-cache": "^10.0.0",
263
263
  "mailparser": "^3.9.8",
@@ -37,7 +37,16 @@ const CHATGPT_PLUS = {
37
37
  displayName: 'ChatGPT Plus',
38
38
  description:
39
39
  'Sign in with your ChatGPT Plus subscription. Uses your existing ChatGPT login — no API key needed. Inference is routed through the Codex Responses API.',
40
- defaultModels: ['gpt-5', 'gpt-5-mini'],
40
+ // v4.9.0 — `defaultModels[0]` is what `setupWizard.ts:810` picks
41
+ // as a new ChatGPT-Plus user's first model. The Codex OAuth backend
42
+ // rejects `gpt-5` outright with a 400 ("not supported when using
43
+ // Codex with a ChatGPT account") for new accounts; `gpt-5.5` is the
44
+ // first non-codex slug in the registry's modelIds and works
45
+ // reliably. `gpt-5` stays in the array so users who specifically
46
+ // want it can still see it in /model. `gpt-5-mini` was removed —
47
+ // it's a direct OpenAI API name and is NOT valid on the Codex OAuth
48
+ // endpoint (see providers/v4/registry.ts:117–119).
49
+ defaultModels: ['gpt-5.5', 'gpt-5'],
41
50
 
42
51
  clientId: 'app_EMoamEEZ73f0CkXaXp7hrann',
43
52
  issuer: 'https://auth.openai.com',
@@ -0,0 +1,52 @@
1
+ # Aiden Theme — Default
2
+ # This is the bundled default theme. To customise, copy this file to
3
+ # ~/.aiden/theme.yaml and edit any field. Aiden hot-reloads on save.
4
+ # Bad values warn + fall back per-field; the theme system never crashes
5
+ # Aiden on a typo. Slash commands: /theme reload, /theme reset, /theme edit.
6
+
7
+ name: "default"
8
+ description: "Aiden's signature brand-orange theme on a dark terminal."
9
+
10
+ # ── Colors — hex strings ───────────────────────────────────────────────
11
+ colors:
12
+ brand:
13
+ primary: "#FF6B35" # signature brand orange
14
+ muted: "#7a3119" # 30%-luma brand for borders + dim brand surfaces
15
+ content:
16
+ primary: "#e8ebf0" # brightest text — replies, headings on dark bg
17
+ secondary: "#b8a89a" # warm muted — gutter, secondary detail
18
+ tertiary: "#6a6a6a" # captions, deprecated rows, dim status
19
+ semantic:
20
+ success: "#7fc28b"
21
+ warn: "#e0a040"
22
+ error: "#e05a5a"
23
+ info: "#7da7c7"
24
+ metrics:
25
+ model: "#9cdcfe" # status bar — model name (cyan)
26
+ tokens: "#e0a040" # status bar — token ratio (amber)
27
+ turnCount: "#a48be0" # status bar — turn counter (purple)
28
+ timer: "#7fc28b" # status bar — per-turn elapsed (teal/green)
29
+ surface:
30
+ bg: "#0d0e10"
31
+ elevated: "#16181b"
32
+ border: "#2a2a2a"
33
+ divider: "#3a3a3a"
34
+
35
+ # ── Glyphs ─────────────────────────────────────────────────────────────
36
+ glyphs:
37
+ panel:
38
+ bar: "│" # left-edge bar on /help, approval, Aiden reply chrome
39
+ trail:
40
+ gutter: "┊" # tool-trail row gutter
41
+ status:
42
+ triangle: "▲" # user-prompt prefix + brand mark
43
+ dot: "●"
44
+ dotOpen: "○"
45
+ sep: "│" # status footer column separator
46
+ timer: "⌛"
47
+ bar:
48
+ filled: "●" # context-bar filled cell
49
+ empty: "○" # context-bar empty cell
50
+ shimmer:
51
+ block: "█" # activity-indicator sliding block
52
+ track: "─" # activity-indicator track
@@ -0,0 +1,32 @@
1
+ # Aiden Theme — Dracula
2
+ # Palette inspired by the public Dracula theme by Zeno Rocha
3
+ # (draculatheme.com). High-contrast dark with vivid accents, widely
4
+ # re-implemented across editors and terminals. Hex values are the
5
+ # public palette. Glyphs unchanged from default.
6
+
7
+ name: "dracula"
8
+ description: "Inspired by Dracula (draculatheme.com). High-contrast dark with vivid accents."
9
+
10
+ colors:
11
+ brand:
12
+ primary: "#FF79C6"
13
+ muted: "#BD7AA3"
14
+ content:
15
+ primary: "#F8F8F2"
16
+ secondary: "#BFBFBF"
17
+ tertiary: "#6272A4"
18
+ semantic:
19
+ success: "#50FA7B"
20
+ warn: "#F1FA8C"
21
+ error: "#FF5555"
22
+ info: "#8BE9FD"
23
+ metrics:
24
+ model: "#8BE9FD"
25
+ tokens: "#F1FA8C"
26
+ timer: "#8BE9FD"
27
+ turnCount: "#BD93F9"
28
+ surface:
29
+ bg: "#282A36"
30
+ elevated: "#21222C"
31
+ border: "#44475A"
32
+ divider: "#1E1F29"
@@ -0,0 +1,32 @@
1
+ # Aiden Theme — Light
2
+ # Dark text on light terminal background. Brand orange retained as the
3
+ # single warm accent. For users on light-theme terminals (Apple Terminal
4
+ # default profile, Windows Terminal Light, light VS Code integrated, etc.).
5
+ # Glyphs unchanged from default.
6
+
7
+ name: "light"
8
+ description: "Light terminal. Dark text on light background. Brand orange accent."
9
+
10
+ colors:
11
+ brand:
12
+ primary: "#D9531E"
13
+ muted: "#A03D0E"
14
+ content:
15
+ primary: "#1A1A1A"
16
+ secondary: "#555555"
17
+ tertiary: "#888888"
18
+ semantic:
19
+ success: "#2E8B2E"
20
+ warn: "#B8860B"
21
+ error: "#C8202C"
22
+ info: "#1F5FA8"
23
+ metrics:
24
+ model: "#1F5FA8"
25
+ tokens: "#B8860B"
26
+ timer: "#1F5FA8"
27
+ turnCount: "#7B3FBF"
28
+ surface:
29
+ bg: "#F8F8F8"
30
+ elevated: "#EFEFEF"
31
+ border: "#D0D0D0"
32
+ divider: "#E5E5E5"
@@ -0,0 +1,31 @@
1
+ # Aiden Theme — Monochrome
2
+ # Pure greyscale. Two semantic accents (error red + success green) are
3
+ # retained for readability of state signals. Everything else greyscale.
4
+ # Glyphs unchanged from default — themes change colors, not shapes.
5
+
6
+ name: "monochrome"
7
+ description: "Pure greyscale. Semantic accents retained for error/success readability."
8
+
9
+ colors:
10
+ brand:
11
+ primary: "#C8CCD4"
12
+ muted: "#888888"
13
+ content:
14
+ primary: "#C8CCD4"
15
+ secondary: "#888888"
16
+ tertiary: "#555555"
17
+ semantic:
18
+ success: "#7DC97D"
19
+ warn: "#999999"
20
+ error: "#D97777"
21
+ info: "#AAAAAA"
22
+ metrics:
23
+ model: "#AAAAAA"
24
+ tokens: "#AAAAAA"
25
+ timer: "#888888"
26
+ turnCount: "#BBBBBB"
27
+ surface:
28
+ bg: "#0A0A0A"
29
+ elevated: "#1A1A1A"
30
+ border: "#444444"
31
+ divider: "#3A3A3A"