aiden-runtime 4.9.0 → 4.9.2

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 (44) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/v4/aidenCLI.js +2 -2
  3. package/dist/cli/v4/aidenPrompt.js +12 -0
  4. package/dist/cli/v4/chatSession.js +43 -17
  5. package/dist/cli/v4/commands/channel.js +4 -6
  6. package/dist/cli/v4/commands/cron.js +6 -1
  7. package/dist/cli/v4/commands/daemon.js +6 -1
  8. package/dist/cli/v4/commands/daemonDoctor.js +6 -6
  9. package/dist/cli/v4/commands/daemonStatus.js +46 -27
  10. package/dist/cli/v4/commands/help.js +3 -0
  11. package/dist/cli/v4/commands/hooks.js +39 -1
  12. package/dist/cli/v4/commands/hooksSlash.js +33 -0
  13. package/dist/cli/v4/commands/index.js +9 -1
  14. package/dist/cli/v4/commands/mcp.js +2 -2
  15. package/dist/cli/v4/commands/memory.js +6 -1
  16. package/dist/cli/v4/commands/memorySlash.js +38 -0
  17. package/dist/cli/v4/commands/plugins.js +4 -6
  18. package/dist/cli/v4/commands/trigger.js +18 -18
  19. package/dist/cli/v4/confirmPrompt.js +67 -0
  20. package/dist/cli/v4/ui/progressBar.js +179 -0
  21. package/dist/cli/v4/util/closestAction.js +48 -0
  22. package/dist/core/v4/daemon/db/migrations.js +398 -398
  23. package/dist/core/v4/daemon/idempotency/runIdempotencyStore.js +10 -10
  24. package/dist/core/v4/daemon/incarnationStore.js +9 -9
  25. package/dist/core/v4/daemon/runs/attemptStore.js +8 -8
  26. package/dist/core/v4/daemon/runs/reclaim.js +12 -12
  27. package/dist/core/v4/daemon/runs/stuckAttemptWatchdog.js +19 -19
  28. package/dist/core/v4/daemon/spans/spanStore.js +14 -14
  29. package/dist/core/v4/daemon/triggerBus.js +61 -61
  30. package/dist/core/v4/hooks/auditQuery.js +11 -11
  31. package/dist/core/v4/hooks/dispatcher.js +13 -13
  32. package/dist/core/v4/hooks/registry.js +8 -8
  33. package/dist/core/v4/mcp/transport.js +9 -9
  34. package/dist/core/v4/update/depWarningFilter.js +76 -0
  35. package/dist/core/v4/update/executeInstall.js +70 -53
  36. package/dist/core/v4/update/platformInstructions.js +128 -0
  37. package/dist/core/v4/update/recoveryScript.js +70 -0
  38. package/dist/core/v4/util/spawnCommand.js +151 -0
  39. package/package.json +1 -1
  40. package/themes/default.yaml +52 -52
  41. package/themes/dracula.yaml +32 -32
  42. package/themes/light.yaml +32 -32
  43. package/themes/monochrome.yaml +31 -31
  44. package/themes/tokyo-night.yaml +32 -32
