agent-gauntlet 0.10.0 → 0.11.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 +25 -23
- package/dist/index.js +9226 -0
- package/dist/index.js.map +65 -0
- package/dist/scripts/status.js +280 -0
- package/dist/scripts/status.js.map +10 -0
- package/package.json +22 -8
- package/src/built-in-reviews/code-quality.md +0 -25
- package/src/built-in-reviews/index.ts +0 -28
- package/src/bun-plugins.d.ts +0 -4
- package/src/cli-adapters/claude.ts +0 -327
- package/src/cli-adapters/codex.ts +0 -290
- package/src/cli-adapters/cursor.ts +0 -128
- package/src/cli-adapters/gemini.ts +0 -510
- package/src/cli-adapters/github-copilot.ts +0 -141
- package/src/cli-adapters/index.ts +0 -250
- package/src/cli-adapters/thinking-budget.ts +0 -23
- package/src/commands/check.ts +0 -311
- package/src/commands/ci/index.ts +0 -15
- package/src/commands/ci/init.ts +0 -96
- package/src/commands/ci/list-jobs.ts +0 -90
- package/src/commands/clean.ts +0 -54
- package/src/commands/detect.ts +0 -173
- package/src/commands/health.ts +0 -169
- package/src/commands/help.ts +0 -34
- package/src/commands/index.ts +0 -13
- package/src/commands/init.ts +0 -1878
- package/src/commands/list.ts +0 -33
- package/src/commands/review.ts +0 -311
- package/src/commands/run.ts +0 -29
- package/src/commands/shared.ts +0 -267
- package/src/commands/stop-hook.ts +0 -567
- package/src/commands/validate.ts +0 -20
- package/src/commands/wait-ci.ts +0 -518
- package/src/config/ci-loader.ts +0 -33
- package/src/config/ci-schema.ts +0 -28
- package/src/config/global.ts +0 -87
- package/src/config/loader.ts +0 -301
- package/src/config/schema.ts +0 -165
- package/src/config/stop-hook-config.ts +0 -130
- package/src/config/types.ts +0 -65
- package/src/config/validator.ts +0 -592
- package/src/core/change-detector.ts +0 -137
- package/src/core/diff-stats.ts +0 -442
- package/src/core/entry-point.ts +0 -190
- package/src/core/job.ts +0 -96
- package/src/core/run-executor.ts +0 -621
- package/src/core/runner.ts +0 -290
- package/src/gates/check.ts +0 -118
- package/src/gates/resolve-check-command.ts +0 -21
- package/src/gates/result.ts +0 -54
- package/src/gates/review.ts +0 -1333
- package/src/hooks/adapters/claude-stop-hook.ts +0 -99
- package/src/hooks/adapters/cursor-stop-hook.ts +0 -122
- package/src/hooks/adapters/types.ts +0 -94
- package/src/hooks/stop-hook-handler.ts +0 -748
- package/src/index.ts +0 -47
- package/src/output/app-logger.ts +0 -214
- package/src/output/console-log.ts +0 -168
- package/src/output/console.ts +0 -359
- package/src/output/logger.ts +0 -126
- package/src/output/sinks/console-sink.ts +0 -59
- package/src/output/sinks/file-sink.ts +0 -110
- package/src/scripts/status.ts +0 -433
- package/src/templates/workflow.yml +0 -79
- package/src/types/gauntlet-status.ts +0 -79
- package/src/utils/debug-log.ts +0 -392
- package/src/utils/diff-parser.ts +0 -103
- package/src/utils/execution-state.ts +0 -472
- package/src/utils/log-parser.ts +0 -696
- package/src/utils/sanitizer.ts +0 -3
- package/src/utils/session-ref.ts +0 -91
|
@@ -1,472 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import fs from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { getDebugLogger } from "./debug-log.js";
|
|
5
|
-
|
|
6
|
-
const EXECUTION_STATE_FILENAME = ".execution_state";
|
|
7
|
-
const SESSION_REF_FILENAME = ".session_ref";
|
|
8
|
-
|
|
9
|
-
function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
10
|
-
// Use loose equality to check both null and undefined in one comparison
|
|
11
|
-
if (value == null) return false;
|
|
12
|
-
if (Array.isArray(value)) return false;
|
|
13
|
-
return typeof value === "object";
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function extractUnhealthyAdapters(
|
|
17
|
-
rawData: Record<string, unknown> | null,
|
|
18
|
-
): Record<string, UnhealthyAdapter> | undefined {
|
|
19
|
-
const adapters = rawData?.unhealthy_adapters;
|
|
20
|
-
if (!isPlainRecord(adapters)) {
|
|
21
|
-
return undefined;
|
|
22
|
-
}
|
|
23
|
-
return adapters as Record<string, UnhealthyAdapter>;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface UnhealthyAdapter {
|
|
27
|
-
marked_at: string;
|
|
28
|
-
reason: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface ExecutionState {
|
|
32
|
-
last_run_completed_at: string;
|
|
33
|
-
branch: string;
|
|
34
|
-
commit: string;
|
|
35
|
-
working_tree_ref?: string;
|
|
36
|
-
unhealthy_adapters?: Record<string, UnhealthyAdapter>;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Read the execution state from the log directory.
|
|
41
|
-
* Returns null if the state file or directory doesn't exist.
|
|
42
|
-
*/
|
|
43
|
-
function isValidStateData(data: unknown): data is Record<string, unknown> & {
|
|
44
|
-
last_run_completed_at: string;
|
|
45
|
-
branch: string;
|
|
46
|
-
commit: string;
|
|
47
|
-
} {
|
|
48
|
-
if (typeof data !== "object" || data === null) return false;
|
|
49
|
-
const record = data as Record<string, unknown>;
|
|
50
|
-
return (
|
|
51
|
-
typeof record.last_run_completed_at === "string" &&
|
|
52
|
-
typeof record.branch === "string" &&
|
|
53
|
-
typeof record.commit === "string"
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export async function readExecutionState(
|
|
58
|
-
logDir: string,
|
|
59
|
-
): Promise<ExecutionState | null> {
|
|
60
|
-
try {
|
|
61
|
-
const statePath = path.join(logDir, EXECUTION_STATE_FILENAME);
|
|
62
|
-
const content = await fs.readFile(statePath, "utf-8");
|
|
63
|
-
const data = JSON.parse(content) as unknown;
|
|
64
|
-
|
|
65
|
-
if (!isValidStateData(data)) return null;
|
|
66
|
-
|
|
67
|
-
const state: ExecutionState = {
|
|
68
|
-
last_run_completed_at: data.last_run_completed_at,
|
|
69
|
-
branch: data.branch,
|
|
70
|
-
commit: data.commit,
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
if (typeof data.working_tree_ref === "string") {
|
|
74
|
-
state.working_tree_ref = data.working_tree_ref;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (
|
|
78
|
-
data.unhealthy_adapters &&
|
|
79
|
-
typeof data.unhealthy_adapters === "object"
|
|
80
|
-
) {
|
|
81
|
-
state.unhealthy_adapters = data.unhealthy_adapters as Record<
|
|
82
|
-
string,
|
|
83
|
-
UnhealthyAdapter
|
|
84
|
-
>;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return state;
|
|
88
|
-
} catch {
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Create a stash SHA that captures the current working tree state.
|
|
95
|
-
* Uses `git stash create --include-untracked` which creates a stash commit
|
|
96
|
-
* without modifying the working tree.
|
|
97
|
-
* Returns the stash SHA, or HEAD SHA if working tree is clean.
|
|
98
|
-
*/
|
|
99
|
-
export async function createWorkingTreeRef(): Promise<string> {
|
|
100
|
-
return new Promise((resolve, reject) => {
|
|
101
|
-
const child = spawn("git", ["stash", "create", "--include-untracked"], {
|
|
102
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
let stdout = "";
|
|
106
|
-
child.stdout.on("data", (data: Buffer) => {
|
|
107
|
-
stdout += data.toString();
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
child.on("close", async (code) => {
|
|
111
|
-
if (code === 0) {
|
|
112
|
-
const sha = stdout.trim();
|
|
113
|
-
if (sha) {
|
|
114
|
-
// Stash created with working tree changes
|
|
115
|
-
resolve(sha);
|
|
116
|
-
} else {
|
|
117
|
-
// Clean working tree - use HEAD instead
|
|
118
|
-
try {
|
|
119
|
-
const headSha = await getCurrentCommit();
|
|
120
|
-
resolve(headSha);
|
|
121
|
-
} catch (err) {
|
|
122
|
-
reject(err);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
} else {
|
|
126
|
-
// Try to fall back to HEAD
|
|
127
|
-
try {
|
|
128
|
-
const headSha = await getCurrentCommit();
|
|
129
|
-
resolve(headSha);
|
|
130
|
-
} catch {
|
|
131
|
-
reject(new Error(`git stash create failed with code ${code}`));
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
child.on("error", reject);
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Write the execution state to the log directory.
|
|
142
|
-
* Records the current branch, commit SHA, working tree ref, and timestamp.
|
|
143
|
-
* Also cleans up legacy .session_ref file if it exists.
|
|
144
|
-
*/
|
|
145
|
-
export async function writeExecutionState(logDir: string): Promise<void> {
|
|
146
|
-
const statePath = path.join(logDir, EXECUTION_STATE_FILENAME);
|
|
147
|
-
const [branch, commit, workingTreeRef, rawState] = await Promise.all([
|
|
148
|
-
getCurrentBranch(),
|
|
149
|
-
getCurrentCommit(),
|
|
150
|
-
createWorkingTreeRef(),
|
|
151
|
-
readRawState(statePath),
|
|
152
|
-
]);
|
|
153
|
-
const existingUnhealthy = extractUnhealthyAdapters(rawState);
|
|
154
|
-
|
|
155
|
-
const state: ExecutionState = {
|
|
156
|
-
last_run_completed_at: new Date().toISOString(),
|
|
157
|
-
branch,
|
|
158
|
-
commit,
|
|
159
|
-
working_tree_ref: workingTreeRef,
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
// Preserve unhealthy_adapters from existing state
|
|
163
|
-
if (existingUnhealthy) {
|
|
164
|
-
state.unhealthy_adapters = existingUnhealthy;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Log changed fields (skip last_run_completed_at since every log line is timestamped)
|
|
168
|
-
const changes: Record<string, string> = {};
|
|
169
|
-
const oldState = rawState as Record<string, unknown> | null;
|
|
170
|
-
if (oldState) {
|
|
171
|
-
if (oldState.branch !== branch) changes.branch = branch;
|
|
172
|
-
if (oldState.commit !== commit) changes.commit = commit;
|
|
173
|
-
if (oldState.working_tree_ref !== workingTreeRef)
|
|
174
|
-
changes.working_tree_ref = workingTreeRef;
|
|
175
|
-
} else {
|
|
176
|
-
// First write - log all fields
|
|
177
|
-
changes.branch = branch;
|
|
178
|
-
changes.commit = commit;
|
|
179
|
-
changes.working_tree_ref = workingTreeRef;
|
|
180
|
-
}
|
|
181
|
-
await getDebugLogger()?.logStateWrite(changes);
|
|
182
|
-
|
|
183
|
-
// Ensure the log directory exists
|
|
184
|
-
await fs.mkdir(logDir, { recursive: true });
|
|
185
|
-
await fs.writeFile(statePath, JSON.stringify(state, null, 2), "utf-8");
|
|
186
|
-
|
|
187
|
-
// Clean up legacy .session_ref file if it exists
|
|
188
|
-
try {
|
|
189
|
-
const sessionRefPath = path.join(logDir, SESSION_REF_FILENAME);
|
|
190
|
-
await fs.rm(sessionRefPath, { force: true });
|
|
191
|
-
} catch {
|
|
192
|
-
// Ignore errors
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Get the current git branch name.
|
|
198
|
-
*/
|
|
199
|
-
export async function getCurrentBranch(): Promise<string> {
|
|
200
|
-
return new Promise((resolve, reject) => {
|
|
201
|
-
const child = spawn("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
202
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
let stdout = "";
|
|
206
|
-
child.stdout.on("data", (data: Buffer) => {
|
|
207
|
-
stdout += data.toString();
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
child.on("close", (code) => {
|
|
211
|
-
if (code === 0) {
|
|
212
|
-
resolve(stdout.trim());
|
|
213
|
-
} else {
|
|
214
|
-
reject(new Error(`git rev-parse failed with code ${code}`));
|
|
215
|
-
}
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
child.on("error", reject);
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Get the current HEAD commit SHA.
|
|
224
|
-
*/
|
|
225
|
-
export async function getCurrentCommit(): Promise<string> {
|
|
226
|
-
return new Promise((resolve, reject) => {
|
|
227
|
-
const child = spawn("git", ["rev-parse", "HEAD"], {
|
|
228
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
let stdout = "";
|
|
232
|
-
child.stdout.on("data", (data: Buffer) => {
|
|
233
|
-
stdout += data.toString();
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
child.on("close", (code) => {
|
|
237
|
-
if (code === 0) {
|
|
238
|
-
resolve(stdout.trim());
|
|
239
|
-
} else {
|
|
240
|
-
reject(new Error(`git rev-parse failed with code ${code}`));
|
|
241
|
-
}
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
child.on("error", reject);
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Check if a commit is an ancestor of a branch (i.e., the commit has been merged).
|
|
250
|
-
* Uses `git merge-base --is-ancestor`.
|
|
251
|
-
* Returns true if commit is reachable from branch.
|
|
252
|
-
*/
|
|
253
|
-
export async function isCommitInBranch(
|
|
254
|
-
commit: string,
|
|
255
|
-
branch: string,
|
|
256
|
-
): Promise<boolean> {
|
|
257
|
-
return new Promise((resolve) => {
|
|
258
|
-
const child = spawn(
|
|
259
|
-
"git",
|
|
260
|
-
["merge-base", "--is-ancestor", commit, branch],
|
|
261
|
-
{ stdio: ["ignore", "pipe", "pipe"] },
|
|
262
|
-
);
|
|
263
|
-
|
|
264
|
-
child.on("close", (code) => {
|
|
265
|
-
// Exit 0 = is ancestor (merged), exit 1 = not ancestor
|
|
266
|
-
resolve(code === 0);
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
child.on("error", () => {
|
|
270
|
-
resolve(false);
|
|
271
|
-
});
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Get the execution state filename (for use in clean operations).
|
|
277
|
-
*/
|
|
278
|
-
export function getExecutionStateFilename(): string {
|
|
279
|
-
return EXECUTION_STATE_FILENAME;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Check if a git object (commit, tree, blob, etc.) exists in the repository.
|
|
284
|
-
* Uses `git cat-file -t <sha>` to check object type.
|
|
285
|
-
*/
|
|
286
|
-
export async function gitObjectExists(sha: string): Promise<boolean> {
|
|
287
|
-
return new Promise((resolve) => {
|
|
288
|
-
const child = spawn("git", ["cat-file", "-t", sha], {
|
|
289
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
child.on("close", (code) => {
|
|
293
|
-
resolve(code === 0);
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
child.on("error", () => {
|
|
297
|
-
resolve(false);
|
|
298
|
-
});
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* When a commit has been merged, check if working_tree_ref still scopes valid changes.
|
|
304
|
-
*/
|
|
305
|
-
async function resolveFixBaseForMergedCommit(
|
|
306
|
-
working_tree_ref: string | undefined,
|
|
307
|
-
): Promise<{ fixBase: string | null; warning?: string }> {
|
|
308
|
-
if (!working_tree_ref) {
|
|
309
|
-
return { fixBase: null };
|
|
310
|
-
}
|
|
311
|
-
const refExists = await gitObjectExists(working_tree_ref);
|
|
312
|
-
if (!refExists) {
|
|
313
|
-
return { fixBase: null };
|
|
314
|
-
}
|
|
315
|
-
return {
|
|
316
|
-
fixBase: working_tree_ref,
|
|
317
|
-
warning:
|
|
318
|
-
"Commit merged into base branch, using working tree ref for diff scope",
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* Resolve the fixBase for change detection based on execution state.
|
|
324
|
-
* Used for post-clean runs to scope diffs to changes since the last passing run.
|
|
325
|
-
*
|
|
326
|
-
* Returns:
|
|
327
|
-
* - working_tree_ref if valid (not gc'd) and commit not merged
|
|
328
|
-
* - commit as fallback if working_tree_ref is gc'd
|
|
329
|
-
* - null if state is stale (commit merged) or all refs are invalid
|
|
330
|
-
*/
|
|
331
|
-
export async function resolveFixBase(
|
|
332
|
-
executionState: ExecutionState,
|
|
333
|
-
baseBranch: string,
|
|
334
|
-
): Promise<{ fixBase: string | null; warning?: string }> {
|
|
335
|
-
const { commit, working_tree_ref } = executionState;
|
|
336
|
-
|
|
337
|
-
// Check if commit has been merged into base branch (state is stale)
|
|
338
|
-
const commitMerged = await isCommitInBranch(commit, baseBranch);
|
|
339
|
-
if (commitMerged) {
|
|
340
|
-
return resolveFixBaseForMergedCommit(working_tree_ref);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Check if working_tree_ref exists
|
|
344
|
-
if (working_tree_ref) {
|
|
345
|
-
const refExists = await gitObjectExists(working_tree_ref);
|
|
346
|
-
if (refExists) {
|
|
347
|
-
// Use working tree ref for precise diff
|
|
348
|
-
return { fixBase: working_tree_ref };
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// working_tree_ref doesn't exist or was gc'd, try commit as fallback
|
|
353
|
-
const commitExists = await gitObjectExists(commit);
|
|
354
|
-
if (commitExists) {
|
|
355
|
-
return {
|
|
356
|
-
fixBase: commit,
|
|
357
|
-
warning: "Session stash was garbage collected, using commit as fallback",
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
// Everything is gone, fall back to base branch
|
|
362
|
-
return { fixBase: null };
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
const COOLDOWN_MS = 60 * 60 * 1000; // 1 hour
|
|
366
|
-
|
|
367
|
-
/**
|
|
368
|
-
* Check if an unhealthy adapter entry is still within the cooldown period.
|
|
369
|
-
* Returns true if marked_at is less than 1 hour ago.
|
|
370
|
-
* Invalid or missing timestamps default to "expired" (returns false).
|
|
371
|
-
*/
|
|
372
|
-
export function isAdapterCoolingDown(entry: UnhealthyAdapter): boolean {
|
|
373
|
-
const markedAt = new Date(entry.marked_at).getTime();
|
|
374
|
-
if (Number.isNaN(markedAt)) return false;
|
|
375
|
-
return Date.now() - markedAt < COOLDOWN_MS;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* Get the unhealthy adapters map from execution state.
|
|
380
|
-
* Returns an empty object if no unhealthy adapters are recorded.
|
|
381
|
-
*/
|
|
382
|
-
export async function getUnhealthyAdapters(
|
|
383
|
-
logDir: string,
|
|
384
|
-
): Promise<Record<string, UnhealthyAdapter>> {
|
|
385
|
-
const statePath = path.join(logDir, EXECUTION_STATE_FILENAME);
|
|
386
|
-
const rawState = await readRawState(statePath);
|
|
387
|
-
return extractUnhealthyAdapters(rawState) ?? {};
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* Read raw state data from the state file.
|
|
392
|
-
* Returns null if the file doesn't exist or is invalid.
|
|
393
|
-
*/
|
|
394
|
-
async function readRawState(
|
|
395
|
-
statePath: string,
|
|
396
|
-
): Promise<Record<string, unknown> | null> {
|
|
397
|
-
try {
|
|
398
|
-
const content = await fs.readFile(statePath, "utf-8");
|
|
399
|
-
return JSON.parse(content) as Record<string, unknown>;
|
|
400
|
-
} catch {
|
|
401
|
-
return null;
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
/**
|
|
406
|
-
* Mark an adapter as unhealthy in the execution state.
|
|
407
|
-
* Reads the current state, upserts the entry, and writes back.
|
|
408
|
-
*/
|
|
409
|
-
export async function markAdapterUnhealthy(
|
|
410
|
-
logDir: string,
|
|
411
|
-
adapterName: string,
|
|
412
|
-
reason: string,
|
|
413
|
-
): Promise<void> {
|
|
414
|
-
await getDebugLogger()?.logAdapterHealthChange(adapterName, false, reason);
|
|
415
|
-
|
|
416
|
-
const statePath = path.join(logDir, EXECUTION_STATE_FILENAME);
|
|
417
|
-
const rawData = (await readRawState(statePath)) ?? {};
|
|
418
|
-
|
|
419
|
-
const adapters =
|
|
420
|
-
(rawData.unhealthy_adapters as Record<string, UnhealthyAdapter>) ?? {};
|
|
421
|
-
adapters[adapterName] = {
|
|
422
|
-
marked_at: new Date().toISOString(),
|
|
423
|
-
reason,
|
|
424
|
-
};
|
|
425
|
-
rawData.unhealthy_adapters = adapters;
|
|
426
|
-
|
|
427
|
-
await fs.mkdir(logDir, { recursive: true });
|
|
428
|
-
await fs.writeFile(statePath, JSON.stringify(rawData, null, 2), "utf-8");
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
/**
|
|
432
|
-
* Mark an adapter as healthy by removing it from the unhealthy list.
|
|
433
|
-
* Reads the current state, removes the entry, and writes back.
|
|
434
|
-
*/
|
|
435
|
-
export async function markAdapterHealthy(
|
|
436
|
-
logDir: string,
|
|
437
|
-
adapterName: string,
|
|
438
|
-
): Promise<void> {
|
|
439
|
-
await getDebugLogger()?.logAdapterHealthChange(adapterName, true);
|
|
440
|
-
|
|
441
|
-
const statePath = path.join(logDir, EXECUTION_STATE_FILENAME);
|
|
442
|
-
const rawData = await readRawState(statePath);
|
|
443
|
-
if (!rawData) return;
|
|
444
|
-
|
|
445
|
-
const adapters = rawData.unhealthy_adapters as
|
|
446
|
-
| Record<string, UnhealthyAdapter>
|
|
447
|
-
| undefined;
|
|
448
|
-
if (!adapters || !(adapterName in adapters)) return;
|
|
449
|
-
|
|
450
|
-
delete adapters[adapterName];
|
|
451
|
-
if (Object.keys(adapters).length === 0) {
|
|
452
|
-
delete rawData.unhealthy_adapters;
|
|
453
|
-
} else {
|
|
454
|
-
rawData.unhealthy_adapters = adapters;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
await fs.writeFile(statePath, JSON.stringify(rawData, null, 2), "utf-8");
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* Delete the execution state file.
|
|
462
|
-
* Used when auto-clean resets state due to context change.
|
|
463
|
-
*/
|
|
464
|
-
export async function deleteExecutionState(logDir: string): Promise<void> {
|
|
465
|
-
try {
|
|
466
|
-
await getDebugLogger()?.logStateDelete();
|
|
467
|
-
const statePath = path.join(logDir, EXECUTION_STATE_FILENAME);
|
|
468
|
-
await fs.rm(statePath, { force: true });
|
|
469
|
-
} catch {
|
|
470
|
-
// Ignore errors
|
|
471
|
-
}
|
|
472
|
-
}
|