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
@@ -0,0 +1,334 @@
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
+ * tools/v4/subagent/spawnSubAgentTool.ts — v4.6 Phase 1.
10
+ *
11
+ * LLM-callable wrapper for the `spawn_sub_agent` primitive. JSON
12
+ * schema and description text are verbatim from
13
+ * `docs/v4.6/phase-1-design.md` §4. The handler:
14
+ *
15
+ * 1. Reads the parent agent's current AbortSignal via the
16
+ * Flag 1 pattern (`parentAgent.getCurrentSignal()` captured
17
+ * reference, not a widened executor signature).
18
+ * 2. Validates and clamps arguments.
19
+ * 3. Calls `spawnSubAgent` from `core/v4/subagent/spawnSubAgent.ts`.
20
+ * 4. Returns the result envelope as the tool result body.
21
+ *
22
+ * Q9 — additive to the existing `subagent_fanout` tool. Both
23
+ * coexist in Phase 1; Phase 2 will refactor `subagent_fanout` to
24
+ * call this primitive N times.
25
+ */
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.SPAWN_SUB_AGENT_SCHEMA = void 0;
28
+ exports.makeSpawnSubAgentStub = makeSpawnSubAgentStub;
29
+ exports.makeSpawnSubAgentTool = makeSpawnSubAgentTool;
30
+ const factory_1 = require("../../../core/v4/logger/factory");
31
+ const spawnSubAgent_1 = require("../../../core/v4/subagent/spawnSubAgent");
32
+ // v4.6 Phase 3A — operator kill-switch. Checked at handler entry
33
+ // BEFORE any work (no run row written, no child built, no provider
34
+ // call). In-flight children continue uninterrupted; only NEW spawns
35
+ // are blocked. Singleton initialised by REPL/daemon/MCP boot wiring.
36
+ const spawnPause_1 = require("../../../core/v4/subagent/spawnPause");
37
+ // ── Pause helper (v4.6 Phase 3A) ──────────────────────────────────────────
38
+ /**
39
+ * Safe pause read for the handler entry guard. Catches the
40
+ * "not initialized" error from `getSpawnPause()` and returns
41
+ * `{paused: false, status: {paused: false}}` — wiring-order bugs
42
+ * (handler firing before `initSpawnPause` ran) must NOT take down
43
+ * the spawn surface. Production boot wiring always inits first,
44
+ * so this only matters for tests that omit the init step.
45
+ */
46
+ function safeReadPause() {
47
+ try {
48
+ const state = (0, spawnPause_1.getSpawnPause)();
49
+ const status = state.status();
50
+ return { paused: status.paused, status };
51
+ }
52
+ catch {
53
+ return { paused: false, status: { paused: false } };
54
+ }
55
+ }
56
+ // ── Schema description (verbatim from design doc §4) ───────────────────────
57
+ const SCHEMA_DESC = 'Spawn a focused child agent to handle one delegated sub-task synchronously. ' +
58
+ 'The child runs with no access to your conversation history, an intersected ' +
59
+ 'toolset (cannot exceed your capabilities), and a fresh system prompt built ' +
60
+ 'from the goal + optional context. Returns a structured result envelope with ' +
61
+ "the child's summary, metrics, and exit reason. Use this when a sub-task " +
62
+ 'benefits from isolated context (e.g. exploring a separate codebase area, ' +
63
+ 'running a focused investigation, drafting an artifact without polluting your ' +
64
+ 'main turn). Do NOT use for long-running or scheduled work — use daemon ' +
65
+ 'triggers for that. Spawning is bounded: max 1 child at a time in Phase 1, ' +
66
+ 'no nested spawning, max 200 iterations per child. Each spawn pays full ' +
67
+ 'agent-startup cost (system prompt build, tool catalog ship) and roughly ' +
68
+ 'doubles token spend for that sub-task. Prefer inline work for anything you ' +
69
+ 'can answer in 1-3 of your own iterations. Spawn when isolation, focus, or ' +
70
+ 'a restricted toolset actually helps.';
71
+ // ── Schema constant (shared by real factory + boot-time stub) ─────────────
72
+ /**
73
+ * v4.6 Phase 1 — module-level schema constant so the boot-time stub
74
+ * (`makeSpawnSubAgentStub`) advertises the SAME JSON-schema surface
75
+ * the real factory ships. Both register under name `spawn_sub_agent`
76
+ * with `contexts: ['repl']`, so the model sees a consistent surface
77
+ * regardless of whether the stub or the real handler is active.
78
+ */
79
+ exports.SPAWN_SUB_AGENT_SCHEMA = {
80
+ name: 'spawn_sub_agent',
81
+ description: SCHEMA_DESC,
82
+ inputSchema: {
83
+ type: 'object',
84
+ required: ['goal'],
85
+ properties: {
86
+ goal: {
87
+ type: 'string',
88
+ description: 'The single concrete task for the child. Phrase as an imperative ' +
89
+ 'outcome — what should be done, not how. The child cannot ask ' +
90
+ 'follow-up questions; if the goal is ambiguous, refine it before ' +
91
+ 'spawning.',
92
+ },
93
+ context: {
94
+ type: 'string',
95
+ description: "Optional background the child needs but couldn't infer from the " +
96
+ "goal alone (file paths, prior findings, constraints). Plain text. " +
97
+ "The child does NOT see your conversation history; anything it needs " +
98
+ 'must be here or discoverable via its toolset.',
99
+ },
100
+ toolsets: {
101
+ type: 'array',
102
+ description: 'OPTIONAL — when present, RESTRICTS the child to specific toolsets. ' +
103
+ 'OMIT this field to let the child inherit your full toolset (recommended ' +
104
+ 'for most cases — children inherit your capabilities minus the hard ' +
105
+ 'blocklist). Each entry MUST be one of the enumerated valid names ' +
106
+ 'below; invalid names get stripped, and if every requested name is ' +
107
+ 'invalid the child falls back to inheriting your full toolset (with a ' +
108
+ 'warning logged). The child can never exceed your capabilities — this ' +
109
+ 'parameter only narrows them.',
110
+ items: {
111
+ type: 'string',
112
+ // v4.6 Phase 1 — enum reflects the actual toolset string
113
+ // values registered in tools/v4/. Kept in sync with the
114
+ // registry; new toolsets ship by being added to a tool's
115
+ // `toolset` field AND to this list.
116
+ enum: [
117
+ 'browser',
118
+ 'execute',
119
+ 'files',
120
+ 'mcp',
121
+ 'memory',
122
+ 'process',
123
+ 'sessions',
124
+ 'skills',
125
+ 'subagent',
126
+ 'system',
127
+ 'terminal',
128
+ 'web',
129
+ ],
130
+ },
131
+ },
132
+ maxIterations: {
133
+ type: 'integer',
134
+ description: 'Maximum tool-call iterations the child may run. Clamped to [1, 200]. ' +
135
+ 'Choose tight bounds for narrow tasks (5-15) and looser for ' +
136
+ 'exploration (50-100). Default 50.',
137
+ },
138
+ timeoutMs: {
139
+ type: 'integer',
140
+ description: 'Hard wall-clock timeout in milliseconds. Default 10 minutes. The ' +
141
+ "child is signalled to interrupt on timeout; if it doesn't yield " +
142
+ 'cooperatively, the worker leaks but the parent stays responsive.',
143
+ },
144
+ provider: {
145
+ type: 'string',
146
+ description: "OPTIONAL — override the child's provider. Pass a provider ID like " +
147
+ "'groq', 'chatgpt-plus', 'anthropic'. Omit to inherit the parent's " +
148
+ 'provider (recommended for most callers). Mainly used by ' +
149
+ "`subagent_fanout`'s rotation for provider diversity. Validated " +
150
+ "against the parent's available pool at dispatch — an unknown name " +
151
+ "produces a failed envelope with `exitReason: 'provider_not_found'` " +
152
+ 'and lists the valid names in the error message. Single-provider ' +
153
+ '(non-FallbackAdapter) parents reject this field with an error.',
154
+ },
155
+ },
156
+ },
157
+ };
158
+ // ── Boot-time stub (registered before runtime deps are resolved) ──────────
159
+ /**
160
+ * v4.6 Phase 1 — stub handler used until the REPL wiring at
161
+ * `cli/v4/aidenCLI.ts` replaces it with the real factory. Returns
162
+ * the SAME schema surface so `toolRegistry.getSchemas(undefined,
163
+ * 'repl')` at agent construction sees `spawn_sub_agent` and the
164
+ * LLM can address the tool by name. If called before the real
165
+ * wiring lands, returns a clear "not wired" error envelope so the
166
+ * model gets a structured error rather than a crash.
167
+ *
168
+ * Mirrors `makeSubagentFanoutStub` in `tools/v4/index.ts`.
169
+ */
170
+ function makeSpawnSubAgentStub() {
171
+ return {
172
+ schema: exports.SPAWN_SUB_AGENT_SCHEMA,
173
+ category: 'network',
174
+ mutates: false,
175
+ toolset: 'subagent',
176
+ riskTier: 'caution',
177
+ contexts: ['repl'],
178
+ async execute() {
179
+ return {
180
+ ok: false,
181
+ status: 'failed',
182
+ summary: null,
183
+ error: 'spawn_sub_agent: tool not wired — runtime did not replace the stub. ' +
184
+ 'Call register(makeSpawnSubAgentTool({...})) after buildAgentRuntime.',
185
+ exitReason: 'error',
186
+ metrics: { apiCalls: 0, durationMs: 0, tokensIn: 0, tokensOut: 0 },
187
+ childRunId: '0',
188
+ childSessionId: '',
189
+ };
190
+ },
191
+ };
192
+ }
193
+ // ── Implementation ────────────────────────────────────────────────────────
194
+ function makeSpawnSubAgentTool(factory) {
195
+ return {
196
+ schema: exports.SPAWN_SUB_AGENT_SCHEMA,
197
+ // The tool itself spends tokens. Disk / process side effects, if
198
+ // any, happen INSIDE the child agent whose toolset is intersected
199
+ // with the parent's and stripped of the v4.6 blocklist.
200
+ category: 'network',
201
+ mutates: false,
202
+ toolset: 'subagent',
203
+ riskTier: 'caution',
204
+ // v4.6 Phase 1 — REPL-only execution context per Q6.
205
+ // Daemon-fired agents must not initiate sub-agent spawns in
206
+ // Phase 1: the spawn factory captured the REPL agent reference
207
+ // at construction, so a daemon-fired turn invoking this tool
208
+ // would route its child's signal chain through the REPL agent's
209
+ // state rather than the daemon turn's. Tagging it `['repl']`
210
+ // here causes `toolRegistry.getSchemas(_, 'daemon')` (used by
211
+ // daemonAgentBuilder.ts:130) to exclude `spawn_sub_agent` from
212
+ // the daemon agent's tool catalog, so the model never sees it
213
+ // when running in daemon mode. Phase 3+ may add a daemon-mode
214
+ // spawn factory tied to the daemon agent's own reference.
215
+ contexts: ['repl'],
216
+ async execute(args, _ctx) {
217
+ // ── 0. Operator kill-switch (v4.6 Phase 3A) ─────────────────────────
218
+ // First thing — before arg validation, run-row insertion, or
219
+ // any child build. A paused state must short-circuit cleanly
220
+ // with NO side effects (no `runs` row, no log noise beyond
221
+ // the rejection). Locked decision: paused calls are operator-
222
+ // induced, NOT real failures; they don't pollute `aiden runs
223
+ // list`. Envelope intentionally drops the standard SubAgentResult
224
+ // shape because (a) no childRunId exists (no row was written)
225
+ // and (b) the error class is qualitatively different from a
226
+ // spawn that ran and failed.
227
+ const pauseGate = safeReadPause();
228
+ if (pauseGate.paused) {
229
+ const s = pauseGate.status;
230
+ const reasonSuffix = s.reason ? ` (reason: ${s.reason})` : '';
231
+ return {
232
+ success: false,
233
+ errorCode: 'SUBAGENT_SPAWN_PAUSED',
234
+ message: `spawn_sub_agent: spawning is paused${reasonSuffix}. ` +
235
+ 'Run /spawn-pause off to resume.',
236
+ pausedAt: s.pausedAt ?? null,
237
+ reason: s.reason ?? null,
238
+ pausedBy: s.pausedBy ?? null,
239
+ durationMs: s.durationMs ?? null,
240
+ };
241
+ }
242
+ // ── 1. Validate + coerce ─────────────────────────────────────────────
243
+ const goal = typeof args.goal === 'string' ? args.goal.trim() : '';
244
+ if (!goal) {
245
+ return {
246
+ ok: false,
247
+ status: 'failed',
248
+ summary: null,
249
+ error: "spawn_sub_agent: 'goal' is required and must be a non-empty string",
250
+ exitReason: 'error',
251
+ metrics: { apiCalls: 0, durationMs: 0, tokensIn: 0, tokensOut: 0 },
252
+ childRunId: '0',
253
+ childSessionId: '',
254
+ };
255
+ }
256
+ const spec = {
257
+ goal,
258
+ context: typeof args.context === 'string' ? args.context : undefined,
259
+ toolsets: Array.isArray(args.toolsets)
260
+ ? args.toolsets.filter((t) => typeof t === 'string')
261
+ : undefined,
262
+ maxIterations: typeof args.maxIterations === 'number' ? args.maxIterations : undefined,
263
+ timeoutMs: typeof args.timeoutMs === 'number' ? args.timeoutMs : undefined,
264
+ // v4.6 Phase 2P — per-spawn provider override (per design doc §12.2).
265
+ // Validation happens in childBuilder against the parent's FallbackAdapter
266
+ // provider pool; an unknown name produces a failed envelope.
267
+ provider: typeof args.provider === 'string' ? args.provider : undefined,
268
+ };
269
+ // v4.6 Phase 1 observability — log the parsed spec so the next
270
+ // smoke test can correlate "what the model asked for" with the
271
+ // child's actual behaviour. Goal is truncated to keep the log
272
+ // line readable. The parent's sessionId (read off the agent
273
+ // instance) is included so logs from one user turn cluster.
274
+ const logger = factory.logger ?? (0, factory_1.noopLogger)();
275
+ const goalPreview = spec.goal.length > 200 ? spec.goal.slice(0, 200) + '…' : spec.goal;
276
+ logger.info('spawn_sub_agent invoked', {
277
+ parentSessionId: factory.parentAgent.getCurrentSignal !== undefined
278
+ ? factory.parentAgent.sessionId ?? null
279
+ : null,
280
+ goalPreview,
281
+ goalLen: spec.goal.length,
282
+ contextLen: spec.context?.length ?? 0,
283
+ toolsets: spec.toolsets ?? null,
284
+ maxIterations: spec.maxIterations ?? null,
285
+ timeoutMs: spec.timeoutMs ?? null,
286
+ });
287
+ // ── 2. Read the parent's current signal (Flag 1 pattern) ─────────────
288
+ const parentSignal = factory.parentAgent.getCurrentSignal();
289
+ // ── 3. Resolve optional parent run / session identifiers ─────────────
290
+ const parentRunId = factory.resolveParentRunId?.();
291
+ const parentSessionId = factory.resolveParentSessionId?.();
292
+ // ── 4. Invoke the primitive. NEVER throws — always envelope. ─────────
293
+ const result = await (0, spawnSubAgent_1.spawnSubAgent)(spec, {
294
+ // ChildBuilderDeps fields:
295
+ toolRegistry: factory.toolRegistry,
296
+ parentToolContext: factory.parentToolContext,
297
+ parentProvider: factory.parentProvider,
298
+ parentProviderId: factory.parentProviderId,
299
+ parentModelId: factory.parentModelId,
300
+ resolveVerifiedFlag: factory.resolveVerifiedFlag,
301
+ resolveToolset: factory.resolveToolset,
302
+ resolveMutates: factory.resolveMutates,
303
+ // Persistence:
304
+ runStore: factory.runStore,
305
+ instanceId: factory.instanceId,
306
+ // v4.6 Phase 1 observability:
307
+ logger,
308
+ }, {
309
+ signal: parentSignal,
310
+ parentRunId,
311
+ parentSessionId,
312
+ });
313
+ // Completion log — pairs with "spawn_sub_agent invoked" so a
314
+ // grep on parentSessionId surfaces invoke → complete in order.
315
+ logger.info('spawn_sub_agent completed', {
316
+ childRunId: result.childRunId,
317
+ childSessionId: result.childSessionId,
318
+ status: result.status,
319
+ exitReason: result.exitReason,
320
+ ok: result.ok,
321
+ apiCalls: result.metrics.apiCalls,
322
+ durationMs: result.metrics.durationMs,
323
+ tokensIn: result.metrics.tokensIn,
324
+ tokensOut: result.metrics.tokensOut,
325
+ summaryLen: result.summary?.length ?? 0,
326
+ errorPreview: result.error?.slice(0, 200) ?? null,
327
+ });
328
+ // The envelope IS the tool result body. The agent loop's tool-
329
+ // result handling will JSON-stringify this and feed it back to
330
+ // the parent's LLM as the tool message content.
331
+ return result;
332
+ },
333
+ };
334
+ }
@@ -41,6 +41,13 @@ exports.makeSubagentFanoutTool = makeSubagentFanoutTool;
41
41
  const factory_1 = require("../../../core/v4/logger/factory");
