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,98 @@
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/daemon/dispatcher/agentRunner.ts — v4.5 Phase 5a.
10
+ *
11
+ * The seam between the dispatcher's claim/markDone loop and an
12
+ * AidenAgent invocation. Keeps the dispatcher testable without
13
+ * dragging in the full agent / provider / tool stack.
14
+ *
15
+ * Two responsibilities:
16
+ * 1. Define the `DaemonAgentRunner` interface (one `invoke` method).
17
+ * 2. Define `DaemonAgentInput` / `DaemonAgentResult` so the
18
+ * dispatcher and bootstrap-side wiring agree on shape.
19
+ *
20
+ * Bootstrap is responsible for building a runner that holds the
21
+ * AidenAgent + provider + toolExecutor + plumbing. Tests pass a
22
+ * stub runner.
23
+ *
24
+ * Two pure helpers also live here:
25
+ * - `buildInitialHistory(input)` — turns a `DaemonAgentInput`
26
+ * into the `Message[]` the agent's `runConversation` expects.
27
+ * Used by both the real runner and integration tests that
28
+ * assert the dispatched message shape.
29
+ * - `deliverOnlyStub(input, runStore, instanceId)` — the
30
+ * deliver-only short-circuit. Q-P5-4(a): logs delivery via
31
+ * a run_event row and returns immediately without invoking
32
+ * the agent. Channel-adapter integration (Telegram, Discord,
33
+ * etc.) is deferred to a future phase.
34
+ *
35
+ * The runner returns the `runId` written into `runs`. The
36
+ * dispatcher passes that to `triggerBus.markDone(eventId,
37
+ * claimToken, runId)` so the link FK is populated.
38
+ */
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.buildInitialHistory = buildInitialHistory;
41
+ exports.deliverOnlyStub = deliverOnlyStub;
42
+ exports.makeRunner = makeRunner;
43
+ // ── Pure helpers ───────────────────────────────────────────────────────────
44
+ /**
45
+ * Construct the initial `Message[]` history fed to
46
+ * `AidenAgent.runConversation`. Single user message carrying
47
+ * the rendered initial message. The system prompt is layered
48
+ * on by the agent itself.
49
+ *
50
+ * Pure — no side effects, deterministic per input.
51
+ */
52
+ function buildInitialHistory(input) {
53
+ return [
54
+ {
55
+ role: 'user',
56
+ content: input.initialMessage,
57
+ },
58
+ ];
59
+ }
60
+ /**
61
+ * Q-P5-4(a) deliver-only short-circuit.
62
+ *
63
+ * Creates a `runs` row with status `completed`, emits a
64
+ * `delivered` run_event capturing the rendered message, and
65
+ * returns a `DaemonAgentResult` with finishReason='delivered'.
66
+ *
67
+ * Channel-adapter integration (Telegram / Discord / Slack
68
+ * webhook target / etc.) is deferred. The stub records that
69
+ * "delivery" happened (logs only) so operators can verify the
70
+ * code path via run_events without yet wiring a transport.
71
+ */
72
+ function deliverOnlyStub(input, runStore) {
73
+ const runId = runStore.create({
74
+ sessionId: input.sessionId,
75
+ instanceId: input.instanceId,
76
+ triggerEventId: input.triggerEventId,
77
+ status: 'running',
78
+ });
79
+ runStore.emitEvent(runId, 'delivered', {
80
+ source: input.triggerContext.source,
81
+ triggerId: input.triggerContext.triggerId,
82
+ eventId: input.triggerEventId,
83
+ messageBytes: input.initialMessage.length,
84
+ deliverOnly: true,
85
+ /* future: target channel, adapter, response */
86
+ });
87
+ runStore.setStatus(runId, 'completed', { finishReason: 'delivered' });
88
+ return { runId, finishReason: 'delivered' };
89
+ }
90
+ /**
91
+ * Wrap a function-shaped invocation into the `DaemonAgentRunner`
92
+ * interface. Convenience for tests + simple wiring.
93
+ *
94
+ * const runner = makeRunner(async (input) => ({ runId, finishReason: 'stop' }));
95
+ */
96
+ function makeRunner(invoke) {
97
+ return { invoke };
98
+ }
@@ -0,0 +1,127 @@
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/daemon/dispatcher/budgetGate.ts — v4.5 Phase 7.
10
+ *
11
+ * Two-layer cost guardrail for daemon-fired agent turns:
12
+ *
13
+ * 1. PER-TRIGGER (soft) — `spec.maxTokensPerFire`. Each turn
14
+ * tracks token usage via `AidenAgent.onBudgetWarning`. When
15
+ * crossed, the turn ABORTS via the wrap-controller's abort
16
+ * signal; finishReason becomes `budget_exhausted`. NO bus-
17
+ * level retry — that would just re-trigger the same blowup.
18
+ *
19
+ * 2. GLOBAL (hard) — `AIDEN_DAEMON_DAILY_BUDGET=<tokens>` env.
20
+ * Pre-flight check before invoking the agent at all. When
21
+ * hit, the dispatcher rejects new claims with a
22
+ * `trigger_quota` tag (classifier maps to that category;
23
+ * dispatcher dead-letters by retry-matrix).
24
+ *
25
+ * The gate exposes a single `evaluatePreTurn()` that combines
26
+ * both layers + returns a structured verdict. Callers wrap the
27
+ * agent loop in an `AbortController` whose `signal` the per-trigger
28
+ * watcher trips on threshold cross.
29
+ *
30
+ * Token accounting is best-effort — the agent's onToolCall +
31
+ * provider response token counts feed the watcher. Providers
32
+ * that don't surface usage (rare) get a zero-cost turn and
33
+ * neither cap fires. That's acceptable: the global daily budget
34
+ * is the strict safety net regardless.
35
+ */
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.evaluatePreTurn = evaluatePreTurn;
38
+ exports.consumePostTurn = consumePostTurn;
39
+ exports.createPerTurnBudgetWatcher = createPerTurnBudgetWatcher;
40
+ /**
41
+ * Pre-flight check before invoking the agent. When the daily
42
+ * budget is already exhausted, returns allowed=false WITHOUT
43
+ * consuming. When estimated tokens would push over the cap,
44
+ * also rejects pre-emptively. When the estimate fits (or
45
+ * unknown), allows — the per-trigger watcher catches in-flight
46
+ * overruns.
47
+ *
48
+ * Does NOT consume tokens itself. Token consumption happens
49
+ * mid/post-turn via `consumePostTurn()`.
50
+ */
51
+ function evaluatePreTurn(input) {
52
+ const snapshot = input.tracker.peek({ budget: input.dailyBudget, now: input.now });
53
+ if (snapshot.exhausted) {
54
+ return {
55
+ allowed: false,
56
+ daily: snapshot,
57
+ reason: `trigger_quota: daily_budget_exhausted (used=${snapshot.used}/${snapshot.budget})`,
58
+ };
59
+ }
60
+ if (input.estimatedTokens && input.estimatedTokens > 0 && snapshot.budget !== null && snapshot.budget > 0) {
61
+ if (snapshot.used + input.estimatedTokens > snapshot.budget) {
62
+ return {
63
+ allowed: false,
64
+ daily: snapshot,
65
+ reason: `trigger_quota: estimated ${input.estimatedTokens} tokens exceeds remaining ${snapshot.remaining}`,
66
+ };
67
+ }
68
+ }
69
+ return { allowed: true, daily: snapshot };
70
+ }
71
+ /**
72
+ * Post-turn consumption. Caller passes the ACTUAL tokens the
73
+ * agent reported (or 0 when the provider didn't surface usage).
74
+ * Returns the post-consume snapshot for the dispatcher:completed
75
+ * event payload. Never throws — over-budget consume is just
76
+ * recorded (the next pre-turn check will reject the next claim).
77
+ */
78
+ function consumePostTurn(input) {
79
+ if (input.actualTokens <= 0)
80
+ return input.tracker.peek({ budget: input.dailyBudget, now: input.now });
81
+ const r = input.tracker.addAndCheck(input.actualTokens, {
82
+ budget: input.dailyBudget,
83
+ now: input.now,
84
+ });
85
+ return r.snapshot;
86
+ }
87
+ function createPerTurnBudgetWatcher(opts) {
88
+ const limit = opts.maxTokensPerFire ?? null;
89
+ const ctl = new AbortController();
90
+ let used = 0;
91
+ let hit = false;
92
+ let reason = null;
93
+ function check() {
94
+ if (hit)
95
+ return;
96
+ if (limit === null || limit <= 0)
97
+ return;
98
+ if (used >= limit) {
99
+ hit = true;
100
+ reason = `per_trigger_budget_exhausted: ${used}/${limit}`;
101
+ try {
102
+ ctl.abort(reason);
103
+ }
104
+ catch { /* abort may throw on older Node; safe to swallow */ }
105
+ }
106
+ }
107
+ return {
108
+ tally(tokens) {
109
+ if (tokens > 0) {
110
+ used += tokens;
111
+ check();
112
+ }
113
+ },
114
+ used() { return used; },
115
+ hit() { return hit; },
116
+ reason() { return reason; },
117
+ signal: ctl.signal,
118
+ abort(r) {
119
+ hit = true;
120
+ reason = r ?? 'manual_abort';
121
+ try {
122
+ ctl.abort(reason);
123
+ }
124
+ catch { /* noop */ }
125
+ },
126
+ };
127
+ }
@@ -0,0 +1,113 @@
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/daemon/dispatcher/daemonApproval.ts — v4.5 Phase 7.
10
+ *
11
+ * Non-interactive approval callbacks for daemon-mode agent turns.
12
+ * Interactive `approvalEngine.promptUser` requires a TTY; daemon-
13
+ * fired turns have no user, so we hand the engine an auto-decider
14
+ * keyed on the per-trigger policy (Q-P7-1a default: 'safe-only').
15
+ *
16
+ * Decision table:
17
+ *
18
+ * policy safe caution dangerous
19
+ * --------------- ------ -------- ---------
20
+ * safe-only allow DENY DENY ← default
21
+ * caution-ok allow allow DENY
22
+ * dangerous-ok allow allow allow
23
+ *
24
+ * Every decision emits an `approval_decision` run_event with
25
+ * `{toolName, riskTier, decision, policy}` so the operator can
26
+ * audit what was let through and what was blocked.
27
+ *
28
+ * Denied tools surface to the agent as an `approval_denied`
29
+ * envelope; the daemon:dispatcher classifier maps these to
30
+ * `trigger_misconfigured` (already wired in failureClassifier.ts).
31
+ * Per-trigger override is loaded by the runner via
32
+ * `triggers.spec_json.daemonApproval` and passed to this builder.
33
+ */
34
+ Object.defineProperty(exports, "__esModule", { value: true });
35
+ exports.DEFAULT_DAEMON_APPROVAL_POLICY = void 0;
36
+ exports.buildDaemonApprovalCallbacks = buildDaemonApprovalCallbacks;
37
+ exports.decideForPolicy = decideForPolicy;
38
+ exports.isDaemonApprovalPolicy = isDaemonApprovalPolicy;
39
+ exports.DEFAULT_DAEMON_APPROVAL_POLICY = 'safe-only';
40
+ /**
41
+ * Build a non-interactive `ApprovalCallbacks` instance for use by
42
+ * a daemon-mode `ApprovalEngine`. Returns the same shape the
43
+ * interactive REPL builds — only `promptUser` is auto-deciding
44
+ * (instead of asking the user). `riskAssess` is intentionally
45
+ * absent: smart-mode auxiliary classifier doesn't fit a non-
46
+ * interactive turn (it'd burn an extra LLM call per tool and the
47
+ * tier table already encodes the policy).
48
+ */
49
+ function buildDaemonApprovalCallbacks(input) {
50
+ const policy = input.policy;
51
+ const log = input.log ?? (() => { });
52
+ return {
53
+ /**
54
+ * The agent loop only invokes `promptUser` when the engine
55
+ * can't auto-decide via mode + tier. Daemon callbacks NEVER
56
+ * defer to a human — every call resolves here synchronously
57
+ * (well, via Promise.resolve for shape parity).
58
+ */
59
+ promptUser: async (req) => {
60
+ const decision = decideForPolicy(policy, req.riskTier ?? 'caution');
61
+ return decision;
62
+ },
63
+ /**
64
+ * `onDecision` fires AFTER every decision (allow + deny). We
65
+ * use it as the emission hook for the `approval_decision`
66
+ * run_event so EVERY decision lands in the audit log — not
67
+ * just the deferred-to-promptUser ones.
68
+ */
69
+ onDecision: (req, decision) => {
70
+ try {
71
+ input.runStore.emitEvent(input.runId, 'approval_decision', {
72
+ toolName: req.toolName,
73
+ category: req.category,
74
+ riskTier: req.riskTier ?? 'caution',
75
+ reason: req.reason ?? null,
76
+ policy,
77
+ decision,
78
+ });
79
+ if (decision === 'deny') {
80
+ log('warn', `[daemon-approval] denied ${req.toolName} (tier=${req.riskTier ?? 'caution'} policy=${policy})`);
81
+ }
82
+ }
83
+ catch { /* never let logging crash the agent loop */ }
84
+ },
85
+ };
86
+ }
87
+ /**
88
+ * Pure decision function. Public for testing.
89
+ *
90
+ * safe-only → safe → allow_session, caution|dangerous → deny
91
+ * caution-ok → safe|caution → allow_session, dangerous → deny
92
+ * dangerous-ok → safe|caution|dangerous → allow_session
93
+ *
94
+ * Returns `allow_session` (not `allow_always`) so the agent's
95
+ * session-scoped allowlist doesn't grow unbounded across daemon
96
+ * turns of the same sessionId. The session-scoped allowlist DOES
97
+ * persist across multiple tool calls within ONE runConversation,
98
+ * which is the optimization we want: a webhook turn that touches
99
+ * `file_read` twice doesn't pay the auto-decide cost twice.
100
+ */
101
+ function decideForPolicy(policy, tier) {
102
+ if (tier === 'safe')
103
+ return 'allow_session';
104
+ if (tier === 'caution') {
105
+ return policy === 'safe-only' ? 'deny' : 'allow_session';
106
+ }
107
+ // dangerous
108
+ return policy === 'dangerous-ok' ? 'allow_session' : 'deny';
109
+ }
110
+ /** Type-guard for runtime spec validation. */
111
+ function isDaemonApprovalPolicy(s) {
112
+ return s === 'safe-only' || s === 'caution-ok' || s === 'dangerous-ok';
113
+ }
@@ -0,0 +1,120 @@
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/daemon/dispatcher/dailyBudgetTracker.ts — v4.5 Phase 7.
10
+ *
11
+ * Persistent daily-token-budget counter for daemon-mode agent
12
+ * turns. Defends against "I set up 50 triggers and the bill
13
+ * exploded" (Q-P7-4a).
14
+ *
15
+ * Storage: piggyback on the existing `idempotency_keys` table
16
+ * via `scope='daemon_budget'` rows — no schema bump. One row per
17
+ * UTC day, `key=<yyyy-mm-dd>`, `response_json` carries the
18
+ * running total. `status_code` unused (200). `expires_at` set 7
19
+ * days out so the L2 sweep cleans old rows automatically.
20
+ *
21
+ * Threading: synchronous SQL writes serialize the counter at
22
+ * the SQLite level. Concurrency-safe against multiple producer
23
+ * threads in the same process. Cross-process daemons would need
24
+ * a separate locking step (out of scope — single-daemon-per-host
25
+ * is the v4.5 invariant).
26
+ *
27
+ * Cap evaluation: `addAndCheck(tokens)` returns `{allowed,
28
+ * remaining, used}`. Allowed=false means EITHER the add would
29
+ * exceed the budget OR the budget is already exhausted. Callers
30
+ * (`budgetGate.ts`) handle the reject path.
31
+ */
32
+ Object.defineProperty(exports, "__esModule", { value: true });
33
+ exports.createDailyBudgetTracker = createDailyBudgetTracker;
34
+ exports.utcDateKey = utcDateKey;
35
+ const BUDGET_SCOPE = 'daemon_budget';
36
+ const ROW_TTL_DAYS = 7;
37
+ function createDailyBudgetTracker(opts) {
38
+ const db = opts.db;
39
+ const configuredBudget = opts.budget ?? null;
40
+ function readRow(date) {
41
+ const row = db.prepare(`SELECT response_json FROM idempotency_keys WHERE scope = ? AND key = ?`).get(BUDGET_SCOPE, date);
42
+ if (!row)
43
+ return 0;
44
+ try {
45
+ const parsed = JSON.parse(row.response_json);
46
+ return typeof parsed.used === 'number' ? parsed.used : 0;
47
+ }
48
+ catch {
49
+ return 0;
50
+ }
51
+ }
52
+ function writeRow(date, used, now) {
53
+ const expiresAt = now + ROW_TTL_DAYS * 24 * 60 * 60 * 1000;
54
+ db.prepare(`INSERT OR REPLACE INTO idempotency_keys
55
+ (scope, key, fingerprint, response_json, status_code, created_at, expires_at)
56
+ VALUES (?, ?, NULL, ?, 200, ?, ?)`).run(BUDGET_SCOPE, date, JSON.stringify({ used }), now, expiresAt);
57
+ }
58
+ function buildSnapshot(date, used, budget) {
59
+ return {
60
+ date,
61
+ used,
62
+ budget,
63
+ remaining: budget !== null && budget > 0 ? Math.max(0, budget - used) : Number.POSITIVE_INFINITY,
64
+ exhausted: budget !== null && budget > 0 && used >= budget,
65
+ };
66
+ }
67
+ return {
68
+ addAndCheck(tokens, opts2 = {}) {
69
+ const now = opts2.now ?? Date.now();
70
+ const date = utcDateKey(now);
71
+ const budget = opts2.budget !== undefined ? opts2.budget : configuredBudget;
72
+ const tx = db.transaction(() => {
73
+ const used = readRow(date);
74
+ // Refuse to consume when already at/over cap.
75
+ if (budget !== null && budget > 0 && used >= budget) {
76
+ return {
77
+ allowed: false,
78
+ snapshot: buildSnapshot(date, used, budget),
79
+ reason: `daily_budget_exhausted: ${used}/${budget} tokens used today (${date})`,
80
+ };
81
+ }
82
+ // Refuse to consume when this call would push over cap.
83
+ const next = used + Math.max(0, tokens);
84
+ if (budget !== null && budget > 0 && next > budget) {
85
+ return {
86
+ allowed: false,
87
+ snapshot: buildSnapshot(date, used, budget),
88
+ reason: `daily_budget_would_exceed: ${tokens} > remaining ${budget - used}`,
89
+ };
90
+ }
91
+ writeRow(date, next, now);
92
+ return {
93
+ allowed: true,
94
+ snapshot: buildSnapshot(date, next, budget),
95
+ };
96
+ });
97
+ return tx();
98
+ },
99
+ peek(opts2 = {}) {
100
+ const now = opts2.now ?? Date.now();
101
+ const date = utcDateKey(now);
102
+ const budget = opts2.budget !== undefined ? opts2.budget : configuredBudget;
103
+ const used = readRow(date);
104
+ return buildSnapshot(date, used, budget);
105
+ },
106
+ reset(opts2 = {}) {
107
+ const now = opts2.now ?? Date.now();
108
+ const date = utcDateKey(now);
109
+ writeRow(date, 0, now);
110
+ },
111
+ };
112
+ }
113
+ /** `2026-05-17` for the day midnight UTC of `nowMs`. Public for tests. */
114
+ function utcDateKey(nowMs) {
115
+ const d = new Date(nowMs);
116
+ const y = d.getUTCFullYear();
117
+ const m = String(d.getUTCMonth() + 1).padStart(2, '0');
118
+ const day = String(d.getUTCDate()).padStart(2, '0');
119
+ return `${y}-${m}-${day}`;
120
+ }