aiden-runtime 4.5.0 → 4.6.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.
Files changed (50) hide show
  1. package/README.md +17 -2
  2. package/dist/cli/v4/aidenCLI.js +207 -100
  3. package/dist/cli/v4/chatSession.js +120 -0
  4. package/dist/cli/v4/commands/_runtimeToggleHelpers.js +2 -0
  5. package/dist/cli/v4/commands/fanout.js +42 -59
  6. package/dist/cli/v4/commands/help.js +8 -0
  7. package/dist/cli/v4/commands/index.js +21 -1
  8. package/dist/cli/v4/commands/mcp.js +80 -54
  9. package/dist/cli/v4/commands/plannerGuard.js +53 -0
  10. package/dist/cli/v4/commands/recovery.js +122 -0
  11. package/dist/cli/v4/commands/runs.js +22 -2
  12. package/dist/cli/v4/commands/spawnPause.js +93 -0
  13. package/dist/cli/v4/commands/walkthrough.js +140 -0
  14. package/dist/cli/v4/daemonAgentBuilder.js +4 -1
  15. package/dist/cli/v4/defaultSoul.js +1 -1
  16. package/dist/cli/v4/onboarding/disclaimer.js +162 -0
  17. package/dist/cli/v4/onboarding/loading.js +208 -0
  18. package/dist/cli/v4/onboarding/providerPicker.js +126 -0
  19. package/dist/cli/v4/onboarding/successScreen.js +68 -0
  20. package/dist/cli/v4/repl/firstRunHint.js +107 -0
  21. package/dist/cli/v4/setupWizard.js +201 -31
  22. package/dist/core/v4/aidenAgent.js +219 -1
  23. package/dist/core/v4/daemon/bootstrap.js +47 -0
  24. package/dist/core/v4/daemon/db/migrations.js +66 -0
  25. package/dist/core/v4/daemon/runStore.js +33 -3
  26. package/dist/core/v4/providerFallback.js +35 -2
  27. package/dist/core/v4/providers/modelFetch.js +179 -0
  28. package/dist/core/v4/providers/probe.js +275 -0
  29. package/dist/core/v4/runtimeToggles.js +30 -3
  30. package/dist/core/v4/selfimprovement/recoveryStore.js +307 -0
  31. package/dist/core/v4/selfimprovement/signatureBuilder.js +158 -0
  32. package/dist/core/v4/subagent/childBuilder.js +391 -0
  33. package/dist/core/v4/subagent/fanout.js +75 -51
  34. package/dist/core/v4/subagent/spawnPause.js +191 -0
  35. package/dist/core/v4/subagent/spawnSubAgent.js +310 -0
  36. package/dist/core/v4/toolRegistry.js +19 -3
  37. package/dist/core/v4/ui/banner.js +133 -0
  38. package/dist/core/v4/ui/theme.js +164 -0
  39. package/dist/core/version.js +1 -1
  40. package/dist/moat/plannerGuard.js +29 -0
  41. package/dist/providers/v4/anthropicAdapter.js +31 -3
  42. package/dist/providers/v4/chatCompletionsAdapter.js +26 -3
  43. package/dist/providers/v4/codexResponsesAdapter.js +25 -2
  44. package/dist/providers/v4/ollamaPromptToolsAdapter.js +57 -2
  45. package/dist/tools/v4/index.js +17 -3
  46. package/dist/tools/v4/skills/lookupToolSchema.js +6 -1
  47. package/dist/tools/v4/subagent/spawnSubAgentTool.js +334 -0
  48. package/dist/tools/v4/subagent/subagentFanout.js +53 -1
  49. package/dist/tools/v4/ui/_uiSmokeTool.js +60 -0
  50. package/package.json +7 -3
@@ -26,8 +26,6 @@
26
26
  Object.defineProperty(exports, "__esModule", { value: true });
27
27
  exports.parseFanoutArgs = parseFanoutArgs;
28
28
  exports.runFanoutCli = runFanoutCli;
29
- /* eslint-disable @typescript-eslint/no-explicit-any */
30
- const fanout_1 = require("../../../core/v4/subagent/fanout");
31
29
  function parseFanoutArgs(argv) {
32
30
  const args = { positional: [] };
33
31
  let i = 0;
@@ -172,65 +170,50 @@ async function runFanoutCli(argv, opts = {}) {
172
170
  return 1;
173
171
  }
174
172
  }