@@ -79,9 +79,9 @@ async function runTriggerSubcommand(action, args, argv, opts = {}) {
79
79
  spec.paths = spec.paths.map((p) => node_path_1.default.resolve(p));
80
80
  const id = (0, node_crypto_1.randomUUID)();
81
81
  const now = Date.now();
82
- db.prepare(`INSERT INTO triggers
83
- (id, source, name, spec_json, enabled, prompt_template, deliver_only,
84
- created_at, updated_at)
82
+ db.prepare(`INSERT INTO triggers
83
+ (id, source, name, spec_json, enabled, prompt_template, deliver_only,
84
+ created_at, updated_at)
85
85
  VALUES (?, 'file', ?, ?, ?, ?, 0, ?, ?)`).run(id, a.name, JSON.stringify(spec), a.disabled ? 0 : 1, spec.promptTemplate ?? null, now, now);
86
86
  out(`trigger added: ${id} (${a.name})\n`);
87
87
  out('Restart the daemon to activate the watcher.\n');
@@ -182,11 +182,11 @@ async function runTriggerSubcommand(action, args, argv, opts = {}) {
182
182
  return 1;
183
183
  }
184
184
  const prefix = `trigger:${trig.source}:${id}:`;
185
- const rows = db.prepare(`SELECT re.ts, re.kind, re.payload, r.id AS run_id
186
- FROM run_events re
187
- JOIN runs r ON re.run_id = r.id
188
- WHERE r.session_id LIKE ?
189
- ORDER BY re.ts DESC
185
+ const rows = db.prepare(`SELECT re.ts, re.kind, re.payload, r.id AS run_id
186
+ FROM run_events re
187
+ JOIN runs r ON re.run_id = r.id
188
+ WHERE r.session_id LIKE ?
189
+ ORDER BY re.ts DESC
190
190
  LIMIT 50`).all(`${prefix}%`);
191
191
  if (rows.length === 0) {
192
192
  out(`No run events recorded for trigger ${id} (${trig.name}).\n`);
@@ -213,10 +213,10 @@ async function runTriggerSubcommand(action, args, argv, opts = {}) {
213
213
  return 1;
214
214
  }
215
215
  const prefix = `trigger:${trig.source}:${id}:`;
216
- const rows = db.prepare(`SELECT id, status, finish_reason, started_at, completed_at
217
- FROM runs
218
- WHERE session_id LIKE ?
219
- ORDER BY started_at DESC
216
+ const rows = db.prepare(`SELECT id, status, finish_reason, started_at, completed_at
217
+ FROM runs
218
+ WHERE session_id LIKE ?
219
+ ORDER BY started_at DESC
220
220
  LIMIT 50`).all(`${prefix}%`);
221
221
  if (rows.length === 0) {
222
222
  out(`No runs recorded for trigger ${id} (${trig.name}).\n`);
@@ -261,9 +261,9 @@ function runAddWebhook(db, argv, out, err) {
261
261
  });
262
262
  const id = (0, node_crypto_1.randomUUID)();
263
263
  const now = Date.now();
264
- db.prepare(`INSERT INTO triggers
265
- (id, source, name, spec_json, enabled, prompt_template, deliver_only,
266
- created_at, updated_at)
264
+ db.prepare(`INSERT INTO triggers
265
+ (id, source, name, spec_json, enabled, prompt_template, deliver_only,
266
+ created_at, updated_at)
267
267
  VALUES (?, 'webhook', ?, ?, ?, ?, ?, ?, ?)`).run(id, a.name, JSON.stringify(spec), a.disabled ? 0 : 1, spec.promptTemplate ?? null, spec.deliverOnly ? 1 : 0, now, now);
268
268
  const cfg = (0, daemon_1.getDaemonConfig)();
269
269
  const host = process.env.AIDEN_DAEMON_BIND ?? '127.0.0.1';
@@ -359,9 +359,9 @@ async function runAddEmail(db, argv, out, err) {
359
359
  }
360
360
  const id = (0, node_crypto_1.randomUUID)();
361
361
  const now = Date.now();
362
- db.prepare(`INSERT INTO triggers
363
- (id, source, name, spec_json, enabled, prompt_template, deliver_only,
364
- created_at, updated_at)
362
+ db.prepare(`INSERT INTO triggers
363
+ (id, source, name, spec_json, enabled, prompt_template, deliver_only,
364
+ created_at, updated_at)
365
365
  VALUES (?, 'email', ?, ?, ?, ?, ?, ?, ?)`).run(id, a.name, JSON.stringify(spec), a.disabled ? 0 : 1, spec.promptTemplate ?? null, spec.deliverOnly ? 1 : 0, now, now);
366
366
  out(`trigger added: ${id} (${a.name})\n`);
367
367
  out(`imap host: ${spec.imap.host}:${spec.imap.port}${spec.imap.tls ? ' (TLS)' : ''}\n`);
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) 2026 Shiva Deore (Taracod).
4
+ * Licensed under AGPL-3.0. See LICENSE for details.
5
+ *
6
+ * Aiden — local-first agent.
7
+ */
8
+ /**
9
+ * cli/v4/confirmPrompt.ts — v4.9.2 SLICE 3.
10
+ *
11
+ * The slash-command `ctx.confirm()` primitive, extracted from the
12
+ * chatSession closure so it has one source of truth + is unit-testable
13
+ * without spinning up a full REPL.
14
+ *
15
+ * Behaviour:
16
+ * - Canonicalises the y/n hint: strips any caller-appended ` (y/N) `
17
+ * / ` [y/N] ` / ` (Y/n) ` so the primitive can append exactly one
18
+ * ` (y/N) ` in canonical lowercase-y / capital-N form.
19
+ * - Prefixes with a warn-tinted `?` glyph so the confirmation chrome
20
+ * is visually distinct from the main ▲ chat prompt (Slice 3 root
21
+ * cause: users couldn't tell a prompt was open).
22
+ * - Routes through `promptApi.readLine` with `suggestionsDisabled:true`
23
+ * so the inquirer-input path runs (no ghost-text from outer chat
24
+ * history, no slash dropdown — irrelevant for y/n).
25
+ * - Emits a per-input cancellation reason:
26
+ * empty / Enter alone → "Cancelled (press 'y' to confirm; …)"
27
+ * 'n' / 'no' → "Cancelled." (deliberate decline)
28
+ * other non-y → `Cancelled ("<x>" not recognized — …)`
29
+ * null / non-string → "Cancelled (no input)."
30
+ * Callers no longer print their own "Cancelled." line — the
31
+ * primitive owns the rejection message.
32
+ */
33
+ Object.defineProperty(exports, "__esModule", { value: true });
34
+ exports.runConfirm = runConfirm;
35
+ /** Strip any caller-appended `(y/N)` / `[y/N]` / `(Y/n)` so we can
36
+ * re-append the canonical hint without duplication. */
37
+ const TRAILING_YN_HINT_RE = /\s*[\[(](y\/[nN]|Y\/n)[\])]\s*$/i;
38
+ /**
39
+ * Run a single confirmation prompt. Resolves to `true` on `y` / `yes`
40
+ * (case insensitive, trimmed); `false` on anything else, with a
41
+ * specific cancellation line written to `display.dim()`.
42
+ *
43
+ * Never throws — readLine errors and non-string returns degrade to
44
+ * `false` with an honest "no input" reason.
45
+ */
46
+ async function runConfirm(msg, promptApi, display) {
47
+ const stripped = msg.replace(TRAILING_YN_HINT_RE, '').trimEnd();
48
+ const decorated = `${display.paint('?', 'warn')} ${stripped} (y/N) `;
49
+ const r = await promptApi.readLine(decorated, { suggestionsDisabled: true });
50
+ if (typeof r !== 'string') {
51
+ display.dim('Cancelled (no input).');
52
+ return false;
53
+ }
54
+ const trimmed = r.trim();
55
+ if (/^(y|yes)$/i.test(trimmed))
56
+ return true;
57
+ if (trimmed === '') {
58
+ display.dim(`Cancelled (press 'y' to confirm; Enter alone = no).`);
59
+ }
60
+ else if (/^(n|no)$/i.test(trimmed)) {
61
+ display.dim('Cancelled.');
62
+ }
63
+ else {
64
+ display.dim(`Cancelled ("${trimmed}" not recognized — expected y/yes/n/no).`);
65
+ }
66
+ return false;
67
+ }
@@ -0,0 +1,179 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) 2026 Shiva Deore (Taracod).
4
+ * Licensed under AGPL-3.0. See LICENSE for details.
5
+ *
6
+ * Aiden — local-first agent.
7
+ */
8
+ /**
9
+ * cli/v4/ui/progressBar.ts — v4.9.1 reusable progress animation.
10
+ * Auto-detects TTY / NO_COLOR / TERM=dumb / CI to pick render mode
11
+ * (block glyphs vs `#-`, color vs plain, animated vs once-per-second
12
+ * non-TTY lines). Cursor hidden during animation, restored on exit.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.detectRenderMode = detectRenderMode;
16
+ exports.renderLine = renderLine;
17
+ exports.startProgressBar = startProgressBar;
18
+ exports.npmInstallPhasePercent = npmInstallPhasePercent;
19
+ exports.detectNpmPhase = detectNpmPhase;
20
+ const DEFAULT_WIDTH = 28;
21
+ const DEFAULT_TICK_MS = 100;
22
+ /** Minimum elapsed before we paint anything — avoids flicker on sub-300ms ops. */
23
+ const PAINT_AFTER_MS = 300;
24
+ const ANSI_HIDE_CURSOR = '\x1b[?25l';
25
+ const ANSI_SHOW_CURSOR = '\x1b[?25h';
26
+ const ANSI_CLEAR_LINE = '\r\x1b[2K';
27
+ const ANSI_BRAND = '\x1b[38;2;255;107;53m'; // RGB 255,107,53 (Aiden orange)
28
+ const ANSI_MUTED = '\x1b[38;2;106;106;106m';
29
+ const ANSI_SUCCESS = '\x1b[38;2;127;194;139m';
30
+ const ANSI_ERROR = '\x1b[38;2;224;90;90m';
31
+ const ANSI_RESET = '\x1b[0m';
32
+ /** Detect the right render mode from TTY + env. Pure function. */
33
+ function detectRenderMode(isTTY, env = process.env) {
34
+ if (!isTTY)
35
+ return { color: false, blocks: false, animated: false };
36
+ const noColor = env.NO_COLOR !== undefined && env.NO_COLOR !== '';
37
+ const dumb = env.TERM === 'dumb' || env.CI === 'true' || env.CI === '1';
38
+ return {
39
+ color: !noColor && !dumb,
40
+ blocks: !dumb,
41
+ animated: true,
42
+ };
43
+ }
44
+ /**
45
+ * Build the rendered line (without trailing newline). Pure so tests
46
+ * can assert byte-for-byte without timing.
47
+ */
48
+ function renderLine(opts) {
49
+ const pct = Math.max(0, Math.min(100, Math.round(opts.percent)));
50
+ const filled = Math.round((pct / 100) * opts.width);
51
+ const empty = opts.width - filled;
52
+ const full = opts.mode.blocks ? '█' : '#';
53
+ const blank = opts.mode.blocks ? '░' : '-';
54
+ const elapsed = `${(opts.elapsedMs / 1000).toFixed(1)}s`;
55
+ const bar = full.repeat(filled) + blank.repeat(empty);
56
+ if (opts.mode.color) {
57
+ return `${ANSI_BRAND}[${bar}]${ANSI_RESET} ${pct}% ${ANSI_MUTED}${opts.phase}${ANSI_RESET} ${ANSI_MUTED}${elapsed}${ANSI_RESET}`;
58
+ }
59
+ return `[${bar}] ${pct}% ${opts.phase} ${elapsed}`;
60
+ }
61
+ /**
62
+ * Start a progress bar. Returns a controller object. Never throws —
63
+ * any I/O failure on output degrades the bar to a silent no-op while
64
+ * still honoring `complete` / `fail` semantics for the caller.
65
+ */
66
+ function startProgressBar(opts) {
67
+ const width = opts.width ?? DEFAULT_WIDTH;
68
+ const out = opts.out ?? process.stdout;
69
+ const env = opts.env ?? process.env;
70
+ const tickMs = opts.tickMs ?? DEFAULT_TICK_MS;
71
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
72
+ const isTTY = opts.isTTY ?? Boolean(out.isTTY);
73
+ const mode = detectRenderMode(isTTY, env);
74
+ const startedAt = Date.now();
75
+ let phase = opts.phases[0] ?? '';
76
+ let percent = 0;
77
+ let painted = false;
78
+ let closed = false;
79
+ const write = (s) => {
80
+ try {
81
+ out.write(s);
82
+ }
83
+ catch { /* swallow — never break caller */ }
84
+ };
85
+ // SIGINT: restore cursor + clear the partial line before bubbling.
86
+ const onSigint = () => {
87
+ try {
88
+ write(ANSI_CLEAR_LINE + ANSI_SHOW_CURSOR);
89
+ }
90
+ catch { /* noop */ }
91
+ };
92
+ if (mode.animated) {
93
+ try {
94
+ process.once('SIGINT', onSigint);
95
+ }
96
+ catch { /* noop */ }
97
+ }
98
+ // Label line paints once, immediately.
99
+ write(`${mode.color ? ANSI_MUTED : ''}${opts.label}${mode.color ? ANSI_RESET : ''}\n`);
100
+ const paint = () => {
101
+ if (closed)
102
+ return;
103
+ const elapsedMs = Date.now() - startedAt;
104
+ if (elapsedMs < PAINT_AFTER_MS)
105
+ return;
106
+ const line = renderLine({ width, percent, phase, elapsedMs, mode });
107
+ if (mode.animated) {
108
+ if (!painted) {
109
+ write(ANSI_HIDE_CURSOR);
110
+ painted = true;
111
+ }
112
+ write(ANSI_CLEAR_LINE + line);
113
+ }
114
+ else {
115
+ write(line + '\n');
116
+ }
117
+ };
118
+ let timer = null;
119
+ if (mode.animated) {
120
+ timer = setInterval(paint, tickMs);
121
+ if (typeof timer.unref === 'function')
122
+ timer.unref();
123
+ }
124
+ const close = (icon, color, message) => {
125
+ if (closed)
126
+ return;
127
+ closed = true;
128
+ if (timer)
129
+ clearInterval(timer);
130
+ try {
131
+ process.removeListener('SIGINT', onSigint);
132
+ }
133
+ catch { /* noop */ }
134
+ const finalLine = mode.color
135
+ ? `${color}${icon}${ANSI_RESET} ${message}`
136
+ : `${icon} ${message}`;
137
+ if (mode.animated && painted)
138
+ write(ANSI_CLEAR_LINE);
139
+ write(finalLine + '\n');
140
+ if (mode.animated)
141
+ write(ANSI_SHOW_CURSOR);
142
+ };
143
+ return {
144
+ setPhase(name) { phase = name; if (!mode.animated)
145
+ paint(); },
146
+ setPercent(p) { percent = p; if (!mode.animated)
147
+ paint(); },
148
+ complete(message) { close('✓', ANSI_SUCCESS, message); },
149
+ fail(message) { close('✗', ANSI_ERROR, message); },
150
+ };
151
+ }
152
+ /** npm install phase → default percent. Best-effort bar shaping. */
153
+ function npmInstallPhasePercent(phase) {
154
+ switch (phase) {
155
+ case 'spawning': return 3;
156
+ case 'resolving': return 15;
157
+ case 'downloading': return 50;
158
+ case 'extracting': return 85;
159
+ case 'verifying': return 97;
160
+ case 'installed': return 100;
161
+ case 'failed': return 100;
162
+ default: return 0;
163
+ }
164
+ }
165
+ /** Detect npm phase from a stdout/stderr line. Checks ordered for npm 9/10/11. */
166
+ function detectNpmPhase(line) {
167
+ const l = line.toLowerCase();
168
+ if (l.includes('added ') && l.includes('package'))
169
+ return 'verifying';
170
+ if (l.includes('http fetch'))
171
+ return 'downloading';
172
+ if (l.includes('extracting') || l.includes('extract'))
173
+ return 'extracting';
174
+ if (l.includes('reify:'))
175
+ return 'downloading';
176
+ if (l.includes('resolved') || l.includes('audit'))
177
+ return 'resolving';
178
+ return null;
179
+ }
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.closestAction = closestAction;
4
+ /**
5
+ * Copyright (c) 2026 Shiva Deore (Taracod).
6
+ * Licensed under AGPL-3.0. See LICENSE for details.
7
+ *
8
+ * Aiden — local-first agent.
9
+ */
10
+ /**
11
+ * cli/v4/util/closestAction.ts — v4.9.1 amendment.
12
+ * Suggest the closest known action when the user mis-types a subcommand.
13
+ * Matches if input is a substring of a known action OR Levenshtein
14
+ * distance ≤ 2. Returns null when nothing is reasonably close.
15
+ */
16
+ function lev(a, b) {
17
+ const m = a.length, n = b.length;
18
+ if (m === 0)
19
+ return n;
20
+ if (n === 0)
21
+ return m;
22
+ const row = Array.from({ length: n + 1 }, (_, i) => i);
23
+ for (let i = 1; i <= m; i++) {
24
+ let prev = i - 1;
25
+ row[0] = i;
26
+ for (let j = 1; j <= n; j++) {
27
+ const cur = row[j];
28
+ row[j] = a[i - 1] === b[j - 1] ? prev : Math.min(prev, row[j - 1], row[j]) + 1;
29
+ prev = cur;
30
+ }
31
+ }
32
+ return row[n];
33
+ }
34
+ function closestAction(input, known) {
35
+ if (!input)
36
+ return null;
37
+ const lo = input.toLowerCase();
38
+ let best = null;
39
+ for (const k of known) {
40
+ const kl = k.toLowerCase();
41
+ if (kl.includes(lo) || lo.includes(kl))
42
+ return k;
43
+ const d = lev(lo, kl);
44
+ if (d <= 2 && (!best || d < best.d))
45
+ best = { name: k, d };
46
+ }
47
+ return best?.name ?? null;
48
+ }