@codyswann/lisa 2.277.3 → 2.278.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/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +5 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/setup-project.d.ts.map +1 -1
- package/dist/cli/setup-project.js +12 -0
- package/dist/cli/setup-project.js.map +1 -1
- package/dist/cli/ui-automations-adapters.d.ts +22 -0
- package/dist/cli/ui-automations-adapters.d.ts.map +1 -1
- package/dist/cli/ui-automations-adapters.js +24 -12
- package/dist/cli/ui-automations-adapters.js.map +1 -1
- package/dist/cli/ui-automations.d.ts +1 -0
- package/dist/cli/ui-automations.d.ts.map +1 -1
- package/dist/cli/ui-automations.js +1 -0
- package/dist/cli/ui-automations.js.map +1 -1
- package/dist/cli/ui-cmd.d.ts +4 -1
- package/dist/cli/ui-cmd.d.ts.map +1 -1
- package/dist/cli/ui-cmd.js +72 -17
- package/dist/cli/ui-cmd.js.map +1 -1
- package/dist/cli/ui-confined-project-read.d.ts +22 -0
- package/dist/cli/ui-confined-project-read.d.ts.map +1 -0
- package/dist/cli/ui-confined-project-read.js +38 -0
- package/dist/cli/ui-confined-project-read.js.map +1 -0
- package/dist/cli/ui-deploy-pipeline.d.ts.map +1 -1
- package/dist/cli/ui-deploy-pipeline.js +4 -15
- package/dist/cli/ui-deploy-pipeline.js.map +1 -1
- package/dist/cli/ui-detected-stacks.d.ts +9 -8
- package/dist/cli/ui-detected-stacks.d.ts.map +1 -1
- package/dist/cli/ui-detected-stacks.js +111 -0
- package/dist/cli/ui-detected-stacks.js.map +1 -1
- package/dist/cli/ui-github-repo-gh.d.ts +6 -0
- package/dist/cli/ui-github-repo-gh.d.ts.map +1 -1
- package/dist/cli/ui-github-repo-gh.js.map +1 -1
- package/dist/cli/ui-github-repo-map.d.ts.map +1 -1
- package/dist/cli/ui-github-repo-map.js +26 -1
- package/dist/cli/ui-github-repo-map.js.map +1 -1
- package/dist/cli/ui-setup-readiness-config.d.ts +28 -0
- package/dist/cli/ui-setup-readiness-config.d.ts.map +1 -0
- package/dist/cli/ui-setup-readiness-config.js +116 -0
- package/dist/cli/ui-setup-readiness-config.js.map +1 -0
- package/dist/cli/ui-setup-readiness-contract.d.ts +32 -0
- package/dist/cli/ui-setup-readiness-contract.d.ts.map +1 -0
- package/dist/cli/ui-setup-readiness-contract.js +61 -0
- package/dist/cli/ui-setup-readiness-contract.js.map +1 -0
- package/dist/cli/ui-setup-readiness-install.d.ts +24 -0
- package/dist/cli/ui-setup-readiness-install.d.ts.map +1 -0
- package/dist/cli/ui-setup-readiness-install.js +207 -0
- package/dist/cli/ui-setup-readiness-install.js.map +1 -0
- package/dist/cli/ui-setup-readiness-local.d.ts +40 -0
- package/dist/cli/ui-setup-readiness-local.d.ts.map +1 -0
- package/dist/cli/ui-setup-readiness-local.js +235 -0
- package/dist/cli/ui-setup-readiness-local.js.map +1 -0
- package/dist/cli/ui-setup-readiness-remote.d.ts +30 -0
- package/dist/cli/ui-setup-readiness-remote.d.ts.map +1 -0
- package/dist/cli/ui-setup-readiness-remote.js +108 -0
- package/dist/cli/ui-setup-readiness-remote.js.map +1 -0
- package/dist/cli/ui-setup-readiness.d.ts +52 -0
- package/dist/cli/ui-setup-readiness.d.ts.map +1 -0
- package/dist/cli/ui-setup-readiness.js +125 -0
- package/dist/cli/ui-setup-readiness.js.map +1 -0
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +14 -2
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/health/read-only-fs.d.ts +7 -0
- package/dist/health/read-only-fs.d.ts.map +1 -1
- package/dist/health/read-only-fs.js +40 -1
- package/dist/health/read-only-fs.js.map +1 -1
- package/dist/sync/registry.d.ts +14 -0
- package/dist/sync/registry.d.ts.map +1 -1
- package/dist/sync/registry.js +54 -15
- package/dist/sync/registry.js.map +1 -1
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/scripts/automation-status-codex-adapter.mjs +270 -23
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/scripts/automation-status-codex-adapter.mjs +270 -23
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/scripts/automation-status-codex-adapter.mjs +270 -23
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/scripts/automation-status-codex-adapter.mjs +270 -23
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/scripts/automation-status-codex-adapter.mjs +270 -23
- package/ui/index.html +1 -1
|
@@ -11,9 +11,11 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import fs from "node:fs/promises";
|
|
14
|
+
import { constants as fsConstants } from "node:fs";
|
|
14
15
|
import os from "node:os";
|
|
15
16
|
import path from "node:path";
|
|
16
17
|
import { execFile } from "node:child_process";
|
|
18
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
17
19
|
import { promisify } from "node:util";
|
|
18
20
|
|
|
19
21
|
import { compareAutomationFleet } from "./automation-status-contract-drift.mjs";
|
|
@@ -34,6 +36,12 @@ const RUN_FAILURE_PATTERN =
|
|
|
34
36
|
const NEGATED_FAILURE_PATTERN =
|
|
35
37
|
/\b(no|without)\s+(?:recent\s+)?(?:fail(?:ure|ed)|errors?|exceptions?)\b/i;
|
|
36
38
|
const execFileAsync = promisify(execFile);
|
|
39
|
+
const MAX_SCHEDULER_ENTRIES = 1_000;
|
|
40
|
+
const MAX_MATCHING_AUTOMATIONS = 100;
|
|
41
|
+
const MAX_AUTOMATION_TOML_BYTES = 256 * 1024;
|
|
42
|
+
const MAX_AUTOMATION_MEMORY_BYTES = 512 * 1024;
|
|
43
|
+
const READ_CHUNK_BYTES = 64 * 1024;
|
|
44
|
+
const UNSAFE_SCHEDULER_FILE = "Unsafe Codex scheduler file";
|
|
37
45
|
|
|
38
46
|
/**
|
|
39
47
|
* Git location env vars that, when inherited, override the `-C <dir>` flag and
|
|
@@ -101,7 +109,9 @@ async function execGitWithRetry(args, options, attempts = 4) {
|
|
|
101
109
|
if (!transient || attempt >= attempts) {
|
|
102
110
|
throw error;
|
|
103
111
|
}
|
|
104
|
-
await
|
|
112
|
+
await delay(2 ** attempt * 25, undefined, {
|
|
113
|
+
signal: options?.signal,
|
|
114
|
+
});
|
|
105
115
|
}
|
|
106
116
|
}
|
|
107
117
|
}
|
|
@@ -223,29 +233,149 @@ export async function inspectCodexAutomationFleet(input) {
|
|
|
223
233
|
* @param {{
|
|
224
234
|
* readonly automationsDir?: string
|
|
225
235
|
* readonly automationPrefix: string
|
|
236
|
+
* readonly signal?: AbortSignal
|
|
226
237
|
* }} input
|
|
227
238
|
* @returns {Promise<readonly ObservedCodexAutomation[]>}
|
|
228
239
|
*/
|
|
229
240
|
export async function listCodexAutomations(input) {
|
|
230
|
-
|
|
241
|
+
input.signal?.throwIfAborted();
|
|
242
|
+
const requestedRoot =
|
|
231
243
|
input.automationsDir ?? resolveDefaultCodexAutomationsDir();
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
244
|
+
const automationsRoot = await fs.realpath(requestedRoot);
|
|
245
|
+
input.signal?.throwIfAborted();
|
|
246
|
+
const automationDirs = await listConfinedAutomationDirectories(
|
|
247
|
+
automationsRoot,
|
|
248
|
+
input.automationPrefix,
|
|
249
|
+
input.signal
|
|
250
|
+
);
|
|
251
|
+
return await readCodexAutomations(automationDirs, input.signal);
|
|
252
|
+
}
|
|
235
253
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
254
|
+
/**
|
|
255
|
+
* Enumerate a bounded number of direct, regular scheduler directories.
|
|
256
|
+
* @param {string} automationsRoot Canonical scheduler root.
|
|
257
|
+
* @param {string} automationPrefix Expected repository automation prefix.
|
|
258
|
+
* @param {AbortSignal | undefined} signal Probe cancellation signal.
|
|
259
|
+
* @returns {Promise<readonly string[]>} Canonical matching directories.
|
|
260
|
+
*/
|
|
261
|
+
async function listConfinedAutomationDirectories(
|
|
262
|
+
automationsRoot,
|
|
263
|
+
automationPrefix,
|
|
264
|
+
signal
|
|
265
|
+
) {
|
|
266
|
+
const directory = await fs.opendir(automationsRoot);
|
|
267
|
+
try {
|
|
268
|
+
const names = await readBoundedDirectoryNames(
|
|
269
|
+
directory,
|
|
270
|
+
automationPrefix,
|
|
271
|
+
signal
|
|
272
|
+
);
|
|
273
|
+
const directories = await Promise.all(
|
|
274
|
+
names.map(name => confinedAutomationDirectory(automationsRoot, name))
|
|
275
|
+
);
|
|
276
|
+
signal?.throwIfAborted();
|
|
277
|
+
return directories.toSorted((left, right) => left.localeCompare(right));
|
|
278
|
+
} finally {
|
|
279
|
+
await closeDirectory(directory);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Close a scheduler directory while tolerating runtimes that auto-close it
|
|
285
|
+
* after async iteration.
|
|
286
|
+
* @param {import("node:fs").Dir} directory Open scheduler directory handle.
|
|
287
|
+
* @returns {Promise<void>}
|
|
288
|
+
*/
|
|
289
|
+
async function closeDirectory(directory) {
|
|
290
|
+
try {
|
|
291
|
+
await directory.close();
|
|
292
|
+
} catch (error) {
|
|
293
|
+
if (error?.code !== "ERR_DIR_CLOSED") throw error;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
243
296
|
|
|
244
|
-
|
|
245
|
-
|
|
297
|
+
/**
|
|
298
|
+
* Consume directory entries with total and prefix-match bounds.
|
|
299
|
+
* @param {import("node:fs").Dir} directory Open scheduler directory handle.
|
|
300
|
+
* @param {string} automationPrefix Expected repository automation prefix.
|
|
301
|
+
* @param {AbortSignal | undefined} signal Probe cancellation signal.
|
|
302
|
+
* @param {number} [entryCount] Entries consumed so far.
|
|
303
|
+
* @param {readonly string[]} [matchingNames] Matching directory names.
|
|
304
|
+
* @returns {Promise<readonly string[]>} Bounded matching names.
|
|
305
|
+
*/
|
|
306
|
+
async function readBoundedDirectoryNames(
|
|
307
|
+
directory,
|
|
308
|
+
automationPrefix,
|
|
309
|
+
signal,
|
|
310
|
+
entryCount = 0,
|
|
311
|
+
matchingNames = []
|
|
312
|
+
) {
|
|
313
|
+
signal?.throwIfAborted();
|
|
314
|
+
if (entryCount >= MAX_SCHEDULER_ENTRIES) {
|
|
315
|
+
const overflow = await directory.read();
|
|
316
|
+
if (overflow !== null) {
|
|
317
|
+
throw new Error("Codex scheduler enumeration exceeds entry limit");
|
|
318
|
+
}
|
|
319
|
+
return matchingNames;
|
|
320
|
+
}
|
|
321
|
+
const entry = await directory.read();
|
|
322
|
+
signal?.throwIfAborted();
|
|
323
|
+
if (entry === null) return matchingNames;
|
|
324
|
+
const matches =
|
|
325
|
+
entry.isDirectory() && entry.name.startsWith(automationPrefix);
|
|
326
|
+
const nextNames = matches ? [...matchingNames, entry.name] : matchingNames;
|
|
327
|
+
if (nextNames.length > MAX_MATCHING_AUTOMATIONS) {
|
|
328
|
+
throw new Error("Codex scheduler automation count exceeds limit");
|
|
329
|
+
}
|
|
330
|
+
return await readBoundedDirectoryNames(
|
|
331
|
+
directory,
|
|
332
|
+
automationPrefix,
|
|
333
|
+
signal,
|
|
334
|
+
entryCount + 1,
|
|
335
|
+
nextNames
|
|
246
336
|
);
|
|
337
|
+
}
|
|
247
338
|
|
|
248
|
-
|
|
339
|
+
/**
|
|
340
|
+
* Verify one direct scheduler entry remains a confined real directory.
|
|
341
|
+
* @param {string} automationsRoot Canonical scheduler root.
|
|
342
|
+
* @param {string} name Direct entry name.
|
|
343
|
+
* @returns {Promise<string>} Canonical automation directory.
|
|
344
|
+
*/
|
|
345
|
+
async function confinedAutomationDirectory(automationsRoot, name) {
|
|
346
|
+
const candidate = path.join(automationsRoot, name);
|
|
347
|
+
const stat = await fs.lstat(candidate);
|
|
348
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
349
|
+
throw new Error("Unsafe Codex scheduler directory");
|
|
350
|
+
}
|
|
351
|
+
const actual = await fs.realpath(candidate);
|
|
352
|
+
if (!actual.startsWith(`${automationsRoot}${path.sep}`)) {
|
|
353
|
+
throw new Error("Codex scheduler directory escapes root");
|
|
354
|
+
}
|
|
355
|
+
return actual;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Read scheduler entries sequentially to bound simultaneously open files.
|
|
360
|
+
* @param {readonly string[]} automationDirs Canonical automation directories.
|
|
361
|
+
* @param {AbortSignal | undefined} signal Probe cancellation signal.
|
|
362
|
+
* @param {number} [index] Next directory index.
|
|
363
|
+
* @param {readonly ObservedCodexAutomation[]} [results] Completed observations.
|
|
364
|
+
* @returns {Promise<readonly ObservedCodexAutomation[]>} Observations.
|
|
365
|
+
*/
|
|
366
|
+
async function readCodexAutomations(
|
|
367
|
+
automationDirs,
|
|
368
|
+
signal,
|
|
369
|
+
index = 0,
|
|
370
|
+
results = []
|
|
371
|
+
) {
|
|
372
|
+
signal?.throwIfAborted();
|
|
373
|
+
if (index >= automationDirs.length) return results;
|
|
374
|
+
const observation = await readCodexAutomation(automationDirs[index], signal);
|
|
375
|
+
return await readCodexAutomations(automationDirs, signal, index + 1, [
|
|
376
|
+
...results,
|
|
377
|
+
observation,
|
|
378
|
+
]);
|
|
249
379
|
}
|
|
250
380
|
|
|
251
381
|
/**
|
|
@@ -361,16 +491,130 @@ function findLatestAutomationMemoryBlock(lines) {
|
|
|
361
491
|
};
|
|
362
492
|
}
|
|
363
493
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
494
|
+
/**
|
|
495
|
+
* Read one stable bounded file without following a scheduler symlink or FIFO.
|
|
496
|
+
* @param {string} automationDir Canonical automation directory.
|
|
497
|
+
* @param {string} filename Direct scheduler filename.
|
|
498
|
+
* @param {number} maximumBytes Maximum allowed bytes.
|
|
499
|
+
* @param {AbortSignal | undefined} signal Probe cancellation signal.
|
|
500
|
+
* @param {boolean} [optional] Whether a missing file resolves to undefined.
|
|
501
|
+
* @returns {Promise<string | undefined>} Strict UTF-8 contents.
|
|
502
|
+
*/
|
|
503
|
+
async function readConfinedSchedulerText(
|
|
504
|
+
automationDir,
|
|
505
|
+
filename,
|
|
506
|
+
maximumBytes,
|
|
507
|
+
signal,
|
|
508
|
+
optional = false
|
|
509
|
+
) {
|
|
510
|
+
signal?.throwIfAborted();
|
|
511
|
+
const target = path.join(automationDir, filename);
|
|
512
|
+
const before = await fs.lstat(target).catch(error => {
|
|
513
|
+
if (optional && error?.code === "ENOENT") return null;
|
|
514
|
+
throw error;
|
|
515
|
+
});
|
|
516
|
+
if (before === null) return undefined;
|
|
517
|
+
assertBoundedSchedulerFile(before, maximumBytes);
|
|
518
|
+
const actual = await fs.realpath(target);
|
|
519
|
+
if (!actual.startsWith(`${automationDir}${path.sep}`)) {
|
|
520
|
+
throw new Error("Codex scheduler file escapes automation directory");
|
|
521
|
+
}
|
|
522
|
+
const handle = await fs.open(
|
|
523
|
+
target,
|
|
524
|
+
fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK
|
|
525
|
+
);
|
|
526
|
+
try {
|
|
527
|
+
const opened = await handle.stat();
|
|
528
|
+
assertBoundedSchedulerFile(opened, maximumBytes);
|
|
529
|
+
if (opened.dev !== before.dev || opened.ino !== before.ino) {
|
|
530
|
+
throw new Error("Codex scheduler file changed during inspection");
|
|
531
|
+
}
|
|
532
|
+
const bytes = await readBoundedSchedulerFile(handle, maximumBytes, signal);
|
|
533
|
+
const after = await handle.stat();
|
|
534
|
+
if (after.size !== opened.size || bytes.length !== after.size) {
|
|
535
|
+
throw new Error("Codex scheduler file changed during read");
|
|
536
|
+
}
|
|
537
|
+
signal?.throwIfAborted();
|
|
538
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
539
|
+
} finally {
|
|
540
|
+
await handle.close();
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Reject non-regular and oversized scheduler files before opening them.
|
|
546
|
+
* @param {import("node:fs").Stats} stat Scheduler file metadata.
|
|
547
|
+
* @param {number} maximumBytes Maximum allowed bytes.
|
|
548
|
+
* @returns {void}
|
|
549
|
+
*/
|
|
550
|
+
function assertBoundedSchedulerFile(stat, maximumBytes) {
|
|
551
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
552
|
+
throw new Error(UNSAFE_SCHEDULER_FILE);
|
|
553
|
+
}
|
|
554
|
+
if (stat.size > maximumBytes) {
|
|
555
|
+
throw new Error("Codex scheduler file exceeds size limit");
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Read at most maximum plus one bytes from an already confined file handle.
|
|
561
|
+
* @param {import("node:fs/promises").FileHandle} handle Open file handle.
|
|
562
|
+
* @param {number} maximumBytes Maximum allowed bytes.
|
|
563
|
+
* @param {AbortSignal | undefined} signal Probe cancellation signal.
|
|
564
|
+
* @param {number} [offset] Current file offset.
|
|
565
|
+
* @returns {Promise<Buffer>} Bounded bytes.
|
|
566
|
+
*/
|
|
567
|
+
async function readBoundedSchedulerFile(
|
|
568
|
+
handle,
|
|
569
|
+
maximumBytes,
|
|
570
|
+
signal,
|
|
571
|
+
offset = 0
|
|
572
|
+
) {
|
|
573
|
+
signal?.throwIfAborted();
|
|
574
|
+
const remaining = maximumBytes + 1 - offset;
|
|
575
|
+
if (remaining <= 0) {
|
|
576
|
+
throw new Error("Codex scheduler file exceeds size limit");
|
|
577
|
+
}
|
|
578
|
+
const chunk = Buffer.allocUnsafe(Math.min(READ_CHUNK_BYTES, remaining));
|
|
579
|
+
const { bytesRead } = await handle.read(chunk, 0, chunk.length, offset);
|
|
580
|
+
signal?.throwIfAborted();
|
|
581
|
+
if (bytesRead === 0) return Buffer.alloc(0);
|
|
582
|
+
const current = chunk.subarray(0, bytesRead);
|
|
583
|
+
const tail = await readBoundedSchedulerFile(
|
|
584
|
+
handle,
|
|
585
|
+
maximumBytes,
|
|
586
|
+
signal,
|
|
587
|
+
offset + bytesRead
|
|
588
|
+
);
|
|
589
|
+
return Buffer.concat([current, tail], bytesRead + tail.length);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Read one confined Codex automation contract and optional memory.
|
|
594
|
+
* @param {string} automationDir Canonical automation directory.
|
|
595
|
+
* @param {AbortSignal | undefined} signal Probe cancellation signal.
|
|
596
|
+
* @returns {Promise<ObservedCodexAutomation>} Normalized observation.
|
|
597
|
+
*/
|
|
598
|
+
async function readCodexAutomation(automationDir, signal) {
|
|
599
|
+
const tomlContent = await readConfinedSchedulerText(
|
|
600
|
+
automationDir,
|
|
601
|
+
"automation.toml",
|
|
602
|
+
MAX_AUTOMATION_TOML_BYTES,
|
|
603
|
+
signal
|
|
604
|
+
);
|
|
368
605
|
const automation = parseAutomationToml(tomlContent);
|
|
369
|
-
const memoryContent =
|
|
606
|
+
const memoryContent =
|
|
607
|
+
(await readConfinedSchedulerText(
|
|
608
|
+
automationDir,
|
|
609
|
+
"memory.md",
|
|
610
|
+
MAX_AUTOMATION_MEMORY_BYTES,
|
|
611
|
+
signal,
|
|
612
|
+
true
|
|
613
|
+
)) ?? "";
|
|
370
614
|
const memory = parseCodexAutomationMemory(memoryContent);
|
|
371
615
|
const cwd = Array.isArray(automation.cwds) ? automation.cwds[0] : null;
|
|
372
616
|
const normalizedCwd = typeof cwd === "string" ? cwd : null;
|
|
373
|
-
const cwdHealth = await inspectAutomationCwd(normalizedCwd);
|
|
617
|
+
const cwdHealth = await inspectAutomationCwd(normalizedCwd, signal);
|
|
374
618
|
|
|
375
619
|
return {
|
|
376
620
|
automationId:
|
|
@@ -546,7 +790,8 @@ function classifyAutomationRunSignal(input) {
|
|
|
546
790
|
return null;
|
|
547
791
|
}
|
|
548
792
|
|
|
549
|
-
async function inspectAutomationCwd(cwd) {
|
|
793
|
+
async function inspectAutomationCwd(cwd, signal) {
|
|
794
|
+
signal?.throwIfAborted();
|
|
550
795
|
if (!cwd) {
|
|
551
796
|
return {
|
|
552
797
|
status: "missing",
|
|
@@ -560,6 +805,7 @@ async function inspectAutomationCwd(cwd) {
|
|
|
560
805
|
}
|
|
561
806
|
throw error;
|
|
562
807
|
});
|
|
808
|
+
signal?.throwIfAborted();
|
|
563
809
|
|
|
564
810
|
if (!stat) {
|
|
565
811
|
return {
|
|
@@ -578,7 +824,7 @@ async function inspectAutomationCwd(cwd) {
|
|
|
578
824
|
try {
|
|
579
825
|
const { stdout } = await execGitWithRetry(
|
|
580
826
|
["-C", cwd, "rev-parse", "--is-inside-work-tree", "--is-bare-repository"],
|
|
581
|
-
{ timeout: 5000 }
|
|
827
|
+
{ timeout: 5000, signal }
|
|
582
828
|
);
|
|
583
829
|
const [insideWorkTree, bareRepository] = stdout.trim().split(/\r?\n/);
|
|
584
830
|
|
|
@@ -601,6 +847,7 @@ async function inspectAutomationCwd(cwd) {
|
|
|
601
847
|
summary: `${cwd} is a valid Git work tree`,
|
|
602
848
|
};
|
|
603
849
|
} catch (error) {
|
|
850
|
+
signal?.throwIfAborted();
|
|
604
851
|
return {
|
|
605
852
|
status: "git_error",
|
|
606
853
|
summary: `git could not inspect ${cwd}: ${String(error?.message ?? error)}`,
|