175
- // ── Dry-run path: stub providers, stub child runner, stub aggregator
176
- // exercises the Promise.all + abort + merge plumbing against the
177
- // built artifact without touching network or provider keys.
178
- const providers = [
179
- { providerId: 'stub-a', modelId: 'fake-model' },
180
- { providerId: 'stub-b', modelId: 'fake-model' },
181
- ];
182
- const stubAdapter = {
183
- apiMode: 'chat_completions',
184
- async call() {
185
- return {
186
- content: '[dry-run aggregator]',
187
- toolCalls: [],
188
- finishReason: 'stop',
189
- usage: { inputTokens: 0, outputTokens: 0 },
190
- };
191
- },
192
- };
193
- try {
194
- const result = await (0, fanout_1.runFanout)({
195
- mode: args.mode,
196
- query: args.query,
197
- tasks: args.mode === 'partition'
198
- ? Array.from({ length: args.n }, (_, i) => ({
199
- goal: `Task ${i + 1} from CLI: ${args.query}`,
200
- }))
201
- : undefined,
202
- n: args.n,
203
- merge: args.merge,
204
- providers,
205
- runChild: async ({ index, prompt, provider }) => {
206
- return `[dry-run child ${index} via ${provider.providerId}] echo: ${prompt}`;
207
- },
208
- aggregatorAdapter: stubAdapter,
209
- aggregatorModel: { providerId: 'stub-a', modelId: 'fake-model' },
210
- timeoutMs: args.timeoutMs,
173
+ // ── Dry-run path ─────────────────────────────────────────────
174
+ // v4.6 Phase 2Q `runFanout` now routes children through
175
+ // `spawnSubAgent`, which needs real `SpawnSubAgentDeps`
176
+ // (toolRegistry, parentProvider, runStore, …). Pre-2Q the dry-run
177
+ // exercised Promise.all + abort + merge dispatch via simple stubs;
178
+ // post-refactor the equivalent coverage now lives in
179
+ // `tests/v4/subagent/fanout.behavioral.test.ts` (Slice 5).
180
+ //
181
+ // Dry-run therefore emits a synthetic snapshot — same observable
182
+ // shape (mode/n/merge/per-child rows) so the runtime smoke can
183
+ // still assert "the CLI subcommand parses + runs to exit 0
184
+ // against the built artifact" without booting a runtime.
185
+ const stubProviders = ['stub-a', 'stub-b'];
186
+ const childRows = [];
187
+ for (let i = 0; i < args.n; i += 1) {
188
+ const providerId = stubProviders[i % stubProviders.length];
189
+ const prompt = args.mode === 'partition'
190
+ ? `Task ${i + 1} from CLI: ${args.query}`
191
+ : args.query;
192
+ childRows.push({
193
+ index: i,
194
+ providerId,
195
+ modelId: 'fake-model',
196
+ output: `[dry-run child ${i} via ${providerId}] echo: ${prompt}`,
197
+ elapsedMs: 0,
211
198
  });
212
- writeOut(`fanout dry-run\n`);
213
- writeOut(` mode: ${args.mode}\n`);
214
- writeOut(` n: ${args.n}\n`);
215
- writeOut(` merge: ${args.merge}\n`);
216
- writeOut(` query: ${args.query}\n`);
217
- writeOut(` succeeded: ${result.diagnostics.succeeded}/${args.n}\n`);
218
- writeOut(` totalMs: ${result.diagnostics.totalMs}\n`);
219
- writeOut(` providers: ${result.diagnostics.providerDistribution.join(', ')}\n`);
220
- writeOut(`\n--- merged ---\n`);
221
- if (args.merge === 'all') {
222
- for (const r of result.results) {
223
- writeOut(`\n[${r.index}] ${r.providerId}:${r.modelId} (${r.elapsedMs}ms)\n`);
224
- writeOut(`${r.output}\n`);
225
- }
226
- }
227
- else {
228
- writeOut(`${result.merged ?? '(no merged output)'}\n`);
199
+ }
200
+ writeOut(`fanout dry-run\n`);
201
+ writeOut(` mode: ${args.mode}\n`);
202
+ writeOut(` n: ${args.n}\n`);
203
+ writeOut(` merge: ${args.merge}\n`);
204
+ writeOut(` query: ${args.query}\n`);
205
+ writeOut(` succeeded: ${args.n}/${args.n}\n`);
206
+ writeOut(` totalMs: 0\n`);
207
+ writeOut(` providers: ${childRows.map((r) => r.providerId).join(', ')}\n`);
208
+ writeOut(`\n--- merged ---\n`);
209
+ if (args.merge === 'all') {
210
+ for (const r of childRows) {
211
+ writeOut(`\n[${r.index}] ${r.providerId}:${r.modelId} (${r.elapsedMs}ms)\n`);
212
+ writeOut(`${r.output}\n`);
229
213
  }
230
- return 0;
231
214
  }
232
- catch (err) {
233
- writeErr(`fanout failed: ${err.message}\n`);
234
- return 1;
215
+ else {
216
+ writeOut(`[dry-run aggregator]\n`);
235
217
  }
218
+ return 0;
236
219
  }
@@ -65,6 +65,14 @@ exports.SUBSECTION_MAP = {
65
65
  daemon: 'System',
66
66
  // v4.5 Phase 8b — contextual capability suggestions.
67
67
  suggestions: 'System',
68
+ // v4.6 Phase 2M — opt-in keyword-based tool narrower.
69
+ 'planner-guard': 'System',
70
+ // v4.6 Phase 3A — operator kill-switch for sub-agent spawning.
71
+ 'spawn-pause': 'System',
72
+ // v4.6 Phase 3b — self-improvement loop operator surface.
73
+ recovery: 'System',
74
+ // v4.6 ONB1 slice 10 — new-user guided tour.
75
+ walkthrough: 'System',
68
76
  // ── Authentication ──
69
77
  auth: 'Authentication',
70
78
  // ── Help ──
@@ -12,7 +12,7 @@
12
12
  * and registers each on the global CommandRegistry at boot.
13
13
  */
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.allCommands = exports.suggestions = exports.daemonStatus = exports.browserDepth = exports.tce = exports.sandbox = exports.update = exports.reloadSoul = exports.history = exports.show = exports.status = exports.voice = exports.channel = exports.setup = exports.cron = exports.doctor = exports.license = exports.auth = exports.plugins = exports.streaming = exports.debugPrompt = exports.identity = exports.providers = exports.quit = exports.clear = exports.verbose = exports.reasoning = exports.reloadMcp = exports.skills = exports.skin = exports.yolo = exports.usage = exports.compress = exports.title = exports.save = exports.personality = exports.model = exports.tools = exports.help = void 0;
15
+ exports.allCommands = exports.walkthrough = exports.recovery = exports.spawnPause = exports.plannerGuard = exports.suggestions = exports.daemonStatus = exports.browserDepth = exports.tce = exports.sandbox = exports.update = exports.reloadSoul = exports.history = exports.show = exports.status = exports.voice = exports.channel = exports.setup = exports.cron = exports.doctor = exports.license = exports.auth = exports.plugins = exports.streaming = exports.debugPrompt = exports.identity = exports.providers = exports.quit = exports.clear = exports.verbose = exports.reasoning = exports.reloadMcp = exports.skills = exports.skin = exports.yolo = exports.usage = exports.compress = exports.title = exports.save = exports.personality = exports.model = exports.tools = exports.help = void 0;
16
16
  const help_1 = require("./help");
17
17
  Object.defineProperty(exports, "help", { enumerable: true, get: function () { return help_1.help; } });
18
18
  const tools_1 = require("./tools");
@@ -91,6 +91,18 @@ Object.defineProperty(exports, "daemonStatus", { enumerable: true, get: function
91
91
  // v4.5 Phase 8b — contextual capability suggestions toggle.
92
92
  const suggestions_1 = require("./suggestions");
93
93
  Object.defineProperty(exports, "suggestions", { enumerable: true, get: function () { return suggestions_1.suggestions; } });
94
+ // v4.6 Phase 2M — opt-in keyword-based tool narrower (default OFF).
95
+ const plannerGuard_1 = require("./plannerGuard");
96
+ Object.defineProperty(exports, "plannerGuard", { enumerable: true, get: function () { return plannerGuard_1.plannerGuard; } });
97
+ // v4.6 Phase 3A — operator kill-switch for sub-agent spawning.
98
+ const spawnPause_1 = require("./spawnPause");
99
+ Object.defineProperty(exports, "spawnPause", { enumerable: true, get: function () { return spawnPause_1.spawnPause; } });
100
+ // v4.6 Phase 3b — self-improvement loop operator surface.
101
+ const recovery_1 = require("./recovery");
102
+ Object.defineProperty(exports, "recovery", { enumerable: true, get: function () { return recovery_1.recovery; } });
103
+ // ONB1 slice 10 — new-user guided tour.
104
+ const walkthrough_1 = require("./walkthrough");
105
+ Object.defineProperty(exports, "walkthrough", { enumerable: true, get: function () { return walkthrough_1.walkthrough; } });
94
106
  /** All built-in system commands, in canonical order. */
95
107
  exports.allCommands = [
96
108
  help_1.help,
@@ -134,6 +146,14 @@ exports.allCommands = [
134
146
  daemonStatus_1.daemonStatus,
135
147
  // v4.5 Phase 8b — contextual suggestions toggle.
136
148
  suggestions_1.suggestions,
149
+ // v4.6 Phase 2M — opt-in keyword-based tool narrower (default OFF).
150
+ plannerGuard_1.plannerGuard,
151
+ // v4.6 Phase 3A — operator kill-switch for sub-agent spawning.
152
+ spawnPause_1.spawnPause,
153
+ // v4.6 Phase 3b — self-improvement loop operator surface.
154
+ recovery_1.recovery,
155
+ // ONB1 slice 10 — new-user guided tour.
156
+ walkthrough_1.walkthrough,
137
157
  clear_1.clear,
138
158
  quit_1.quit,
139
159
  ];
@@ -32,10 +32,15 @@
32
32
  * tools by default; opting in means the user accepted server-side
33
33
  * execution risk at config time.
34
34
  */
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
35
38
  Object.defineProperty(exports, "__esModule", { value: true });
36
39
  exports.AIDEN_MCP_BUILD = void 0;
37
40
  exports.runMcpSubcommand = runMcpSubcommand;
38
41
  /* eslint-disable @typescript-eslint/no-explicit-any */
42
+ const node_os_1 = __importDefault(require("node:os"));
43
+ const node_crypto_1 = require("node:crypto");
39
44
  const paths_1 = require("../../../core/v4/paths");
40
45
  const toolRegistry_1 = require("../../../core/v4/toolRegistry");
41
46
  const skillLoader_1 = require("../../../core/v4/skillLoader");
@@ -46,12 +51,26 @@ const processRegistry_1 = require("../../../core/v4/processRegistry");
46
51
  const config_1 = require("../../../core/v4/config");
47
52
  const providerFallback_1 = require("../../../core/v4/providerFallback");
48
53
  const chatCompletionsAdapter_1 = require("../../../providers/v4/chatCompletionsAdapter");
49
- const aidenAgent_1 = require("../../../core/v4/aidenAgent");
50
54
  const factory_1 = require("../../../core/v4/logger/factory");
51
55
  const index_1 = require("../../../tools/v4/index");
52
56
  const envSources_1 = require("../envSources");
53
57
  const credentialResolver_1 = require("../../../providers/v4/credentialResolver");
54
58
  const runtimeResolver_1 = require("../../../providers/v4/runtimeResolver");
59
+ // v4.6 Phase 2R — MCP-mode subagent_fanout now routes children
60
+ // through the `spawnSubAgent` primitive (same as REPL after 2Q-A).
61
+ // The legacy `runChild` closure + RunChildArgs import are deleted
62
+ // per design doc §12.3 (2R cleanup). MCP needs its own daemon-db
63
+ // connection + instance id so child runs persist under
64
+ // `aiden runs list --include-children` for cross-runtime
65
+ // observability.
66
+ const daemon_1 = require("../../../core/v4/daemon");
67
+ const version_1 = require("../../../core/version");
68
+ // v4.6 Phase 3A — operator kill-switch. Initialised here so
69
+ // MCP-side `subagent_fanout` (and any future MCP-side
70
+ // `spawn_sub_agent` exposure) reads from the same marker file the
71
+ // REPL writes via /spawn-pause. Cross-process coordination is the
72
+ // whole point of the file-marker design.
73
+ const spawnPause_1 = require("../../../core/v4/subagent/spawnPause");
55
74
  const stdioServer_1 = require("../../../core/v4/mcp/server/stdioServer");
56
75
  Object.defineProperty(exports, "AIDEN_MCP_BUILD", { enumerable: true, get: function () { return stdioServer_1.AIDEN_MCP_BUILD; } });
57
76
  const diagnostics_1 = require("../../../core/v4/mcp/server/diagnostics");
@@ -206,6 +225,43 @@ async function wireSubagentFanout(opts) {
206
225
  }
207
226
  }
208
227
  const finalAdapter = wrapped;
228
+ // v4.6 Phase 2R — open a daemon-db connection + seed an MCP
229
+ // instance row so child sub-agent runs (spawned by
230
+ // subagent_fanout below) persist to the same `runs` table the
231
+ // REPL writes to. Operators can then see MCP-side fanout
232
+ // activity under `aiden runs list --include-children`. Same
233
+ // WAL-coexistence model as REPL — connection.ts caches per-path.
234
+ const mcpInstanceId = `mcp-${(0, node_crypto_1.randomUUID)().slice(0, 8)}`;
235
+ const mcpDb = (0, daemon_1.openDaemonDb)((0, daemon_1.daemonDbPath)(opts.paths.root));
236
+ mcpDb.prepare(`INSERT OR IGNORE INTO daemon_instances
237
+ (instance_id, pid, hostname, started_at, last_heartbeat, version)
238
+ VALUES (?, ?, ?, ?, ?, ?)`).run(mcpInstanceId, process.pid, node_os_1.default.hostname(), Date.now(), Date.now(), version_1.VERSION);
239
+ const mcpRunStore = (0, daemon_1.createRunStore)({ db: mcpDb });
240
+ // v4.6 Phase 3b — self-improvement loop singleton against the
241
+ // same daemon.db. MCP-side spawn_sub_agent / subagent_fanout
242
+ // dispatches now record failure occurrences + recoveries into
243
+ // the shared ledger, so operators see MCP failures alongside
244
+ // REPL failures in `aiden /recovery list` from a future REPL
245
+ // session.
246
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
247
+ const { initRecoveryStore } = require('../../../core/v4/selfimprovement/recoveryStore');
248
+ initRecoveryStore({ db: mcpDb });
249
+ // v4.6 Phase 3A — wire the pause singleton against the same
250
+ // `paths.root` the REPL uses. The fanout handler's pause-check
251
+ // reads through `getSpawnPause()`, so initing here makes MCP
252
+ // mode respect the operator's /spawn-pause state without any
253
+ // additional plumbing.
254
+ const mcpPauseState = (0, spawnPause_1.initSpawnPause)({ aidenHome: opts.paths.root });
255
+ if (mcpPauseState.isPaused()) {
256
+ const s = mcpPauseState.status();
257
+ const reasonSuffix = s.reason ? ` (reason: ${s.reason})` : '';
258
+ opts.logger.warn(`MCP boot: subagent_fanout is PAUSED${reasonSuffix}. ` +
259
+ 'Operator must run /spawn-pause off in a REPL session to resume.', {
260
+ pausedAt: s.pausedAt ?? null,
261
+ pausedBy: s.pausedBy ?? null,
262
+ durationMs: s.durationMs ?? null,
263
+ });
264
+ }
209
265
  opts.registry.register((0, index_1.makeSubagentFanoutTool)({
210
266
  logger: opts.logger,
211
267
  resolveActiveModel: () => ({ providerId, modelId }),
@@ -224,70 +280,40 @@ async function wireSubagentFanout(opts) {
224
280
  }
225
281
  return [{ providerId, modelId }];
226
282
  },
227
- runChild: async (childOpts) => {
228
- const childCtx = {
283
+ // v4.6 Phase 2R — `spawnDeps` mirrors the REPL wiring in
284
+ // `cli/v4/aidenCLI.ts` (2Q-A). The MCP-mode parentToolContext
285
+ // is intentionally lean: no approvalEngine (a server has no
286
+ // human at the REPL to prompt — children's mutating tools are
287
+ // gated by AIDEN_SUBAGENT_ALLOW_DESTRUCTIVE in childBuilder),
288
+ // no ssrfProtection/tirithScanner/memoryGuard (MCP's slim
289
+ // runtime never wired them — see header comment §9).
290
+ spawnDeps: {
291
+ toolRegistry: opts.registry,
292
+ parentToolContext: {
229
293
  cwd: process.cwd(),
230
294
  paths: opts.paths,
231
295
  sessions: opts.sessionManager,
232
296
  memory: opts.memoryManager,
233
297
  skillLoader: opts.skillLoader,
234
- // approvalEngine intentionally undefined — N children
235
- // contending for one stdin REPL would deadlock under MCP.
236
- };
237
- // Filter the child tool surface — read-only by default; opt-in
238
- // via AIDEN_SUBAGENT_ALLOW_DESTRUCTIVE=1 (mirrors MCP env from
239
- // v4.1-mcp). Recursive fanout disallowed (depth=1).
240
- const allowDestructive = process.env.AIDEN_SUBAGENT_ALLOW_DESTRUCTIVE === '1' ||
241
- process.env.AIDEN_SUBAGENT_ALLOW_DESTRUCTIVE === 'true';
242
- const childToolNames = [];
243
- for (const name of opts.registry.list()) {
244
- const h = opts.registry.get(name);
245
- if (!h)
246
- continue;
247
- if (h.mutates && !allowDestructive)
248
- continue;
249
- if (name === 'subagent_fanout')
250
- continue;
251
- childToolNames.push(name);
252
- }
253
- const childExecutor = opts.registry.buildExecutor(childCtx);
254
- const childTools = childToolNames
255
- .map((n) => opts.registry.get(n)?.schema)
256
- .filter((s) => !!s);
257
- // Per-child cloned FallbackAdapter — own rate-limit state.
258
- const childProvider = finalAdapter instanceof providerFallback_1.FallbackAdapter
259
- ? finalAdapter.clone()
260
- : finalAdapter;
261
- const child = new aidenAgent_1.AidenAgent({
262
- provider: childProvider,
263
- tools: childTools,
264
- toolExecutor: childExecutor,
265
- maxTurns: childOpts.maxIterations,
266
- providerId: childOpts.provider.providerId,
267
- modelId: childOpts.provider.modelId,
268
- // No promptBuilder — children get a brief system prompt
269
- // (same lesson as v4.1-subagent.1: full SOUL.md makes
270
- // trivial fanouts spend 30s+ on verbose self-introductions).
271
- });
272
- if (childOpts.signal.aborted) {
273
- throw new Error('aborted before dispatch');
274
- }
275
- const roleLine = childOpts.role ? `Role: ${childOpts.role}. ` : '';
276
- const childSystemPrompt = `You are one of N parallel subagents. ${roleLine}` +
277
- `Answer the user's request concisely. Use available tools when ` +
278
- `the answer requires real-world information you don't have memorized.`;
279
- const history = [
280
- { role: 'system', content: childSystemPrompt },
281
- { role: 'user', content: childOpts.prompt },
282
- ];
283
- const result = await child.runConversation(history);
284
- return result.finalContent;
298
+ },
299
+ parentProvider: finalAdapter,
300
+ parentProviderId: providerId,
301
+ parentModelId: modelId,
302
+ runStore: mcpRunStore,
303
+ instanceId: mcpInstanceId,
304
+ logger: opts.logger,
285
305
  },
306
+ // No resolveParentRunId / resolveParentSessionId for MCP — the
307
+ // MCP server doesn't run a turn loop with its own `runs` row;
308
+ // each tool invocation arrives discrete from the client. Child
309
+ // rows therefore have NULL spawned_from_run_id, which is the
310
+ // honest representation (no MCP-side parent to link to).
286
311
  }));
287
312
  opts.logger.info('subagent_fanout: wired (replaces stub) [mcp serve]', {
288
313
  providerId,
289
314
  modelId,
290
315
  fallback: finalAdapter instanceof providerFallback_1.FallbackAdapter ? 'FallbackAdapter' : 'direct',
316
+ instanceId: mcpInstanceId,
291
317
  });
292
318
  }
293
319
  async function runMcpSubcommand(action, opts = {}) {
@@ -0,0 +1,53 @@
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/plannerGuard.ts — v4.6 Phase 2M.
10
+ *
11
+ * `/planner-guard on|off|status` — flip the keyword-based per-turn
12
+ * tool narrower (`moat/plannerGuard.ts`) live, no restart needed.
13
+ *
14
+ * Default: OFF. Smart models (GPT-5.5, Claude Sonnet 4.5+, Opus)
15
+ * select tools fine from the full catalog every turn, the way the
16
+ * reference multi-agent systems do. Per-turn narrowing was a
17
+ * v4.1-era workaround for smaller local models that got overwhelmed
18
+ * by 50+ tool schemas — opt in for that case via env (set
19
+ * `AIDEN_PLANNER_GUARD=1` at boot) or this slash command.
20
+ *
21
+ * Persists to `runtime_toggles.planner_guard` in config.yaml when
22
+ * a ConfigManager is wired (the normal REPL path). Env var
23
+ * `AIDEN_PLANNER_GUARD` always wins over both — see runtimeToggles.ts.
24
+ *
25
+ * Mirrors `/sandbox`, `/tce`, `/browser-depth`, `/suggestions`
26
+ * verbatim — same helpers, same output shape.
27
+ */
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.plannerGuard = void 0;
30
+ const _runtimeToggleHelpers_1 = require("./_runtimeToggleHelpers");
31
+ exports.plannerGuard = {
32
+ name: 'planner-guard',
33
+ description: 'Toggle keyword-based per-turn tool narrowing (default OFF, opt-in).',
34
+ category: 'system',
35
+ icon: '🧭',
36
+ handler: async (ctx) => {
37
+ const sub = (0, _runtimeToggleHelpers_1.parseSubcommand)(ctx.args[0]);
38
+ if (sub === 'on') {
39
+ await (0, _runtimeToggleHelpers_1.flip)('planner_guard', true, ctx);
40
+ return {};
41
+ }
42
+ if (sub === 'off') {
43
+ await (0, _runtimeToggleHelpers_1.flip)('planner_guard', false, ctx);
44
+ return {};
45
+ }
46
+ if (sub === 'status') {
47
+ (0, _runtimeToggleHelpers_1.printStatus)('planner_guard', ctx);
48
+ return {};
49
+ }
50
+ ctx.display.printError('Usage: /planner-guard on|off|status');
51
+ return {};
52
+ },
53
+ };
@@ -0,0 +1,122 @@
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/recovery.ts — v4.6 Phase 3b.
10
+ *
11
+ * `/recovery list [limit]` — top N recurring failure patterns
12
+ * `/recovery show <signature>` — details for one signature + reports
13
+ * `/recovery clear <signature>` — operator says "fixed; stop counting"
14
+ *
15
+ * Backed by the v7 `failure_signatures` + `recovery_reports` tables
16
+ * via the `RecoveryStore` singleton (initialised at REPL/daemon/MCP
17
+ * boot). All three sub-actions degrade cleanly when the store isn't
18
+ * initialised — print a non-fatal error and return.
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.recovery = void 0;
22
+ const recoveryStore_1 = require("../../../core/v4/selfimprovement/recoveryStore");
23
+ /** Format a wall-clock ms timestamp as a compact UTC label. */
24
+ function formatTs(ms) {
25
+ return new Date(ms).toISOString().slice(0, 19) + 'Z';
26
+ }
27
+ /** Right-pad to width; truncate with ellipsis when too long. */
28
+ function pad(value, width) {
29
+ const s = String(value);
30
+ if (s.length === width)
31
+ return s;
32
+ if (s.length < width)
33
+ return s + ' '.repeat(width - s.length);
34
+ return s.slice(0, Math.max(0, width - 1)) + '…';
35
+ }
36
+ exports.recovery = {
37
+ name: 'recovery',
38
+ description: 'Inspect recurring failure patterns + recoveries (v4.6 Phase 3b).',
39
+ category: 'system',
40
+ icon: '🩹',
41
+ handler: async (ctx) => {
42
+ const action = (ctx.args[0] ?? 'list').toLowerCase();
43
+ const store = (0, recoveryStore_1.getRecoveryStore)();
44
+ if (!store) {
45
+ ctx.display.printError('recovery: recovery store not initialised — daemon DB unavailable?');
46
+ return {};
47
+ }
48
+ if (action === 'list') {
49
+ const limitArg = ctx.args[1];
50
+ const parsed = limitArg !== undefined ? Number.parseInt(limitArg, 10) : NaN;
51
+ const limit = Number.isFinite(parsed) && parsed > 0 ? parsed : 10;
52
+ const rows = store.listTopFailures(limit);
53
+ if (rows.length === 0) {
54
+ ctx.display.write('No recurring failures recorded yet.\n');
55
+ ctx.display.dim(' TCE writes failure signatures on classify; recoveries on failure→success transition.');
56
+ return {};
57
+ }
58
+ ctx.display.write(`${pad('signature', 50)} ${pad('occur', 6)} ${pad('recov', 6)} last_strategy\n`);
59
+ for (const r of rows) {
60
+ ctx.display.write(`${pad(r.signature, 50)} ${pad(r.occurrences, 6)} ${pad(r.recoveredCount, 6)} ${r.lastRecoveryStrategy ?? '-'}\n`);
61
+ }
62
+ ctx.display.write(`\n${rows.length} signature${rows.length === 1 ? '' : 's'} shown\n`);
63
+ return {};
64
+ }
65
+ if (action === 'show') {
66
+ const sig = ctx.args[1];
67
+ if (!sig) {
68
+ ctx.display.printError('Usage: /recovery show <signature>');
69
+ return {};
70
+ }
71
+ const row = store.getBySignature(sig);
72
+ if (!row) {
73
+ ctx.display.printError(`recovery: signature not found: ${sig}`);
74
+ return {};
75
+ }
76
+ ctx.display.write(`signature: ${row.signature}\n`);
77
+ ctx.display.write(`tool_name: ${row.toolName}\n`);
78
+ ctx.display.write(`failure_category: ${row.failureCategory}\n`);
79
+ ctx.display.write(`occurrences: ${row.occurrences}\n`);
80
+ ctx.display.write(`recovered_count: ${row.recoveredCount}\n`);
81
+ ctx.display.write(`first_seen: ${formatTs(row.firstSeenAt)}\n`);
82
+ ctx.display.write(`last_seen: ${formatTs(row.lastSeenAt)}\n`);
83
+ const reports = store.listReportsForSignature(row.id, 20);
84
+ if (reports.length === 0) {
85
+ ctx.display.dim(' (no recovery reports yet for this signature)');
86
+ return {};
87
+ }
88
+ ctx.display.write(`\nrecovery reports (${reports.length}, newest first):\n`);
89
+ for (const r of reports) {
90
+ ctx.display.write(` [${formatTs(r.createdAt)}] strategy=${r.successfulStrategy}`);
91
+ if (r.failedAttempts !== undefined) {
92
+ ctx.display.write(` failed=${r.failedAttempts}`);
93
+ }
94
+ if (r.sessionId)
95
+ ctx.display.write(` session=${r.sessionId}`);
96
+ ctx.display.write('\n');
97
+ if (r.notes)
98
+ ctx.display.dim(` ${r.notes}`);
99
+ if (r.verification)
100
+ ctx.display.dim(` verified: ${r.verification}`);
101
+ }
102
+ return {};
103
+ }
104
+ if (action === 'clear') {
105
+ const sig = ctx.args[1];
106
+ if (!sig) {
107
+ ctx.display.printError('Usage: /recovery clear <signature>');
108
+ return {};
109
+ }
110
+ const ok = store.clearSignature(sig);
111
+ if (ok) {
112
+ ctx.display.write(`recovery: cleared signature ${sig}\n`);
113
+ }
114
+ else {
115
+ ctx.display.printError(`recovery: signature not found: ${sig}`);
116
+ }
117
+ return {};
118
+ }
119
+ ctx.display.printError('Usage: /recovery list [limit] | show <signature> | clear <signature>');
120
+ return {};
121
+ },
122
+ };
@@ -62,11 +62,17 @@ function cmdList(runStore, argv, out) {
62
62
  const status = argv.status && allowedStatuses.has(argv.status)
63
63
  ? argv.status
64
64
  : undefined;
65
+ // v4.6 Phase 2Q-B — default `topLevelOnly: true` hides children.
66
+ // `--include-children` flag (parsed by the CLI argv layer into
67
+ // `includeChildren: true`) flips the predicate to drop the IS NULL
68
+ // filter so child rows appear inline with parents.
69
+ const includeChildren = argv.includeChildren === true;
65
70
  const rows = runStore.listRecent({
66
71
  limit: argv.limit ?? 50,
67
72
  status,
68
73
  source: argv.source,
69
74
  sessionIdPrefix: argv.trigger,
75
+ topLevelOnly: !includeChildren,
70
76
  });
71
77
  if (rows.length === 0) {
72
78
  out('No runs match the filter.\n');
@@ -76,9 +82,23 @@ function cmdList(runStore, argv, out) {
76
82
  for (const r of rows) {
77
83
  const started = new Date(r.startedAt).toISOString().slice(0, 19) + 'Z';
78
84
  const finish = r.finishReason ?? '-';
79
- out(`${String(r.id).padEnd(6)} ${r.status.padEnd(11)} ${finish.padEnd(11)} ${started.padEnd(20)} ${r.sessionId}\n`);
85
+ // v4.6 Phase 2Q-B — child-count badge. Only relevant for the
86
+ // top-level view (when --include-children is OFF). Skipped on
87
+ // the flat view to avoid double-counting visual weight: in flat
88
+ // mode the children are already on screen as their own rows.
89
+ let badge = '';
90
+ if (!includeChildren) {
91
+ const { total, completed } = runStore.countChildren(r.id);
92
+ if (total > 0) {
93
+ badge = ` (${total} ${total === 1 ? 'child' : 'children'}, ${completed} OK)`;
94
+ }
95
+ }
96
+ out(`${String(r.id).padEnd(6)} ${r.status.padEnd(11)} ${finish.padEnd(11)} ${started.padEnd(20)} ${r.sessionId}${badge}\n`);
80
97
  }
81
- out(`\n${rows.length} run${rows.length === 1 ? '' : 's'} shown\n`);
98
+ const hint = includeChildren
99
+ ? ' (parents + sub-agent children)'
100
+ : ' (top-level; use --include-children for sub-agents)';
101
+ out(`\n${rows.length} run${rows.length === 1 ? '' : 's'} shown${hint}\n`);
82
102
  return 0;
83
103
  }
84
104
  // ── show ──────────────────────────────────────────────────────────────────