42
42
  const fanout_1 = require("../../../core/v4/subagent/fanout");
43
43
  const merger_1 = require("../../../core/v4/subagent/merger");
44
+ // v4.6 Phase 3A — operator kill-switch. Same check the
45
+ // `spawn_sub_agent` tool runs, applied here at fanout's handler
46
+ // entry. Locked decision (§12): check ONCE at entry, not inside
47
+ // the spawn loop — atomicity is per-call. A pause applied
48
+ // mid-fanout leaves the in-flight Promise.all to complete
49
+ // naturally; only the NEXT fanout call hits the rejection.
50
+ const spawnPause_1 = require("../../../core/v4/subagent/spawnPause");
44
51
  const SCHEMA_DESC = 'Spawn N parallel agent children against the same problem (ensemble) or a partitioned task list, ' +
45
52
  'then merge results via the chosen strategy. Use this for multi-perspective research, ' +
46
53
  'provider-diverse fact-checking, or analyzing N independent inputs in parallel. ' +
@@ -124,6 +131,32 @@ function makeSubagentFanoutTool(factory) {
124
131
  toolset: 'subagent',
125
132
  riskTier: 'caution', // v4.4 Phase 1
126
133
  async execute(args, _ctx) {
134
+ // ── Operator kill-switch (v4.6 Phase 3A) ────────────────────
135
+ // Top of handler — before parsing, provider resolution, or
136
+ // rotation. Reject the WHOLE call if paused; spawn loop never
137
+ // fires, so partial fanouts are impossible.
138
+ try {
139
+ const pauseStatus = (0, spawnPause_1.getSpawnPause)().status();
140
+ if (pauseStatus.paused) {
141
+ const reasonSuffix = pauseStatus.reason ? ` (reason: ${pauseStatus.reason})` : '';
142
+ return {
143
+ success: false,
144
+ errorCode: 'SUBAGENT_SPAWN_PAUSED',
145
+ message: `subagent_fanout: spawning is paused${reasonSuffix}. ` +
146
+ 'Run /spawn-pause off to resume.',
147
+ pausedAt: pauseStatus.pausedAt ?? null,
148
+ reason: pauseStatus.reason ?? null,
149
+ pausedBy: pauseStatus.pausedBy ?? null,
150
+ durationMs: pauseStatus.durationMs ?? null,
151
+ };
152
+ }
153
+ }
154
+ catch {
155
+ // getSpawnPause() throws when not initialized — let the
156
+ // fanout proceed rather than blocking on a wiring bug.
157
+ // Production boot always inits before any tool handler
158
+ // can fire; this catch only matters for unit tests.
159
+ }
127
160
  const logger = factory.logger ?? (0, factory_1.noopLogger)();
128
161
  // ── Coerce args ────────────────────────────────────────────
129
162
  const mode = (args.mode === 'partition' || args.mode === 'ensemble')
@@ -160,6 +193,23 @@ function makeSubagentFanoutTool(factory) {
160
193
  }
161
194
  const aggOverride = (0, merger_1.resolveAggregatorOverride)();
162
195
  const aggregatorModel = aggOverride ?? factory.resolveActiveModel();
196
+ // v4.6 Phase 2Q — spawnDeps absent means we're still bound to
197
+ // the boot-time stub (the runtime hasn't replaced the
198
+ // registration with the real factory yet). Surface the same
199
+ // "tool not wired" failure shape MCP / REPL surface in their
200
+ // own pre-wired states.
201
+ if (!factory.spawnDeps) {
202
+ return {
203
+ success: false,
204
+ error: 'subagent_fanout: tool not wired — runtime did not supply spawnDeps. ' +
205
+ 'Call register(makeSubagentFanoutTool({...spawnDeps})) after buildAgentRuntime.',
206
+ };
207
+ }
208
+ // v4.6 Phase 2Q — resolve parent identity at dispatch time so
209
+ // REPL turns that opened a run row between boot and now still
210
+ // link children to the right parent.
211
+ const parentRunId = factory.resolveParentRunId?.();
212
+ const parentSessionId = factory.resolveParentSessionId?.();
163
213
  const fanoutOpts = {
164
214
  mode,
165
215
  query,
@@ -167,7 +217,9 @@ function makeSubagentFanoutTool(factory) {
167
217
  n,
168
218
  merge,
169
219
  providers,
170
- runChild: factory.runChild,
220
+ spawnDeps: factory.spawnDeps,
221
+ parentRunId,
222
+ parentSessionId,
171
223
  aggregatorAdapter: factory.aggregatorAdapter,
172
224
  aggregatorModel,
173
225
  timeoutMs,
@@ -0,0 +1,60 @@
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
+ * tools/v4/ui/_uiSmokeTool.ts — v4.7 Slice 1 smoke harness.
10
+ *
11
+ * Internal tool used ONLY to verify the uiOnly dispatch seam from
12
+ * Slice 1 (ToolHandler.uiOnly + resolveUiOnly + onUiEvent +
13
+ * Display.renderUiEvent). NOT for end-user LLM workflows.
14
+ *
15
+ * Registered behind `AIDEN_UI_SMOKE=1` env flag in
16
+ * `tools/v4/index.ts::registerAllTools`. Will be deleted once
17
+ * Slice 2 lands the real ui_task_update / ui_task_done tools.
18
+ *
19
+ * When invoked, the agent's dispatch loop:
20
+ * - resolves uiOnly=true via the resolveUiOnly closure
21
+ * - fires runOptions.onUiEvent('_ui_smoke', args)
22
+ * - SKIPS execute() entirely
23
+ * - SKIPS turnToolMessages push + toolCallCount increment + verifier
24
+ *
25
+ * The handler's `execute` MUST never be called by the dispatch path
26
+ * when uiOnly is honoured. Throws if reached — that throw is a
27
+ * regression alarm.
28
+ */
29
+ Object.defineProperty(exports, "__esModule", { value: true });
30
+ exports.uiSmokeTool = void 0;
31
+ exports.uiSmokeTool = {
32
+ schema: {
33
+ name: '_ui_smoke',
34
+ description: 'Internal smoke-test tool for the v4.7 uiOnly dispatch path. ' +
35
+ 'Renders a single debug line through the UI event seam. ' +
36
+ 'Does NOT round-trip back to the model. Only available when ' +
37
+ 'AIDEN_UI_SMOKE=1.',
38
+ inputSchema: {
39
+ type: 'object',
40
+ properties: {
41
+ message: {
42
+ type: 'string',
43
+ description: 'Free-text payload echoed in the rendered debug line.',
44
+ },
45
+ },
46
+ required: ['message'],
47
+ },
48
+ },
49
+ category: 'read',
50
+ mutates: false,
51
+ uiOnly: true,
52
+ execute() {
53
+ // Defensive — if `resolveUiOnly` is wired correctly, the
54
+ // dispatch loop short-circuits BEFORE reaching this body. A
55
+ // call here means the resolver returned false/undefined and
56
+ // the seam regressed. Throwing surfaces the regression at
57
+ // smoke time instead of silently behaving like a regular tool.
58
+ throw new Error('_ui_smoke.execute() should never be called — uiOnly dispatch path regressed');
59
+ },
60
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiden-runtime",
3
- "version": "4.5.0",
3
+ "version": "4.6.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -35,8 +35,8 @@
35
35
  "node": ">=18"
36
36
  },
37
37
  "bin": {
38
- "devos-ai": "./dist/bin/npx-init.js",
39
- "aiden": "./dist/cli/v4/aidenCLI.js"
38
+ "aiden": "./dist/cli/v4/aidenCLI.js",
39
+ "aiden-runtime": "./dist/cli/v4/aidenCLI.js"
40
40
  },
41
41
  "main": "./dist/cli/v4/aidenCLI.js",
42
42
  "files": [
@@ -254,10 +254,12 @@
254
254
  "epub2": "^3.0.2",
255
255
  "execa": "^8.0.1",
256
256
  "express": "^4.18.2",
257
+ "form-data": "^4.0.0",
257
258
  "imap-simple": "^5.1.0",
258
259
  "js-tiktoken": "^1.0.21",
259
260
  "js-yaml": "^4.1.1",
260
261
  "kleur": "^4.1.5",
262
+ "lru-cache": "^10.0.0",
261
263
  "mailparser": "^3.9.8",
262
264
  "marked": "^15.0.12",
263
265
  "marked-terminal": "^7.3.0",
@@ -267,6 +269,7 @@
267
269
  "open": "^11.0.0",
268
270
  "ora": "^9.3.0",
269
271
  "pdf-parse": "^1.1.1",
272
+ "picomatch": "^4.0.0",
270
273
  "playwright": "^1.58.2",
271
274
  "proper-lockfile": "^4.1.2",
272
275
  "puppeteer": "^24.39.1",
@@ -277,6 +280,7 @@
277
280
  "stripe": "^20.4.1",
278
281
  "tar-stream": "^3.1.8",
279
282
  "twilio": "^5.13.1",
283
+ "undici": "^6.0.0",
280
284
  "uuid": "^9.0.0",
281
285
  "whatsapp-web.js": "^1.26.0",
282
286
  "wrap-ansi": "^9.0.2",