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
|
@@ -40,8 +40,73 @@
|
|
|
40
40
|
* `urlProvenance.ts`, `intentPreArm.ts`. Those modules predate this rewrite
|
|
41
41
|
* and stay as-is.
|
|
42
42
|
*/
|
|
43
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
44
|
+
if (k2 === undefined) k2 = k;
|
|
45
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
46
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
47
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
48
|
+
}
|
|
49
|
+
Object.defineProperty(o, k2, desc);
|
|
50
|
+
}) : (function(o, m, k, k2) {
|
|
51
|
+
if (k2 === undefined) k2 = k;
|
|
52
|
+
o[k2] = m[k];
|
|
53
|
+
}));
|
|
54
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
55
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
56
|
+
}) : function(o, v) {
|
|
57
|
+
o["default"] = v;
|
|
58
|
+
});
|
|
59
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
60
|
+
var ownKeys = function(o) {
|
|
61
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
62
|
+
var ar = [];
|
|
63
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
64
|
+
return ar;
|
|
65
|
+
};
|
|
66
|
+
return ownKeys(o);
|
|
67
|
+
};
|
|
68
|
+
return function (mod) {
|
|
69
|
+
if (mod && mod.__esModule) return mod;
|
|
70
|
+
var result = {};
|
|
71
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
72
|
+
__setModuleDefault(result, mod);
|
|
73
|
+
return result;
|
|
74
|
+
};
|
|
75
|
+
})();
|
|
43
76
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
77
|
exports.AidenAgent = void 0;
|
|
78
|
+
// v4.1.6 spike — Task Completion Engine (TCE) per-turn loop detector
|
|
79
|
+
// + recovery controller. Default ON as of v4.2 Phase 6 — set
|
|
80
|
+
// AIDEN_TCE=0 to disable. Zero
|
|
81
|
+
// behavioral change when unset. See core/v4/turnState.ts.
|
|
82
|
+
const turnState_1 = require("./turnState");
|
|
83
|
+
// v4.2 Phase 1 — per-tool result verifier. Same TCE gate as
|
|
84
|
+
// TurnState (default ON, opt-out via AIDEN_TCE=0); classification
|
|
85
|
+
// feeds the recovery controller.
|
|
86
|
+
const verifier_1 = require("./verifier");
|
|
87
|
+
// v4.2 Phase 2 — tool-failure WHY-classifier. Runs after the verifier
|
|
88
|
+
// when verification.ok === false. Records-only; Phase 3 will act.
|
|
89
|
+
const failureClassifier_1 = require("./failureClassifier");
|
|
90
|
+
// v4.2 Phase 3 — structured RecoveryReport. Built ONLY when the
|
|
91
|
+
// recovery controller's surface stage fires (tool_loop); enriches the
|
|
92
|
+
// existing surface card with summary + category breakdown + dominant
|
|
93
|
+
// guidance. Implicitly gated by TCE being enabled (surface only
|
|
94
|
+
// reachable when TurnState is enabled — default ON as of Phase 6).
|
|
95
|
+
const recoveryReport_1 = require("./recoveryReport");
|
|
96
|
+
// v4.6 Phase 3b — self-improvement loop. Durable cross-session
|
|
97
|
+
// failure ledger + recovery report writes. Loaded lazily inside the
|
|
98
|
+
// per-call branch so a missing singleton (test agents without a
|
|
99
|
+
// daemon DB) never blocks the agent loop.
|
|
100
|
+
const signatureBuilder_1 = require("./selfimprovement/signatureBuilder");
|
|
101
|
+
const recoveryStore_1 = require("./selfimprovement/recoveryStore");
|
|
102
|
+
// v4.2 Phase 4 — checkpoint / restore. Lets the recovery controller
|
|
103
|
+
// roll conversation messages + TurnState internals back to before a
|
|
104
|
+
// looping tool started failing, so the model retries from a clean
|
|
105
|
+
// baseline. Hard-blocked on iterations containing mutating tools
|
|
106
|
+
// (never claim to undo executed side effects). All-no-op when
|
|
107
|
+
// TCE is opted out via AIDEN_TCE=0 — capture / mark / find /
|
|
108
|
+
// restore all short-circuit.
|
|
109
|
+
const checkpoint_1 = require("./checkpoint");
|
|
45
110
|
const skillEnforcement_1 = require("./agent/skillEnforcement");
|
|
46
111
|
const urlProvenance_1 = require("./agent/urlProvenance");
|
|
47
112
|
const intentPreArm_1 = require("./agent/intentPreArm");
|
|
@@ -58,6 +123,14 @@ class AidenAgent {
|
|
|
58
123
|
constructor(opts) {
|
|
59
124
|
this.skillMinerTurnIdx = 0;
|
|
60
125
|
// ── Cross-call state ─────────────────────────────────────────────────
|
|
126
|
+
/**
|
|
127
|
+
* v4.6 Phase 1 — current per-turn AbortSignal, exposed to tools that need
|
|
128
|
+
* to construct child signal chains (specifically `spawn_sub_agent`). Set
|
|
129
|
+
* at the top of `runTurnLoop` from `runOptions.signal`, cleared before
|
|
130
|
+
* the loop returns. Read via `getCurrentSignal()`. Per-agent-instance —
|
|
131
|
+
* not shared across agents; a child agent has its own `_currentSignal`.
|
|
132
|
+
*/
|
|
133
|
+
this._currentSignal = undefined;
|
|
61
134
|
/** Cached system prompt — invalidated by setPersonalityOverlay/markMemoryDirty/explicit. */
|
|
62
135
|
this.cachedSystemPrompt = null;
|
|
63
136
|
this.compressionEvents = 0;
|
|
@@ -92,6 +165,7 @@ class AidenAgent {
|
|
|
92
165
|
this.onSkillCandidate = opts.onSkillCandidate;
|
|
93
166
|
this.resolveVerifiedFlag = opts.resolveVerifiedFlag;
|
|
94
167
|
this.resolveToolset = opts.resolveToolset;
|
|
168
|
+
this.resolveMutates = opts.resolveMutates;
|
|
95
169
|
this.promptBuilder = opts.promptBuilder;
|
|
96
170
|
this.promptBuilderOptions = opts.promptBuilderOptions;
|
|
97
171
|
this.contextCompressor = opts.contextCompressor;
|
|
@@ -108,6 +182,15 @@ class AidenAgent {
|
|
|
108
182
|
this.onPromptBuilt = opts.onPromptBuilt;
|
|
109
183
|
this.onProviderRequestStart = opts.onProviderRequestStart;
|
|
110
184
|
this.lookupSkillRequiredTools = opts.lookupSkillRequiredTools;
|
|
185
|
+
// v4.5 Phase 7 — explicit sessionId. Existing access path
|
|
186
|
+
// `(this as { sessionId?: string }).sessionId` at line 751–752
|
|
187
|
+
// already reads from `this.sessionId`; setting it here keys
|
|
188
|
+
// docker / browser / TurnState per session for daemon-mode
|
|
189
|
+
// turns. Interactive REPL callers don't pass this and continue
|
|
190
|
+
// hitting the 'session' fallback.
|
|
191
|
+
if (typeof opts.sessionId === 'string' && opts.sessionId.length > 0) {
|
|
192
|
+
this.sessionId = opts.sessionId;
|
|
193
|
+
}
|
|
111
194
|
// Phase v4.1.2-slice3: optional health registry (constructor-
|
|
112
195
|
// injected per the slice3 decision tree — no singleton). When
|
|
113
196
|
// wired, the caller already plumbed trackers into each subsystem
|
|
@@ -227,6 +310,17 @@ class AidenAgent {
|
|
|
227
310
|
getEmptyResponseMetrics() {
|
|
228
311
|
return { ...this.emptyResponseMetrics };
|
|
229
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* v4.6 Phase 1 — return the AbortSignal currently associated with this
|
|
315
|
+
* agent's active `runTurnLoop`, or `undefined` if the agent is between
|
|
316
|
+
* turns. Used by the `spawn_sub_agent` tool to construct a child signal
|
|
317
|
+
* chain that cascades parent aborts to the child (Flag 1 pattern: tool
|
|
318
|
+
* captures the parent agent reference at construction time and reads
|
|
319
|
+
* the current signal from the instance at dispatch time).
|
|
320
|
+
*/
|
|
321
|
+
getCurrentSignal() {
|
|
322
|
+
return this._currentSignal;
|
|
323
|
+
}
|
|
230
324
|
// ── Main entry: runConversation ──────────────────────────────────────
|
|
231
325
|
async runConversation(history, options = {}) {
|
|
232
326
|
// 1. Refresh memory snapshot if the dirty bit was set since last turn.
|
|
@@ -304,7 +398,21 @@ class AidenAgent {
|
|
|
304
398
|
}
|
|
305
399
|
}
|
|
306
400
|
// 10. SkillTeacher post-loop observation + proposal.
|
|
401
|
+
//
|
|
402
|
+
// v4.1.6 Polish 2 — `handleProposal` previously ran INLINE here,
|
|
403
|
+
// awaiting `callbacks.promptUser` (an inquirer modal) before
|
|
404
|
+
// `runConversation` returned. That made the modal fire BEFORE
|
|
405
|
+
// chatSession rendered the agent's reply on screen, so users
|
|
406
|
+
// saw "Save this as a reusable skill?" pop up mid-turn — feels
|
|
407
|
+
// like an interruption.
|
|
408
|
+
//
|
|
409
|
+
// New flow: agent ONLY observes here. When a proposal needs user
|
|
410
|
+
// confirmation (tier_3_propose with a promptUser callback), the
|
|
411
|
+
// proposal is surfaced in `AidenAgentResult.skillProposal` and
|
|
412
|
+
// chatSession handles the prompt + create dance AFTER rendering
|
|
413
|
+
// the reply. Tier_4_auto still runs inline (no prompt needed).
|
|
307
414
|
let skillCreated;
|
|
415
|
+
let skillProposal;
|
|
308
416
|
if (this.skillTeacher) {
|
|
309
417
|
try {
|
|
310
418
|
const traceForTeacher = loopResult.toolCallTrace.map((entry, i) => ({
|
|
@@ -316,9 +424,20 @@ class AidenAgent {
|
|
|
316
424
|
}));
|
|
317
425
|
const proposal = await this.skillTeacher.observeTurn(history, traceForTeacher, loopResult.finishReason !== 'stop');
|
|
318
426
|
if (proposal) {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
427
|
+
// Defer to chatSession only when there's a prompt callback
|
|
428
|
+
// wired (tier_3_propose path). Otherwise run inline to
|
|
429
|
+
// preserve tier_4_auto and tier_off behaviour.
|
|
430
|
+
const hasPromptCallback = typeof this.skillTeacherCallbacks?.promptUser === 'function';
|
|
431
|
+
if (hasPromptCallback) {
|
|
432
|
+
// Surface the proposal back to chatSession; do NOT call
|
|
433
|
+
// handleProposal here.
|
|
434
|
+
skillProposal = proposal;
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
const result = await this.skillTeacher.handleProposal(proposal, this.skillTeacherCallbacks);
|
|
438
|
+
if (result.created && result.skillName) {
|
|
439
|
+
skillCreated = result.skillName;
|
|
440
|
+
}
|
|
322
441
|
}
|
|
323
442
|
}
|
|
324
443
|
}
|
|
@@ -369,11 +488,20 @@ class AidenAgent {
|
|
|
369
488
|
toolCallTrace: loopResult.toolCallTrace,
|
|
370
489
|
honestyFindings,
|
|
371
490
|
skillCreated,
|
|
491
|
+
// v4.1.6 Polish 2 — deferred to chatSession's post-render
|
|
492
|
+
// handler when the SkillTeacher proposal needs user
|
|
493
|
+
// confirmation. Undefined when no proposal, when tier auto-
|
|
494
|
+
// handled inline, or when the teacher's observation faulted.
|
|
495
|
+
skillProposal,
|
|
372
496
|
compressionEvents: this.compressionEvents,
|
|
373
497
|
auxiliaryUsage: this.auxiliaryClient?.getUsage() ?? {},
|
|
374
498
|
skillEnforcement: { ...this.skillEnforcementMetrics },
|
|
375
499
|
urlProvenance: { ...this.urlProvenanceMetrics },
|
|
376
500
|
emptyResponse: { ...this.emptyResponseMetrics },
|
|
501
|
+
// v4.1.6 spike (TCE) — surfaced when TurnState hit the surface
|
|
502
|
+
// threshold mid-turn. chatSession reads this to render the
|
|
503
|
+
// structured-failure card; undefined on all other finishReasons.
|
|
504
|
+
toolLoopCard: loopResult.toolLoopCard,
|
|
377
505
|
};
|
|
378
506
|
}
|
|
379
507
|
// ── Private helpers ──────────────────────────────────────────────────
|
|
@@ -442,6 +570,23 @@ class AidenAgent {
|
|
|
442
570
|
async narrowTools(userMsg, history) {
|
|
443
571
|
if (!this.plannerGuard)
|
|
444
572
|
return this.tools;
|
|
573
|
+
// v4.6 Phase 2M — runtime toggle gates the keyword-based narrower.
|
|
574
|
+
// Default OFF: smart models (GPT-5.5, Claude Sonnet 4.5+, Opus)
|
|
575
|
+
// pick tools fine from the full catalog every turn, matching the
|
|
576
|
+
// reference multi-agent system's pattern. Opt in via env
|
|
577
|
+
// (AIDEN_PLANNER_GUARD=1) or `/planner-guard on` for small local
|
|
578
|
+
// models that need help. The toggle is read on each call so a
|
|
579
|
+
// mid-conversation flip takes effect on the next turn without
|
|
580
|
+
// restarting the agent.
|
|
581
|
+
//
|
|
582
|
+
// Lazy `require` to avoid a hard import dependency in the agent
|
|
583
|
+
// core — pure unit tests of AidenAgent that don't initialise the
|
|
584
|
+
// runtime toggles singleton keep working (the lazy getter returns
|
|
585
|
+
// an env-only fallback resolver per runtimeToggles.ts:213).
|
|
586
|
+
const { getRuntimeToggles } = await Promise.resolve().then(() => __importStar(require('./runtimeToggles')));
|
|
587
|
+
if (!getRuntimeToggles().isEnabled('planner_guard')) {
|
|
588
|
+
return this.tools;
|
|
589
|
+
}
|
|
445
590
|
const decision = await this.plannerGuard.decide(userMsg, history);
|
|
446
591
|
this.onPlannerGuardDecision?.(decision);
|
|
447
592
|
const allowed = new Set(decision.selectedTools);
|
|
@@ -458,13 +603,33 @@ class AidenAgent {
|
|
|
458
603
|
* `runConversation` enriches with post-loop scan output.
|
|
459
604
|
*/
|
|
460
605
|
async runTurnLoop(initialMessages, tools, trackers, runOptions) {
|
|
606
|
+
// v4.6 Phase 1 — expose the per-turn signal to tools via
|
|
607
|
+
// `getCurrentSignal()`. Set at loop entry; cleared before the return
|
|
608
|
+
// below. Tools that need the parent's signal (e.g. `spawn_sub_agent`
|
|
609
|
+
// building a child cancellation chain) capture the agent reference at
|
|
610
|
+
// construction time and read this field at dispatch time. If the loop
|
|
611
|
+
// throws, the stale value persists until the next call's set —
|
|
612
|
+
// acceptable because the only consumer is in-flight tool dispatch,
|
|
613
|
+
// which can only run while the loop is mid-execution.
|
|
614
|
+
this._currentSignal = runOptions.signal;
|
|
461
615
|
const messages = [...initialMessages];
|
|
462
616
|
const toolCallTrace = [];
|
|
617
|
+
// v4.6 Phase 3b — per-turn signature tracker for failure → success
|
|
618
|
+
// transitions. Each entry records the signatureId + failure count
|
|
619
|
+
// observed so far for a given signature THIS turn. When a verifier
|
|
620
|
+
// later reports `ok` for a tool call whose signature has prior
|
|
621
|
+
// failures, we record a recovery report. Keyed by signature string
|
|
622
|
+
// (the canonical `tool:category[:hash]` form).
|
|
623
|
+
const turnFailureTracker = new Map();
|
|
463
624
|
// Internal trace mirror that retains tool-call arguments — Honesty's
|
|
464
625
|
// shape doesn't include args, but SkillTeacher needs them. Both live
|
|
465
626
|
// off the same entry index.
|
|
466
627
|
const fullTrace = [];
|
|
467
628
|
const totalUsage = { inputTokens: 0, outputTokens: 0 };
|
|
629
|
+
// v4.2 Phase 3 — turn start timestamp for RecoveryReport duration.
|
|
630
|
+
// Captured here so any code path (early-return / error / surface)
|
|
631
|
+
// can compute wallclock duration consistently.
|
|
632
|
+
const turnStartedAt = Date.now();
|
|
468
633
|
let turnCount = 0;
|
|
469
634
|
let toolCallCount = 0;
|
|
470
635
|
let fallbackActivated = false;
|
|
@@ -473,7 +638,37 @@ class AidenAgent {
|
|
|
473
638
|
let emptyRetriesUsed = 0;
|
|
474
639
|
let finishReason = 'stop';
|
|
475
640
|
let finalContent = '';
|
|
641
|
+
// v4.1.6 spike (TCE) — per-turn loop detection + recovery state.
|
|
642
|
+
// Default ON as of v4.2 Phase 6 — set AIDEN_TCE=0 to disable.
|
|
643
|
+
// When disabled, TurnState.recordToolCall short-circuits with
|
|
644
|
+
// `{kind: 'allow'}` and the entire v4.2 recovery surface stays
|
|
645
|
+
// dormant (zero behavioural change vs v4.1.6).
|
|
646
|
+
const turnState = new turnState_1.TurnState();
|
|
647
|
+
// v4.2 Phase 1 — per-tool verifier registry. Constructed
|
|
648
|
+
// unconditionally (cheap, no side effects) but only used to
|
|
649
|
+
// classify tool outcomes when TCE is enabled; verification args
|
|
650
|
+
// are passed to TurnState only inside the gated branch below.
|
|
651
|
+
const verifierRegistry = (0, verifier_1.buildDefaultRegistry)();
|
|
652
|
+
// v4.2 Phase 2 — per-tool failure classifier. Same gating as
|
|
653
|
+
// the verifier; only runs when verification.ok === false. Phase 2
|
|
654
|
+
// records-only — Phase 3 wires recovery actions off the category.
|
|
655
|
+
const failureClassifier = (0, failureClassifier_1.buildDefaultClassifier)();
|
|
656
|
+
let toolLoopCard = undefined;
|
|
476
657
|
while (true) {
|
|
658
|
+
// v4.6 prep — between-iteration cooperative-cancellation check.
|
|
659
|
+
// When the caller passed an AbortSignal that has aborted, exit
|
|
660
|
+
// immediately with `finishReason: 'interrupted'`. Delta accumulation
|
|
661
|
+
// on abort is deferred — finalContent stays '' in this prep dispatch
|
|
662
|
+
// (see docs/v4.6/phase-1-design.md §11.0).
|
|
663
|
+
if (runOptions.signal?.aborted) {
|
|
664
|
+
finishReason = 'interrupted';
|
|
665
|
+
finalContent = '';
|
|
666
|
+
break;
|
|
667
|
+
}
|
|
668
|
+
// v4.1.6 spike — decrement cooldown counters once per iteration
|
|
669
|
+
// so cooled-down tools eventually return to the schemas. No-op
|
|
670
|
+
// when TCE is disabled.
|
|
671
|
+
turnState.advanceIteration();
|
|
477
672
|
if (turnCount >= this.maxTurns) {
|
|
478
673
|
finishReason = 'budget_exhausted';
|
|
479
674
|
break;
|
|
@@ -491,12 +686,36 @@ class AidenAgent {
|
|
|
491
686
|
this.onBudgetWarning?.('warning', turnCount, this.maxTurns);
|
|
492
687
|
}
|
|
493
688
|
// ── Provider call (stream or non-stream) ──────────────────────────
|
|
689
|
+
//
|
|
690
|
+
// v4.1.6 spike (TCE) — filter cooled-down tools out of the
|
|
691
|
+
// schemas we send to the provider. The model literally cannot
|
|
692
|
+
// see (and therefore cannot request) a cooled-down tool until
|
|
693
|
+
// its cooldown counter decrements to zero via
|
|
694
|
+
// `turnState.advanceIteration()`. No-op when TCE disabled
|
|
695
|
+
// (`getCooledDownTools()` returns []).
|
|
696
|
+
let effectiveTools = tools;
|
|
697
|
+
const cooledDown = turnState.getCooledDownTools();
|
|
698
|
+
if (cooledDown.length > 0) {
|
|
699
|
+
const cdSet = new Set(cooledDown);
|
|
700
|
+
effectiveTools = tools.filter((t) => !cdSet.has(t.name));
|
|
701
|
+
}
|
|
494
702
|
let output;
|
|
495
703
|
try {
|
|
496
|
-
output = await this.callProvider(messages,
|
|
704
|
+
output = await this.callProvider(messages, effectiveTools, runOptions);
|
|
497
705
|
}
|
|
498
706
|
catch (err) {
|
|
499
707
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
708
|
+
// v4.6 prep — external abort takes priority over fallback. An
|
|
709
|
+
// AbortError surfaced from the adapter when input.signal aborted
|
|
710
|
+
// is NOT a transient transport failure; surface it immediately
|
|
711
|
+
// as `finishReason: 'interrupted'` so the calling spawn primitive
|
|
712
|
+
// can route correctly. Detect via either the live signal flag or
|
|
713
|
+
// the error name (covers both pre-fetch and mid-flight aborts).
|
|
714
|
+
if (runOptions.signal?.aborted || error.name === 'AbortError') {
|
|
715
|
+
finishReason = 'interrupted';
|
|
716
|
+
finalContent = '';
|
|
717
|
+
break;
|
|
718
|
+
}
|
|
500
719
|
if (this.fallback && !fallbackActivated) {
|
|
501
720
|
const next = await this.fallback.activate(error, turnCount);
|
|
502
721
|
if (next) {
|
|
@@ -511,6 +730,25 @@ class AidenAgent {
|
|
|
511
730
|
}
|
|
512
731
|
totalUsage.inputTokens += output.usage?.inputTokens ?? 0;
|
|
513
732
|
totalUsage.outputTokens += output.usage?.outputTokens ?? 0;
|
|
733
|
+
// v4.2 Phase 4 — capture the state going INTO this iteration's
|
|
734
|
+
// tool dispatch. MUST run BEFORE `messages.push(assistantMsg)`
|
|
735
|
+
// so the checkpoint represents "the conversation before the
|
|
736
|
+
// model decided to call this iteration's tools". If rollback
|
|
737
|
+
// fires later, truncating `messages.length` to
|
|
738
|
+
// `checkpoint.messages.length` drops the assistant tool_call
|
|
739
|
+
// message together with its tool result messages — preserving
|
|
740
|
+
// tool_call/tool_result pairing in the rolled-back state.
|
|
741
|
+
//
|
|
742
|
+
// Capturing AFTER the assistant push (the prior placement) was
|
|
743
|
+
// a real bug: rollback would leave the assistant tool_call in
|
|
744
|
+
// history without its tool results, producing strict-provider
|
|
745
|
+
// 400 errors of the form "No tool output found for function
|
|
746
|
+
// call <id>". Tests in tests/v4/core/checkpoint-integration
|
|
747
|
+
// assert the post-rollback messages array contains zero orphan
|
|
748
|
+
// assistant tool_calls — this position is part of the contract.
|
|
749
|
+
//
|
|
750
|
+
// No-op when TCE is disabled (AIDEN_TCE=0) or checkpointDepth=0.
|
|
751
|
+
turnState.captureCheckpoint(messages, turnCount);
|
|
514
752
|
// ── Append assistant message ──────────────────────────────────────
|
|
515
753
|
const assistantMsg = output.toolCalls.length > 0
|
|
516
754
|
? { role: 'assistant', content: output.content ?? '', toolCalls: output.toolCalls }
|
|
@@ -585,8 +823,40 @@ class AidenAgent {
|
|
|
585
823
|
}
|
|
586
824
|
// ── Dispatch tools sequentially ──────────────────────────────────
|
|
587
825
|
const turnToolMessages = [];
|
|
826
|
+
// v4.1.6 spike (TCE) — set when TurnState surfaces a tool_loop
|
|
827
|
+
// mid-batch. The agent stops dispatching remaining calls in the
|
|
828
|
+
// batch and breaks out of the outer iteration loop cleanly.
|
|
829
|
+
let surfaceDecision = null;
|
|
830
|
+
// v4.2 Phase 4 — set when TurnState's recovery controller asks
|
|
831
|
+
// for a rollback. The agent loop truncates messages + restores
|
|
832
|
+
// TurnState internals + pushes a corrective system message,
|
|
833
|
+
// then continues the outer iteration loop from a clean baseline.
|
|
834
|
+
let rollbackDecision = null;
|
|
588
835
|
for (const call of output.toolCalls) {
|
|
836
|
+
// v4.6 prep — pre-tool-call cooperative-cancellation check.
|
|
837
|
+
// If the caller aborted between the model emitting tool calls
|
|
838
|
+
// and us dispatching them, skip the remaining calls in this
|
|
839
|
+
// batch. We set finishReason here; the outer-while break is
|
|
840
|
+
// handled after the for-of exits.
|
|
841
|
+
if (runOptions.signal?.aborted) {
|
|
842
|
+
finishReason = 'interrupted';
|
|
843
|
+
finalContent = '';
|
|
844
|
+
break;
|
|
845
|
+
}
|
|
589
846
|
this.onToolCall?.(call, 'before');
|
|
847
|
+
// v4.2 Phase 4 — mark any active checkpoints as containing a
|
|
848
|
+
// mutating call BEFORE dispatch. Done pre-dispatch (not post)
|
|
849
|
+
// so that even if the tool throws / errors / produces a
|
|
850
|
+
// partial side effect, the mutation flag is set — rollback
|
|
851
|
+
// safety errs on the side of "this iteration mutated state".
|
|
852
|
+
// The mutability resolver is wired from the CLI's tool
|
|
853
|
+
// registry (`resolveMutates`); unknown tools return undefined,
|
|
854
|
+
// which we treat as non-mutating (leave the flag alone).
|
|
855
|
+
// Plugin authors should declare `mutates` honestly on their
|
|
856
|
+
// tool handlers — this is the structural enforcement point.
|
|
857
|
+
if (turnState.isEnabled() && this.resolveMutates?.(call.name) === true) {
|
|
858
|
+
turnState.markMutationOnLiveCheckpoint(call.name);
|
|
859
|
+
}
|
|
590
860
|
let result;
|
|
591
861
|
try {
|
|
592
862
|
result = await this.toolExecutor(call);
|
|
@@ -600,11 +870,114 @@ class AidenAgent {
|
|
|
600
870
|
};
|
|
601
871
|
}
|
|
602
872
|
toolCallCount += 1;
|
|
873
|
+
// v4.2 Phase 1 — verifier classification. Runs only when TCE
|
|
874
|
+
// is enabled; the registry resolves a per-tool verifier or
|
|
875
|
+
// falls back to the heuristic default. Synchronous + pure;
|
|
876
|
+
// no network, no side effects.
|
|
877
|
+
let verification;
|
|
878
|
+
let classification = null;
|
|
879
|
+
if (turnState.isEnabled()) {
|
|
880
|
+
try {
|
|
881
|
+
verification = verifierRegistry.resolve(call.name)(call.name, call.arguments, result);
|
|
882
|
+
}
|
|
883
|
+
catch {
|
|
884
|
+
// Defensive — a buggy verifier never breaks the agent loop.
|
|
885
|
+
verification = undefined;
|
|
886
|
+
}
|
|
887
|
+
// v4.2 Phase 2 — classify WHY when the verifier said !ok.
|
|
888
|
+
// classify(...) returns null for ok results, so happy-path
|
|
889
|
+
// calls incur zero classifier work.
|
|
890
|
+
if (verification && !verification.ok) {
|
|
891
|
+
try {
|
|
892
|
+
classification = failureClassifier.classify(verification, call.name, call.arguments, result);
|
|
893
|
+
}
|
|
894
|
+
catch {
|
|
895
|
+
// Defensive — a buggy classifier never breaks the loop.
|
|
896
|
+
classification = null;
|
|
897
|
+
}
|
|
898
|
+
// v4.6 Phase 3b — write-through to the durable failure
|
|
899
|
+
// ledger. Best-effort: a null/missing store (test agents
|
|
900
|
+
// without a daemon DB wired) silently no-ops. The
|
|
901
|
+
// signature builder is pure + cheap.
|
|
902
|
+
if (classification) {
|
|
903
|
+
try {
|
|
904
|
+
const store = (0, recoveryStore_1.getRecoveryStore)();
|
|
905
|
+
if (store) {
|
|
906
|
+
const sig = (0, signatureBuilder_1.buildFailureSignature)({
|
|
907
|
+
toolName: call.name,
|
|
908
|
+
category: classification.category,
|
|
909
|
+
args: call.arguments,
|
|
910
|
+
});
|
|
911
|
+
const signatureId = store.recordFailureOccurrence({
|
|
912
|
+
signature: sig.signature,
|
|
913
|
+
toolName: call.name,
|
|
914
|
+
category: classification.category,
|
|
915
|
+
argsHash: sig.argsHash,
|
|
916
|
+
});
|
|
917
|
+
if (signatureId > 0) {
|
|
918
|
+
const existing = turnFailureTracker.get(sig.signature);
|
|
919
|
+
turnFailureTracker.set(sig.signature, {
|
|
920
|
+
signatureId,
|
|
921
|
+
failedAttempts: (existing?.failedAttempts ?? 0) + 1,
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
catch {
|
|
927
|
+
// Defensive — persistence failure must never break the loop.
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
else if (verification && verification.ok) {
|
|
932
|
+
// v4.6 Phase 3b — failure → success transition detection.
|
|
933
|
+
// We don't know the failure CATEGORY for this successful
|
|
934
|
+
// call (the verifier said ok, so classify() wasn't run),
|
|
935
|
+
// but the per-turn tracker remembers every signature seen
|
|
936
|
+
// failing this turn. Walk the tracker; if any entry's
|
|
937
|
+
// signature starts with `<call.name>:`, this tool now
|
|
938
|
+
// succeeded — record a recovery and drop the entry so
|
|
939
|
+
// subsequent successes don't double-count.
|
|
940
|
+
try {
|
|
941
|
+
const store = (0, recoveryStore_1.getRecoveryStore)();
|
|
942
|
+
if (store) {
|
|
943
|
+
const matching = [];
|
|
944
|
+
for (const sig of turnFailureTracker.keys()) {
|
|
945
|
+
if (sig.startsWith(`${call.name}:`))
|
|
946
|
+
matching.push(sig);
|
|
947
|
+
}
|
|
948
|
+
for (const sig of matching) {
|
|
949
|
+
const entry = turnFailureTracker.get(sig);
|
|
950
|
+
if (!entry)
|
|
951
|
+
continue;
|
|
952
|
+
store.recordRecovery({
|
|
953
|
+
signatureId: entry.signatureId,
|
|
954
|
+
sessionId: this.sessionId,
|
|
955
|
+
failedAttempts: entry.failedAttempts,
|
|
956
|
+
successfulStrategy: 'in_turn_retry',
|
|
957
|
+
notes: `${call.name} succeeded after ${entry.failedAttempts} prior failure(s) this turn`,
|
|
958
|
+
});
|
|
959
|
+
turnFailureTracker.delete(sig);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
catch {
|
|
964
|
+
// Defensive — recovery persistence failure must never break the loop.
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
}
|
|
603
968
|
toolCallTrace.push({
|
|
604
969
|
name: call.name,
|
|
605
970
|
result: result.result,
|
|
606
971
|
error: result.error,
|
|
607
972
|
verified: this.resolveVerifiedFlag?.(result),
|
|
973
|
+
// v4.2 Phase 1 — verification surfaces alongside the trace
|
|
974
|
+
// entry for downstream callers (chatSession, loopTrace,
|
|
975
|
+
// future RecoveryReport). Undefined when TCE is off.
|
|
976
|
+
verification,
|
|
977
|
+
// v4.2 Phase 2 — classification surfaces alongside verification.
|
|
978
|
+
// Undefined for verifier-ok calls (classifier skips them) and
|
|
979
|
+
// when TCE is off.
|
|
980
|
+
classification: classification ?? undefined,
|
|
608
981
|
});
|
|
609
982
|
fullTrace.push({ name: call.name, args: call.arguments });
|
|
610
983
|
// URL ledger ingest — extracts ids from result body for next turn.
|
|
@@ -623,6 +996,134 @@ class AidenAgent {
|
|
|
623
996
|
? `[error] ${result.error}`
|
|
624
997
|
: stringifyToolResult(result.result),
|
|
625
998
|
});
|
|
999
|
+
// v4.1.6 spike (TCE) — after the tool result lands in the
|
|
1000
|
+
// message history, consult the recovery controller. Returns
|
|
1001
|
+
// `allow` immediately when TCE disabled (zero overhead).
|
|
1002
|
+
// v4.2 Phase 1 — pass the verifier outcome so TurnState's
|
|
1003
|
+
// consecFailed counter can fast-fail on demonstrably failing
|
|
1004
|
+
// tool calls before the slower signature/name counters fire.
|
|
1005
|
+
// v4.2 Phase 2 — also pass the classification so TurnState
|
|
1006
|
+
// records the WHY for Phase 3's RecoveryReport.
|
|
1007
|
+
const recovery = turnState.recordToolCall(call.name, call.arguments, verification, classification);
|
|
1008
|
+
if (recovery.kind === 'hint' && recovery.hintMessage) {
|
|
1009
|
+
// Stage 1: append a corrective system message so the model
|
|
1010
|
+
// sees it on the next provider call. Same pattern as the
|
|
1011
|
+
// existing skill-enforcement + URL-provenance correctives.
|
|
1012
|
+
turnToolMessages.push({
|
|
1013
|
+
role: 'system',
|
|
1014
|
+
content: recovery.hintMessage,
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
else if (recovery.kind === 'cooldown_with_rollback' && recovery.rollback) {
|
|
1018
|
+
// v4.2 Phase 4 — controller asks us to roll back. Capture
|
|
1019
|
+
// the decision; we apply it AFTER the inner dispatch loop
|
|
1020
|
+
// exits so we don't leave partial turnToolMessages in a
|
|
1021
|
+
// half-state. Break out of dispatch immediately — no point
|
|
1022
|
+
// running more tools whose results we're about to drop.
|
|
1023
|
+
rollbackDecision = recovery;
|
|
1024
|
+
break;
|
|
1025
|
+
}
|
|
1026
|
+
else if (recovery.kind === 'cooldown' && recovery.cooldownMessage) {
|
|
1027
|
+
// Stage 2: cooldown has already been recorded internally
|
|
1028
|
+
// (next iteration's schema-filter step excludes this tool).
|
|
1029
|
+
// Inject a system message announcing the cooldown so the
|
|
1030
|
+
// model knows why the tool just disappeared from its menu.
|
|
1031
|
+
turnToolMessages.push({
|
|
1032
|
+
role: 'system',
|
|
1033
|
+
content: recovery.cooldownMessage,
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
else if (recovery.kind === 'surface' && recovery.surfaceCard) {
|
|
1037
|
+
// Stage 3: structured failure. Stop dispatching the rest of
|
|
1038
|
+
// the batch — anything else is throwing good budget after
|
|
1039
|
+
// bad. The outer loop reads `surfaceDecision` below and
|
|
1040
|
+
// exits cleanly.
|
|
1041
|
+
surfaceDecision = recovery;
|
|
1042
|
+
break;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
// v4.6 prep — if the per-tool-call abort check fired inside the
|
|
1046
|
+
// for-of above, finishReason is now 'interrupted'. Break the outer
|
|
1047
|
+
// while immediately so we don't run another provider call. Done
|
|
1048
|
+
// here (post-for-of) rather than inside the for-of because the
|
|
1049
|
+
// inner `break` only exits the inner loop.
|
|
1050
|
+
if (finishReason === 'interrupted') {
|
|
1051
|
+
break;
|
|
1052
|
+
}
|
|
1053
|
+
// v4.2 Phase 4 — apply rollback if the controller asked for it.
|
|
1054
|
+
// Truncate messages to the captured snapshot length, restore
|
|
1055
|
+
// TurnState internals, then push a corrective system message
|
|
1056
|
+
// and continue the OUTER iteration loop. We deliberately drop
|
|
1057
|
+
// any partial `turnToolMessages` collected before the rollback
|
|
1058
|
+
// trigger — those are the noise we're trying to undo.
|
|
1059
|
+
//
|
|
1060
|
+
// Hard-block invariant: TurnState only emits
|
|
1061
|
+
// `cooldown_with_rollback` when the target checkpoint has
|
|
1062
|
+
// `containedMutations === false`, so we never get here for an
|
|
1063
|
+
// iteration that ran a mutating tool. The optional
|
|
1064
|
+
// `rollback.blockedBy` is empty in Phase 4 (kept on the type
|
|
1065
|
+
// for a Phase 5+ soft-rollback variant).
|
|
1066
|
+
if (rollbackDecision && rollbackDecision.rollback) {
|
|
1067
|
+
const { checkpoint, blockedBy } = rollbackDecision.rollback;
|
|
1068
|
+
// Truncate messages array to the captured length. The captured
|
|
1069
|
+
// items are immutable Message references; we keep them as-is
|
|
1070
|
+
// and just shorten the live array.
|
|
1071
|
+
messages.length = checkpoint.messages.length;
|
|
1072
|
+
// Restore TurnState mutable internals (stage / streaks /
|
|
1073
|
+
// cooledDownTools / arrays). The cooled-down tools map is
|
|
1074
|
+
// preserved as it was at checkpoint time — but the controller
|
|
1075
|
+
// already added the looping tool to `cooledDownTools` before
|
|
1076
|
+
// emitting the decision, so we need to RE-apply that cooldown
|
|
1077
|
+
// after restore to honour the cooldown intent.
|
|
1078
|
+
turnState.restoreInternalsFrom(checkpoint);
|
|
1079
|
+
// Re-cool the tool that triggered the rollback so the next
|
|
1080
|
+
// provider call sees the constrained schema.
|
|
1081
|
+
if (rollbackDecision.toolName) {
|
|
1082
|
+
turnState.reapplyCooldown(rollbackDecision.toolName);
|
|
1083
|
+
}
|
|
1084
|
+
// Inject corrective system message so the model sees what
|
|
1085
|
+
// happened and why the tool just disappeared from its menu.
|
|
1086
|
+
messages.push({
|
|
1087
|
+
role: 'system',
|
|
1088
|
+
content: (0, checkpoint_1.buildRollbackMessage)({
|
|
1089
|
+
iteration: checkpoint.iteration,
|
|
1090
|
+
toolName: rollbackDecision.toolName,
|
|
1091
|
+
blockedBy,
|
|
1092
|
+
}),
|
|
1093
|
+
});
|
|
1094
|
+
// Continue the outer iteration loop from the restored
|
|
1095
|
+
// baseline. The next provider call gets the filtered tool
|
|
1096
|
+
// schema (cooldown applied) and the corrective message.
|
|
1097
|
+
continue;
|
|
1098
|
+
}
|
|
1099
|
+
// v4.1.6 spike (TCE) — terminal surface handling.
|
|
1100
|
+
if (surfaceDecision && surfaceDecision.kind === 'surface') {
|
|
1101
|
+
finishReason = 'tool_loop';
|
|
1102
|
+
// v4.2 Phase 3 — enrich the base surface card with a
|
|
1103
|
+
// structured RecoveryReport. Pure synthesis from TurnState's
|
|
1104
|
+
// diagnostic snapshot + first-user-message goal + duration.
|
|
1105
|
+
// Implicit gating: this branch is only reachable when
|
|
1106
|
+
// TurnState is enabled, so AIDEN_TCE=0 (opt-out) never
|
|
1107
|
+
// builds a report.
|
|
1108
|
+
if (surfaceDecision.surfaceCard) {
|
|
1109
|
+
const report = (0, recoveryReport_1.buildRecoveryReport)({
|
|
1110
|
+
snapshot: turnState.getDiagnosticSnapshot(),
|
|
1111
|
+
goal: (0, recoveryReport_1.extractGoal)(messages),
|
|
1112
|
+
exitReason: 'tool_loop',
|
|
1113
|
+
durationMs: Date.now() - turnStartedAt,
|
|
1114
|
+
});
|
|
1115
|
+
toolLoopCard = (0, recoveryReport_1.enrichCardWithReport)(surfaceDecision.surfaceCard, report);
|
|
1116
|
+
}
|
|
1117
|
+
else {
|
|
1118
|
+
toolLoopCard = surfaceDecision.surfaceCard;
|
|
1119
|
+
}
|
|
1120
|
+
// Push the partial tool messages we collected so honesty +
|
|
1121
|
+
// history downstream see the full sequence including the
|
|
1122
|
+
// loop-trigger call. No final assistant message — the
|
|
1123
|
+
// tool_loop card IS the user-facing surface.
|
|
1124
|
+
messages.push(...turnToolMessages);
|
|
1125
|
+
finalContent = '';
|
|
1126
|
+
break;
|
|
626
1127
|
}
|
|
627
1128
|
// ── Iteration-budget injection on the LAST tool message ──────────
|
|
628
1129
|
if (this.iterationBudgetInjection && turnToolMessages.length > 0) {
|
|
@@ -635,6 +1136,11 @@ class AidenAgent {
|
|
|
635
1136
|
messages.push(...turnToolMessages);
|
|
636
1137
|
// Loop continues — provider gets the tool results next iteration.
|
|
637
1138
|
}
|
|
1139
|
+
// v4.6 Phase 1 — clear the per-turn signal exposure before returning.
|
|
1140
|
+
// No-throw guarantee: if any prior code in this loop threw, the next
|
|
1141
|
+
// call's `this._currentSignal = runOptions.signal` at the top will
|
|
1142
|
+
// overwrite the stale value before any tool can read it.
|
|
1143
|
+
this._currentSignal = undefined;
|
|
638
1144
|
return {
|
|
639
1145
|
finalContent,
|
|
640
1146
|
messages,
|
|
@@ -645,6 +1151,7 @@ class AidenAgent {
|
|
|
645
1151
|
totalUsage,
|
|
646
1152
|
toolCallTrace,
|
|
647
1153
|
fullTrace,
|
|
1154
|
+
toolLoopCard,
|
|
648
1155
|
};
|
|
649
1156
|
}
|
|
650
1157
|
/**
|
|
@@ -669,7 +1176,9 @@ class AidenAgent {
|
|
|
669
1176
|
}
|
|
670
1177
|
catch { /* defensive */ }
|
|
671
1178
|
if (!wantStream) {
|
|
672
|
-
|
|
1179
|
+
// v4.6 prep — forward the abort signal into the provider call so
|
|
1180
|
+
// an in-flight HTTP request can be cancelled mid-flight.
|
|
1181
|
+
return this.provider.call({ messages, tools, signal: runOptions.signal });
|
|
673
1182
|
}
|
|
674
1183
|
let firstDeltaFired = false;
|
|
675
1184
|
let finalOutput = null;
|
|
@@ -677,6 +1186,9 @@ class AidenAgent {
|
|
|
677
1186
|
messages,
|
|
678
1187
|
tools,
|
|
679
1188
|
stream: true,
|
|
1189
|
+
// v4.6 prep — also forward to streaming adapters; mid-stream
|
|
1190
|
+
// aborts cancel the underlying SSE read via the same signal.
|
|
1191
|
+
signal: runOptions.signal,
|
|
680
1192
|
});
|
|
681
1193
|
for await (const evt of stream) {
|
|
682
1194
|
if (evt.type === 'delta') {
|
|
@@ -703,6 +1215,16 @@ class AidenAgent {
|
|
|
703
1215
|
}
|
|
704
1216
|
}
|
|
705
1217
|
if (!finalOutput) {
|
|
1218
|
+
// v4.6 prep — if the stream consumer exited without a `done`
|
|
1219
|
+
// event because the signal was aborted mid-stream, surface a
|
|
1220
|
+
// synthetic AbortError so the outer catch routes it as
|
|
1221
|
+
// 'interrupted' rather than the misleading "closed without done"
|
|
1222
|
+
// generic error.
|
|
1223
|
+
if (runOptions.signal?.aborted) {
|
|
1224
|
+
const abortErr = new Error('Streaming provider aborted before done event');
|
|
1225
|
+
abortErr.name = 'AbortError';
|
|
1226
|
+
throw abortErr;
|
|
1227
|
+
}
|
|
706
1228
|
throw new Error('Streaming provider closed without a done event');
|
|
707
1229
|
}
|
|
708
1230
|
return finalOutput;
|