@orkestrel/scaffold 0.0.77 → 0.0.78
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/agents/skills/orkestrel-dispatch/scripts/bench.js +204 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/brief.js +102 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/cite.js +95 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/helpers.js +207 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/launch.js +108 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/login.js +114 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/result.js +108 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/sweep.js +156 -0
- package/dist/agents/skills/orkestrel-harden/scripts/discovery.js +196 -0
- package/dist/agents/skills/orkestrel-publish/scripts/compare.js +206 -0
- package/dist/agents/skills/orkestrel-publish/scripts/pins.js +93 -0
- package/dist/agents/skills/orkestrel-publish/scripts/wave.js +458 -0
- package/dist/agents/skills/orkestrel-publish/scripts/window.js +188 -0
- package/dist/agents/skills/orkestrel-scout/scripts/map.js +300 -0
- package/dist/agents/templates/brief.md +55 -0
- package/dist/bin/main.js +4 -2
- package/dist/bin/main.js.map +1 -1
- package/dist/host/AGENTS.md +77 -135
- package/dist/host/agents/orchestration.md +147 -998
- package/dist/host/agents/skills/enterprise-bootstrap/SKILL.md +2 -2
- package/dist/host/agents/skills/enterprise-bootstrap/references/inspection.md +1 -1
- package/dist/host/agents/skills/{orkestrel-align-packages → orkestrel-align}/SKILL.md +6 -13
- package/dist/host/agents/skills/{orkestrel-align-packages → orkestrel-align}/agents/openai.yaml +1 -1
- package/dist/host/agents/skills/{orkestrel-align-packages → orkestrel-align}/references/fleet.md +5 -7
- package/dist/host/agents/skills/{orkestrel-build-application → orkestrel-build}/SKILL.md +11 -22
- package/dist/host/agents/skills/{orkestrel-build-application → orkestrel-build}/agents/openai.yaml +1 -1
- package/dist/host/agents/skills/orkestrel-debrief/SKILL.md +8 -16
- package/dist/host/agents/skills/orkestrel-debrief/references/instruction-audit.md +3 -3
- package/dist/host/agents/skills/orkestrel-debrief/references/retention.md +13 -13
- package/dist/host/agents/skills/orkestrel-dispatch/SKILL.md +61 -0
- package/dist/host/agents/skills/orkestrel-dispatch/agents/openai.yaml +4 -0
- package/dist/host/agents/skills/orkestrel-dispatch/references/bench.md +25 -0
- package/dist/host/agents/skills/orkestrel-dispatch/references/launch.md +32 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/bench.ts +259 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/brief.ts +110 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/cite.ts +115 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/helpers.ts +239 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/launch.ts +124 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/login.ts +123 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/result.ts +129 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/sweep.ts +157 -0
- package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +42 -193
- package/dist/host/agents/skills/orkestrel-falsify/references/brief.md +38 -108
- package/dist/host/agents/skills/orkestrel-falsify/references/reconcile.md +35 -134
- package/dist/host/agents/skills/{orkestrel-harden-package → orkestrel-harden}/SKILL.md +10 -14
- package/dist/host/agents/skills/{orkestrel-harden-package → orkestrel-harden}/agents/openai.yaml +1 -1
- package/dist/host/agents/skills/{orkestrel-harden-package → orkestrel-harden}/references/hardening.md +3 -4
- package/dist/host/agents/skills/orkestrel-harden/scripts/discovery.ts +228 -0
- package/dist/host/agents/skills/{orkestrel-prove-journey → orkestrel-journey}/SKILL.md +15 -23
- package/dist/host/agents/skills/orkestrel-journey/agents/openai.yaml +4 -0
- package/dist/host/agents/skills/{orkestrel-prove-journey → orkestrel-journey}/references/captures.md +1 -1
- package/dist/host/agents/skills/{orkestrel-polish-surface → orkestrel-polish}/SKILL.md +25 -33
- package/dist/host/agents/skills/{orkestrel-polish-surface → orkestrel-polish}/agents/openai.yaml +1 -1
- package/dist/host/agents/skills/{orkestrel-polish-surface → orkestrel-polish}/references/capture-harness.md +3 -3
- package/dist/host/agents/skills/orkestrel-publish/SKILL.md +33 -20
- package/dist/host/agents/skills/orkestrel-publish/references/release.md +39 -0
- package/dist/host/agents/skills/orkestrel-publish/references/wave.md +22 -21
- package/dist/host/agents/skills/orkestrel-publish/references/window.md +27 -14
- package/dist/host/agents/skills/orkestrel-publish/scripts/compare.ts +220 -0
- package/dist/host/agents/skills/orkestrel-publish/scripts/pins.ts +114 -0
- package/dist/host/agents/skills/orkestrel-publish/scripts/wave.ts +629 -0
- package/dist/host/agents/skills/orkestrel-publish/scripts/window.ts +242 -0
- package/dist/host/agents/skills/orkestrel-scout/SKILL.md +28 -0
- package/dist/host/agents/skills/orkestrel-scout/agents/openai.yaml +4 -0
- package/dist/host/agents/skills/orkestrel-scout/scripts/map.ts +352 -0
- package/dist/host/agents/templates/brief.md +21 -142
- package/dist/host/agents/transports/claude-cli.md +21 -0
- package/dist/host/agents/transports/codex.md +38 -159
- package/dist/host/agents/transports/cursor.md +16 -65
- package/dist/host/claude/AGENTS.md +38 -0
- package/dist/host/claude/agents/analyst.md +14 -53
- package/dist/host/claude/agents/astra.md +26 -0
- package/dist/host/claude/agents/builder.md +14 -30
- package/dist/host/claude/agents/checker.md +13 -57
- package/dist/host/claude/agents/distiller.md +11 -26
- package/dist/host/claude/agents/grok.md +12 -35
- package/dist/host/claude/agents/opus.md +14 -30
- package/dist/host/claude/agents/planner.md +10 -44
- package/dist/host/claude/agents/researcher.md +11 -30
- package/dist/host/claude/agents/reviewer.md +11 -95
- package/dist/host/claude/agents/scout.md +9 -23
- package/dist/host/claude/agents/verifier.md +15 -33
- package/dist/host/claude/rules/documentation.md +8 -2
- package/dist/host/claude/rules/portability.md +7 -1
- package/dist/host/claude/rules/quality.md +36 -96
- package/dist/host/claude/rules/styles.md +3 -0
- package/dist/host/claude/rules/tests.md +6 -3
- package/dist/host/claude/rules/workspace.md +19 -15
- package/dist/host/claude/rules/writing.md +57 -108
- package/dist/host/claude/settings.json +5 -3
- package/dist/host/claude/skills/enterprise-bootstrap/SKILL.md +1 -1
- package/dist/host/claude/skills/{orkestrel-align-packages → orkestrel-align}/SKILL.md +2 -2
- package/dist/host/claude/skills/{orkestrel-build-application → orkestrel-build}/SKILL.md +2 -2
- package/dist/host/claude/skills/orkestrel-dispatch/SKILL.md +11 -0
- package/dist/host/claude/skills/orkestrel-falsify/SKILL.md +2 -1
- package/dist/host/claude/skills/{orkestrel-harden-package → orkestrel-harden}/SKILL.md +2 -2
- package/dist/host/claude/skills/{orkestrel-prove-journey → orkestrel-journey}/SKILL.md +2 -2
- package/dist/host/claude/skills/orkestrel-polish/SKILL.md +12 -0
- package/dist/host/claude/skills/orkestrel-scout/SKILL.md +11 -0
- package/dist/host/codex/agents/analyst.toml +14 -31
- package/dist/host/codex/agents/astra.toml +25 -0
- package/dist/host/codex/agents/builder.toml +13 -20
- package/dist/host/codex/agents/checker.toml +13 -27
- package/dist/host/codex/agents/distiller.toml +9 -22
- package/dist/host/codex/agents/grok.toml +11 -30
- package/dist/host/codex/agents/opus.toml +14 -22
- package/dist/host/codex/agents/orkestrel.toml +1 -1
- package/dist/host/codex/agents/planner.toml +11 -28
- package/dist/host/codex/agents/researcher.toml +10 -22
- package/dist/host/codex/agents/reviewer.toml +11 -27
- package/dist/host/codex/agents/scout.toml +11 -17
- package/dist/host/codex/agents/verifier.toml +16 -12
- package/dist/host/codex/config.toml +18 -21
- package/dist/host/cursor/mcp.json +0 -4
- package/dist/host/cursor/rules/orchestration.mdc +12 -20
- package/dist/host/dotfiles/mcp.json +0 -4
- package/dist/host/dotfiles/oxlintrc.json +7 -0
- package/dist/host/guides/probe.md +9 -9
- package/dist/host/guides/scaffold.md +117 -71
- package/dist/host/guides/test.md +1 -1
- package/dist/host/manifest.json +321 -184
- package/dist/host/scripts/codex.sh +0 -0
- package/dist/host/scripts/cursor.sh +0 -0
- package/dist/host/scripts/deps.sh +0 -0
- package/dist/host/scripts/ollama.sh +0 -0
- package/dist/host/tests/config.test.ts +68 -46
- package/dist/host/tests/policy.test.ts +1 -5
- package/dist/host/tests/setupPolicy.ts +179 -4
- package/dist/src/core/index.cjs +255 -84
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +94 -29
- package/dist/src/core/index.d.ts +94 -29
- package/dist/src/core/index.js +253 -85
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +55 -9
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +29 -4
- package/dist/src/server/index.d.ts +29 -4
- package/dist/src/server/index.js +56 -11
- package/dist/src/server/index.js.map +1 -1
- package/package.json +15 -11
- package/dist/host/CLAUDE.md +0 -61
- package/dist/host/agents/skills/orkestrel-prove-journey/agents/openai.yaml +0 -4
- package/dist/host/agents/transports/claude.md +0 -49
- package/dist/host/claude/agents/application.md +0 -36
- package/dist/host/claude/agents/sol.md +0 -61
- package/dist/host/claude/skills/orkestrel-polish-surface/SKILL.md +0 -12
- package/dist/host/codex/agents/application.toml +0 -25
- package/dist/host/codex/agents/sol.toml +0 -19
- /package/dist/host/agents/skills/{orkestrel-align-packages → orkestrel-align}/references/integration.md +0 -0
- /package/dist/host/agents/skills/{orkestrel-harden-package → orkestrel-harden}/references/centralization.md +0 -0
- /package/dist/host/agents/skills/{orkestrel-harden-package → orkestrel-harden}/references/contract.md +0 -0
- /package/dist/host/agents/skills/{orkestrel-harden-package → orkestrel-harden}/references/research.md +0 -0
- /package/dist/host/agents/skills/{orkestrel-prove-journey → orkestrel-journey}/references/decide.md +0 -0
- /package/dist/host/agents/skills/{orkestrel-prove-journey → orkestrel-journey}/references/layer.md +0 -0
- /package/dist/host/agents/skills/{orkestrel-prove-journey → orkestrel-journey}/references/statechart.md +0 -0
- /package/dist/host/agents/skills/{orkestrel-prove-journey → orkestrel-journey}/references/styles.md +0 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Journaled launch of a bench exec or any command that outlives a turn. Run from the checkout root:
|
|
2
|
+
// node .agents/skills/orkestrel-dispatch/scripts/launch.ts --journal tmp/codex/UNIT.jsonl --errors tmp/codex/UNIT.err --cap 1500 [--status] -- codex exec --json ...
|
|
3
|
+
// The command's stdout goes to --journal and its stderr to --errors; stdin is closed. Arguments after
|
|
4
|
+
// `--` pass to the command verbatim, so no shell quoting is involved. --cap is in seconds: when it
|
|
5
|
+
// expires the process tree is killed and the errors file records `capped=true`. --status records
|
|
6
|
+
// `git status --porcelain` before and after beside the journal, which is how a read-only lane's
|
|
7
|
+
// containment is checked. Stdout carries up to two JSON lines. The spawn line
|
|
8
|
+
// `{"pid","journal","errors"}` prints as soon as the command starts, and the pid, as decimal digits
|
|
9
|
+
// and a newline, goes to `<journal>.pid`. A pid file an earlier run left on the same journal is
|
|
10
|
+
// removed before the spawn, so neither appears when the command cannot start. The summary line
|
|
11
|
+
// prints last, after the command exits, and the errors file ends with `exit=<code>`. At the cap,
|
|
12
|
+
// Windows kills the process tree; any other host kills the command alone, not its descendants.
|
|
13
|
+
// The launcher's own flags are read before `--` alone. Exit: the command's code; 124 when capped;
|
|
14
|
+
// 127 when the command could not start (the errors file names why); 64 on usage, including a
|
|
15
|
+
// journal and errors path that are the same file and a cap that is not a number of seconds.
|
|
16
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
17
|
+
import { appendFileSync, closeSync, mkdirSync, openSync, rmSync, writeFileSync } from 'node:fs';
|
|
18
|
+
import { dirname } from 'node:path';
|
|
19
|
+
import { readMissingFlags, readOption } from "./helpers.js";
|
|
20
|
+
function readStatus() {
|
|
21
|
+
const result = spawnSync('git', ['status', '--porcelain'], { encoding: 'utf8' });
|
|
22
|
+
return result.status === 0 ? result.stdout : `git status failed: ${result.stderr}`;
|
|
23
|
+
}
|
|
24
|
+
function killTree(pid) {
|
|
25
|
+
if (process.platform === 'win32') {
|
|
26
|
+
spawnSync('taskkill', ['/pid', String(pid), '/T', '/F'], { stdio: 'ignore' });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
process.kill(pid, 'SIGKILL');
|
|
31
|
+
}
|
|
32
|
+
catch { }
|
|
33
|
+
}
|
|
34
|
+
function waitForExit(child) {
|
|
35
|
+
return new Promise((resolve) => {
|
|
36
|
+
child.on('error', (error) => resolve({ code: null, signal: null, error: error.message }));
|
|
37
|
+
child.on('close', (code, signal) => resolve({ code, signal, error: undefined }));
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
async function main(argv) {
|
|
41
|
+
const separator = argv.indexOf('--');
|
|
42
|
+
// The launcher's own flags end at the separator; everything after it belongs to the command.
|
|
43
|
+
const own = separator === -1 ? argv : argv.slice(0, separator);
|
|
44
|
+
const journal = readOption(own, '--journal');
|
|
45
|
+
const errors = readOption(own, '--errors');
|
|
46
|
+
const cap = readMissingFlags(own, ['--cap']).length > 0
|
|
47
|
+
? Number.NaN
|
|
48
|
+
: Number(readOption(own, '--cap') ?? '0');
|
|
49
|
+
const [command, ...args] = separator === -1 ? [] : argv.slice(separator + 1);
|
|
50
|
+
if (separator === -1 ||
|
|
51
|
+
journal === undefined ||
|
|
52
|
+
errors === undefined ||
|
|
53
|
+
journal === errors ||
|
|
54
|
+
!Number.isFinite(cap) ||
|
|
55
|
+
cap < 0 ||
|
|
56
|
+
command === undefined ||
|
|
57
|
+
command === '') {
|
|
58
|
+
console.error('usage: launch.ts --journal PATH --errors PATH --cap SECONDS [--status] -- COMMAND [ARGS...]; the journal and the errors file differ, and the cap is a number of seconds (0 for none)');
|
|
59
|
+
return 64;
|
|
60
|
+
}
|
|
61
|
+
const status = own.includes('--status');
|
|
62
|
+
mkdirSync(dirname(journal), { recursive: true });
|
|
63
|
+
mkdirSync(dirname(errors), { recursive: true });
|
|
64
|
+
if (status)
|
|
65
|
+
writeFileSync(`${journal}.status-before.txt`, readStatus());
|
|
66
|
+
const journalFd = openSync(journal, 'w');
|
|
67
|
+
const errorsFd = openSync(errors, 'w');
|
|
68
|
+
rmSync(`${journal}.pid`, { force: true });
|
|
69
|
+
const started = Date.now();
|
|
70
|
+
const child = spawn(command, args, { stdio: ['ignore', journalFd, errorsFd], windowsHide: true });
|
|
71
|
+
if (child.pid !== undefined) {
|
|
72
|
+
writeFileSync(`${journal}.pid`, `${child.pid}\n`);
|
|
73
|
+
console.log(JSON.stringify({ pid: child.pid, journal, errors }));
|
|
74
|
+
}
|
|
75
|
+
let capped = false;
|
|
76
|
+
const timer = cap > 0
|
|
77
|
+
? setTimeout(() => {
|
|
78
|
+
capped = true;
|
|
79
|
+
if (child.pid !== undefined)
|
|
80
|
+
killTree(child.pid);
|
|
81
|
+
}, cap * 1000)
|
|
82
|
+
: undefined;
|
|
83
|
+
const exit = await waitForExit(child);
|
|
84
|
+
if (timer !== undefined)
|
|
85
|
+
clearTimeout(timer);
|
|
86
|
+
closeSync(journalFd);
|
|
87
|
+
closeSync(errorsFd);
|
|
88
|
+
const durationMs = Date.now() - started;
|
|
89
|
+
appendFileSync(errors, `${exit.error === undefined ? '' : `launch: the command could not start: ${exit.error}\n`}\nexit=${exit.error === undefined ? (exit.code ?? 'null') : '127'} signal=${exit.signal ?? 'none'} capped=${capped} duration_ms=${durationMs}\n`);
|
|
90
|
+
if (status)
|
|
91
|
+
writeFileSync(`${journal}.status-after.txt`, readStatus());
|
|
92
|
+
console.log(JSON.stringify({
|
|
93
|
+
pid: child.pid,
|
|
94
|
+
exit: exit.error === undefined ? exit.code : 127,
|
|
95
|
+
signal: exit.signal,
|
|
96
|
+
capped,
|
|
97
|
+
durationMs,
|
|
98
|
+
journal,
|
|
99
|
+
errors,
|
|
100
|
+
error: exit.error,
|
|
101
|
+
}));
|
|
102
|
+
if (exit.error !== undefined)
|
|
103
|
+
return 127;
|
|
104
|
+
return capped ? 124 : (exit.code ?? 1);
|
|
105
|
+
}
|
|
106
|
+
main(process.argv.slice(2)).then((code) => {
|
|
107
|
+
process.exitCode = code;
|
|
108
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// Recover a bench login. Run from the checkout root:
|
|
2
|
+
// node .agents/skills/orkestrel-dispatch/scripts/login.ts --codex [--cap SECONDS]
|
|
3
|
+
// node .agents/skills/orkestrel-dispatch/scripts/login.ts --claude
|
|
4
|
+
// --codex reads `codex login status`; when it reports no login, the script runs
|
|
5
|
+
// `codex login --device-auth` with its output journaled to tmp/codex/login.log, prints the
|
|
6
|
+
// verification URL and the one-time code the moment the journal carries them, polls
|
|
7
|
+
// `codex login status` every 5 seconds until it answers or the cap (default 600 seconds) ends, then
|
|
8
|
+
// kills the login by its pid. --claude reads `claude auth status` and prints it. The JSON summary
|
|
9
|
+
// carries `live`; exit 0 when the bench is logged in, 3 when it is not, 64 on usage.
|
|
10
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
11
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync } from 'node:fs';
|
|
12
|
+
import { readMissingFlags, readOption } from "./helpers.js";
|
|
13
|
+
const JOURNAL = 'tmp/codex/login.log';
|
|
14
|
+
const POLL_MS = 5_000;
|
|
15
|
+
const URL_PATTERN = /https?:\/\/[^\s"'<>]+/u;
|
|
16
|
+
const CODE_PATTERN = /\b([A-Z0-9]{4,5}-[A-Z0-9]{4,5})\b/u;
|
|
17
|
+
function readStatus(command, args) {
|
|
18
|
+
const result = spawnSync(command, args, { encoding: 'utf8', windowsHide: true });
|
|
19
|
+
if (result.error !== undefined)
|
|
20
|
+
return { live: false, text: `${command} could not start: ${result.error.message}` };
|
|
21
|
+
return { live: result.status === 0, text: `${result.stdout ?? ''}${result.stderr ?? ''}`.trim() };
|
|
22
|
+
}
|
|
23
|
+
function killTree(pid) {
|
|
24
|
+
if (process.platform === 'win32') {
|
|
25
|
+
spawnSync('taskkill', ['/pid', String(pid), '/T', '/F'], { stdio: 'ignore' });
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
process.kill(pid, 'SIGTERM');
|
|
30
|
+
}
|
|
31
|
+
catch { }
|
|
32
|
+
}
|
|
33
|
+
function waitFor(ms) {
|
|
34
|
+
return new Promise((resolve) => {
|
|
35
|
+
setTimeout(resolve, ms);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
async function loginCodex(capSeconds) {
|
|
39
|
+
const before = readStatus('codex', ['login', 'status']);
|
|
40
|
+
if (before.live) {
|
|
41
|
+
console.log(JSON.stringify({ bench: 'codex', live: true, status: before.text }));
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
mkdirSync('tmp/codex', { recursive: true });
|
|
45
|
+
const journal = openSync(JOURNAL, 'w');
|
|
46
|
+
const started = Date.now();
|
|
47
|
+
const child = spawn('codex', ['login', '--device-auth'], {
|
|
48
|
+
stdio: ['ignore', journal, journal],
|
|
49
|
+
windowsHide: true,
|
|
50
|
+
});
|
|
51
|
+
let url;
|
|
52
|
+
let code;
|
|
53
|
+
let live = false;
|
|
54
|
+
while (Date.now() - started < capSeconds * 1000) {
|
|
55
|
+
await waitFor(POLL_MS);
|
|
56
|
+
const text = existsSync(JOURNAL) ? readFileSync(JOURNAL, 'utf8') : '';
|
|
57
|
+
if (url === undefined) {
|
|
58
|
+
url = text.match(URL_PATTERN)?.[0];
|
|
59
|
+
if (url !== undefined)
|
|
60
|
+
console.error(`login: open ${url}`);
|
|
61
|
+
}
|
|
62
|
+
if (code === undefined) {
|
|
63
|
+
code = text.match(CODE_PATTERN)?.[1];
|
|
64
|
+
if (code !== undefined)
|
|
65
|
+
console.error(`login: enter the code ${code}`);
|
|
66
|
+
}
|
|
67
|
+
if (child.exitCode !== null && readStatus('codex', ['login', 'status']).live) {
|
|
68
|
+
live = true;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
if (child.exitCode === null && readStatus('codex', ['login', 'status']).live) {
|
|
72
|
+
live = true;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (child.exitCode === null && child.pid !== undefined)
|
|
77
|
+
killTree(child.pid);
|
|
78
|
+
closeSync(journal);
|
|
79
|
+
console.log(JSON.stringify({
|
|
80
|
+
bench: 'codex',
|
|
81
|
+
live,
|
|
82
|
+
url,
|
|
83
|
+
code,
|
|
84
|
+
durationMs: Date.now() - started,
|
|
85
|
+
journal: JOURNAL,
|
|
86
|
+
status: readStatus('codex', ['login', 'status']).text,
|
|
87
|
+
}));
|
|
88
|
+
return live ? 0 : 3;
|
|
89
|
+
}
|
|
90
|
+
function loginClaude() {
|
|
91
|
+
const status = readStatus('claude', ['auth', 'status']);
|
|
92
|
+
console.log(JSON.stringify({ bench: 'claude', live: status.live, status: status.text }));
|
|
93
|
+
return status.live ? 0 : 3;
|
|
94
|
+
}
|
|
95
|
+
async function main(argv) {
|
|
96
|
+
const missing = readMissingFlags(argv, ['--cap']);
|
|
97
|
+
if (missing.length > 0) {
|
|
98
|
+
console.error(`login: ${missing.join(', ')} given with no value`);
|
|
99
|
+
return 64;
|
|
100
|
+
}
|
|
101
|
+
if (argv.includes('--codex') && argv.includes('--claude')) {
|
|
102
|
+
console.error('usage: login.ts names one bench: --codex [--cap SECONDS] | --claude');
|
|
103
|
+
return 64;
|
|
104
|
+
}
|
|
105
|
+
if (argv.includes('--codex'))
|
|
106
|
+
return loginCodex(Number(readOption(argv, '--cap') ?? 600));
|
|
107
|
+
if (argv.includes('--claude'))
|
|
108
|
+
return loginClaude();
|
|
109
|
+
console.error('usage: login.ts --codex [--cap SECONDS] | --claude');
|
|
110
|
+
return 64;
|
|
111
|
+
}
|
|
112
|
+
main(process.argv.slice(2)).then((code) => {
|
|
113
|
+
process.exitCode = code;
|
|
114
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Read a bench lane's answer, session id, and liveness from its journal. Run from the checkout root:
|
|
2
|
+
// node .agents/skills/orkestrel-dispatch/scripts/result.ts --cursor tmp/cursor/UNIT.jsonl [--out tmp/cursor/UNIT-result.md]
|
|
3
|
+
// node .agents/skills/orkestrel-dispatch/scripts/result.ts --claude tmp/claude/UNIT.jsonl [--out tmp/claude/UNIT-result.md]
|
|
4
|
+
// node .agents/skills/orkestrel-dispatch/scripts/result.ts --codex tmp/codex/UNIT.jsonl [--out tmp/codex/UNIT-result.md]
|
|
5
|
+
// A Cursor or Claude journal is the `--output-format stream-json` stream: the `init` event carries
|
|
6
|
+
// the session id and the `result` event carries the answer. A Codex journal is the `--json` stream:
|
|
7
|
+
// the `thread.started` event carries the thread id and the answer sits in the sibling `UNIT-last.md`
|
|
8
|
+
// file that `--output-last-message` wrote. The summary also reads the sibling `UNIT.err` file that
|
|
9
|
+
// launch.ts wrote (`exit`, `capped`, `durationMs`) and the journal's `bytes`, `ageMs`, and last
|
|
10
|
+
// event, which is the liveness reading for a run still going. The answer is written to --out when
|
|
11
|
+
// given, and a journal with no session or answer exits 3 so a driver cannot pass off an empty lane.
|
|
12
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
13
|
+
import { dirname } from 'node:path';
|
|
14
|
+
import { readMissingFlags, readNumber, readOption, readString } from "./helpers.js";
|
|
15
|
+
function readEvents(path) {
|
|
16
|
+
const events = [];
|
|
17
|
+
for (const line of readFileSync(path, 'utf8').split(/\r\n|\n/)) {
|
|
18
|
+
if (line.trim() === '')
|
|
19
|
+
continue;
|
|
20
|
+
try {
|
|
21
|
+
const parsed = JSON.parse(line);
|
|
22
|
+
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
23
|
+
events.push(Object.fromEntries(Object.entries(parsed)));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch { }
|
|
27
|
+
}
|
|
28
|
+
return events;
|
|
29
|
+
}
|
|
30
|
+
function readAnswer(text) {
|
|
31
|
+
return text === undefined || text.trim() === '' ? undefined : text;
|
|
32
|
+
}
|
|
33
|
+
function readStream(events) {
|
|
34
|
+
const init = events.find((event) => event.type === 'system' && event.subtype === 'init');
|
|
35
|
+
const result = events.find((event) => event.type === 'result');
|
|
36
|
+
return {
|
|
37
|
+
session: init === undefined ? undefined : readString(init, 'session_id'),
|
|
38
|
+
answer: result === undefined ? undefined : readAnswer(readString(result, 'result')),
|
|
39
|
+
durationMs: result === undefined ? undefined : readNumber(result, 'duration_ms'),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function readCodex(path, events) {
|
|
43
|
+
const started = events.find((event) => event.type === 'thread.started');
|
|
44
|
+
const last = path.replace(/\.jsonl$/u, '-last.md');
|
|
45
|
+
return {
|
|
46
|
+
session: started === undefined ? undefined : readString(started, 'thread_id'),
|
|
47
|
+
answer: existsSync(last) ? readAnswer(readFileSync(last, 'utf8')) : undefined,
|
|
48
|
+
durationMs: undefined,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function readErrors(path) {
|
|
52
|
+
const errors = path.replace(/\.jsonl$/u, '.err');
|
|
53
|
+
if (!existsSync(errors))
|
|
54
|
+
return { exit: undefined, capped: undefined, durationMs: undefined };
|
|
55
|
+
const match = readFileSync(errors, 'utf8').match(/exit=(\S+) signal=\S+ capped=(true|false) duration_ms=(\d+)\s*$/u);
|
|
56
|
+
if (match === null)
|
|
57
|
+
return { exit: undefined, capped: undefined, durationMs: undefined };
|
|
58
|
+
const exit = Number(match[1]);
|
|
59
|
+
return {
|
|
60
|
+
exit: Number.isFinite(exit) ? exit : undefined,
|
|
61
|
+
capped: match[2] === 'true',
|
|
62
|
+
durationMs: Number(match[3]),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function describeEvent(event) {
|
|
66
|
+
if (event === undefined)
|
|
67
|
+
return undefined;
|
|
68
|
+
const type = readString(event, 'type');
|
|
69
|
+
const subtype = readString(event, 'subtype');
|
|
70
|
+
return subtype === undefined ? type : `${type}/${subtype}`;
|
|
71
|
+
}
|
|
72
|
+
function main(argv) {
|
|
73
|
+
const missing = readMissingFlags(argv, ['--cursor', '--claude', '--codex', '--out']);
|
|
74
|
+
if (missing.length > 0) {
|
|
75
|
+
console.error(`result: ${missing.join(', ')} given with no value`);
|
|
76
|
+
return 64;
|
|
77
|
+
}
|
|
78
|
+
const modes = ['--cursor', '--claude', '--codex'].filter((flag) => argv.includes(flag));
|
|
79
|
+
const mode = modes[0];
|
|
80
|
+
const path = mode === undefined ? undefined : readOption(argv, mode);
|
|
81
|
+
const out = readOption(argv, '--out');
|
|
82
|
+
if (modes.length !== 1 || mode === undefined || path === undefined || !existsSync(path)) {
|
|
83
|
+
console.error('usage: result.ts (--cursor JOURNAL | --claude JOURNAL | --codex JOURNAL) [--out PATH]; name one mode');
|
|
84
|
+
return 64;
|
|
85
|
+
}
|
|
86
|
+
const events = readEvents(path);
|
|
87
|
+
const reading = mode === '--codex' ? readCodex(path, events) : readStream(events);
|
|
88
|
+
const errors = readErrors(path);
|
|
89
|
+
const stat = statSync(path);
|
|
90
|
+
if (reading.answer !== undefined && out !== undefined) {
|
|
91
|
+
mkdirSync(dirname(out), { recursive: true });
|
|
92
|
+
writeFileSync(out, reading.answer);
|
|
93
|
+
}
|
|
94
|
+
console.log(JSON.stringify({
|
|
95
|
+
journal: path,
|
|
96
|
+
session: reading.session,
|
|
97
|
+
durationMs: reading.durationMs ?? errors.durationMs,
|
|
98
|
+
exit: errors.exit,
|
|
99
|
+
capped: errors.capped,
|
|
100
|
+
bytes: stat.size,
|
|
101
|
+
ageMs: Math.max(0, Math.round(Date.now() - stat.mtimeMs)),
|
|
102
|
+
lastEvent: describeEvent(events.at(-1)),
|
|
103
|
+
answerLength: reading.answer?.length ?? 0,
|
|
104
|
+
out: reading.answer === undefined ? undefined : out,
|
|
105
|
+
}));
|
|
106
|
+
return reading.session === undefined || reading.answer === undefined ? 3 : 0;
|
|
107
|
+
}
|
|
108
|
+
process.exitCode = main(process.argv.slice(2));
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// Campaign artifact sweep. Run from the checkout root with Node's type stripping:
|
|
2
|
+
// node .agents/skills/orkestrel-dispatch/scripts/sweep.ts --report
|
|
3
|
+
// node .agents/skills/orkestrel-dispatch/scripts/sweep.ts --tmp [--older-than MINUTES]
|
|
4
|
+
// node .agents/skills/orkestrel-dispatch/scripts/sweep.ts --unit UNIT [--delete]
|
|
5
|
+
// --report lists leftovers under tmp/ and .orkestrel/ by age and deletes nothing.
|
|
6
|
+
// --tmp deletes launch files older than the threshold (default 60 minutes), removes emptied
|
|
7
|
+
// directories, and refuses while any launch file changed in the last 2 minutes.
|
|
8
|
+
// --unit lists one accepted unit's files under the launch directories (the brief, report, claims,
|
|
9
|
+
// journal, errors, status, pid, and last-message files whose name is UNIT followed by `-` or `.`), and
|
|
10
|
+
// --delete removes them, refusing while one changed in the last 2 minutes.
|
|
11
|
+
// Exit 0 on success, 2 when a deletion is refused, 64 on usage.
|
|
12
|
+
import { existsSync, readdirSync, rmSync } from 'node:fs';
|
|
13
|
+
import { basename, join } from 'node:path';
|
|
14
|
+
import { listFiles, readMissingFlags, readOption } from "./helpers.js";
|
|
15
|
+
const LAUNCH_DIRECTORIES = [
|
|
16
|
+
'tmp/units',
|
|
17
|
+
'tmp/cursor',
|
|
18
|
+
'tmp/codex',
|
|
19
|
+
'tmp/claude',
|
|
20
|
+
'tmp/probes',
|
|
21
|
+
'tmp/type',
|
|
22
|
+
'tmp/captures',
|
|
23
|
+
];
|
|
24
|
+
const RECENT_MS = 2 * 60_000;
|
|
25
|
+
// A launch directory holds nothing a walk skips, so every file beneath it counts.
|
|
26
|
+
const NOTHING_SKIPPED = new Set();
|
|
27
|
+
function formatDay(modified) {
|
|
28
|
+
return new Date(modified).toISOString().slice(0, 10);
|
|
29
|
+
}
|
|
30
|
+
function reportDirectory(directory) {
|
|
31
|
+
if (!existsSync(directory))
|
|
32
|
+
return undefined;
|
|
33
|
+
const files = listFiles(directory, NOTHING_SKIPPED);
|
|
34
|
+
if (files.length === 0)
|
|
35
|
+
return undefined;
|
|
36
|
+
const oldest = files.reduce((low, file) => Math.min(low, file.modified), Number.POSITIVE_INFINITY);
|
|
37
|
+
return `sweep: ${directory} holds ${files.length} file(s), oldest ${formatDay(oldest)}`;
|
|
38
|
+
}
|
|
39
|
+
function listCampaignDirectories() {
|
|
40
|
+
if (!existsSync('.orkestrel'))
|
|
41
|
+
return [];
|
|
42
|
+
return readdirSync('.orkestrel', { withFileTypes: true })
|
|
43
|
+
.filter((entry) => entry.isDirectory())
|
|
44
|
+
.map((entry) => join('.orkestrel', entry.name));
|
|
45
|
+
}
|
|
46
|
+
function reportCampaignFiles() {
|
|
47
|
+
if (!existsSync('.orkestrel'))
|
|
48
|
+
return undefined;
|
|
49
|
+
const files = readdirSync('.orkestrel', { withFileTypes: true }).filter((entry) => entry.isFile());
|
|
50
|
+
if (files.length === 0)
|
|
51
|
+
return undefined;
|
|
52
|
+
return `sweep: .orkestrel holds ${files.length} ecosystem file(s): ${files.map((entry) => entry.name).join(', ')}`;
|
|
53
|
+
}
|
|
54
|
+
function removeEmptyDirectories(directory) {
|
|
55
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
56
|
+
if (!entry.isDirectory())
|
|
57
|
+
continue;
|
|
58
|
+
const path = join(directory, entry.name);
|
|
59
|
+
removeEmptyDirectories(path);
|
|
60
|
+
if (readdirSync(path).length === 0)
|
|
61
|
+
rmSync(path, { recursive: true });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function readThreshold(argv) {
|
|
65
|
+
if (!argv.includes('--older-than'))
|
|
66
|
+
return 60;
|
|
67
|
+
const value = Number(readOption(argv, '--older-than'));
|
|
68
|
+
return Number.isFinite(value) && value > 0 ? value : undefined;
|
|
69
|
+
}
|
|
70
|
+
function runReport() {
|
|
71
|
+
for (const directory of [...LAUNCH_DIRECTORIES, ...listCampaignDirectories()]) {
|
|
72
|
+
const line = reportDirectory(directory);
|
|
73
|
+
if (line !== undefined)
|
|
74
|
+
console.log(line);
|
|
75
|
+
}
|
|
76
|
+
const files = reportCampaignFiles();
|
|
77
|
+
if (files !== undefined)
|
|
78
|
+
console.log(files);
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
function runSweep(thresholdMinutes) {
|
|
82
|
+
const now = Date.now();
|
|
83
|
+
const present = LAUNCH_DIRECTORIES.filter((directory) => existsSync(directory));
|
|
84
|
+
const files = present.flatMap((directory) => listFiles(directory, NOTHING_SKIPPED));
|
|
85
|
+
if (files.some((file) => now - file.modified < RECENT_MS)) {
|
|
86
|
+
console.error('sweep: a launch file changed within the last 2 minutes; refusing to delete');
|
|
87
|
+
return 2;
|
|
88
|
+
}
|
|
89
|
+
const cutoff = now - thresholdMinutes * 60_000;
|
|
90
|
+
for (const file of files) {
|
|
91
|
+
if (file.modified > cutoff)
|
|
92
|
+
continue;
|
|
93
|
+
rmSync(file.path, { force: true });
|
|
94
|
+
console.log(`sweep: deleted ${file.path}`);
|
|
95
|
+
}
|
|
96
|
+
for (const directory of present)
|
|
97
|
+
removeEmptyDirectories(directory);
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
function listUnitFiles(unit) {
|
|
101
|
+
return LAUNCH_DIRECTORIES.filter((directory) => existsSync(directory))
|
|
102
|
+
.flatMap((directory) => listFiles(directory, NOTHING_SKIPPED))
|
|
103
|
+
.filter((file) => {
|
|
104
|
+
const name = basename(file.path);
|
|
105
|
+
return name.startsWith(unit) && /^[-.]/u.test(name.slice(unit.length));
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
function runUnit(unit, remove) {
|
|
109
|
+
if (unit === undefined || !/^[A-Za-z0-9._][A-Za-z0-9._-]*$/u.test(unit)) {
|
|
110
|
+
console.error('usage: sweep.ts --unit UNIT [--delete]');
|
|
111
|
+
return 64;
|
|
112
|
+
}
|
|
113
|
+
const files = listUnitFiles(unit);
|
|
114
|
+
if (remove && files.some((file) => Date.now() - file.modified < RECENT_MS)) {
|
|
115
|
+
console.error(`sweep: a file of ${unit} changed within the last 2 minutes; refusing to delete`);
|
|
116
|
+
return 2;
|
|
117
|
+
}
|
|
118
|
+
for (const file of files) {
|
|
119
|
+
if (remove)
|
|
120
|
+
rmSync(file.path, { force: true });
|
|
121
|
+
console.log(`sweep: ${remove ? 'deleted' : 'found'} ${file.path}`);
|
|
122
|
+
}
|
|
123
|
+
console.log(`sweep: ${files.length} file(s) for ${unit}`);
|
|
124
|
+
return 0;
|
|
125
|
+
}
|
|
126
|
+
function main(argv) {
|
|
127
|
+
if (!existsSync('.git')) {
|
|
128
|
+
console.error('sweep: run from the repository root');
|
|
129
|
+
return 64;
|
|
130
|
+
}
|
|
131
|
+
const missing = readMissingFlags(argv, ['--unit', '--older-than']);
|
|
132
|
+
if (missing.length > 0) {
|
|
133
|
+
console.error(`sweep: ${missing.join(', ')} given with no value`);
|
|
134
|
+
return 64;
|
|
135
|
+
}
|
|
136
|
+
const modes = ['--report', '--tmp', '--unit'].filter((flag) => argv.includes(flag));
|
|
137
|
+
if (modes.length > 1) {
|
|
138
|
+
console.error('usage: sweep.ts names one mode: --report | --tmp [--older-than MINUTES] | --unit UNIT [--delete]');
|
|
139
|
+
return 64;
|
|
140
|
+
}
|
|
141
|
+
if (argv.includes('--tmp')) {
|
|
142
|
+
const threshold = readThreshold(argv);
|
|
143
|
+
if (threshold === undefined) {
|
|
144
|
+
console.error('usage: sweep.ts --tmp [--older-than MINUTES]; MINUTES is a positive number');
|
|
145
|
+
return 64;
|
|
146
|
+
}
|
|
147
|
+
return runSweep(threshold);
|
|
148
|
+
}
|
|
149
|
+
if (argv.includes('--unit'))
|
|
150
|
+
return runUnit(readOption(argv, '--unit'), argv.includes('--delete'));
|
|
151
|
+
if (argv.length === 0 || argv.includes('--report'))
|
|
152
|
+
return runReport();
|
|
153
|
+
console.error('usage: sweep.ts --report | --tmp [--older-than MINUTES] | --unit UNIT [--delete]');
|
|
154
|
+
return 64;
|
|
155
|
+
}
|
|
156
|
+
process.exitCode = main(process.argv.slice(2));
|