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,214 @@
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/runtimeToggles.ts — v4.5 Phase 8a.
10
+ *
11
+ * Single source of truth for the v4.2/v4.3/v4.4 subsystem
12
+ * default-on toggles (TCE, browser depth, sandbox). Replaces the
13
+ * direct `process.env.AIDEN_*` reads scattered across:
14
+ *
15
+ * - core/v4/sandboxConfig.ts (AIDEN_SANDBOX)
16
+ * - core/v4/turnState.ts (AIDEN_TCE)
17
+ * - core/v4/browserState.ts (AIDEN_BROWSER_DEPTH)
18
+ *
19
+ * with a centralised resolver that supports:
20
+ *
21
+ * - **Live flip** via slash commands (/sandbox on|off, /tce on|off,
22
+ * /browser-depth on|off). The slash command updates the in-process
23
+ * state + persists to config.yaml, and fires onChange callbacks so
24
+ * cached consumers (sandboxConfig's singleton) invalidate.
25
+ *
26
+ * - **Persistence** across restarts via
27
+ * `<AIDEN_HOME>/config.yaml :: runtime_toggles.{sandbox,tce,browser_depth}`.
28
+ *
29
+ * - **Env-var precedence** (Q-P8a-1a): explicit env var > config.yaml >
30
+ * default (true for all three). Matches the existing AIDEN_*
31
+ * escape-hatch contract.
32
+ *
33
+ * The singleton is initialised by the CLI at boot via `initRuntimeToggles`
34
+ * with a ConfigProvider seam. Core modules that read the toggles call
35
+ * `getRuntimeToggles().isEnabled(key)` — when the singleton hasn't been
36
+ * initialised (test bench, core-only invocation), an env-only fallback
37
+ * resolver is used so the modules keep working with their pre-v4.5
38
+ * semantics.
39
+ */
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports._TOGGLE_KEYS = void 0;
42
+ exports.buildRuntimeToggles = buildRuntimeToggles;
43
+ exports.getRuntimeToggles = getRuntimeToggles;
44
+ exports.initRuntimeToggles = initRuntimeToggles;
45
+ exports._resetRuntimeTogglesForTests = _resetRuntimeTogglesForTests;
46
+ // ── Env var mapping ────────────────────────────────────────────────────────
47
+ const ENV_VAR = {
48
+ sandbox: 'AIDEN_SANDBOX',
49
+ tce: 'AIDEN_TCE',
50
+ browser_depth: 'AIDEN_BROWSER_DEPTH',
51
+ // v4.5 Phase 8b — contextual capability suggestions. Rarely set as
52
+ // env (this is mostly a UX toggle) but included for symmetry with
53
+ // the other subsystem toggles.
54
+ suggestions: 'AIDEN_SUGGESTIONS',
55
+ // v4.6 Phase 2M — keyword-based per-turn tool narrowing.
56
+ // Default OFF: smart models (GPT-5.5, Claude Sonnet 4.5+, Opus)
57
+ // pick tools fine from a full catalog. PlannerGuard adds latency
58
+ // (1 LLM call when mode=llm_classified) and occasionally strips
59
+ // tools the model genuinely needed. Opt in for small local models
60
+ // that get overwhelmed by 50+ tool schemas.
61
+ planner_guard: 'AIDEN_PLANNER_GUARD',
62
+ };
63
+ const CONFIG_KEY = {
64
+ sandbox: 'runtime_toggles.sandbox',
65
+ tce: 'runtime_toggles.tce',
66
+ browser_depth: 'runtime_toggles.browser_depth',
67
+ suggestions: 'runtime_toggles.suggestions',
68
+ planner_guard: 'runtime_toggles.planner_guard',
69
+ };
70
+ const ALL_KEYS = [
71
+ 'sandbox', 'tce', 'browser_depth', 'suggestions', 'planner_guard',
72
+ ];
73
+ /**
74
+ * v4.6 Phase 2M — per-key default. Pre-2M every toggle defaulted to
75
+ * `true` (sandbox/tce/browser-depth/suggestions all ship on); the
76
+ * `planner_guard` toggle is the first to default `false`, so the
77
+ * resolver needs a per-key default map rather than a hardcoded `true`.
78
+ *
79
+ * Smart models (GPT-5.5, Claude Sonnet 4.5+, Opus) pick from the
80
+ * full tool catalog without help — keyword-based narrowing is a
81
+ * legacy workaround for smaller local models, opt in when needed.
82
+ */
83
+ const DEFAULT_VALUE = {
84
+ sandbox: true,
85
+ tce: true,
86
+ browser_depth: true,
87
+ suggestions: true,
88
+ planner_guard: false,
89
+ };
90
+ // ── Resolver primitives ────────────────────────────────────────────────────
91
+ /**
92
+ * Strict env interpretation matching existing v4.2/v4.3/v4.4
93
+ * semantics: literal `'0'` (or `'false'` for forgiveness) means off;
94
+ * unset means defer to next leg; anything else means on. Returns
95
+ * `null` when the env var is unset / empty — caller falls through to
96
+ * config or default.
97
+ */
98
+ function readEnv(env, key) {
99
+ const raw = env[ENV_VAR[key]];
100
+ if (typeof raw !== 'string' || raw.length === 0)
101
+ return null;
102
+ const trimmed = raw.trim().toLowerCase();
103
+ if (trimmed === '0' || trimmed === 'false' || trimmed === 'off' || trimmed === 'no') {
104
+ return false;
105
+ }
106
+ return true;
107
+ }
108
+ function readConfig(cfg, key) {
109
+ if (!cfg)
110
+ return null;
111
+ const raw = cfg(CONFIG_KEY[key]);
112
+ if (raw === undefined || raw === null)
113
+ return null;
114
+ if (typeof raw === 'boolean')
115
+ return raw;
116
+ if (typeof raw === 'string') {
117
+ const t = raw.trim().toLowerCase();
118
+ if (t === 'true' || t === '1' || t === 'on' || t === 'yes')
119
+ return true;
120
+ if (t === 'false' || t === '0' || t === 'off' || t === 'no')
121
+ return false;
122
+ }
123
+ if (typeof raw === 'number')
124
+ return raw !== 0;
125
+ return null;
126
+ }
127
+ // ── Singleton ──────────────────────────────────────────────────────────────
128
+ let _singleton = null;
129
+ /**
130
+ * Build a RuntimeToggles instance bound to the supplied deps.
131
+ * Public so tests can construct isolated instances.
132
+ */
133
+ function buildRuntimeToggles(deps = {}) {
134
+ const env = deps.env ?? process.env;
135
+ // In-process overrides — set() updates this map; subsequent
136
+ // isEnabled() reads see the override before falling through to
137
+ // env/config/default.
138
+ const overrides = new Map();
139
+ const subscribers = new Map();
140
+ function resolve(key) {
141
+ // 1. env (Q-P8a-1a — explicit env always wins)
142
+ const envValue = readEnv(env, key);
143
+ if (envValue !== null)
144
+ return { value: envValue, source: 'env' };
145
+ // 2. in-process override (slash-command flip not yet persisted)
146
+ if (overrides.has(key))
147
+ return { value: overrides.get(key), source: 'config' };
148
+ // 3. config.yaml
149
+ const cfgValue = readConfig(deps.configRead, key);
150
+ if (cfgValue !== null)
151
+ return { value: cfgValue, source: 'config' };
152
+ // 4. default (v4.6 Phase 2M — per-key, see DEFAULT_VALUE)
153
+ return { value: DEFAULT_VALUE[key], source: 'default' };
154
+ }
155
+ function fire(key) {
156
+ const set = subscribers.get(key);
157
+ if (!set)
158
+ return;
159
+ for (const cb of set) {
160
+ try {
161
+ cb();
162
+ }
163
+ catch { /* never let an invalidation callback crash the flip */ }
164
+ }
165
+ }
166
+ return {
167
+ isEnabled(key) { return resolve(key).value; },
168
+ async set(key, value, opts = {}) {
169
+ overrides.set(key, value);
170
+ if (opts.persist !== false && deps.configWriteAndSave) {
171
+ await deps.configWriteAndSave(CONFIG_KEY[key], value);
172
+ }
173
+ fire(key);
174
+ },
175
+ snapshot() {
176
+ const out = {};
177
+ for (const k of ALL_KEYS)
178
+ out[k] = resolve(k);
179
+ return out;
180
+ },
181
+ onChange(key, cb) {
182
+ let set = subscribers.get(key);
183
+ if (!set) {
184
+ set = new Set();
185
+ subscribers.set(key, set);
186
+ }
187
+ set.add(cb);
188
+ },
189
+ };
190
+ }
191
+ /**
192
+ * Return the process-wide RuntimeToggles. When `initRuntimeToggles`
193
+ * hasn't been called, returns a env-only fallback resolver so core
194
+ * modules (sandboxConfig, turnState, browserState) keep working in
195
+ * test benches + core-only invocations.
196
+ */
197
+ function getRuntimeToggles() {
198
+ if (!_singleton)
199
+ _singleton = buildRuntimeToggles();
200
+ return _singleton;
201
+ }
202
+ /**
203
+ * Initialise the singleton with the CLI's ConfigManager seam. Called
204
+ * once by `aidenCLI.ts::buildAgentRuntime` after config.yaml is loaded.
205
+ */
206
+ function initRuntimeToggles(deps) {
207
+ _singleton = buildRuntimeToggles(deps);
208
+ return _singleton;
209
+ }
210
+ /** Test-only reset. */
211
+ function _resetRuntimeTogglesForTests() {
212
+ _singleton = null;
213
+ }
214
+ exports._TOGGLE_KEYS = ALL_KEYS;
@@ -0,0 +1,285 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.resolveRealPath = resolveRealPath;
7
+ exports._clearRealPathCacheForTests = _clearRealPathCacheForTests;
8
+ exports.inferDefaultRiskTier = inferDefaultRiskTier;
9
+ exports.readSandboxConfig = readSandboxConfig;
10
+ exports.getSandboxConfig = getSandboxConfig;
11
+ exports._resetSandboxConfigForTests = _resetSandboxConfigForTests;
12
+ /**
13
+ * Copyright (c) 2026 Shiva Deore (Taracod).
14
+ * Licensed under AGPL-3.0. See LICENSE for details.
15
+ *
16
+ * Aiden — local-first agent.
17
+ */
18
+ const runtimeToggles_1 = require("./runtimeToggles");
19
+ /**
20
+ * core/v4/sandboxConfig.ts — v4.4 Phase 1: Execution sandbox configuration.
21
+ *
22
+ * Single source of truth for sandbox enablement + resource policies +
23
+ * filesystem allow/deny lists + Docker hardening flags. Read from
24
+ * environment variables at construction time (matches v4.2's TurnState
25
+ * + v4.3's BrowserState env-driven pattern).
26
+ *
27
+ * Phase 1 ships the config types + reader + default-tier inference;
28
+ * downstream phases consume:
29
+ * - Phase 2 — fsAllowList / fsDenyList used by file_* tools
30
+ * - Phase 3 — defaultBackend / resourceLimits / networkMode /
31
+ * persistent / idleReaperMs used by shell_exec + Docker
32
+ * backend (long-lived container reuse, hardening flags)
33
+ * - Phase 4 — dryRun used by all `mutates: true` tools
34
+ * - Phase 5 — riskTier annotations consumed by FailureClassifier +
35
+ * ApprovalEngine (as a FLOOR; patterns can escalate)
36
+ *
37
+ * v4.4 Phase 6 — default-on transition. AIDEN_SANDBOX is now
38
+ * enabled by default; users opt out with `AIDEN_SANDBOX=0`. The
39
+ * strict `!== '0'` flip mirrors v4.2 Phase 6 (TCE) + v4.3 Phase 6
40
+ * (browser depth) semantics exactly.
41
+ *
42
+ * AIDEN_DRYRUN is orthogonal — independent flag, independent semantics.
43
+ * Phase 6 does NOT flip dry-run; it stays opt-in by design (dry-run
44
+ * is a deliberate "preview-only" mode users opt into, not a default).
45
+ *
46
+ * Pure module — no I/O, no side effects, no Playwright/Docker
47
+ * dependencies. Just env-var reads + path normalization helpers.
48
+ * Easy to unit test by passing a stubbed `env` argument.
49
+ */
50
+ const node_os_1 = __importDefault(require("node:os"));
51
+ const node_path_1 = __importDefault(require("node:path"));
52
+ const node_fs_1 = __importDefault(require("node:fs"));
53
+ // ── Defaults ────────────────────────────────────────────────────────────────
54
+ const DEFAULT_RESOURCE_LIMITS = {
55
+ memory: '1g',
56
+ cpus: '2',
57
+ pidsLimit: 256,
58
+ };
59
+ const DEFAULT_IDLE_REAPER_MS = 5 * 60 * 1000; // 5 minutes
60
+ const DEFAULT_IMAGE = 'node:22-alpine';
61
+ /**
62
+ * v4.4 Phase 2 — default write-permitted paths. Real-resolved at
63
+ * config-build time to handle symlinked HOME / cwd. Caller can
64
+ * extend via `AIDEN_SANDBOX_ALLOW=p1:p2:...`.
65
+ */
66
+ function buildDefaultAllowList() {
67
+ const home = node_os_1.default.homedir();
68
+ const tmp = node_os_1.default.tmpdir();
69
+ const cwd = process.cwd();
70
+ const paths = [
71
+ cwd,
72
+ node_path_1.default.join(home, 'Documents'),
73
+ node_path_1.default.join(home, 'Downloads'),
74
+ node_path_1.default.join(home, 'Desktop'),
75
+ tmp,
76
+ ];
77
+ return resolveRealPaths(paths);
78
+ }
79
+ /**
80
+ * v4.4 Phase 2 — default write-denied paths. Always wins over the
81
+ * allow list. Mirrors the consult-shaped deny-list pattern (sensitive
82
+ * configs, system dirs).
83
+ */
84
+ function buildDefaultDenyList() {
85
+ const home = node_os_1.default.homedir();
86
+ const paths = [
87
+ node_path_1.default.join(home, '.ssh'),
88
+ node_path_1.default.join(home, '.aws'),
89
+ node_path_1.default.join(home, '.gnupg'),
90
+ node_path_1.default.join(home, '.env'),
91
+ node_path_1.default.join(home, '.netrc'),
92
+ node_path_1.default.join(home, '.pgpass'),
93
+ node_path_1.default.join(home, '.npmrc'),
94
+ node_path_1.default.join(home, '.pypirc'),
95
+ node_path_1.default.join(home, '.bashrc'),
96
+ node_path_1.default.join(home, '.zshrc'),
97
+ node_path_1.default.join(home, '.profile'),
98
+ '/etc',
99
+ '/var',
100
+ '/usr',
101
+ '/boot',
102
+ '/sys',
103
+ '/proc',
104
+ ];
105
+ return resolveRealPaths(paths);
106
+ }
107
+ // ── Path normalization ──────────────────────────────────────────────────────
108
+ const _realPathCache = new Map();
109
+ /**
110
+ * Resolve a path to its canonical absolute form. `path.resolve` first
111
+ * (handles relative + `..`); then `fs.realpathSync` to follow symlinks.
112
+ * Symlink resolution defeats the bypass attack where an allowlisted
113
+ * directory contains a symlink to a denied path.
114
+ *
115
+ * Results cached for the lifetime of the module (paths rarely change
116
+ * during a process; the cache hit rate is high on the file-tool path
117
+ * where every call resolves the same handful of allowlist entries).
118
+ *
119
+ * Falls back gracefully when the path doesn't exist (returns the
120
+ * resolved-but-unrealpath form) — caller may be checking a path
121
+ * about to be created.
122
+ */
123
+ function resolveRealPath(input) {
124
+ if (_realPathCache.has(input))
125
+ return _realPathCache.get(input);
126
+ const resolved = node_path_1.default.resolve(input);
127
+ let real = resolved;
128
+ try {
129
+ real = node_fs_1.default.realpathSync.native ? node_fs_1.default.realpathSync.native(resolved) : node_fs_1.default.realpathSync(resolved);
130
+ }
131
+ catch {
132
+ // Path may not exist yet (e.g. file_write target). Use the
133
+ // resolved form; symlink-bypass on a non-existent path isn't
134
+ // a real attack vector.
135
+ }
136
+ _realPathCache.set(input, real);
137
+ return real;
138
+ }
139
+ /** Resolve an array of paths to their canonical forms; deduplicate. */
140
+ function resolveRealPaths(paths) {
141
+ const seen = new Set();
142
+ const out = [];
143
+ for (const p of paths) {
144
+ const real = resolveRealPath(p);
145
+ if (!seen.has(real)) {
146
+ seen.add(real);
147
+ out.push(real);
148
+ }
149
+ }
150
+ return out;
151
+ }
152
+ /** Public for tests — clears the realpath cache so env-var changes
153
+ * in test isolation pick up fresh resolutions. Production code never
154
+ * calls this. */
155
+ function _clearRealPathCacheForTests() {
156
+ _realPathCache.clear();
157
+ }
158
+ // ── Env-var parsing helpers ─────────────────────────────────────────────────
159
+ function parseList(raw) {
160
+ if (!raw)
161
+ return [];
162
+ return raw.split(':').map((s) => s.trim()).filter((s) => s.length > 0);
163
+ }
164
+ function parseIntSafe(raw, fallback) {
165
+ if (raw === undefined || raw === null || raw === '')
166
+ return fallback;
167
+ const n = Number.parseInt(raw, 10);
168
+ return Number.isFinite(n) && n > 0 ? n : fallback;
169
+ }
170
+ function parseNetworkMode(raw) {
171
+ if (raw === 'none')
172
+ return 'none';
173
+ return 'bridge'; // bridge for unset / 'bridge' / junk
174
+ }
175
+ // ── Risk-tier inference ─────────────────────────────────────────────────────
176
+ /**
177
+ * Default risk tier for a tool that doesn't carry an explicit
178
+ * `riskTier` annotation. Leverages the existing `mutates: boolean`
179
+ * field — mutating tools default to `caution`, read-only tools
180
+ * default to `safe`. Plugin tools without annotation get a safe
181
+ * default for free.
182
+ *
183
+ * Phase 5 ApprovalEngine integration treats explicit annotations as
184
+ * a FLOOR — DANGEROUS_PATTERNS can escalate but never demote. The
185
+ * inference here is the floor when no annotation exists at all.
186
+ */
187
+ function inferDefaultRiskTier(mutates) {
188
+ return mutates ? 'caution' : 'safe';
189
+ }
190
+ // ── Reader ──────────────────────────────────────────────────────────────────
191
+ /**
192
+ * Pure factory. Reads env vars + defaults into a frozen-snapshot
193
+ * SandboxConfig. Idempotent for a given env. The CLI calls this
194
+ * once at boot via the singleton factory below; tests pass a custom
195
+ * `env` argument.
196
+ */
197
+ function readSandboxConfig(env = process.env) {
198
+ // v4.4 Phase 6 — default-on transition. AIDEN_SANDBOX is now
199
+ // enabled by default; users opt out with `AIDEN_SANDBOX=0`.
200
+ //
201
+ // v4.5 Phase 8a — the actual read goes through the runtimeToggles
202
+ // singleton so /sandbox slash-command flips and config.yaml
203
+ // overrides take effect without restart. When the singleton was
204
+ // initialised by the CLI it consults env > config.yaml > default;
205
+ // when not initialised (test bench) it falls back to env > default
206
+ // — the existing semantics.
207
+ // unset / '1' / 'true' / junk → enabled
208
+ // '0' → disabled
209
+ // Mirrors v4.2 Phase 6 (TCE) + v4.3 Phase 6 (browser depth)
210
+ // semantics exactly. The strict `!== '0'` check matches the
211
+ // pattern those phases set: any explicit "off" value disables;
212
+ // everything else (including unset) enables.
213
+ // v4.5 Phase 8a — when env (the readSandboxConfig input) is not the
214
+ // process env (test bench passing a custom env), honour the input
215
+ // directly to keep test isolation. Otherwise route through the
216
+ // runtimeToggles singleton.
217
+ const enabled = env === process.env
218
+ ? (0, runtimeToggles_1.getRuntimeToggles)().isEnabled('sandbox')
219
+ : env.AIDEN_SANDBOX !== '0';
220
+ // Allow/deny lists: defaults + user-provided extensions.
221
+ const customAllow = parseList(env.AIDEN_SANDBOX_ALLOW).map(resolveRealPath);
222
+ const customDeny = parseList(env.AIDEN_SANDBOX_DENY).map(resolveRealPath);
223
+ const fsAllowList = Array.from(new Set([...buildDefaultAllowList(), ...customAllow]));
224
+ const fsDenyList = Array.from(new Set([...buildDefaultDenyList(), ...customDeny]));
225
+ // Resource limits — string values pass through Docker as-is.
226
+ const resourceLimits = {
227
+ memory: env.AIDEN_SANDBOX_MEMORY ?? DEFAULT_RESOURCE_LIMITS.memory,
228
+ cpus: env.AIDEN_SANDBOX_CPUS ?? DEFAULT_RESOURCE_LIMITS.cpus,
229
+ pidsLimit: parseIntSafe(env.AIDEN_SANDBOX_PIDS, DEFAULT_RESOURCE_LIMITS.pidsLimit),
230
+ };
231
+ const networkMode = parseNetworkMode(env.AIDEN_SANDBOX_NETWORK);
232
+ const persistent = env.AIDEN_SANDBOX_PERSISTENT !== '0'; // default true
233
+ const idleReaperMs = parseIntSafe(env.AIDEN_SANDBOX_IDLE_MS, DEFAULT_IDLE_REAPER_MS);
234
+ const dryRun = env.AIDEN_DRYRUN === '1';
235
+ const image = typeof env.AIDEN_SANDBOX_IMAGE === 'string' && env.AIDEN_SANDBOX_IMAGE.trim().length > 0
236
+ ? env.AIDEN_SANDBOX_IMAGE.trim()
237
+ : DEFAULT_IMAGE;
238
+ // Phase 3 will route to 'docker' when enabled AND docker is
239
+ // available. Phase 1 reports the abstract default — Phase 3's
240
+ // runtime probe decides the actual route.
241
+ const defaultBackend = enabled ? 'docker' : 'local';
242
+ return {
243
+ enabled,
244
+ fsAllowList,
245
+ fsDenyList,
246
+ defaultBackend,
247
+ persistent,
248
+ resourceLimits,
249
+ networkMode,
250
+ idleReaperMs,
251
+ dryRun,
252
+ image,
253
+ };
254
+ }
255
+ // ── Singleton ───────────────────────────────────────────────────────────────
256
+ let _singleton = null;
257
+ let _toggleHookInstalled = false;
258
+ /**
259
+ * Read the singleton sandbox config. Initialized on first call from
260
+ * `process.env` (matches v4.2 TurnState / v4.3 BrowserState lifecycle).
261
+ * Tests construct fresh configs via `readSandboxConfig(env)` directly
262
+ * — the singleton path is for production CLI startup.
263
+ */
264
+ function getSandboxConfig() {
265
+ if (!_toggleHookInstalled) {
266
+ // v4.5 Phase 8a — invalidate the singleton when /sandbox toggles.
267
+ // Registered lazily on first read so test benches that build
268
+ // their own runtimeToggles + reset between cases don't carry
269
+ // hooks across instances.
270
+ try {
271
+ (0, runtimeToggles_1.getRuntimeToggles)().onChange('sandbox', () => { _singleton = null; });
272
+ _toggleHookInstalled = true;
273
+ }
274
+ catch { /* runtimeToggles import / init race — best-effort */ }
275
+ }
276
+ if (!_singleton)
277
+ _singleton = readSandboxConfig();
278
+ return _singleton;
279
+ }
280
+ /** Reset the singleton for test isolation. Production code never calls this. */
281
+ function _resetSandboxConfigForTests() {
282
+ _singleton = null;
283
+ _toggleHookInstalled = false;
284
+ _clearRealPathCacheForTests();
285
+ }