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.
- package/README.md +265 -847
- package/dist/api/server.js +32 -5
- package/dist/cli/v4/aidenCLI.js +536 -152
- package/dist/cli/v4/callbacks.js +170 -0
- package/dist/cli/v4/chatSession.js +245 -3
- package/dist/cli/v4/commands/_runtimeToggleHelpers.js +94 -0
- package/dist/cli/v4/commands/browserDepth.js +45 -0
- package/dist/cli/v4/commands/cron.js +264 -0
- package/dist/cli/v4/commands/daemon.js +541 -0
- package/dist/cli/v4/commands/daemonStatus.js +253 -0
- package/dist/cli/v4/commands/fanout.js +42 -59
- package/dist/cli/v4/commands/help.js +13 -0
- package/dist/cli/v4/commands/index.js +35 -1
- package/dist/cli/v4/commands/mcp.js +80 -54
- package/dist/cli/v4/commands/plannerGuard.js +53 -0
- package/dist/cli/v4/commands/recovery.js +122 -0
- package/dist/cli/v4/commands/runs.js +223 -0
- package/dist/cli/v4/commands/sandbox.js +48 -0
- package/dist/cli/v4/commands/spawnPause.js +93 -0
- package/dist/cli/v4/commands/suggestions.js +68 -0
- package/dist/cli/v4/commands/tce.js +41 -0
- package/dist/cli/v4/commands/trigger.js +378 -0
- package/dist/cli/v4/commands/update.js +95 -3
- package/dist/cli/v4/daemonAgentBuilder.js +145 -0
- package/dist/cli/v4/defaultSoul.js +1 -1
- package/dist/cli/v4/display/capabilityCard.js +26 -0
- package/dist/cli/v4/display.js +18 -8
- package/dist/cli/v4/replyRenderer.js +31 -23
- package/dist/cli/v4/updateBootPrompt.js +170 -0
- package/dist/core/playwrightBridge.js +129 -0
- package/dist/core/v4/aidenAgent.js +527 -5
- package/dist/core/v4/browserState.js +436 -0
- package/dist/core/v4/checkpoint.js +79 -0
- package/dist/core/v4/daemon/bootstrap.js +651 -0
- package/dist/core/v4/daemon/cleanShutdown.js +154 -0
- package/dist/core/v4/daemon/cron/cronBridge.js +126 -0
- package/dist/core/v4/daemon/cron/cronEmitter.js +173 -0
- package/dist/core/v4/daemon/cron/migration.js +199 -0
- package/dist/core/v4/daemon/cron/misfirePolicy.js +115 -0
- package/dist/core/v4/daemon/daemonConfig.js +90 -0
- package/dist/core/v4/daemon/db/connection.js +106 -0
- package/dist/core/v4/daemon/db/migrations.js +362 -0
- package/dist/core/v4/daemon/db/schema/v1.spec.js +18 -0
- package/dist/core/v4/daemon/dispatcher/agentRunner.js +98 -0
- package/dist/core/v4/daemon/dispatcher/budgetGate.js +127 -0
- package/dist/core/v4/daemon/dispatcher/daemonApproval.js +113 -0
- package/dist/core/v4/daemon/dispatcher/dailyBudgetTracker.js +120 -0
- package/dist/core/v4/daemon/dispatcher/dispatcher.js +389 -0
- package/dist/core/v4/daemon/dispatcher/fireRateLimiter.js +113 -0
- package/dist/core/v4/daemon/dispatcher/index.js +53 -0
- package/dist/core/v4/daemon/dispatcher/promptTemplate.js +95 -0
- package/dist/core/v4/daemon/dispatcher/realAgentRunner.js +356 -0
- package/dist/core/v4/daemon/dispatcher/resolveModel.js +93 -0
- package/dist/core/v4/daemon/dispatcher/sessionId.js +93 -0
- package/dist/core/v4/daemon/drain.js +156 -0
- package/dist/core/v4/daemon/eventLoopLag.js +73 -0
- package/dist/core/v4/daemon/health.js +159 -0
- package/dist/core/v4/daemon/idempotencyStore.js +204 -0
- package/dist/core/v4/daemon/index.js +179 -0
- package/dist/core/v4/daemon/instanceTracker.js +99 -0
- package/dist/core/v4/daemon/resourceRegistry.js +150 -0
- package/dist/core/v4/daemon/restartCode.js +32 -0
- package/dist/core/v4/daemon/restartFailureCounter.js +77 -0
- package/dist/core/v4/daemon/runStore.js +144 -0
- package/dist/core/v4/daemon/runtimeLock.js +167 -0
- package/dist/core/v4/daemon/signals.js +50 -0
- package/dist/core/v4/daemon/supervisor.js +272 -0
- package/dist/core/v4/daemon/triggerBus.js +279 -0
- package/dist/core/v4/daemon/triggers/email/allowlist.js +70 -0
- package/dist/core/v4/daemon/triggers/email/automatedSender.js +78 -0
- package/dist/core/v4/daemon/triggers/email/bodyExtractor.js +0 -0
- package/dist/core/v4/daemon/triggers/email/emailSeenStore.js +99 -0
- package/dist/core/v4/daemon/triggers/email/emailSpec.js +107 -0
- package/dist/core/v4/daemon/triggers/email/imapConnection.js +211 -0
- package/dist/core/v4/daemon/triggers/email/index.js +332 -0
- package/dist/core/v4/daemon/triggers/email/seenUids.js +60 -0
- package/dist/core/v4/daemon/triggers/fileObservationsStore.js +93 -0
- package/dist/core/v4/daemon/triggers/fileWatcher.js +253 -0
- package/dist/core/v4/daemon/triggers/fileWatcherSpec.js +88 -0
- package/dist/core/v4/daemon/triggers/fsIdentity.js +42 -0
- package/dist/core/v4/daemon/triggers/globMatcher.js +100 -0
- package/dist/core/v4/daemon/triggers/reconcile.js +206 -0
- package/dist/core/v4/daemon/triggers/settleStat.js +81 -0
- package/dist/core/v4/daemon/triggers/webhook.js +376 -0
- package/dist/core/v4/daemon/triggers/webhookDeliveriesStore.js +109 -0
- package/dist/core/v4/daemon/triggers/webhookIdempotency.js +72 -0
- package/dist/core/v4/daemon/triggers/webhookRateLimit.js +56 -0
- package/dist/core/v4/daemon/triggers/webhookSpec.js +76 -0
- package/dist/core/v4/daemon/triggers/webhookVerifier.js +128 -0
- package/dist/core/v4/daemon/types.js +15 -0
- package/dist/core/v4/dockerSession.js +461 -0
- package/dist/core/v4/dryRun.js +117 -0
- package/dist/core/v4/failureClassifier.js +779 -0
- package/dist/core/v4/providerFallback.js +35 -2
- package/dist/core/v4/recoveryReport.js +449 -0
- package/dist/core/v4/runtimeToggles.js +214 -0
- package/dist/core/v4/sandboxConfig.js +285 -0
- package/dist/core/v4/sandboxFs.js +316 -0
- package/dist/core/v4/selfimprovement/recoveryStore.js +307 -0
- package/dist/core/v4/selfimprovement/signatureBuilder.js +158 -0
- package/dist/core/v4/subagent/childBuilder.js +391 -0
- package/dist/core/v4/subagent/fanout.js +75 -51
- package/dist/core/v4/subagent/spawnPause.js +191 -0
- package/dist/core/v4/subagent/spawnSubAgent.js +310 -0
- package/dist/core/v4/suggestionCatalog.js +41 -0
- package/dist/core/v4/suggestionEngine.js +210 -0
- package/dist/core/v4/toolRegistry.js +37 -3
- package/dist/core/v4/turnState.js +587 -0
- package/dist/core/v4/update/checkUpdate.js +63 -3
- package/dist/core/v4/update/installMethodDetect.js +115 -0
- package/dist/core/v4/update/registryClient.js +121 -0
- package/dist/core/v4/update/skipState.js +75 -0
- package/dist/core/v4/verifier.js +448 -0
- package/dist/core/version.js +1 -1
- package/dist/moat/plannerGuard.js +29 -0
- package/dist/providers/v4/anthropicAdapter.js +31 -3
- package/dist/providers/v4/chatCompletionsAdapter.js +26 -3
- package/dist/providers/v4/codexResponsesAdapter.js +25 -2
- package/dist/providers/v4/ollamaPromptToolsAdapter.js +57 -2
- package/dist/tools/v4/browser/_observer.js +224 -0
- package/dist/tools/v4/browser/browserBlocker.js +396 -0
- package/dist/tools/v4/browser/browserClick.js +18 -1
- package/dist/tools/v4/browser/browserClose.js +18 -1
- package/dist/tools/v4/browser/browserExtract.js +5 -1
- package/dist/tools/v4/browser/browserFill.js +17 -1
- package/dist/tools/v4/browser/browserGetUrl.js +5 -1
- package/dist/tools/v4/browser/browserNavigate.js +16 -1
- package/dist/tools/v4/browser/browserScreenshot.js +5 -1
- package/dist/tools/v4/browser/browserScroll.js +18 -1
- package/dist/tools/v4/browser/browserType.js +17 -1
- package/dist/tools/v4/browser/captchaCheck.js +5 -1
- package/dist/tools/v4/executeCode.js +1 -0
- package/dist/tools/v4/files/fileCopy.js +56 -2
- package/dist/tools/v4/files/fileDelete.js +38 -1
- package/dist/tools/v4/files/fileList.js +12 -1
- package/dist/tools/v4/files/fileMove.js +59 -2
- package/dist/tools/v4/files/filePatch.js +43 -1
- package/dist/tools/v4/files/fileRead.js +12 -1
- package/dist/tools/v4/files/fileWrite.js +41 -1
- package/dist/tools/v4/index.js +88 -61
- package/dist/tools/v4/memory/memoryAdd.js +14 -0
- package/dist/tools/v4/memory/memoryRemove.js +14 -0
- package/dist/tools/v4/memory/memoryReplace.js +15 -0
- package/dist/tools/v4/memory/sessionSummary.js +12 -0
- package/dist/tools/v4/process/processKill.js +19 -0
- package/dist/tools/v4/process/processList.js +1 -0
- package/dist/tools/v4/process/processLogRead.js +1 -0
- package/dist/tools/v4/process/processSpawn.js +13 -0
- package/dist/tools/v4/process/processWait.js +1 -0
- package/dist/tools/v4/sessions/recallSession.js +1 -0
- package/dist/tools/v4/sessions/sessionList.js +1 -0
- package/dist/tools/v4/sessions/sessionSearch.js +1 -0
- package/dist/tools/v4/skills/lookupToolSchema.js +7 -0
- package/dist/tools/v4/skills/skillManage.js +13 -0
- package/dist/tools/v4/skills/skillView.js +1 -0
- package/dist/tools/v4/skills/skillsList.js +1 -0
- package/dist/tools/v4/subagent/spawnSubAgentTool.js +334 -0
- package/dist/tools/v4/subagent/subagentFanout.js +54 -1
- package/dist/tools/v4/system/aidenSelfUpdate.js +16 -0
- package/dist/tools/v4/system/appClose.js +13 -0
- package/dist/tools/v4/system/appInput.js +13 -0
- package/dist/tools/v4/system/appLaunch.js +13 -0
- package/dist/tools/v4/system/clipboardRead.js +1 -0
- package/dist/tools/v4/system/clipboardWrite.js +14 -0
- package/dist/tools/v4/system/mediaKey.js +12 -0
- package/dist/tools/v4/system/mediaSessions.js +1 -0
- package/dist/tools/v4/system/mediaTransport.js +13 -0
- package/dist/tools/v4/system/naturalEvents.js +1 -0
- package/dist/tools/v4/system/nowPlaying.js +1 -0
- package/dist/tools/v4/system/osProcessList.js +1 -0
- package/dist/tools/v4/system/screenshot.js +1 -0
- package/dist/tools/v4/system/systemInfo.js +1 -0
- package/dist/tools/v4/system/volumeSet.js +17 -0
- package/dist/tools/v4/terminal/shellExec.js +81 -9
- package/dist/tools/v4/web/deepResearch.js +1 -0
- package/dist/tools/v4/web/openUrl.js +1 -0
- package/dist/tools/v4/web/webFetch.js +1 -0
- package/dist/tools/v4/web/webPage.js +1 -0
- package/dist/tools/v4/web/webSearch.js +1 -0
- package/dist/tools/v4/web/youtubeSearch.js +1 -0
- package/package.json +13 -3
|
@@ -0,0 +1,356 @@
|
|
|
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/realAgentRunner.ts — v4.5 Phase 7.
|
|
10
|
+
*
|
|
11
|
+
* Replaces the Phase 5a placeholder runner with a real
|
|
12
|
+
* `AidenAgent.runConversation` invocation.
|
|
13
|
+
*
|
|
14
|
+
* Why this is a *factory* + *injectable agent builder* rather than
|
|
15
|
+
* a direct `new AidenAgent(...)` call: the daemon module sits
|
|
16
|
+
* BELOW the CLI in the import graph — pulling provider /
|
|
17
|
+
* toolExecutor / plannerGuard / honesty / skillTeacher / ...
|
|
18
|
+
* construction into `core/v4/daemon` would invert the dependency
|
|
19
|
+
* direction. Instead, the CLI (which already owns agent
|
|
20
|
+
* construction for the REPL) injects an `AgentBuilder` function
|
|
21
|
+
* the runner calls per turn. Tests pass stubs.
|
|
22
|
+
*
|
|
23
|
+
* Bootstrap wiring (in `bootstrap.ts`):
|
|
24
|
+
*
|
|
25
|
+
* - When AIDEN_DAEMON=1 AND an `agentBuilder` is provided →
|
|
26
|
+
* `createRealAgentRunner({ ..., agentBuilder })` is the
|
|
27
|
+
* dispatcher's runner factory.
|
|
28
|
+
* - When no builder is provided → falls back to the Phase 5a
|
|
29
|
+
* placeholder (still useful for rails-only integration tests
|
|
30
|
+
* and for environments where the user has no provider
|
|
31
|
+
* configured yet).
|
|
32
|
+
*
|
|
33
|
+
* Lifecycle per claim:
|
|
34
|
+
*
|
|
35
|
+
* 1. evaluatePreTurn — global daily budget check; reject with
|
|
36
|
+
* `trigger_quota` tag when exhausted.
|
|
37
|
+
* 2. resolveDaemonModel — trigger spec → env → persisted chain.
|
|
38
|
+
* 3. buildDaemonApprovalCallbacks — non-interactive auto-decide
|
|
39
|
+
* per Q-P7-1a policy.
|
|
40
|
+
* 4. createPerTurnBudgetWatcher — per-trigger soft cap; AbortSignal
|
|
41
|
+
* threads into the agent invocation.
|
|
42
|
+
* 5. runStore.create() → runId; emit `dispatcher:invoked` with
|
|
43
|
+
* sessionId, model, modelSource, policy, dailySnapshot.
|
|
44
|
+
* 6. Build initial history via buildInitialHistory(input).
|
|
45
|
+
* 7. agentBuilder({...}) → AidenAgent (caller-injected).
|
|
46
|
+
* 8. agent.runConversation(history) — major events emitted via
|
|
47
|
+
* onToolCall + onBudgetWarning hooks → run_events.
|
|
48
|
+
* 9. Post-turn: consumePostTurn updates daily tracker; emit
|
|
49
|
+
* `dispatcher:completed` with finishReason + totalTokens +
|
|
50
|
+
* classification + retry decision.
|
|
51
|
+
* 10. Map AidenAgentResult → DaemonAgentResult.
|
|
52
|
+
*
|
|
53
|
+
* Failure handling: any throw or `finishReason: 'error'` is
|
|
54
|
+
* surfaced via DaemonAgentResult — the dispatcher (caller) maps
|
|
55
|
+
* to triggerBus.markFailed / deadLetter per the retry matrix.
|
|
56
|
+
*/
|
|
57
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
58
|
+
exports.RETRY_DECISION = void 0;
|
|
59
|
+
exports.createRealAgentRunner = createRealAgentRunner;
|
|
60
|
+
exports.computeRetryCooldownMs = computeRetryCooldownMs;
|
|
61
|
+
const agentRunner_1 = require("./agentRunner");
|
|
62
|
+
const resolveModel_1 = require("./resolveModel");
|
|
63
|
+
const daemonApproval_1 = require("./daemonApproval");
|
|
64
|
+
const dailyBudgetTracker_1 = require("./dailyBudgetTracker");
|
|
65
|
+
const budgetGate_1 = require("./budgetGate");
|
|
66
|
+
// ── Implementation ─────────────────────────────────────────────────────────
|
|
67
|
+
const ENV_DAEMON_MODEL = 'AIDEN_DAEMON_MODEL';
|
|
68
|
+
const ENV_DAILY_BUDGET = 'AIDEN_DAEMON_DAILY_BUDGET';
|
|
69
|
+
function createRealAgentRunner(opts) {
|
|
70
|
+
const log = opts.log ?? (() => { });
|
|
71
|
+
const now = opts.now ?? Date.now;
|
|
72
|
+
const tracker = (0, dailyBudgetTracker_1.createDailyBudgetTracker)({
|
|
73
|
+
db: opts.db,
|
|
74
|
+
budget: opts.dailyBudget ?? readDailyBudgetFromEnv(),
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
async invoke(input) {
|
|
78
|
+
const dailyBudget = opts.dailyBudget ?? readDailyBudgetFromEnv();
|
|
79
|
+
// ── 1: pre-turn budget gate ────────────────────────────────────────
|
|
80
|
+
const verdict = (0, budgetGate_1.evaluatePreTurn)({ tracker, dailyBudget, now: now() });
|
|
81
|
+
if (!verdict.allowed) {
|
|
82
|
+
// Reject without invoking the agent. Surface as trigger_quota.
|
|
83
|
+
const runId = opts.runStore.create({
|
|
84
|
+
sessionId: input.sessionId,
|
|
85
|
+
instanceId: input.instanceId,
|
|
86
|
+
triggerEventId: input.triggerEventId,
|
|
87
|
+
status: 'running',
|
|
88
|
+
});
|
|
89
|
+
opts.runStore.emitEvent(runId, 'dispatcher:rejected', {
|
|
90
|
+
reason: verdict.reason ?? 'trigger_quota',
|
|
91
|
+
dailySnapshot: verdict.daily,
|
|
92
|
+
});
|
|
93
|
+
opts.runStore.setStatus(runId, 'failed', { finishReason: 'budget_exhausted' });
|
|
94
|
+
log('warn', `[real-runner] rejected eventId=${input.triggerEventId}: ${verdict.reason}`);
|
|
95
|
+
return {
|
|
96
|
+
runId,
|
|
97
|
+
finishReason: 'error',
|
|
98
|
+
error: verdict.reason ?? 'trigger_quota: daily budget exhausted',
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
// ── 2: resolve model from chain ───────────────────────────────────
|
|
102
|
+
const triggerSpec = readTriggerSpec(opts.db, input.triggerContext.triggerId);
|
|
103
|
+
const resolved = (0, resolveModel_1.resolveDaemonModel)({
|
|
104
|
+
triggerSpec: {
|
|
105
|
+
provider: triggerSpec?.provider ?? null,
|
|
106
|
+
model: triggerSpec?.model ?? null,
|
|
107
|
+
},
|
|
108
|
+
envOverride: process.env[ENV_DAEMON_MODEL],
|
|
109
|
+
persistedDefault: opts.persistedDefault ?? { provider: '', model: '' },
|
|
110
|
+
});
|
|
111
|
+
// ── 3: approval callbacks ─────────────────────────────────────────
|
|
112
|
+
const approvalPolicy = triggerSpec?.daemonApproval && (0, daemonApproval_1.isDaemonApprovalPolicy)(triggerSpec.daemonApproval)
|
|
113
|
+
? triggerSpec.daemonApproval
|
|
114
|
+
: daemonApproval_1.DEFAULT_DAEMON_APPROVAL_POLICY;
|
|
115
|
+
// ── 4: per-turn budget watcher ────────────────────────────────────
|
|
116
|
+
const perTurnWatcher = (0, budgetGate_1.createPerTurnBudgetWatcher)({
|
|
117
|
+
maxTokensPerFire: triggerSpec?.maxTokensPerFire ?? null,
|
|
118
|
+
});
|
|
119
|
+
// ── 5: run row + dispatcher:invoked event ─────────────────────────
|
|
120
|
+
const runId = opts.runStore.create({
|
|
121
|
+
sessionId: input.sessionId,
|
|
122
|
+
instanceId: input.instanceId,
|
|
123
|
+
triggerEventId: input.triggerEventId,
|
|
124
|
+
status: 'running',
|
|
125
|
+
});
|
|
126
|
+
opts.runStore.emitEvent(runId, 'dispatcher:invoked', {
|
|
127
|
+
source: input.triggerContext.source,
|
|
128
|
+
triggerId: input.triggerContext.triggerId,
|
|
129
|
+
eventId: input.triggerEventId,
|
|
130
|
+
sessionId: input.sessionId,
|
|
131
|
+
templated: input.triggerContext.promptTemplate !== null,
|
|
132
|
+
messageLen: input.initialMessage.length,
|
|
133
|
+
attempt: input.triggerContext.attempt,
|
|
134
|
+
maxAttempts: input.triggerContext.maxAttempts,
|
|
135
|
+
model: resolved.model,
|
|
136
|
+
provider: resolved.provider,
|
|
137
|
+
modelSource: resolved.source,
|
|
138
|
+
approvalPolicy,
|
|
139
|
+
dailySnapshot: verdict.daily,
|
|
140
|
+
maxTokensPerFire: triggerSpec?.maxTokensPerFire ?? null,
|
|
141
|
+
});
|
|
142
|
+
const approvalCallbacks = (0, daemonApproval_1.buildDaemonApprovalCallbacks)({
|
|
143
|
+
policy: approvalPolicy,
|
|
144
|
+
runStore: opts.runStore,
|
|
145
|
+
runId,
|
|
146
|
+
log: (lvl, msg) => log(lvl, msg),
|
|
147
|
+
});
|
|
148
|
+
// ── 6: initial history ────────────────────────────────────────────
|
|
149
|
+
const history = (0, agentRunner_1.buildInitialHistory)(input);
|
|
150
|
+
// ── 7: build agent via injected factory ───────────────────────────
|
|
151
|
+
let agent;
|
|
152
|
+
const startedAt = now();
|
|
153
|
+
try {
|
|
154
|
+
agent = await opts.agentBuilder({
|
|
155
|
+
sessionId: input.sessionId,
|
|
156
|
+
resolvedModel: resolved,
|
|
157
|
+
approvalPolicy,
|
|
158
|
+
approvalCallbacks,
|
|
159
|
+
hooks: {
|
|
160
|
+
onToolCall: (call, phase, result) => emitToolEvent(opts.runStore, runId, call, phase, result, startedAt, now),
|
|
161
|
+
onBudgetWarning: (level, turn, max) => {
|
|
162
|
+
opts.runStore.emitEvent(runId, 'budget_warning', { level, turn, max });
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
abortSignal: perTurnWatcher.signal,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
catch (e) {
|
|
169
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
170
|
+
log('error', `[real-runner] agentBuilder threw eventId=${input.triggerEventId}: ${msg}`);
|
|
171
|
+
opts.runStore.emitEvent(runId, 'dispatcher:builder_failed', { error: msg });
|
|
172
|
+
opts.runStore.setStatus(runId, 'failed', { finishReason: 'error' });
|
|
173
|
+
return { runId, finishReason: 'error', error: msg };
|
|
174
|
+
}
|
|
175
|
+
// ── 8: invoke runConversation ─────────────────────────────────────
|
|
176
|
+
let result = null;
|
|
177
|
+
let invocationError = null;
|
|
178
|
+
try {
|
|
179
|
+
result = await agent.runConversation(history, {
|
|
180
|
+
// The agent honours its own abort signal via per-tool aborts;
|
|
181
|
+
// tools that respect AbortSignal (shell_exec, fetch_*) will
|
|
182
|
+
// bail when perTurnWatcher trips.
|
|
183
|
+
//
|
|
184
|
+
// Note: runConversation doesn't currently take an abort
|
|
185
|
+
// signal in its options — the budget watcher is best-effort
|
|
186
|
+
// observability via tally(). Future enhancement: thread the
|
|
187
|
+
// signal into the loop body via options.
|
|
188
|
+
});
|
|
189
|
+
// Stamp the actual token usage onto the watcher for the
|
|
190
|
+
// post-turn snapshot below.
|
|
191
|
+
const tokens = extractTokens(result);
|
|
192
|
+
if (tokens > 0)
|
|
193
|
+
perTurnWatcher.tally(tokens);
|
|
194
|
+
}
|
|
195
|
+
catch (e) {
|
|
196
|
+
invocationError = e instanceof Error ? (e.stack ?? e.message) : String(e);
|
|
197
|
+
log('error', `[real-runner] runConversation threw eventId=${input.triggerEventId}: ${invocationError.slice(0, 500)}`);
|
|
198
|
+
}
|
|
199
|
+
// ── 9: post-turn budget consume + dispatcher:completed ─────────────
|
|
200
|
+
const finalSnapshot = (0, budgetGate_1.consumePostTurn)({
|
|
201
|
+
tracker,
|
|
202
|
+
actualTokens: perTurnWatcher.used(),
|
|
203
|
+
dailyBudget,
|
|
204
|
+
now: now(),
|
|
205
|
+
});
|
|
206
|
+
const finishReason = pickFinishReason(result, invocationError, perTurnWatcher.hit());
|
|
207
|
+
opts.runStore.emitEvent(runId, 'dispatcher:completed', {
|
|
208
|
+
finishReason,
|
|
209
|
+
totalTokens: perTurnWatcher.used(),
|
|
210
|
+
durationMs: now() - startedAt,
|
|
211
|
+
dailySnapshot: finalSnapshot,
|
|
212
|
+
perTurnBudgetHit: perTurnWatcher.hit(),
|
|
213
|
+
perTurnReason: perTurnWatcher.reason(),
|
|
214
|
+
invocationError: invocationError ? invocationError.slice(0, 200) : null,
|
|
215
|
+
});
|
|
216
|
+
// ── 10: map result → DaemonAgentResult ────────────────────────────
|
|
217
|
+
const runStatus = finishReason === 'stop' ? 'completed' :
|
|
218
|
+
finishReason === 'budget_exhausted' ? 'failed' :
|
|
219
|
+
finishReason === 'error' ? 'failed' :
|
|
220
|
+
finishReason === 'tool_loop' ? 'failed' : 'completed';
|
|
221
|
+
opts.runStore.setStatus(runId, runStatus, { finishReason });
|
|
222
|
+
return {
|
|
223
|
+
runId,
|
|
224
|
+
finishReason,
|
|
225
|
+
totalTokens: perTurnWatcher.used() > 0 ? perTurnWatcher.used() : undefined,
|
|
226
|
+
error: invocationError ?? (perTurnWatcher.hit() ? perTurnWatcher.reason() ?? undefined : undefined),
|
|
227
|
+
};
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
232
|
+
/** Read the trigger spec row + extract Phase 7 spec fields. */
|
|
233
|
+
function readTriggerSpec(db, triggerId) {
|
|
234
|
+
try {
|
|
235
|
+
const row = db.prepare(`SELECT spec_json FROM triggers WHERE id = ?`).get(triggerId);
|
|
236
|
+
if (!row)
|
|
237
|
+
return null;
|
|
238
|
+
const parsed = JSON.parse(row.spec_json);
|
|
239
|
+
return {
|
|
240
|
+
provider: typeof parsed.provider === 'string' ? parsed.provider : undefined,
|
|
241
|
+
model: typeof parsed.model === 'string' ? parsed.model : undefined,
|
|
242
|
+
daemonApproval: typeof parsed.daemonApproval === 'string' ? parsed.daemonApproval : undefined,
|
|
243
|
+
maxTokensPerFire: typeof parsed.maxTokensPerFire === 'number' ? parsed.maxTokensPerFire : undefined,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/** Read AIDEN_DAEMON_DAILY_BUDGET, parse as positive integer; null otherwise. */
|
|
251
|
+
function readDailyBudgetFromEnv() {
|
|
252
|
+
const raw = process.env[ENV_DAILY_BUDGET];
|
|
253
|
+
if (!raw)
|
|
254
|
+
return null;
|
|
255
|
+
const n = Number.parseInt(raw, 10);
|
|
256
|
+
return Number.isFinite(n) && n > 0 ? n : null;
|
|
257
|
+
}
|
|
258
|
+
/** Major-events run_event emitter for tool calls. Truncated payload. */
|
|
259
|
+
function emitToolEvent(runStore, runId, call, phase, result, startedAt, now) {
|
|
260
|
+
try {
|
|
261
|
+
if (phase === 'before') {
|
|
262
|
+
const argsSummary = safeShortJson(call.arguments, 200);
|
|
263
|
+
runStore.emitEvent(runId, 'tool_call_started', {
|
|
264
|
+
toolName: call.name,
|
|
265
|
+
args: argsSummary,
|
|
266
|
+
ts: now(),
|
|
267
|
+
});
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
runStore.emitEvent(runId, 'tool_call_completed', {
|
|
271
|
+
toolName: call.name,
|
|
272
|
+
error: result?.error ?? null,
|
|
273
|
+
hasResult: result?.result !== undefined && result?.result !== null,
|
|
274
|
+
durationMs: now() - startedAt,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
catch { /* never let observability crash the agent loop */ }
|
|
278
|
+
}
|
|
279
|
+
function safeShortJson(value, maxBytes) {
|
|
280
|
+
try {
|
|
281
|
+
const s = JSON.stringify(value);
|
|
282
|
+
return s.length > maxBytes ? s.slice(0, maxBytes) + '…' : s;
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
return String(value).slice(0, maxBytes);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Pull the total-tokens count off an AidenAgentResult. The agent
|
|
290
|
+
* exposes per-turn token usage via its result's `usage` field
|
|
291
|
+
* (mirrors provider conventions). Falls back to 0 when missing.
|
|
292
|
+
*/
|
|
293
|
+
function extractTokens(result) {
|
|
294
|
+
if (!result)
|
|
295
|
+
return 0;
|
|
296
|
+
const r = result;
|
|
297
|
+
return r.usage?.totalTokens ?? r.usage?.total ?? 0;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Map the agent's finishReason + invocation outcome → the
|
|
301
|
+
* DaemonAgentResult finishReason vocab the dispatcher expects.
|
|
302
|
+
*/
|
|
303
|
+
function pickFinishReason(result, invocationError, perTurnHit) {
|
|
304
|
+
if (invocationError)
|
|
305
|
+
return 'error';
|
|
306
|
+
if (perTurnHit)
|
|
307
|
+
return 'budget_exhausted';
|
|
308
|
+
if (!result)
|
|
309
|
+
return 'error';
|
|
310
|
+
const fr = result.finishReason;
|
|
311
|
+
if (fr === 'stop')
|
|
312
|
+
return 'stop';
|
|
313
|
+
if (fr === 'tool_loop')
|
|
314
|
+
return 'tool_loop';
|
|
315
|
+
if (fr === 'budget_exhausted')
|
|
316
|
+
return 'budget_exhausted';
|
|
317
|
+
if (fr === 'error')
|
|
318
|
+
return 'error';
|
|
319
|
+
// Default conservative — the dispatcher will markDone unless we
|
|
320
|
+
// say error; treat unknown finishes as success for forward-compat.
|
|
321
|
+
return 'stop';
|
|
322
|
+
}
|
|
323
|
+
exports.RETRY_DECISION = Object.freeze({
|
|
324
|
+
// Transient — retry with backoff
|
|
325
|
+
timeout: 'retry',
|
|
326
|
+
network: 'retry',
|
|
327
|
+
rate_limit: 'retry',
|
|
328
|
+
dependency_missing: 'retry',
|
|
329
|
+
hallucination: 'retry',
|
|
330
|
+
stale_ref: 'retry',
|
|
331
|
+
// Permanent — dead-letter immediately
|
|
332
|
+
auth: 'dead_letter',
|
|
333
|
+
permission: 'dead_letter',
|
|
334
|
+
sandbox_violation: 'dead_letter',
|
|
335
|
+
manual_blocker: 'dead_letter',
|
|
336
|
+
trigger_misconfigured: 'dead_letter',
|
|
337
|
+
trigger_quota: 'dead_letter',
|
|
338
|
+
trigger_dead_lettered: 'dead_letter',
|
|
339
|
+
invalid_input: 'dead_letter',
|
|
340
|
+
not_found: 'dead_letter',
|
|
341
|
+
other: 'dead_letter',
|
|
342
|
+
});
|
|
343
|
+
/**
|
|
344
|
+
* Compute the cooldown to wait before re-claiming a transient
|
|
345
|
+
* failure. Formula: `min(2^attempts * 1000, 60000)` ms.
|
|
346
|
+
* Exposed as a pure function so tests can assert the schedule:
|
|
347
|
+
*
|
|
348
|
+
* attempts=1 → 2_000 ms
|
|
349
|
+
* attempts=2 → 4_000 ms
|
|
350
|
+
* attempts=3 → 8_000 ms
|
|
351
|
+
* attempts=6 → 60_000 ms (capped)
|
|
352
|
+
*/
|
|
353
|
+
function computeRetryCooldownMs(attempts) {
|
|
354
|
+
const expo = Math.pow(2, Math.max(1, attempts)) * 1000;
|
|
355
|
+
return Math.min(expo, 60000);
|
|
356
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
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/resolveModel.ts — v4.5 Phase 7.
|
|
10
|
+
*
|
|
11
|
+
* Per-trigger model selection chain (Q-P7-2a):
|
|
12
|
+
*
|
|
13
|
+
* 1. Trigger spec — `triggers.spec_json.provider/model` if either
|
|
14
|
+
* is non-empty. Per-trigger configuration is the whole point
|
|
15
|
+
* of trigger registration.
|
|
16
|
+
* 2. Environment override — `AIDEN_DAEMON_MODEL` env var encoded
|
|
17
|
+
* as `<provider>/<model>` (e.g. "ollama/llama3.2:latest").
|
|
18
|
+
* For operators who want one model across every trigger.
|
|
19
|
+
* 3. Persisted user default — last interactive-REPL choice loaded
|
|
20
|
+
* via the caller-provided `persistedDefault` argument.
|
|
21
|
+
*
|
|
22
|
+
* The `source` field on the result is captured in the
|
|
23
|
+
* `dispatcher:invoked` run_event so operators can audit which leg
|
|
24
|
+
* of the chain won for a given turn.
|
|
25
|
+
*
|
|
26
|
+
* Pure module — no I/O, no fs reads. Caller is responsible for
|
|
27
|
+
* loading the persisted default (a separate concern from the
|
|
28
|
+
* chain logic).
|
|
29
|
+
*/
|
|
30
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
+
exports.resolveDaemonModel = resolveDaemonModel;
|
|
32
|
+
/**
|
|
33
|
+
* Resolve the (provider, model) pair to use for a daemon-fired
|
|
34
|
+
* agent turn. Deterministic per input.
|
|
35
|
+
*
|
|
36
|
+
* Edge cases:
|
|
37
|
+
* - Both spec fields set → trigger wins (source='trigger').
|
|
38
|
+
* - Only spec.provider set → trigger.provider + env/persisted.model
|
|
39
|
+
* (still source='trigger' because at least one piece came from
|
|
40
|
+
* the trigger).
|
|
41
|
+
* - Only spec.model set → ditto.
|
|
42
|
+
* - Neither spec field set → fall through to env / persisted.
|
|
43
|
+
* - Env value present but malformed (no slash) → ignored; falls
|
|
44
|
+
* through to persisted.
|
|
45
|
+
*/
|
|
46
|
+
function resolveDaemonModel(input) {
|
|
47
|
+
const spec = input.triggerSpec ?? {};
|
|
48
|
+
const env = parseEnvOverride(input.envOverride);
|
|
49
|
+
const specProvider = nonEmpty(spec.provider);
|
|
50
|
+
const specModel = nonEmpty(spec.model);
|
|
51
|
+
// Trigger wins if it contributes anything.
|
|
52
|
+
if (specProvider || specModel) {
|
|
53
|
+
return {
|
|
54
|
+
provider: specProvider ?? env?.provider ?? input.persistedDefault.provider,
|
|
55
|
+
model: specModel ?? env?.model ?? input.persistedDefault.model,
|
|
56
|
+
source: 'trigger',
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// Env wins next.
|
|
60
|
+
if (env) {
|
|
61
|
+
return {
|
|
62
|
+
provider: env.provider,
|
|
63
|
+
model: env.model,
|
|
64
|
+
source: 'env',
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
// Fallback: persisted user default.
|
|
68
|
+
return {
|
|
69
|
+
provider: input.persistedDefault.provider,
|
|
70
|
+
model: input.persistedDefault.model,
|
|
71
|
+
source: 'persisted',
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** Parse the `AIDEN_DAEMON_MODEL` env value. Returns null on malformed input. */
|
|
75
|
+
function parseEnvOverride(raw) {
|
|
76
|
+
if (typeof raw !== 'string' || raw.length === 0)
|
|
77
|
+
return null;
|
|
78
|
+
const slash = raw.indexOf('/');
|
|
79
|
+
// Require both halves non-empty: "<provider>/<model>".
|
|
80
|
+
if (slash <= 0 || slash >= raw.length - 1)
|
|
81
|
+
return null;
|
|
82
|
+
const provider = raw.slice(0, slash).trim();
|
|
83
|
+
const model = raw.slice(slash + 1).trim();
|
|
84
|
+
if (provider.length === 0 || model.length === 0)
|
|
85
|
+
return null;
|
|
86
|
+
return { provider, model };
|
|
87
|
+
}
|
|
88
|
+
function nonEmpty(v) {
|
|
89
|
+
if (typeof v !== 'string')
|
|
90
|
+
return undefined;
|
|
91
|
+
const t = v.trim();
|
|
92
|
+
return t.length > 0 ? t : undefined;
|
|
93
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
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/sessionId.ts — v4.5 Phase 5a.
|
|
10
|
+
*
|
|
11
|
+
* Per-trigger sessionId derivation. Stable across retries — the
|
|
12
|
+
* same trigger event (same idempotencyKey) always produces the
|
|
13
|
+
* same sessionId, so:
|
|
14
|
+
*
|
|
15
|
+
* - v4.4 docker session cache reuses one container per trigger
|
|
16
|
+
* across retry attempts (cold-start cost amortised).
|
|
17
|
+
* - v4.3 browser observer keeps page state observable for the
|
|
18
|
+
* same trigger across re-attempts.
|
|
19
|
+
* - v4.2 TurnState tracks repeated tool calls within a
|
|
20
|
+
* single retry session (still fresh per turn — TurnState
|
|
21
|
+
* lives per `runConversation`, not per sessionId).
|
|
22
|
+
*
|
|
23
|
+
* Format: `trigger:<source>:<sourceKey>:<idemHash>` where
|
|
24
|
+
* - source: TriggerSource literal ('file' / 'webhook' / 'email'
|
|
25
|
+
* / 'schedule' / 'manual')
|
|
26
|
+
* - sourceKey: the trigger spec id (FK to triggers.id)
|
|
27
|
+
* - idemHash: base64url(sha256(idempotencyKey)).slice(0,12)
|
|
28
|
+
*
|
|
29
|
+
* 12-char b64url prefix gives ~72 bits — far more than enough to
|
|
30
|
+
* keep distinct triggers separate without bloating the sessionId
|
|
31
|
+
* the docker cache / runStore have to carry around.
|
|
32
|
+
*
|
|
33
|
+
* `idempotencyKey === null` falls back to the literal `no-idem`
|
|
34
|
+
* sentinel — keeps the sessionId stable for sources that don't
|
|
35
|
+
* dedup (rare; mostly schedule).
|
|
36
|
+
*/
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
exports.buildTriggerSessionId = buildTriggerSessionId;
|
|
39
|
+
exports.parseTriggerSessionId = parseTriggerSessionId;
|
|
40
|
+
const node_crypto_1 = require("node:crypto");
|
|
41
|
+
const NO_IDEM_SENTINEL = 'no-idem';
|
|
42
|
+
/**
|
|
43
|
+
* Build a stable per-trigger sessionId.
|
|
44
|
+
*
|
|
45
|
+
* Deterministic: same input → same output. Tested for stability
|
|
46
|
+
* across retries (the trigger bus reclaims its existing event row;
|
|
47
|
+
* the dispatcher hands the same triple to this function on every
|
|
48
|
+
* attempt → same sessionId → same docker container / browser tab
|
|
49
|
+
* reused).
|
|
50
|
+
*/
|
|
51
|
+
function buildTriggerSessionId(input) {
|
|
52
|
+
const idem = input.idempotencyKey ?? NO_IDEM_SENTINEL;
|
|
53
|
+
const hash = sha256B64url(idem).slice(0, 12);
|
|
54
|
+
return `trigger:${input.source}:${input.sourceKey}:${hash}`;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Inverse-ish parser. Returns the structural pieces of a sessionId
|
|
58
|
+
* built by `buildTriggerSessionId`. Returns `null` for sessionIds
|
|
59
|
+
* not built by this helper (interactive REPL sessions, plain
|
|
60
|
+
* UUIDs, etc.).
|
|
61
|
+
*
|
|
62
|
+
* Used by recoveryReport to detect daemon-triggered runs and
|
|
63
|
+
* surface the `triggerContext` pill.
|
|
64
|
+
*/
|
|
65
|
+
function parseTriggerSessionId(sessionId) {
|
|
66
|
+
if (!sessionId.startsWith('trigger:'))
|
|
67
|
+
return null;
|
|
68
|
+
const parts = sessionId.split(':');
|
|
69
|
+
// ['trigger', source, sourceKey, idemHash]
|
|
70
|
+
if (parts.length !== 4)
|
|
71
|
+
return null;
|
|
72
|
+
const [, source, sourceKey, idemHash] = parts;
|
|
73
|
+
if (!isTriggerSource(source))
|
|
74
|
+
return null;
|
|
75
|
+
if (!sourceKey || sourceKey.length === 0)
|
|
76
|
+
return null;
|
|
77
|
+
if (!idemHash || idemHash.length === 0)
|
|
78
|
+
return null;
|
|
79
|
+
return { source, sourceKey, idemHash };
|
|
80
|
+
}
|
|
81
|
+
/** Type-guard for TriggerSource literals. */
|
|
82
|
+
function isTriggerSource(s) {
|
|
83
|
+
return s === 'file' || s === 'webhook' || s === 'email'
|
|
84
|
+
|| s === 'schedule' || s === 'manual';
|
|
85
|
+
}
|
|
86
|
+
function sha256B64url(input) {
|
|
87
|
+
return (0, node_crypto_1.createHash)('sha256')
|
|
88
|
+
.update(input)
|
|
89
|
+
.digest('base64')
|
|
90
|
+
.replace(/\+/g, '-')
|
|
91
|
+
.replace(/\//g, '_')
|
|
92
|
+
.replace(/=+$/, '');
|
|
93
|
+
}
|