aiden-runtime 4.8.1 → 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 +34 -9
- 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/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/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/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
package/README.md
CHANGED
|
@@ -94,7 +94,7 @@ Windows · Linux · WSL · macOS (API Mode)
|
|
|
94
94
|

|
|
95
95
|

|
|
96
96
|

|
|
97
|
-

|
|
98
98
|
|
|
99
99
|
</div>
|
|
100
100
|
|
|
@@ -134,6 +134,46 @@ Drop a file in `~/Documents/inbox/anything.txt` and Aiden acts on it. The agent
|
|
|
134
134
|
|
|
135
135
|
<br>
|
|
136
136
|
|
|
137
|
+
## What's new in v4.9.0
|
|
138
|
+
|
|
139
|
+
Aiden v4.9.0 ships three new feature families:
|
|
140
|
+
|
|
141
|
+
- **Memory** — three-namespace persistent memory (memory/user/project) with CLI + auto-review
|
|
142
|
+
- **Hooks** — secure subprocess hook system (observe/decide/transform) with full audit trail
|
|
143
|
+
- **Strategic substrate** — UUIDv7 IDs, W3C trace propagation, durable runs, idempotency, crash recovery
|
|
144
|
+
|
|
145
|
+
Plus theme system, MCP integration (Claude Desktop / Cursor / VS Code), and substantial UI polish.
|
|
146
|
+
|
|
147
|
+
See [CHANGELOG.md](./CHANGELOG.md) for details.
|
|
148
|
+
|
|
149
|
+
<br>
|
|
150
|
+
|
|
151
|
+
## What's new in v4.8
|
|
152
|
+
|
|
153
|
+
**Aiden's UI got an identity, and pasting actually works now.**
|
|
154
|
+
|
|
155
|
+
### v4.8.1 — Paste handling + visual polish
|
|
156
|
+
|
|
157
|
+
- **Robust paste handling.** Stateful parser across stdin chunk boundaries, 800ms watchdog for missing paste-end markers, degraded marker form normalisation, universal CRLF/CR → LF, and timing-based accumulation that catches line-by-line paste delivery. Works across Windows ConPTY, SSH without `-t`, tmux/screen passthrough, and VS Code's integrated terminal. Typed prefix is preserved when paste arrives mid-input.
|
|
158
|
+
- **Markdown tables render as proper grids.** Proportional column-width distribution with header-floor minimums so short labels never wrap mid-word. Model nudged via prompt to prefer sectioned lists for very wide comparisons.
|
|
159
|
+
- **Version reporting reads runtime `package.json`.** Boot card and `/version` no longer report stale numbers after `/update install`.
|
|
160
|
+
- **`/update install` cleanups.** Dropped the Node 20+ deprecation warning. Install progress shown via sliding-shimmer indicator. Install-method detection broadened to Windows user-mode globals.
|
|
161
|
+
- **Loading indicator + spacing.** Indicator aligned at col 2 matching other surfaces; conditional erase preserves clean 1-blank rhythm between turns.
|
|
162
|
+
- **Status bar timer glyph (`⌛`).** Visible at all terminal widths (mid + compact tiers previously dropped it).
|
|
163
|
+
- **Single approval panel** per `file_write` (no duplicate event-row + panel stack).
|
|
164
|
+
|
|
165
|
+
### v4.8.0 — Events backbone + visual identity
|
|
166
|
+
|
|
167
|
+
- **Watch the agent work.** Seven `ui_*` events (`ui_task_update`, `ui_task_done`, `ui_command_result`, `ui_test_result`, `ui_approval_request`, `ui_toast`, `ui_artifact_created`) let the model emit structured progress signals instead of writing them as prose. The REPL paints them as inline rows separate from the reply text. System prompt nudges the model to fire events during multi-step work.
|
|
168
|
+
- **Aiden-native visual chrome.** Orange `│` panel bars with asymmetric top-divider chrome (no closing corners). Token-sourced design system (`cli/v4/design/tokens.ts`) — palette, glyphs, spacing in one place.
|
|
169
|
+
- **Packed status bar.** Cyan model name · amber token ratio · semantic-tiered context bar (`●●●●●●○○○○`) · purple turn counter · teal `⌛` per-turn timer · state dot. Progressive disclosure on narrower terminals.
|
|
170
|
+
- **Sliding-block loading indicator.** 4-cell `█` segment slides L→R on a muted `─` track at 250ms/cell.
|
|
171
|
+
- **Onboarding refresh.** 24-bit ANSI AIDEN banner, capability-bullet disclaimer, 10-cell progress bar in the loading sequence, rounded heavy-frame "Built solo" card.
|
|
172
|
+
- **Markdown polish.** Code blocks get a top-divider with brand-orange language label. Bullet lists use `●`/`○`; task lists use `✔️`/`○`.
|
|
173
|
+
- **ChatGPT Plus + gpt-5 routing fix.** Auxiliary cheap-LLM calls (`risk_assess`, `compression`, `session_summary`, `skill_describe`) route through Groq + `llama-3.1-8b-instant` by default, falling back to the parent provider only when Groq isn't configured. Fixes the v4.6.x bug where every auxiliary call returned 400 from the Codex backend.
|
|
174
|
+
|
|
175
|
+
<br>
|
|
176
|
+
|
|
137
177
|
## What's new in v4.6
|
|
138
178
|
|
|
139
179
|
**Aiden now spawns workers and learns from itself.**
|
|
@@ -296,6 +336,47 @@ export AIDEN_DEFAULT_PROVIDER=groq
|
|
|
296
336
|
export AIDEN_DEFAULT_MODEL=llama-3.3-70b-versatile
|
|
297
337
|
```
|
|
298
338
|
|
|
339
|
+
### Recommended models
|
|
340
|
+
|
|
341
|
+
| Use case | Best models |
|
|
342
|
+
|---|---|
|
|
343
|
+
| **General-purpose / quality** | Claude Sonnet 4.6 / Opus, GPT-5, Gemini 2.5 Pro |
|
|
344
|
+
| **Speed + free tier** | Groq Llama 3.3 70B, Cerebras Llama 3.3 70B |
|
|
345
|
+
| **Fully local** | Ollama with Qwen 2.5 32B, Llama 3.3 70B, or DeepSeek R1 |
|
|
346
|
+
| **Open weights via subscription** | Nous Portal (Hermes-3-405B) |
|
|
347
|
+
| **Long context** | Gemini 2.5 Pro (2M tokens) |
|
|
348
|
+
|
|
349
|
+
> 💡 **Quality scales with model capability.** Aiden's output quality depends heavily on the model you pick. For real work, prefer the highest-capability model your budget allows — Claude Sonnet 4.6, GPT-5, or Gemini 2.5 Pro. Smaller models work for simple tasks but agentic loops benefit from stronger reasoning.
|
|
350
|
+
|
|
351
|
+
> 💡 **ChatGPT Plus / Claude Pro subscribers**: use the OAuth flow instead of an API key. Pick `chatgpt-plus` or `claude-pro` as your provider during `/model` setup, then sign in via browser. No separate API costs — uses your existing subscription's model allowances.
|
|
352
|
+
|
|
353
|
+
<br>
|
|
354
|
+
|
|
355
|
+
## Skills
|
|
356
|
+
|
|
357
|
+
Aiden ships with 74 bundled skills and supports installing more from the community registry.
|
|
358
|
+
|
|
359
|
+
### What are skills?
|
|
360
|
+
|
|
361
|
+
Skills are composable workflows. Each skill has a `SKILL.md` prompt, optional helper scripts, and declared tool requirements. Examples: GitHub PR/issue automation, NSE/Upstox/Zerodha trading flows, Censys/Shodan lookups, Windows Defender management.
|
|
362
|
+
|
|
363
|
+
### Managing skills
|
|
364
|
+
|
|
365
|
+
| Action | Command |
|
|
366
|
+
|---|---|
|
|
367
|
+
| List installed skills | `/skills` |
|
|
368
|
+
| Browse the community registry | Visit [skills.taracod.com](https://skills.taracod.com) |
|
|
369
|
+
| Install a skill from registry | `/skills install <name>` |
|
|
370
|
+
| Remove a skill | `/skills remove <name>` |
|
|
371
|
+
| Reload skills (after edits) | `/skills reload` |
|
|
372
|
+
|
|
373
|
+
### Contributing skills
|
|
374
|
+
|
|
375
|
+
Skills live in [`skills/`](skills/) under Apache-2.0. PRs welcome. Each skill needs:
|
|
376
|
+
- A `SKILL.md` describing what it does and when to use it
|
|
377
|
+
- Optional `tools.json` declaring required tools
|
|
378
|
+
- Optional helper scripts in `lib/`
|
|
379
|
+
|
|
299
380
|
<br>
|
|
300
381
|
|
|
301
382
|
## Configuration
|
|
@@ -485,6 +566,12 @@ Aiden can touch your files, run shell commands, and access the web. It's autonom
|
|
|
485
566
|
|
|
486
567
|
**Built solo. Started as a hobby project. Still rough in spots. Getting better every week.**
|
|
487
568
|
|
|
569
|
+
### Welcome
|
|
570
|
+
|
|
571
|
+
- **Contributors** — bug reports, skill submissions, provider adapters, and documentation PRs are all welcome. See [Contributing](#contributing) above for the workflow.
|
|
572
|
+
- **Sponsors / investors** — Aiden is built by one person funding it from client work. If you'd like to help Aiden grow faster, reach out at [contact@taracod.com](mailto:contact@taracod.com) or sponsor via [Razorpay](https://razorpay.me/@whitelotus9625).
|
|
573
|
+
- **Beta testers** — try it on your workflow, file issues, share what works and what doesn't. Honest feedback shapes the roadmap.
|
|
574
|
+
|
|
488
575
|
---
|
|
489
576
|
|
|
490
577
|
<div align="center">
|
package/dist/cli/v4/aidenCLI.js
CHANGED
|
@@ -472,10 +472,37 @@ async function main(argv, opts = {}) {
|
|
|
472
472
|
});
|
|
473
473
|
process.exit(code);
|
|
474
474
|
});
|
|
475
|
+
// v4.9.0 Slice 9 — memory CLI surface.
|
|
476
|
+
program
|
|
477
|
+
.command('memory [action] [args...]')
|
|
478
|
+
.description('Manage MEMORY.md + USER.md + project memory. Actions: list (default), show, add, remove, edit, backup, restore, diff, namespaces, pending, approve, reject, review.')
|
|
479
|
+
.allowUnknownOption()
|
|
480
|
+
.action(async (action, posArgs) => {
|
|
481
|
+
const { runMemorySubcommand } = await Promise.resolve().then(() => __importStar(require('./commands/memory')));
|
|
482
|
+
const code = await runMemorySubcommand(action ?? 'list', posArgs ?? [], {
|
|
483
|
+
writeOut: opts.writeOut,
|
|
484
|
+
});
|
|
485
|
+
process.exit(code);
|
|
486
|
+
});
|
|
487
|
+
// v4.9.0 Slice 12b — hooks CLI surface.
|
|
488
|
+
program
|
|
489
|
+
.command('hooks [action] [args...]')
|
|
490
|
+
.description('Manage hook subsystem. Actions: list (default), show, trust, revoke, rescan, test, doctor, audit.')
|
|
491
|
+
.allowUnknownOption()
|
|
492
|
+
.action(async (action, posArgs) => {
|
|
493
|
+
const { runHooksSubcommand } = await Promise.resolve().then(() => __importStar(require('./commands/hooks')));
|
|
494
|
+
const code = await runHooksSubcommand(action ?? 'list', posArgs ?? [], {
|
|
495
|
+
writeOut: opts.writeOut,
|
|
496
|
+
});
|
|
497
|
+
process.exit(code);
|
|
498
|
+
});
|
|
475
499
|
// v4.5 Phase 4b — daemon supervisor commands.
|
|
476
500
|
program
|
|
477
501
|
.command('daemon <action> [args...]')
|
|
478
|
-
.description('Manage the
|
|
502
|
+
.description('Manage the Aiden daemon. Actions: install, uninstall, start, stop, restart, status, logs, doctor.')
|
|
503
|
+
// v4.9.0 Slice 8 — `aiden daemon doctor --json` / `--fix`. Allow
|
|
504
|
+
// unknown flags so the subcommand can parse them via posArgs.
|
|
505
|
+
.allowUnknownOption()
|
|
479
506
|
.action(async (action, posArgs) => {
|
|
480
507
|
const { runDaemonSubcommand } = await Promise.resolve().then(() => __importStar(require('./commands/daemon')));
|
|
481
508
|
const code = await runDaemonSubcommand(action, posArgs ?? [], {
|
|
@@ -518,9 +545,11 @@ async function main(argv, opts = {}) {
|
|
|
518
545
|
process.exit(code);
|
|
519
546
|
});
|
|
520
547
|
program
|
|
521
|
-
.command('mcp <action>')
|
|
522
|
-
.description('MCP server mode
|
|
523
|
-
|
|
548
|
+
.command('mcp <action> [target] [extraArgs...]')
|
|
549
|
+
.description('MCP server mode + client config. Actions: serve | status | tools | init <client> | doctor <client> | repair <client> | uninstall <client>. ' +
|
|
550
|
+
'Clients: claude (Claude Desktop), cursor, vscode.')
|
|
551
|
+
.allowUnknownOption()
|
|
552
|
+
.action(async (action, target, extraArgs = []) => {
|
|
524
553
|
if (opts.runMcpHook) {
|
|
525
554
|
await opts.runMcpHook(action);
|
|
526
555
|
return;
|
|
@@ -531,6 +560,8 @@ async function main(argv, opts = {}) {
|
|
|
531
560
|
const code = await runMcpSubcommand(action, {
|
|
532
561
|
writeOut: opts.writeOut,
|
|
533
562
|
writeErr: (t) => process.stderr.write(t),
|
|
563
|
+
args: extraArgs,
|
|
564
|
+
target,
|
|
534
565
|
});
|
|
535
566
|
if (code !== 0)
|
|
536
567
|
process.exit(code);
|
|
@@ -98,6 +98,8 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
98
98
|
const bracketedPaste_1 = require("./bracketedPaste");
|
|
99
99
|
const pasteCompression_1 = require("./pasteCompression");
|
|
100
100
|
const pasteIntercept_1 = require("./pasteIntercept");
|
|
101
|
+
// v4.9.0 Slice 1a — theme hot-reload watcher.
|
|
102
|
+
const themeWatcher_1 = require("../../core/v4/theme/themeWatcher");
|
|
101
103
|
const shellInterpolation_1 = require("./shellInterpolation");
|
|
102
104
|
const resizeGuard_1 = require("./resizeGuard");
|
|
103
105
|
/**
|
|
@@ -425,6 +427,16 @@ class ChatSession {
|
|
|
425
427
|
const restorePasteInterceptor = this.opts.promptApi
|
|
426
428
|
? () => { }
|
|
427
429
|
: (0, pasteIntercept_1.installPasteInterceptor)(process.stdin);
|
|
430
|
+
// v4.9.0 Slice 1a — start the theme hot-reload watcher. Honours
|
|
431
|
+
// `~/.aiden/theme.yaml` if it exists; otherwise no-op until the
|
|
432
|
+
// user creates one. Disposed in the `finally` block alongside
|
|
433
|
+
// the paste interceptor.
|
|
434
|
+
if (this.opts.paths && !this.opts.promptApi) {
|
|
435
|
+
try {
|
|
436
|
+
(0, themeWatcher_1.startThemeWatcher)(require('node:path').join(this.opts.paths.root, 'theme.yaml'));
|
|
437
|
+
}
|
|
438
|
+
catch { /* watcher start failure must not crash REPL */ }
|
|
439
|
+
}
|
|
428
440
|
// Tier-3-essentials: hard-clear the screen on terminal resize so
|
|
429
441
|
// dropdown re-renders + previous prompt frames don't ghost into
|
|
430
442
|
// the new viewport. No-op on non-TTY / MCP serve mode.
|
|
@@ -529,6 +541,12 @@ class ChatSession {
|
|
|
529
541
|
// commands; the footer belongs to agent turns only.
|
|
530
542
|
continue;
|
|
531
543
|
}
|
|
544
|
+
// v4.9.0 pre-ship UI: BOTTOM rule of the prompt zone. The TOP
|
|
545
|
+
// rule fires at `printTurnSeparator()` above (iter > 1). This
|
|
546
|
+
// bottom rule fires only when actual content goes to the LLM —
|
|
547
|
+
// slash commands + empty input + /quit all `continue` above and
|
|
548
|
+
// never reach this line, matching the task's edge-case spec.
|
|
549
|
+
this.opts.display.write(` ${this.opts.display.rule()}\n`);
|
|
532
550
|
await this.runAgentTurn(input);
|
|
533
551
|
}
|
|
534
552
|
}
|
|
@@ -542,6 +560,10 @@ class ChatSession {
|
|
|
542
560
|
if (pasteEnabled)
|
|
543
561
|
(0, bracketedPaste_1.disableBracketedPaste)(stdout);
|
|
544
562
|
restorePasteInterceptor();
|
|
563
|
+
try {
|
|
564
|
+
(0, themeWatcher_1.stopThemeWatcher)();
|
|
565
|
+
}
|
|
566
|
+
catch { /* defensive */ }
|
|
545
567
|
restoreResizeGuard();
|
|
546
568
|
}
|
|
547
569
|
}
|
|
@@ -1362,8 +1384,10 @@ class ChatSession {
|
|
|
1362
1384
|
_deferredTip = null;
|
|
1363
1385
|
}
|
|
1364
1386
|
// Tier-3.1a: dim full-width rule between the agent reply and the
|
|
1365
|
-
// post-turn status footer.
|
|
1366
|
-
|
|
1387
|
+
// post-turn status footer. v4.9.0 pre-ship UI: prepend a blank
|
|
1388
|
+
// line so the response zone gets breathing room above the rule
|
|
1389
|
+
// — matches the `\n\n` blank already below `▎ Aiden` header.
|
|
1390
|
+
this.opts.display.write(`\n ${this.opts.display.rule()}\n`);
|
|
1367
1391
|
this.renderStatusLine();
|
|
1368
1392
|
// v4.1.5+ Path A — finalize the loop trace. No-op if the env
|
|
1369
1393
|
// var is unset OR if the turn didn't trip any threshold. When
|
|
@@ -1648,15 +1672,16 @@ class ChatSession {
|
|
|
1648
1672
|
}
|
|
1649
1673
|
}
|
|
1650
1674
|
catch { /* never let a missing marker crash boot */ }
|
|
1651
|
-
//
|
|
1652
|
-
//
|
|
1653
|
-
//
|
|
1654
|
-
//
|
|
1655
|
-
//
|
|
1656
|
-
|
|
1657
|
-
display.write(` ${display.rule()}\n`);
|
|
1675
|
+
// v4.9.0 pre-ship UI: hint moved BEFORE the closing rule so the
|
|
1676
|
+
// rule sits adjacent to the active prompt (it becomes the visual
|
|
1677
|
+
// top of the prompt zone). New order: blank · hint · blank · rule.
|
|
1678
|
+
// Banner content ends with the hint; the rule below it brackets
|
|
1679
|
+
// the user-input zone together with the new bottom-rule emission
|
|
1680
|
+
// in the REPL loop.
|
|
1658
1681
|
display.write('\n');
|
|
1659
1682
|
display.write(display.bottomPromptHint() + '\n');
|
|
1683
|
+
display.write('\n');
|
|
1684
|
+
display.write(` ${display.rule()}\n`);
|
|
1660
1685
|
}
|
|
1661
1686
|
/**
|
|
1662
1687
|
* v4.5 update system — orchestrates the boot prompt. Lazy-imports
|
|
@@ -27,6 +27,39 @@
|
|
|
27
27
|
* variance + admin requirements make the install footprint too risky
|
|
28
28
|
* for an auto-installer. The docs cover pm2 + foreground patterns.
|
|
29
29
|
*/
|
|
30
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
31
|
+
if (k2 === undefined) k2 = k;
|
|
32
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
33
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
34
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
35
|
+
}
|
|
36
|
+
Object.defineProperty(o, k2, desc);
|
|
37
|
+
}) : (function(o, m, k, k2) {
|
|
38
|
+
if (k2 === undefined) k2 = k;
|
|
39
|
+
o[k2] = m[k];
|
|
40
|
+
}));
|
|
41
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
42
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
43
|
+
}) : function(o, v) {
|
|
44
|
+
o["default"] = v;
|
|
45
|
+
});
|
|
46
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
47
|
+
var ownKeys = function(o) {
|
|
48
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
49
|
+
var ar = [];
|
|
50
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
51
|
+
return ar;
|
|
52
|
+
};
|
|
53
|
+
return ownKeys(o);
|
|
54
|
+
};
|
|
55
|
+
return function (mod) {
|
|
56
|
+
if (mod && mod.__esModule) return mod;
|
|
57
|
+
var result = {};
|
|
58
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
59
|
+
__setModuleDefault(result, mod);
|
|
60
|
+
return result;
|
|
61
|
+
};
|
|
62
|
+
})();
|
|
30
63
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
31
64
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
32
65
|
};
|
|
@@ -59,9 +92,21 @@ async function runDaemonSubcommand(action, args, opts = {}) {
|
|
|
59
92
|
case 'restart': return runRestart({ out, err });
|
|
60
93
|
case 'status': return runStatus({ out, err });
|
|
61
94
|
case 'logs': return runLogs({ out, err });
|
|
95
|
+
case 'doctor': {
|
|
96
|
+
// v4.9.0 Slice 8 — substrate health diagnostic. Read-only by
|
|
97
|
+
// default; `--fix` runs safe sweeps; `--json` outputs machine-
|
|
98
|
+
// parseable shape.
|
|
99
|
+
const { runDaemonDoctor } = await Promise.resolve().then(() => __importStar(require('./daemonDoctor')));
|
|
100
|
+
return runDaemonDoctor({
|
|
101
|
+
json: args.includes('--json'),
|
|
102
|
+
fix: args.includes('--fix'),
|
|
103
|
+
writeOut: out,
|
|
104
|
+
writeErr: err,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
62
107
|
default:
|
|
63
108
|
err(`Unknown daemon action: ${action}\n`);
|
|
64
|
-
err('Actions: install, uninstall, start, stop, restart, status, logs\n');
|
|
109
|
+
err('Actions: install, uninstall, start, stop, restart, status, logs, doctor\n');
|
|
65
110
|
return 2;
|
|
66
111
|
}
|
|
67
112
|
}
|
|
@@ -280,7 +325,7 @@ async function runInstallMacOS(io) {
|
|
|
280
325
|
async function runInstallWindows(io) {
|
|
281
326
|
io.out((0, daemon_1.windowsServiceGuidance)());
|
|
282
327
|
io.out('\n');
|
|
283
|
-
io.out('See docs/
|
|
328
|
+
io.out('See docs/daemon-windows.md for the full walkthrough.\n');
|
|
284
329
|
return 0;
|
|
285
330
|
}
|
|
286
331
|
// ── uninstall ───────────────────────────────────────────────────────────────
|
|
@@ -0,0 +1,212 @@
|
|
|
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/commands/daemonDoctor.ts — v4.9.0 Slice 8.
|
|
10
|
+
*
|
|
11
|
+
* `aiden daemon doctor` — read-only diagnostic of substrate health.
|
|
12
|
+
* `aiden daemon doctor --json` — machine-parseable shape.
|
|
13
|
+
* `aiden daemon doctor --fix` — runs sweeps for safely-fixable issues.
|
|
14
|
+
*
|
|
15
|
+
* Checks (in display order):
|
|
16
|
+
* 1. daemon_id file exists + parses
|
|
17
|
+
* 2. Schema at LATEST_SCHEMA_VERSION
|
|
18
|
+
* 3. Recent incarnation row present + heartbeat fresh
|
|
19
|
+
* 4. Recent crashes (24h) — warn if more than 3
|
|
20
|
+
* 5. Stuck attempts present (sweep candidates)
|
|
21
|
+
* 6. Orphan spans present (sweep candidates)
|
|
22
|
+
* 7. Idempotency table bounded (warn if > 10000 rows)
|
|
23
|
+
* 8. Pending-but-stale trigger events (claim lease expired)
|
|
24
|
+
*/
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.collectDoctorChecks = collectDoctorChecks;
|
|
30
|
+
exports.runDaemonDoctor = runDaemonDoctor;
|
|
31
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
32
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
33
|
+
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
34
|
+
const paths_1 = require("../../../core/v4/paths");
|
|
35
|
+
const identity_1 = require("../../../core/v4/identity");
|
|
36
|
+
const migrations_1 = require("../../../core/v4/daemon/db/migrations");
|
|
37
|
+
const stuckAttemptWatchdog_1 = require("../../../core/v4/daemon/runs/stuckAttemptWatchdog");
|
|
38
|
+
const TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1000;
|
|
39
|
+
/** Compute overall status: error if any error, warn if any warn, else ok. */
|
|
40
|
+
function overall(results) {
|
|
41
|
+
if (results.some((r) => r.status === 'error'))
|
|
42
|
+
return 'error';
|
|
43
|
+
if (results.some((r) => r.status === 'warn'))
|
|
44
|
+
return 'warn';
|
|
45
|
+
return 'ok';
|
|
46
|
+
}
|
|
47
|
+
function tableExists(db, name) {
|
|
48
|
+
const row = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name = ?`).get(name);
|
|
49
|
+
return !!row?.name;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Run all checks against the daemon DB at `<root>/daemon/daemon.db`.
|
|
53
|
+
* Pure-data path; no terminal I/O. Renders happen in `runDaemonDoctor`.
|
|
54
|
+
*/
|
|
55
|
+
function collectDoctorChecks(rootDir) {
|
|
56
|
+
const checks = [];
|
|
57
|
+
// 1. daemon_id file
|
|
58
|
+
const idPath = (0, identity_1.daemonIdFilePath)(rootDir);
|
|
59
|
+
if (!node_fs_1.default.existsSync(idPath)) {
|
|
60
|
+
checks.push({ name: 'daemon_id file', status: 'error',
|
|
61
|
+
detail: `missing: ${idPath} (daemon never booted in this root)`,
|
|
62
|
+
fixable: false });
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
const content = node_fs_1.default.readFileSync(idPath, 'utf8').trim();
|
|
66
|
+
checks.push({ name: 'daemon_id file', status: content.startsWith('dmn_') ? 'ok' : 'error',
|
|
67
|
+
detail: `${idPath} → ${content.slice(0, 40)}...`,
|
|
68
|
+
fixable: false });
|
|
69
|
+
}
|
|
70
|
+
// Open DB. If unavailable, remaining checks short-circuit.
|
|
71
|
+
const dbPath = node_path_1.default.join(rootDir, 'daemon', 'daemon.db');
|
|
72
|
+
if (!node_fs_1.default.existsSync(dbPath)) {
|
|
73
|
+
checks.push({ name: 'daemon DB', status: 'error',
|
|
74
|
+
detail: `missing: ${dbPath}`, fixable: false });
|
|
75
|
+
return checks;
|
|
76
|
+
}
|
|
77
|
+
const db = new better_sqlite3_1.default(dbPath, { readonly: false });
|
|
78
|
+
db.pragma('foreign_keys = ON');
|
|
79
|
+
try {
|
|
80
|
+
// 2. Schema version
|
|
81
|
+
const verRow = db.prepare(`SELECT version FROM schema_version WHERE id = 1`)
|
|
82
|
+
.get();
|
|
83
|
+
const ver = verRow?.version ?? 0;
|
|
84
|
+
checks.push({ name: 'schema version', status: ver === migrations_1.LATEST_SCHEMA_VERSION ? 'ok' : 'warn',
|
|
85
|
+
detail: `current=${ver} latest=${migrations_1.LATEST_SCHEMA_VERSION}`,
|
|
86
|
+
fixable: false });
|
|
87
|
+
// 3. Recent incarnation
|
|
88
|
+
const inc = db.prepare(`SELECT incarnation_id, started_at, ended_at, exit_reason FROM daemon_incarnations
|
|
89
|
+
ORDER BY started_at DESC LIMIT 1`).get();
|
|
90
|
+
if (!inc) {
|
|
91
|
+
checks.push({ name: 'recent incarnation', status: 'warn',
|
|
92
|
+
detail: 'no daemon_incarnations rows (daemon never booted post-Slice-4)',
|
|
93
|
+
fixable: false });
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
const closedDetail = inc.ended_at
|
|
97
|
+
? `last incarnation ${inc.incarnation_id.slice(0, 20)}... ended_at=${inc.ended_at} reason=${inc.exit_reason}`
|
|
98
|
+
: `latest incarnation ${inc.incarnation_id.slice(0, 20)}... still open (started_at=${inc.started_at})`;
|
|
99
|
+
checks.push({ name: 'recent incarnation', status: 'ok', detail: closedDetail, fixable: false });
|
|
100
|
+
}
|
|
101
|
+
// 4. Recent crashes (24h)
|
|
102
|
+
const sinceIso = new Date(Date.now() - TWENTY_FOUR_HOURS_MS).toISOString();
|
|
103
|
+
const crashes = db.prepare(`SELECT COUNT(*) AS c FROM daemon_incarnations
|
|
104
|
+
WHERE exit_reason = 'crash' AND started_at > ?`).get(sinceIso);
|
|
105
|
+
checks.push({ name: 'recent crashes (24h)',
|
|
106
|
+
status: crashes.c === 0 ? 'ok' : crashes.c > 3 ? 'warn' : 'ok',
|
|
107
|
+
detail: `${crashes.c} crash(es) in last 24h`,
|
|
108
|
+
fixable: false });
|
|
109
|
+
// 5. Stuck attempts (require Slice 5 + 8 tables)
|
|
110
|
+
let stuckAttempts = 0;
|
|
111
|
+
if (tableExists(db, 'run_attempts')) {
|
|
112
|
+
const currentInc = inc?.incarnation_id ?? '';
|
|
113
|
+
const cutoffIso = new Date(Date.now() - 30 * 60 * 1000).toISOString();
|
|
114
|
+
stuckAttempts = db.prepare(`SELECT COUNT(*) AS c FROM run_attempts
|
|
115
|
+
WHERE status='running' AND incarnation_id != ? AND started_at < ?`).get(currentInc, cutoffIso).c;
|
|
116
|
+
}
|
|
117
|
+
checks.push({ name: 'stuck attempts',
|
|
118
|
+
status: stuckAttempts === 0 ? 'ok' : 'warn',
|
|
119
|
+
detail: `${stuckAttempts} stuck attempt(s) eligible for sweep`,
|
|
120
|
+
fixable: stuckAttempts > 0,
|
|
121
|
+
fix: stuckAttempts > 0 && inc
|
|
122
|
+
? () => { (0, stuckAttemptWatchdog_1.sweepStuckAttempts)(db, { currentIncarnationId: inc.incarnation_id }); }
|
|
123
|
+
: undefined });
|
|
124
|
+
// 6. Orphan spans (open + non-current incarnation)
|
|
125
|
+
let orphanSpans = 0;
|
|
126
|
+
if (tableExists(db, 'spans')) {
|
|
127
|
+
const currentInc = inc?.incarnation_id ?? '';
|
|
128
|
+
orphanSpans = db.prepare(`SELECT COUNT(*) AS c FROM spans
|
|
129
|
+
WHERE status IS NULL AND ended_at IS NULL AND incarnation_id != ?`).get(currentInc).c;
|
|
130
|
+
}
|
|
131
|
+
checks.push({ name: 'orphan spans',
|
|
132
|
+
status: orphanSpans === 0 ? 'ok' : 'warn',
|
|
133
|
+
detail: `${orphanSpans} orphan span(s) eligible for sweep`,
|
|
134
|
+
fixable: orphanSpans > 0,
|
|
135
|
+
fix: orphanSpans > 0 && inc
|
|
136
|
+
? () => { (0, stuckAttemptWatchdog_1.sweepStuckAttempts)(db, { currentIncarnationId: inc.incarnation_id }); }
|
|
137
|
+
: undefined });
|
|
138
|
+
// 7. Idempotency tables size
|
|
139
|
+
let idemRows = 0;
|
|
140
|
+
if (tableExists(db, 'run_idempotency_keys')) {
|
|
141
|
+
idemRows = db.prepare(`SELECT COUNT(*) AS c FROM run_idempotency_keys`).get().c;
|
|
142
|
+
}
|
|
143
|
+
checks.push({ name: 'run_idempotency_keys size',
|
|
144
|
+
status: idemRows < 10000 ? 'ok' : 'warn',
|
|
145
|
+
detail: `${idemRows} row(s)` + (idemRows >= 10000 ? ' — consider sweepExpired' : ''),
|
|
146
|
+
fixable: false });
|
|
147
|
+
// 8. Stale-claimed trigger events
|
|
148
|
+
let staleClaimed = 0;
|
|
149
|
+
if (tableExists(db, 'trigger_events')) {
|
|
150
|
+
staleClaimed = db.prepare(`SELECT COUNT(*) AS c FROM trigger_events
|
|
151
|
+
WHERE status='claimed' AND claim_expires_at IS NOT NULL AND claim_expires_at < ?`).get(Date.now()).c;
|
|
152
|
+
}
|
|
153
|
+
checks.push({ name: 'stale-claimed trigger events',
|
|
154
|
+
status: staleClaimed === 0 ? 'ok' : 'warn',
|
|
155
|
+
detail: `${staleClaimed} trigger_events past claim_expires_at`,
|
|
156
|
+
fixable: false });
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
try {
|
|
160
|
+
db.close();
|
|
161
|
+
}
|
|
162
|
+
catch { /* noop */ }
|
|
163
|
+
}
|
|
164
|
+
return checks;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* CLI entry. Returns the desired process exit code (0=ok, 1=error,
|
|
168
|
+
* 0 for warn-only since warnings shouldn't break scripts).
|
|
169
|
+
*/
|
|
170
|
+
function runDaemonDoctor(opts = {}) {
|
|
171
|
+
const root = opts.rootDir ?? (0, paths_1.resolveAidenRoot)();
|
|
172
|
+
const out = opts.writeOut ?? ((s) => { process.stdout.write(s); });
|
|
173
|
+
const err = opts.writeErr ?? ((s) => { process.stderr.write(s); });
|
|
174
|
+
const checks = collectDoctorChecks(root);
|
|
175
|
+
const fixed = [];
|
|
176
|
+
if (opts.fix) {
|
|
177
|
+
for (const c of checks) {
|
|
178
|
+
if (c.fixable && c.fix) {
|
|
179
|
+
try {
|
|
180
|
+
c.fix();
|
|
181
|
+
fixed.push(c.name);
|
|
182
|
+
}
|
|
183
|
+
catch (e) {
|
|
184
|
+
err(`[doctor] fix '${c.name}' failed: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const report = {
|
|
190
|
+
overall: overall(checks),
|
|
191
|
+
checks: checks.map((c) => ({ name: c.name, status: c.status, detail: c.detail, fixable: c.fixable })),
|
|
192
|
+
...(opts.fix ? { fixed } : {}),
|
|
193
|
+
};
|
|
194
|
+
if (opts.json) {
|
|
195
|
+
out(JSON.stringify(report, null, 2) + '\n');
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
const sym = (s) => s === 'ok' ? '[ok] ' : s === 'warn' ? '[warn]' : '[err] ';
|
|
199
|
+
out(`aiden daemon doctor — ${report.overall.toUpperCase()}\n`);
|
|
200
|
+
out(`root: ${root}\n\n`);
|
|
201
|
+
for (const c of report.checks) {
|
|
202
|
+
out(` ${sym(c.status)} ${c.name}: ${c.detail}\n`);
|
|
203
|
+
}
|
|
204
|
+
if (opts.fix && fixed.length > 0) {
|
|
205
|
+
out(`\nfixed: ${fixed.join(', ')}\n`);
|
|
206
|
+
}
|
|
207
|
+
else if (opts.fix) {
|
|
208
|
+
out(`\nno fixable issues found\n`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return report.overall === 'error' ? 1 : 0;
|
|
212
|
+
}
|
|
@@ -30,7 +30,7 @@ const daemon_1 = require("../../../core/v4/daemon");
|
|
|
30
30
|
const paths_1 = require("../../../core/v4/paths");
|
|
31
31
|
exports.daemonStatus = {
|
|
32
32
|
name: 'daemon',
|
|
33
|
-
description: 'Show
|
|
33
|
+
description: 'Show daemon status (read-only). Use `aiden daemon` for lifecycle.',
|
|
34
34
|
category: 'system',
|
|
35
35
|
icon: '⚙',
|
|
36
36
|
handler: async (ctx) => {
|
|
@@ -31,6 +31,8 @@ exports.SUBSECTION_MAP = {
|
|
|
31
31
|
providers: 'Configuration',
|
|
32
32
|
personality: 'Configuration',
|
|
33
33
|
skin: 'Configuration',
|
|
34
|
+
// v4.9.0 Slice 1a — unified theme system (parallel to /skin).
|
|
35
|
+
theme: 'Configuration',
|
|
34
36
|
streaming: 'Configuration',
|
|
35
37
|
reasoning: 'Configuration',
|
|
36
38
|
verbose: 'Configuration',
|