aiden-runtime 4.1.4 → 4.5.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 +250 -847
- package/dist/api/server.js +32 -5
- package/dist/cli/v4/aidenCLI.js +379 -53
- package/dist/cli/v4/callbacks.js +248 -0
- package/dist/cli/v4/chatSession.js +292 -4
- package/dist/cli/v4/commands/_runtimeToggleHelpers.js +92 -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/help.js +7 -0
- package/dist/cli/v4/commands/index.js +20 -1
- package/dist/cli/v4/commands/runs.js +203 -0
- package/dist/cli/v4/commands/sandbox.js +48 -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 +142 -0
- package/dist/cli/v4/defaultSoul.js +75 -3
- package/dist/cli/v4/display/capabilityCard.js +26 -0
- package/dist/cli/v4/display/progressBar.js +41 -8
- package/dist/cli/v4/display.js +258 -15
- package/dist/cli/v4/replyRenderer.js +31 -23
- package/dist/cli/v4/toolPreview.js +10 -0
- package/dist/cli/v4/updateBootPrompt.js +170 -0
- package/dist/core/playwrightBridge.js +129 -0
- package/dist/core/toolRegistry.js +7 -1
- package/dist/core/v4/aidenAgent.js +371 -4
- package/dist/core/v4/browserState.js +436 -0
- package/dist/core/v4/checkpoint.js +79 -0
- package/dist/core/v4/daemon/bootstrap.js +604 -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 +296 -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 +114 -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/loopTrace.js +257 -0
- package/dist/core/v4/recoveryReport.js +449 -0
- package/dist/core/v4/runtimeToggles.js +187 -0
- package/dist/core/v4/sandboxConfig.js +285 -0
- package/dist/core/v4/sandboxFs.js +316 -0
- package/dist/core/v4/suggestionCatalog.js +41 -0
- package/dist/core/v4/suggestionEngine.js +210 -0
- package/dist/core/v4/toolRegistry.js +18 -0
- 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/core/webSearch.js +64 -24
- 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 +71 -58
- 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 +2 -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/subagentFanout.js +1 -0
- 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 +7 -1
- package/plugins/aiden-plugin-cdp-browser/.granted-permissions.json +8 -0
|
@@ -0,0 +1,257 @@
|
|
|
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/loopTrace.ts — Phase v4.1.5+ Path A.
|
|
10
|
+
*
|
|
11
|
+
* Env-var-gated per-turn audit log for diagnosing tool-call loops
|
|
12
|
+
* (the user-reported "30+ skill_view calls in 0ms each" failure mode
|
|
13
|
+
* from v4.1.5 visual smoke). Default OFF — adds zero overhead when
|
|
14
|
+
* `AIDEN_DEBUG_LOOP !== '1'`.
|
|
15
|
+
*
|
|
16
|
+
* When enabled, captures:
|
|
17
|
+
* - Full tool-call sequence (name, args, timing) for the turn
|
|
18
|
+
* - Assembled system prompt at turn start
|
|
19
|
+
* - MEMORY.md + USER.md content hashes (sha256, first 12 hex)
|
|
20
|
+
* - Recent skills list (last 10 `skill_view` calls)
|
|
21
|
+
* - Conversation history snapshot (last 5 turns)
|
|
22
|
+
*
|
|
23
|
+
* Auto-writes to `<paths.logsDir>/loop-trace-{ISO-timestamp}.json` at
|
|
24
|
+
* turn end IF the turn triggered loop detection (10+ tool calls OR
|
|
25
|
+
* 5+ consecutive same-name). Quiet otherwise — non-loop turns don't
|
|
26
|
+
* spam the logs directory.
|
|
27
|
+
*
|
|
28
|
+
* A/B harness reproduction lesson: the original 30+ loop the user
|
|
29
|
+
* observed could not be reproduced with a fresh `[system, user]`
|
|
30
|
+
* history (see `scripts/smoke-prompt-bias-ab.ts` results). The loop
|
|
31
|
+
* is either stochastic gpt-5.5 behaviour, history-poisoning from
|
|
32
|
+
* prior turns, or MEMORY/USER context-specific. This logger captures
|
|
33
|
+
* the EXACT context next time the loop happens in live use so we
|
|
34
|
+
* can A/B against the real failure case.
|
|
35
|
+
*
|
|
36
|
+
* Pure module — no Display dependency, no event-emitter, no side
|
|
37
|
+
* effects beyond the gated file write. Safe to import from anywhere.
|
|
38
|
+
*/
|
|
39
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
40
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
41
|
+
};
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.LoopTracer = void 0;
|
|
44
|
+
const node_fs_1 = require("node:fs");
|
|
45
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
46
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
47
|
+
// ── Tracer ──────────────────────────────────────────────────────────────────
|
|
48
|
+
const ARGS_CAP = 200;
|
|
49
|
+
const HISTORY_TAIL_DEPTH = 5;
|
|
50
|
+
const RECENT_SKILLS_MAX = 10;
|
|
51
|
+
/**
|
|
52
|
+
* Tracks tool calls for one turn. Construct fresh per turn (the
|
|
53
|
+
* counters and recent-skills list are turn-scoped). Idempotent
|
|
54
|
+
* `finalize()` — multiple calls produce one file at most.
|
|
55
|
+
*/
|
|
56
|
+
class LoopTracer {
|
|
57
|
+
constructor(rawOpts) {
|
|
58
|
+
this.systemPrompt = '';
|
|
59
|
+
this.history = [];
|
|
60
|
+
this.toolStart = new Map();
|
|
61
|
+
this.sequence = [];
|
|
62
|
+
this.lastName = null;
|
|
63
|
+
this.consecSame = 0;
|
|
64
|
+
this.maxConsec = 0;
|
|
65
|
+
this.warnFired = false;
|
|
66
|
+
this.finalized = false;
|
|
67
|
+
this.recentSkills = [];
|
|
68
|
+
this.enabled = rawOpts.enabled ?? (process.env.AIDEN_DEBUG_LOOP === '1');
|
|
69
|
+
this.opts = {
|
|
70
|
+
paths: rawOpts.paths,
|
|
71
|
+
providerId: rawOpts.providerId,
|
|
72
|
+
modelId: rawOpts.modelId,
|
|
73
|
+
enabled: this.enabled,
|
|
74
|
+
toolCountThreshold: rawOpts.toolCountThreshold ?? 10,
|
|
75
|
+
consecSameThreshold: rawOpts.consecSameThreshold ?? 5,
|
|
76
|
+
warnConsecThreshold: rawOpts.warnConsecThreshold ?? 8,
|
|
77
|
+
onLoopWarning: rawOpts.onLoopWarning ?? (() => { }),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
isEnabled() { return this.enabled; }
|
|
81
|
+
/**
|
|
82
|
+
* Set the assembled system prompt for this turn. Called once at turn
|
|
83
|
+
* start (before any tool calls). Stored verbatim; no truncation.
|
|
84
|
+
*/
|
|
85
|
+
setSystemPrompt(prompt) {
|
|
86
|
+
if (!this.enabled)
|
|
87
|
+
return;
|
|
88
|
+
this.systemPrompt = prompt;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Set the turn's conversation history snapshot. The tracer keeps
|
|
92
|
+
* only the last `HISTORY_TAIL_DEPTH` messages on finalize.
|
|
93
|
+
*/
|
|
94
|
+
setHistory(messages) {
|
|
95
|
+
if (!this.enabled)
|
|
96
|
+
return;
|
|
97
|
+
this.history = messages;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Record the start of a tool call. Pair with `endTool(id, name)` to
|
|
101
|
+
* capture the duration. Caller must pass a stable `id` (the tool
|
|
102
|
+
* call request id) so before/after pairs find each other.
|
|
103
|
+
*/
|
|
104
|
+
startTool(id, _name) {
|
|
105
|
+
if (!this.enabled)
|
|
106
|
+
return;
|
|
107
|
+
this.toolStart.set(id, Date.now());
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Record the end of a tool call. Computes duration from the matching
|
|
111
|
+
* `startTool` call. Updates consec-same counter; fires the live
|
|
112
|
+
* warning when threshold crosses.
|
|
113
|
+
*/
|
|
114
|
+
endTool(id, name, args) {
|
|
115
|
+
if (!this.enabled)
|
|
116
|
+
return;
|
|
117
|
+
const start = this.toolStart.get(id);
|
|
118
|
+
this.toolStart.delete(id);
|
|
119
|
+
const durationMs = start === undefined ? 0 : (Date.now() - start);
|
|
120
|
+
let argsPreview;
|
|
121
|
+
try {
|
|
122
|
+
argsPreview = JSON.stringify(args ?? {});
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
argsPreview = String(args);
|
|
126
|
+
}
|
|
127
|
+
if (argsPreview.length > ARGS_CAP)
|
|
128
|
+
argsPreview = `${argsPreview.slice(0, ARGS_CAP - 1)}…`;
|
|
129
|
+
this.sequence.push({
|
|
130
|
+
name, argsPreview, durationMs,
|
|
131
|
+
ts: new Date().toISOString(),
|
|
132
|
+
});
|
|
133
|
+
// Consec-same accounting.
|
|
134
|
+
if (name === this.lastName) {
|
|
135
|
+
this.consecSame += 1;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
this.consecSame = 1;
|
|
139
|
+
this.lastName = name;
|
|
140
|
+
}
|
|
141
|
+
if (this.consecSame > this.maxConsec)
|
|
142
|
+
this.maxConsec = this.consecSame;
|
|
143
|
+
// Skill-view tracking (separate from sequence for quick reference).
|
|
144
|
+
if (name === 'skill_view' || name === 'lookup_tool_schema') {
|
|
145
|
+
const tk = args;
|
|
146
|
+
const target = tk?.name ?? tk?.toolName ?? '(unknown)';
|
|
147
|
+
this.recentSkills.push(target);
|
|
148
|
+
if (this.recentSkills.length > RECENT_SKILLS_MAX) {
|
|
149
|
+
this.recentSkills = this.recentSkills.slice(-RECENT_SKILLS_MAX);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// Live warning at the loud threshold.
|
|
153
|
+
if (!this.warnFired && this.consecSame >= this.opts.warnConsecThreshold) {
|
|
154
|
+
this.warnFired = true;
|
|
155
|
+
try {
|
|
156
|
+
this.opts.onLoopWarning(`[loop] same tool '${name}' called ${this.consecSame}× — Ctrl+C to interrupt`);
|
|
157
|
+
}
|
|
158
|
+
catch { /* defensive */ }
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Compute whether a snapshot would be written if `finalize()` ran
|
|
163
|
+
* now. Useful for tests + the live warning path. Pure read.
|
|
164
|
+
*/
|
|
165
|
+
shouldEmit() {
|
|
166
|
+
if (!this.enabled)
|
|
167
|
+
return false;
|
|
168
|
+
return (this.sequence.length >= this.opts.toolCountThreshold ||
|
|
169
|
+
this.maxConsec >= this.opts.consecSameThreshold);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Write the trace to disk if thresholds tripped. Idempotent —
|
|
173
|
+
* subsequent calls are no-ops. Returns the snapshot path or `null`
|
|
174
|
+
* if nothing was written.
|
|
175
|
+
*/
|
|
176
|
+
async finalize() {
|
|
177
|
+
if (this.finalized)
|
|
178
|
+
return null;
|
|
179
|
+
this.finalized = true;
|
|
180
|
+
if (!this.enabled)
|
|
181
|
+
return null;
|
|
182
|
+
if (!this.shouldEmit())
|
|
183
|
+
return null;
|
|
184
|
+
const snapshot = await this.buildSnapshot();
|
|
185
|
+
const ts = snapshot.capturedAt.replace(/[:.]/g, '-');
|
|
186
|
+
const filename = `loop-trace-${ts}.json`;
|
|
187
|
+
const filePath = node_path_1.default.join(this.opts.paths.logsDir, filename);
|
|
188
|
+
try {
|
|
189
|
+
await node_fs_1.promises.mkdir(this.opts.paths.logsDir, { recursive: true });
|
|
190
|
+
await node_fs_1.promises.writeFile(filePath, JSON.stringify(snapshot, null, 2), 'utf8');
|
|
191
|
+
return filePath;
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
// Diagnostic logging must never break the turn. Silently swallow
|
|
195
|
+
// write failures — power user with AIDEN_DEBUG_LOOP=1 will notice
|
|
196
|
+
// missing files and can re-run with stricter perms if needed.
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/** Test-accessor: synchronous read of the current consec-same state. */
|
|
201
|
+
getMaxConsecutive() { return this.maxConsec; }
|
|
202
|
+
/** Test-accessor: tool call count so far. */
|
|
203
|
+
getToolCount() { return this.sequence.length; }
|
|
204
|
+
// ── Internals ────────────────────────────────────────────────────────────
|
|
205
|
+
async buildSnapshot() {
|
|
206
|
+
const reason = this.maxConsec >= this.opts.consecSameThreshold
|
|
207
|
+
? (this.sequence.length >= this.opts.toolCountThreshold
|
|
208
|
+
? 'consecutive_same'
|
|
209
|
+
: 'consecutive_same')
|
|
210
|
+
: 'tool_count';
|
|
211
|
+
const memoryMdHash = await hashFileFirst12(this.opts.paths.memoryMd);
|
|
212
|
+
const userMdHash = await hashFileFirst12(this.opts.paths.userMd);
|
|
213
|
+
const historyTail = this.history.slice(-HISTORY_TAIL_DEPTH).map((m) => ({
|
|
214
|
+
role: m.role,
|
|
215
|
+
contentPreview: trunc(typeof m.content === 'string' ? m.content : JSON.stringify(m.content), 300),
|
|
216
|
+
}));
|
|
217
|
+
return {
|
|
218
|
+
schemaVersion: 1,
|
|
219
|
+
capturedAt: new Date().toISOString(),
|
|
220
|
+
reason,
|
|
221
|
+
toolCallCount: this.sequence.length,
|
|
222
|
+
maxConsecSame: this.maxConsec,
|
|
223
|
+
consecSameName: this.maxConsec >= 2 ? this.lastName : null,
|
|
224
|
+
toolSequence: this.sequence,
|
|
225
|
+
systemPrompt: this.systemPrompt,
|
|
226
|
+
memoryMdHash,
|
|
227
|
+
userMdHash,
|
|
228
|
+
recentSkills: this.recentSkills,
|
|
229
|
+
historyTail,
|
|
230
|
+
envHints: {
|
|
231
|
+
provider: this.opts.providerId,
|
|
232
|
+
model: this.opts.modelId,
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
exports.LoopTracer = LoopTracer;
|
|
238
|
+
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
239
|
+
/**
|
|
240
|
+
* Read a file and return the sha256 hash, first 12 hex chars.
|
|
241
|
+
* Returns `null` if the file is missing or unreadable. Pure-async.
|
|
242
|
+
* 12 hex is enough collision resistance for context-fingerprinting
|
|
243
|
+
* without bloating the trace file with full 64-char hashes.
|
|
244
|
+
*/
|
|
245
|
+
async function hashFileFirst12(filePath) {
|
|
246
|
+
try {
|
|
247
|
+
const content = await node_fs_1.promises.readFile(filePath, 'utf8');
|
|
248
|
+
const hash = node_crypto_1.default.createHash('sha256').update(content).digest('hex');
|
|
249
|
+
return hash.slice(0, 12);
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function trunc(s, n) {
|
|
256
|
+
return s.length <= n ? s : `${s.slice(0, n - 1)}…`;
|
|
257
|
+
}
|