aiden-runtime 4.1.5 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/README.md +265 -847
  2. package/dist/api/server.js +32 -5
  3. package/dist/cli/v4/aidenCLI.js +536 -152
  4. package/dist/cli/v4/callbacks.js +170 -0
  5. package/dist/cli/v4/chatSession.js +245 -3
  6. package/dist/cli/v4/commands/_runtimeToggleHelpers.js +94 -0
  7. package/dist/cli/v4/commands/browserDepth.js +45 -0
  8. package/dist/cli/v4/commands/cron.js +264 -0
  9. package/dist/cli/v4/commands/daemon.js +541 -0
  10. package/dist/cli/v4/commands/daemonStatus.js +253 -0
  11. package/dist/cli/v4/commands/fanout.js +42 -59
  12. package/dist/cli/v4/commands/help.js +13 -0
  13. package/dist/cli/v4/commands/index.js +35 -1
  14. package/dist/cli/v4/commands/mcp.js +80 -54
  15. package/dist/cli/v4/commands/plannerGuard.js +53 -0
  16. package/dist/cli/v4/commands/recovery.js +122 -0
  17. package/dist/cli/v4/commands/runs.js +223 -0
  18. package/dist/cli/v4/commands/sandbox.js +48 -0
  19. package/dist/cli/v4/commands/spawnPause.js +93 -0
  20. package/dist/cli/v4/commands/suggestions.js +68 -0
  21. package/dist/cli/v4/commands/tce.js +41 -0
  22. package/dist/cli/v4/commands/trigger.js +378 -0
  23. package/dist/cli/v4/commands/update.js +95 -3
  24. package/dist/cli/v4/daemonAgentBuilder.js +145 -0
  25. package/dist/cli/v4/defaultSoul.js +1 -1
  26. package/dist/cli/v4/display/capabilityCard.js +26 -0
  27. package/dist/cli/v4/display.js +18 -8
  28. package/dist/cli/v4/replyRenderer.js +31 -23
  29. package/dist/cli/v4/updateBootPrompt.js +170 -0
  30. package/dist/core/playwrightBridge.js +129 -0
  31. package/dist/core/v4/aidenAgent.js +527 -5
  32. package/dist/core/v4/browserState.js +436 -0
  33. package/dist/core/v4/checkpoint.js +79 -0
  34. package/dist/core/v4/daemon/bootstrap.js +651 -0
  35. package/dist/core/v4/daemon/cleanShutdown.js +154 -0
  36. package/dist/core/v4/daemon/cron/cronBridge.js +126 -0
  37. package/dist/core/v4/daemon/cron/cronEmitter.js +173 -0
  38. package/dist/core/v4/daemon/cron/migration.js +199 -0
  39. package/dist/core/v4/daemon/cron/misfirePolicy.js +115 -0
  40. package/dist/core/v4/daemon/daemonConfig.js +90 -0
  41. package/dist/core/v4/daemon/db/connection.js +106 -0
  42. package/dist/core/v4/daemon/db/migrations.js +362 -0
  43. package/dist/core/v4/daemon/db/schema/v1.spec.js +18 -0
  44. package/dist/core/v4/daemon/dispatcher/agentRunner.js +98 -0
  45. package/dist/core/v4/daemon/dispatcher/budgetGate.js +127 -0
  46. package/dist/core/v4/daemon/dispatcher/daemonApproval.js +113 -0
  47. package/dist/core/v4/daemon/dispatcher/dailyBudgetTracker.js +120 -0
  48. package/dist/core/v4/daemon/dispatcher/dispatcher.js +389 -0
  49. package/dist/core/v4/daemon/dispatcher/fireRateLimiter.js +113 -0
  50. package/dist/core/v4/daemon/dispatcher/index.js +53 -0
  51. package/dist/core/v4/daemon/dispatcher/promptTemplate.js +95 -0
  52. package/dist/core/v4/daemon/dispatcher/realAgentRunner.js +356 -0
  53. package/dist/core/v4/daemon/dispatcher/resolveModel.js +93 -0
  54. package/dist/core/v4/daemon/dispatcher/sessionId.js +93 -0
  55. package/dist/core/v4/daemon/drain.js +156 -0
  56. package/dist/core/v4/daemon/eventLoopLag.js +73 -0
  57. package/dist/core/v4/daemon/health.js +159 -0
  58. package/dist/core/v4/daemon/idempotencyStore.js +204 -0
  59. package/dist/core/v4/daemon/index.js +179 -0
  60. package/dist/core/v4/daemon/instanceTracker.js +99 -0
  61. package/dist/core/v4/daemon/resourceRegistry.js +150 -0
  62. package/dist/core/v4/daemon/restartCode.js +32 -0
  63. package/dist/core/v4/daemon/restartFailureCounter.js +77 -0
  64. package/dist/core/v4/daemon/runStore.js +144 -0
  65. package/dist/core/v4/daemon/runtimeLock.js +167 -0
  66. package/dist/core/v4/daemon/signals.js +50 -0
  67. package/dist/core/v4/daemon/supervisor.js +272 -0
  68. package/dist/core/v4/daemon/triggerBus.js +279 -0
  69. package/dist/core/v4/daemon/triggers/email/allowlist.js +70 -0
  70. package/dist/core/v4/daemon/triggers/email/automatedSender.js +78 -0
  71. package/dist/core/v4/daemon/triggers/email/bodyExtractor.js +0 -0
  72. package/dist/core/v4/daemon/triggers/email/emailSeenStore.js +99 -0
  73. package/dist/core/v4/daemon/triggers/email/emailSpec.js +107 -0
  74. package/dist/core/v4/daemon/triggers/email/imapConnection.js +211 -0
  75. package/dist/core/v4/daemon/triggers/email/index.js +332 -0
  76. package/dist/core/v4/daemon/triggers/email/seenUids.js +60 -0
  77. package/dist/core/v4/daemon/triggers/fileObservationsStore.js +93 -0
  78. package/dist/core/v4/daemon/triggers/fileWatcher.js +253 -0
  79. package/dist/core/v4/daemon/triggers/fileWatcherSpec.js +88 -0
  80. package/dist/core/v4/daemon/triggers/fsIdentity.js +42 -0
  81. package/dist/core/v4/daemon/triggers/globMatcher.js +100 -0
  82. package/dist/core/v4/daemon/triggers/reconcile.js +206 -0
  83. package/dist/core/v4/daemon/triggers/settleStat.js +81 -0
  84. package/dist/core/v4/daemon/triggers/webhook.js +376 -0
  85. package/dist/core/v4/daemon/triggers/webhookDeliveriesStore.js +109 -0
  86. package/dist/core/v4/daemon/triggers/webhookIdempotency.js +72 -0
  87. package/dist/core/v4/daemon/triggers/webhookRateLimit.js +56 -0
  88. package/dist/core/v4/daemon/triggers/webhookSpec.js +76 -0
  89. package/dist/core/v4/daemon/triggers/webhookVerifier.js +128 -0
  90. package/dist/core/v4/daemon/types.js +15 -0
  91. package/dist/core/v4/dockerSession.js +461 -0
  92. package/dist/core/v4/dryRun.js +117 -0
  93. package/dist/core/v4/failureClassifier.js +779 -0
  94. package/dist/core/v4/providerFallback.js +35 -2
  95. package/dist/core/v4/recoveryReport.js +449 -0
  96. package/dist/core/v4/runtimeToggles.js +214 -0
  97. package/dist/core/v4/sandboxConfig.js +285 -0
  98. package/dist/core/v4/sandboxFs.js +316 -0
  99. package/dist/core/v4/selfimprovement/recoveryStore.js +307 -0
  100. package/dist/core/v4/selfimprovement/signatureBuilder.js +158 -0
  101. package/dist/core/v4/subagent/childBuilder.js +391 -0
  102. package/dist/core/v4/subagent/fanout.js +75 -51
  103. package/dist/core/v4/subagent/spawnPause.js +191 -0
  104. package/dist/core/v4/subagent/spawnSubAgent.js +310 -0
  105. package/dist/core/v4/suggestionCatalog.js +41 -0
  106. package/dist/core/v4/suggestionEngine.js +210 -0
  107. package/dist/core/v4/toolRegistry.js +37 -3
  108. package/dist/core/v4/turnState.js +587 -0
  109. package/dist/core/v4/update/checkUpdate.js +63 -3
  110. package/dist/core/v4/update/installMethodDetect.js +115 -0
  111. package/dist/core/v4/update/registryClient.js +121 -0
  112. package/dist/core/v4/update/skipState.js +75 -0
  113. package/dist/core/v4/verifier.js +448 -0
  114. package/dist/core/version.js +1 -1
  115. package/dist/moat/plannerGuard.js +29 -0
  116. package/dist/providers/v4/anthropicAdapter.js +31 -3
  117. package/dist/providers/v4/chatCompletionsAdapter.js +26 -3
  118. package/dist/providers/v4/codexResponsesAdapter.js +25 -2
  119. package/dist/providers/v4/ollamaPromptToolsAdapter.js +57 -2
  120. package/dist/tools/v4/browser/_observer.js +224 -0
  121. package/dist/tools/v4/browser/browserBlocker.js +396 -0
  122. package/dist/tools/v4/browser/browserClick.js +18 -1
  123. package/dist/tools/v4/browser/browserClose.js +18 -1
  124. package/dist/tools/v4/browser/browserExtract.js +5 -1
  125. package/dist/tools/v4/browser/browserFill.js +17 -1
  126. package/dist/tools/v4/browser/browserGetUrl.js +5 -1
  127. package/dist/tools/v4/browser/browserNavigate.js +16 -1
  128. package/dist/tools/v4/browser/browserScreenshot.js +5 -1
  129. package/dist/tools/v4/browser/browserScroll.js +18 -1
  130. package/dist/tools/v4/browser/browserType.js +17 -1
  131. package/dist/tools/v4/browser/captchaCheck.js +5 -1
  132. package/dist/tools/v4/executeCode.js +1 -0
  133. package/dist/tools/v4/files/fileCopy.js +56 -2
  134. package/dist/tools/v4/files/fileDelete.js +38 -1
  135. package/dist/tools/v4/files/fileList.js +12 -1
  136. package/dist/tools/v4/files/fileMove.js +59 -2
  137. package/dist/tools/v4/files/filePatch.js +43 -1
  138. package/dist/tools/v4/files/fileRead.js +12 -1
  139. package/dist/tools/v4/files/fileWrite.js +41 -1
  140. package/dist/tools/v4/index.js +88 -61
  141. package/dist/tools/v4/memory/memoryAdd.js +14 -0
  142. package/dist/tools/v4/memory/memoryRemove.js +14 -0
  143. package/dist/tools/v4/memory/memoryReplace.js +15 -0
  144. package/dist/tools/v4/memory/sessionSummary.js +12 -0
  145. package/dist/tools/v4/process/processKill.js +19 -0
  146. package/dist/tools/v4/process/processList.js +1 -0
  147. package/dist/tools/v4/process/processLogRead.js +1 -0
  148. package/dist/tools/v4/process/processSpawn.js +13 -0
  149. package/dist/tools/v4/process/processWait.js +1 -0
  150. package/dist/tools/v4/sessions/recallSession.js +1 -0
  151. package/dist/tools/v4/sessions/sessionList.js +1 -0
  152. package/dist/tools/v4/sessions/sessionSearch.js +1 -0
  153. package/dist/tools/v4/skills/lookupToolSchema.js +7 -0
  154. package/dist/tools/v4/skills/skillManage.js +13 -0
  155. package/dist/tools/v4/skills/skillView.js +1 -0
  156. package/dist/tools/v4/skills/skillsList.js +1 -0
  157. package/dist/tools/v4/subagent/spawnSubAgentTool.js +334 -0
  158. package/dist/tools/v4/subagent/subagentFanout.js +54 -1
  159. package/dist/tools/v4/system/aidenSelfUpdate.js +16 -0
  160. package/dist/tools/v4/system/appClose.js +13 -0
  161. package/dist/tools/v4/system/appInput.js +13 -0
  162. package/dist/tools/v4/system/appLaunch.js +13 -0
  163. package/dist/tools/v4/system/clipboardRead.js +1 -0
  164. package/dist/tools/v4/system/clipboardWrite.js +14 -0
  165. package/dist/tools/v4/system/mediaKey.js +12 -0
  166. package/dist/tools/v4/system/mediaSessions.js +1 -0
  167. package/dist/tools/v4/system/mediaTransport.js +13 -0
  168. package/dist/tools/v4/system/naturalEvents.js +1 -0
  169. package/dist/tools/v4/system/nowPlaying.js +1 -0
  170. package/dist/tools/v4/system/osProcessList.js +1 -0
  171. package/dist/tools/v4/system/screenshot.js +1 -0
  172. package/dist/tools/v4/system/systemInfo.js +1 -0
  173. package/dist/tools/v4/system/volumeSet.js +17 -0
  174. package/dist/tools/v4/terminal/shellExec.js +81 -9
  175. package/dist/tools/v4/web/deepResearch.js +1 -0
  176. package/dist/tools/v4/web/openUrl.js +1 -0
  177. package/dist/tools/v4/web/webFetch.js +1 -0
  178. package/dist/tools/v4/web/webPage.js +1 -0
  179. package/dist/tools/v4/web/webSearch.js +1 -0
  180. package/dist/tools/v4/web/youtubeSearch.js +1 -0
  181. package/package.json +13 -3
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) 2026 Shiva Deore (Taracod).
4
+ * Licensed under AGPL-3.0. See LICENSE for details.
5
+ *
6
+ * Aiden — local-first agent.
7
+ */
8
+ /**
9
+ * core/v4/selfimprovement/signatureBuilder.ts — v4.6 Phase 3b.
10
+ *
11
+ * Builds a stable, deterministic signature string for a failed tool
12
+ * call so equivalent failures collapse into one `failure_signatures`
13
+ * row. The shape is:
14
+ *
15
+ * <tool_name>:<failure_category>[:<args_hash_prefix>]
16
+ *
17
+ * The `args_hash_prefix` field is OPTIONAL. When the caller supplies
18
+ * `args`, this module normalises them (strips volatile fields like
19
+ * timestamps, run IDs, UUIDs, monotonic counters), serialises the
20
+ * result deterministically, and takes the first 6 hex chars of a
21
+ * SHA-256 digest. When `args` is omitted, the signature collapses to
22
+ * `<tool>:<category>` only — same logical failure, broader grouping.
23
+ *
24
+ * Granularity trade-offs:
25
+ *
26
+ * * Too granular ("every failure unique") → no aggregation; the
27
+ * `occurrences` column never increments past 1; operators can't
28
+ * see "this tool fails the same way over and over."
29
+ * * Too coarse ("only tool+category") → "file_read failed with
30
+ * `not_found`" groups EVERY missing file together; the operator
31
+ * can't tell which paths are sore points.
32
+ *
33
+ * The args-hash compromise: same tool + same category + same
34
+ * normalized args → same signature (good); same tool + same category
35
+ * + meaningfully different args → different signatures (also good).
36
+ * Volatile fields are stripped BEFORE hashing so re-hashing on a
37
+ * later turn produces the same signature even when only the
38
+ * timestamp / call id changes.
39
+ *
40
+ * Volatile field list (`VOLATILE_KEYS`) — defensive; covers the
41
+ * fields Aiden's tool layer tends to thread through args. Plugin
42
+ * authors who emit custom volatile keys should pre-normalise before
43
+ * calling this module.
44
+ */
45
+ var __importDefault = (this && this.__importDefault) || function (mod) {
46
+ return (mod && mod.__esModule) ? mod : { "default": mod };
47
+ };
48
+ Object.defineProperty(exports, "__esModule", { value: true });
49
+ exports.buildFailureSignature = buildFailureSignature;
50
+ const node_crypto_1 = __importDefault(require("node:crypto"));
51
+ // ── Implementation ───────────────────────────────────────────────────────
52
+ /**
53
+ * Keys whose values are stripped from the args object before hashing.
54
+ * These are fields that DO change between otherwise-identical
55
+ * failures (turn timestamps, run row ids, etc.) so leaving them in
56
+ * would prevent any signature from ever grouping.
57
+ *
58
+ * The list is intentionally narrow — only fields Aiden's tool layer
59
+ * is known to inject. Plugins emitting custom volatile keys must
60
+ * pre-normalise their args before calling this module.
61
+ */
62
+ const VOLATILE_KEYS = new Set([
63
+ 'timestamp',
64
+ 'ts',
65
+ 'requestId',
66
+ 'request_id',
67
+ 'runId',
68
+ 'run_id',
69
+ 'callId',
70
+ 'call_id',
71
+ 'sessionId',
72
+ 'session_id',
73
+ 'turnId',
74
+ 'turn_id',
75
+ 'eventId',
76
+ 'event_id',
77
+ 'createdAt',
78
+ 'created_at',
79
+ 'updatedAt',
80
+ 'updated_at',
81
+ // Common UUID/idempotency-key names.
82
+ 'uuid',
83
+ 'idempotencyKey',
84
+ 'idempotency_key',
85
+ ]);
86
+ /**
87
+ * Deterministically stringify a value. Sorts object keys so
88
+ * `{a:1, b:2}` and `{b:2, a:1}` produce identical bytes. Strips
89
+ * volatile keys from any nested object before stringifying.
90
+ *
91
+ * Non-JSON-serialisable values (functions, symbols, circular refs)
92
+ * collapse to the literal string `'[unserializable]'` so the hash
93
+ * remains stable. Better-than-throwing is the right trade-off for
94
+ * a write-through hot path.
95
+ */
96
+ function deterministicStringify(value) {
97
+ const seen = new WeakSet();
98
+ const visit = (v) => {
99
+ if (v === null || v === undefined)
100
+ return null;
101
+ const t = typeof v;
102
+ if (t === 'string' || t === 'number' || t === 'boolean')
103
+ return v;
104
+ if (t === 'function' || t === 'symbol')
105
+ return '[unserializable]';
106
+ if (typeof v === 'bigint')
107
+ return v.toString();
108
+ if (Array.isArray(v)) {
109
+ if (seen.has(v))
110
+ return '[circular]';
111
+ seen.add(v);
112
+ return v.map(visit);
113
+ }
114
+ if (t === 'object') {
115
+ const obj = v;
116
+ if (seen.has(obj))
117
+ return '[circular]';
118
+ seen.add(obj);
119
+ const out = {};
120
+ const keys = Object.keys(obj).filter((k) => !VOLATILE_KEYS.has(k));
121
+ keys.sort();
122
+ for (const k of keys)
123
+ out[k] = visit(obj[k]);
124
+ return out;
125
+ }
126
+ return '[unserializable]';
127
+ };
128
+ try {
129
+ return JSON.stringify(visit(value));
130
+ }
131
+ catch {
132
+ return '[unserializable]';
133
+ }
134
+ }
135
+ /**
136
+ * Build a failure signature. Pure function — no I/O, no side
137
+ * effects. Safe to call on the hot path of every classified
138
+ * failure; SHA-256 of a small JSON string is cheap (microseconds).
139
+ */
140
+ function buildFailureSignature(input) {
141
+ const base = `${input.toolName}:${input.category}`;
142
+ if (input.args === undefined) {
143
+ return { signature: base };
144
+ }
145
+ const normalized = deterministicStringify(input.args);
146
+ // Empty / trivially-null args don't deserve a hash suffix —
147
+ // collapse to the base signature so "args: {}" and "no args"
148
+ // group together.
149
+ if (normalized === 'null' || normalized === '{}' || normalized === '[]') {
150
+ return { signature: base };
151
+ }
152
+ const digest = node_crypto_1.default.createHash('sha256').update(normalized).digest('hex');
153
+ const argsHash = digest.slice(0, 6);
154
+ return {
155
+ signature: `${base}:${argsHash}`,
156
+ argsHash,
157
+ };
158
+ }
@@ -0,0 +1,391 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) 2026 Shiva Deore (Taracod).
4
+ * Licensed under AGPL-3.0. See LICENSE for details.
5
+ *
6
+ * Aiden — local-first agent.
7
+ */
8
+ /**
9
+ * core/v4/subagent/childBuilder.ts — v4.6 Phase 1.
10
+ *
11
+ * Constructs the child `AidenAgent` for one `spawn_sub_agent` call.
12
+ * Mirrors the closure-capture shape of `cli/v4/daemonAgentBuilder.ts`
13
+ * — shared deps captured at REPL bootstrap, fresh per-spawn state
14
+ * built inline.
15
+ *
16
+ * Per the design doc §5 state-isolation matrix:
17
+ * - Conversation history, system prompt, TCE, file-op cache:
18
+ * ISOLATED (child gets fresh).
19
+ * - Toolset: intersection of (parent's enabled toolsets) ∩
20
+ * (spec.toolsets or parent's full set) MINUS the hard blocklist.
21
+ * - Provider + model + credentials: INHERITED (same adapter).
22
+ * - FallbackAdapter rate-limit state: CLONED per child (when the
23
+ * adapter exposes `clone()`) so a child's 429 doesn't poison
24
+ * the parent's quota tracking.
25
+ * - ApprovalEngine: fresh instance with auto-deny callbacks
26
+ * (child cannot prompt the user).
27
+ * - plannerGuard / honestyEnforcement / skillTeacher / skillMiner:
28
+ * OMITTED (focused worker config, matching daemon agent shape).
29
+ * - Working directory / sandbox / runtimeToggles: shared via the
30
+ * process-level singletons read on each tool dispatch.
31
+ */
32
+ Object.defineProperty(exports, "__esModule", { value: true });
33
+ exports.ProviderNotFoundError = exports.SUBAGENT_BLOCKED_TOOL_NAMES = void 0;
34
+ exports.buildChildAgent = buildChildAgent;
35
+ const approvalEngine_1 = require("../../../moat/approvalEngine");
36
+ const aidenAgent_1 = require("../aidenAgent");
37
+ const providerFallback_1 = require("../providerFallback");
38
+ // ── Hard-coded blocklist (Q5 from design doc §2) ────────────────────────────
39
+ /**
40
+ * Tools children must NEVER receive, even if the parent's enabled toolsets
41
+ * cover them and the spec explicitly requests them. Filtered post-intersection.
42
+ *
43
+ * Each entry's rationale, in order:
44
+ * - `spawn_sub_agent` — no recursive spawning (depth cap = 1 in Phase 1)
45
+ * - `clarify` — child cannot prompt the user
46
+ * - `memory` — no writes to shared MEMORY.md / USER.md
47
+ * - `execute_code` — children reason step-by-step, not write scripts
48
+ * - `send_message` — no cross-platform side effects from a child
49
+ */
50
+ exports.SUBAGENT_BLOCKED_TOOL_NAMES = new Set([
51
+ 'spawn_sub_agent',
52
+ 'clarify',
53
+ 'memory',
54
+ 'execute_code',
55
+ 'send_message',
56
+ ]);
57
+ /**
58
+ * v4.6 Phase 2P — error thrown by `buildChildAgent` when
59
+ * `input.providerOverride` doesn't match any provider in the
60
+ * parent's pool. Caught by `spawnSubAgent` and converted to a
61
+ * `status: 'failed', exitReason: 'provider_not_found'` envelope
62
+ * with the failing name + the list of valid alternatives.
63
+ */
64
+ class ProviderNotFoundError extends Error {
65
+ constructor(requested, available, hint) {
66
+ const base = `spawn_sub_agent: provider "${requested}" not in parent's pool.`;
67
+ const list = available.length > 0
68
+ ? ` Available: ${available.join(', ')}.`
69
+ : ' Parent has no FallbackAdapter pool (single-provider configuration).';
70
+ super(`${base}${list}${hint ? ' ' + hint : ' Omit the provider field to inherit the parent\'s provider.'}`);
71
+ this.name = 'ProviderNotFoundError';
72
+ this.requested = requested;
73
+ this.available = available;
74
+ }
75
+ }
76
+ exports.ProviderNotFoundError = ProviderNotFoundError;
77
+ // ── Implementation ──────────────────────────────────────────────────────────
78
+ /**
79
+ * Build the child agent + initial history. Pure factory — no side
80
+ * effects beyond constructing in-memory objects. The caller is
81
+ * responsible for running `agent.runConversation(...)` and writing
82
+ * the `runs` row.
83
+ */
84
+ function buildChildAgent(deps, input) {
85
+ // ── 1. ApprovalEngine: fresh, auto-deny callbacks ────────────────────────
86
+ // 'smart' mode: safe auto-allows, dangerous auto-denies, caution
87
+ // calls promptUser which we wire to a synchronous deny — children
88
+ // cannot interact with a TUI.
89
+ const autoDenyCallbacks = {
90
+ promptUser: async () => 'deny',
91
+ };
92
+ const childApprovalEngine = new approvalEngine_1.ApprovalEngine('smart', autoDenyCallbacks);
93
+ // ── 2. ToolContext: parent's services + child approval engine + session ──
94
+ const childToolContext = {
95
+ ...deps.parentToolContext,
96
+ sessionId: input.sessionId,
97
+ approvalEngine: childApprovalEngine,
98
+ };
99
+ // ── 3. Build the child's toolExecutor from the parent's registry ─────────
100
+ // Same registry, different context. The registry stays read-only.
101
+ const childToolExecutor = deps.toolRegistry.buildExecutor(childToolContext);
102
+ // ── 4. Tool array: intersection + blocklist filter ───────────────────────
103
+ // Step 4a — pick the parent's toolsets we care about.
104
+ // If the spec named toolsets, intersect with the parent's known set.
105
+ // Otherwise the child gets the parent's full enabled set (which on
106
+ // REPL means every toolset the registry knows).
107
+ const allHandlers = deps.toolRegistry.list();
108
+ const parentToolsetNames = new Set();
109
+ for (const name of allHandlers) {
110
+ const handler = deps.toolRegistry.get(name);
111
+ if (handler?.toolset)
112
+ parentToolsetNames.add(handler.toolset);
113
+ }
114
+ let chosenToolsets = input.requestedToolsets && input.requestedToolsets.length > 0
115
+ ? input.requestedToolsets.filter((t) => parentToolsetNames.has(t))
116
+ : [...parentToolsetNames];
117
+ // v4.6 Phase 1 (Dispatch 2L) — zero-tools-bug fallback. When the
118
+ // model passes `toolsets: [...]` with values that DON'T match any
119
+ // real registry toolset (e.g. `["functions"]`, a name fabricated
120
+ // from the OpenAI tool-use vocabulary, or `["file_operations"]`, a
121
+ // skill name confused for a toolset), the strict filter strips all
122
+ // entries → `chosenToolsets` is `[]` → child gets ZERO tools → it
123
+ // hallucinates an answer rather than admit it can't do the work.
124
+ //
125
+ // Recover by inheriting the full parent set when the requested
126
+ // names ALL miss. Logs a warning so the operator sees what the
127
+ // model asked for and what real names exist. Partial intersections
128
+ // (some valid, some invalid) keep the valid subset — that's the
129
+ // user's explicit narrowing intent, not a bug.
130
+ if (input.requestedToolsets && input.requestedToolsets.length > 0 &&
131
+ chosenToolsets.length === 0) {
132
+ deps.logger?.warn?.('spawn_sub_agent: requested toolsets stripped to empty, falling back to full parent set', {
133
+ requested: input.requestedToolsets,
134
+ validParentToolsets: [...parentToolsetNames],
135
+ });
136
+ chosenToolsets = [...parentToolsetNames];
137
+ }
138
+ // Step 4b — pull the schemas for those toolsets.
139
+ // v4.6 Phase 1 — pass 'repl' context: in Phase 1 spawn_sub_agent
140
+ // is REPL-only (Q6), so children always spawn from a REPL parent.
141
+ // Phase 3+ may extend the child builder to receive the parent's
142
+ // context dynamically; for now, 'repl' is the only path.
143
+ const candidateSchemas = chosenToolsets.length > 0
144
+ ? deps.toolRegistry.getSchemas(chosenToolsets, 'repl')
145
+ : []; // No matching toolsets means an empty child toolset.
146
+ // Step 4c — strip the hard blocklist (with v4.6 Phase 2P legacy
147
+ // env-flag escape hatch per design doc §12.4). Default: full
148
+ // 5-name blocklist. When AIDEN_SUBAGENT_ALLOW_DESTRUCTIVE=1 is
149
+ // set in the environment, `execute_code` is removed from the
150
+ // blocklist for THIS spawn — backward-compat preservation of any
151
+ // user's existing v4.1.0 .env workflow. The other 4 names
152
+ // (spawn_sub_agent, clarify, memory, send_message) remain
153
+ // blocked regardless of the flag.
154
+ const blocked = resolveBlocklist(deps.logger);
155
+ const childTools = candidateSchemas.filter((t) => !blocked.has(t.name));
156
+ // ── 5. Provider: clone FallbackAdapter rate-limit state if supported ─────
157
+ // Per Q11 (verbatim mirror of providerFallback.ts:578 clone pattern).
158
+ // Best-effort — if the adapter doesn't expose `clone()`, fall back to
159
+ // sharing the parent's adapter. Phase 1 accepts that fallback case
160
+ // means a child's 429 affects the parent's quota tracking; that's
161
+ // explicit in the design-doc §5 row.
162
+ //
163
+ // v4.6 Phase 2P — when `input.providerOverride` is supplied, resolve
164
+ // it against the parent's FallbackAdapter slot pool. Fail-loud on
165
+ // unknown names (`ProviderNotFoundError`) so fanout's diversity
166
+ // invariant is preserved (silent fallback would collapse the
167
+ // rotation). Single-provider parents (non-FallbackAdapter) reject
168
+ // any override, since there's no pool to select from.
169
+ const childProvider = resolveChildProvider(deps.parentProvider, input.providerOverride);
170
+ // ── 6. Observability — onToolCall → run_events + log ─────────────────────
171
+ // When deps.runStore + deps.childRunId are present (the production
172
+ // path from spawnSubAgent.ts), wire an `onToolCall` callback that
173
+ // mirrors the daemon dispatcher's audit shape (see
174
+ // `core/v4/daemon/dispatcher/realAgentRunner.ts:367-382`). Per-call
175
+ // start time is tracked in a Map keyed by tool_call_id so the
176
+ // `durationMs` on completed events is per-tool, not per-turn.
177
+ // Pure no-op when runStore is absent (unit tests of buildChildAgent).
178
+ const onToolCall = buildOnToolCall(deps);
179
+ // ── 7. Build the child agent ─────────────────────────────────────────────
180
+ // Focused worker config: omit plannerGuard, honestyEnforcement,
181
+ // skillTeacher, skillMiner, contextCompressor, promptCaching,
182
+ // promptBuilder. Match the daemon agent's "act on the task, don't
183
+ // self-improve" shape.
184
+ const agent = new aidenAgent_1.AidenAgent({
185
+ provider: childProvider,
186
+ tools: childTools,
187
+ toolExecutor: childToolExecutor,
188
+ sessionId: input.sessionId,
189
+ maxTurns: input.maxIterations,
190
+ providerId: deps.parentProviderId,
191
+ modelId: deps.parentModelId,
192
+ resolveVerifiedFlag: deps.resolveVerifiedFlag,
193
+ resolveToolset: deps.resolveToolset,
194
+ resolveMutates: deps.resolveMutates,
195
+ onToolCall,
196
+ // iterationBudgetInjection inherits the default (true) — child
197
+ // sees its own remaining-budget hint near the end of the run.
198
+ });
199
+ // ── 7. Initial history: fresh system prompt + the user-shaped goal ───────
200
+ const systemContent = buildChildSystemPrompt(input.goal, input.context);
201
+ const userContent = composeUserMessage(input.goal, input.context);
202
+ const history = [
203
+ { role: 'system', content: systemContent },
204
+ { role: 'user', content: userContent },
205
+ ];
206
+ return { agent, history };
207
+ }
208
+ // ── Helpers ─────────────────────────────────────────────────────────────────
209
+ /**
210
+ * v4.6 Phase 2P — resolve the child's provider adapter.
211
+ *
212
+ * No override (Phase 1 behavior unchanged):
213
+ * - FallbackAdapter → clone with fresh mutable state, all slots.
214
+ * - Any other adapter shape with `clone()` → clone.
215
+ * - Adapter without `clone()` → reuse the parent's instance.
216
+ *
217
+ * Override supplied (Phase 2P):
218
+ * - Parent must be FallbackAdapter (only adapter type with a
219
+ * multi-provider pool). Otherwise throw `ProviderNotFoundError`.
220
+ * - Override must match one of `parent.getProviderIds()`. Otherwise
221
+ * throw `ProviderNotFoundError` listing the available pool.
222
+ * - On match: clone with slot-subset filter restricted to that
223
+ * provider's slots. Child rotates only within the chosen
224
+ * provider's slots; the diversity invariant fanout depends on
225
+ * is preserved.
226
+ */
227
+ function resolveChildProvider(parent, override) {
228
+ if (!override) {
229
+ // ── No override path — Phase 1 clone semantics, unchanged. ────────
230
+ const maybeClone = parent.clone;
231
+ if (typeof maybeClone === 'function') {
232
+ try {
233
+ return maybeClone.call(parent);
234
+ }
235
+ catch {
236
+ return parent;
237
+ }
238
+ }
239
+ return parent;
240
+ }
241
+ // ── Override path — must be FallbackAdapter. ───────────────────────
242
+ if (!(parent instanceof providerFallback_1.FallbackAdapter)) {
243
+ throw new ProviderNotFoundError(override, [], 'Parent agent uses a single-provider adapter; provider override is only available when parent is configured with FallbackAdapter (multi-provider pool).');
244
+ }
245
+ const available = parent.getProviderIds();
246
+ if (!available.includes(override)) {
247
+ throw new ProviderNotFoundError(override, available);
248
+ }
249
+ return parent.clone({ providerId: override });
250
+ }
251
+ /**
252
+ * v4.6 Phase 2P — resolve the effective tool blocklist for THIS
253
+ * child build. Default is the full 5-name set
254
+ * (`SUBAGENT_BLOCKED_TOOL_NAMES`). When
255
+ * `AIDEN_SUBAGENT_ALLOW_DESTRUCTIVE=1` (or any truthy value) is
256
+ * present in `process.env`, drop `execute_code` — preserves the
257
+ * v4.1.0 fanout escape hatch for users with that flag in `.env`.
258
+ * Other 4 names stay blocked regardless.
259
+ *
260
+ * Logs a one-line warning when the escape hatch is active so the
261
+ * relaxation is visible in observability — silently relaxing a
262
+ * security boundary is exactly the failure mode we want to avoid.
263
+ */
264
+ function resolveBlocklist(logger) {
265
+ const raw = process.env.AIDEN_SUBAGENT_ALLOW_DESTRUCTIVE;
266
+ if (!raw)
267
+ return exports.SUBAGENT_BLOCKED_TOOL_NAMES;
268
+ const flag = String(raw).trim().toLowerCase();
269
+ if (flag === '1' || flag === 'true' || flag === 'on' || flag === 'yes') {
270
+ logger?.warn?.('spawn_sub_agent: AIDEN_SUBAGENT_ALLOW_DESTRUCTIVE escape hatch active; ' +
271
+ 'execute_code dropped from child blocklist (other 4 names still blocked)', { source: 'env', flag: raw });
272
+ const relaxed = new Set(exports.SUBAGENT_BLOCKED_TOOL_NAMES);
273
+ relaxed.delete('execute_code');
274
+ return relaxed;
275
+ }
276
+ return exports.SUBAGENT_BLOCKED_TOOL_NAMES;
277
+ }
278
+ /**
279
+ * Compose the child's system prompt. Intentionally minimal — no
280
+ * SOUL.md, no parent identity, no MEMORY.md preamble. Goal-focused.
281
+ */
282
+ function buildChildSystemPrompt(goal, context) {
283
+ const lines = [
284
+ 'You are a focused sub-agent dispatched to handle ONE concrete task.',
285
+ 'You have no memory of any prior conversation — only the goal and',
286
+ 'optional context below. You cannot ask the user follow-up questions',
287
+ '(your `clarify` tool is disabled), you cannot spawn further sub-agents,',
288
+ 'and you cannot write to MEMORY.md.',
289
+ '',
290
+ 'When the task is done, produce a single final assistant message',
291
+ 'summarising what you did and what you found. That summary is the',
292
+ 'ONLY output the parent agent will see — no tool traces, no',
293
+ 'intermediate reasoning. Make it self-contained, factual, and tight.',
294
+ '',
295
+ `## Goal`,
296
+ goal.trim(),
297
+ ];
298
+ if (context && context.trim().length > 0) {
299
+ lines.push('', '## Background context', context.trim());
300
+ }
301
+ return lines.join('\n');
302
+ }
303
+ /**
304
+ * Compose the initial user message. Currently a stub repeat of the goal
305
+ * — kept distinct from the system prompt so providers that prefer the
306
+ * system role for instructions and the user role for the immediate
307
+ * request both get a sensible payload. Context is included only in
308
+ * the system prompt so the user message stays compact.
309
+ */
310
+ function composeUserMessage(goal, _context) {
311
+ return goal.trim();
312
+ }
313
+ /**
314
+ * v4.6 Phase 1 — build the child agent's `onToolCall` callback. Emits
315
+ * `tool_call_started` + `tool_call_completed` events to the child's
316
+ * `runs` row via the supplied runStore, mirroring the daemon
317
+ * dispatcher's audit shape. Also logs each call at info level so
318
+ * users tailing aiden.log see what the child actually did.
319
+ *
320
+ * Returns `undefined` when persistence + logger are both absent —
321
+ * the agent constructor accepts `undefined` for `onToolCall` and
322
+ * dispatches without notifying.
323
+ */
324
+ function buildOnToolCall(deps) {
325
+ const { runStore, childRunId, logger } = deps;
326
+ if (!runStore && !logger && childRunId === undefined)
327
+ return undefined;
328
+ // Per-call start time keyed by tool_call_id so `durationMs` on the
329
+ // completed event reflects per-tool wall-clock, not per-turn.
330
+ const callStarts = new Map();
331
+ return (call, phase, result) => {
332
+ try {
333
+ if (phase === 'before') {
334
+ const startedAt = Date.now();
335
+ callStarts.set(call.id, startedAt);
336
+ const argsSummary = safeShortJson(call.arguments, 200);
337
+ if (runStore && childRunId !== undefined) {
338
+ runStore.emitEvent(childRunId, 'tool_call_started', {
339
+ toolName: call.name,
340
+ args: argsSummary,
341
+ ts: startedAt,
342
+ });
343
+ }
344
+ if (logger) {
345
+ logger.info('sub-agent tool call', {
346
+ childRunId: childRunId ?? null,
347
+ toolName: call.name,
348
+ args: argsSummary,
349
+ });
350
+ }
351
+ return;
352
+ }
353
+ // phase === 'after'
354
+ const startedAt = callStarts.get(call.id) ?? Date.now();
355
+ callStarts.delete(call.id);
356
+ const durationMs = Date.now() - startedAt;
357
+ if (runStore && childRunId !== undefined) {
358
+ runStore.emitEvent(childRunId, 'tool_call_completed', {
359
+ toolName: call.name,
360
+ error: result?.error ?? null,
361
+ hasResult: result?.result !== undefined && result?.result !== null,
362
+ durationMs,
363
+ });
364
+ }
365
+ if (logger) {
366
+ logger.info('sub-agent tool result', {
367
+ childRunId: childRunId ?? null,
368
+ toolName: call.name,
369
+ ok: !result?.error,
370
+ durationMs,
371
+ });
372
+ }
373
+ }
374
+ catch {
375
+ // Observability must never crash the agent loop.
376
+ }
377
+ };
378
+ }
379
+ /** JSON-stringify with a byte cap; returns the truncated string with an
380
+ * ellipsis when the payload exceeds `maxBytes`. */
381
+ function safeShortJson(value, maxBytes) {
382
+ try {
383
+ const s = JSON.stringify(value);
384
+ if (s === undefined)
385
+ return '';
386
+ return s.length > maxBytes ? s.slice(0, maxBytes) + '…' : s;
387
+ }
388
+ catch {
389
+ return String(value).slice(0, maxBytes);
390
+ }
391
+ }