@lyupro/codex-bridge 0.6.9 → 0.6.10
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/CHANGELOG.md +51 -0
- package/README.md +1 -1
- package/cli/lifecycle-lock-inspect.mjs +3 -2
- package/cli/lifecycle-lock.mjs +19 -132
- package/cli/read.mjs +20 -0
- package/cli/settings-merge.mjs +3 -29
- package/package.json +2 -2
- package/scripts/home-write-inventory.mjs +12 -2
- package/src/agents/codex-advisor.md +22 -12
- package/src/agents/codex-build.md +24 -14
- package/src/agents/codex-review.md +17 -10
- package/src/agents/codex-scout.md +22 -12
- package/src/home/hooks/order-gate.mjs +25 -11
- package/src/home/hooks/reply-parser.mjs +1 -1
- package/src/home/lib/command-path.mjs +32 -0
- package/src/home/lib/dispatcher-command.mjs +4 -2
- package/src/home/lib/kernel-lock.mjs +171 -0
- package/src/home/lib/meta/chain.mjs +19 -8
- package/src/home/lib/meta/context-only.mjs +43 -0
- package/src/home/lib/meta/events.mjs +14 -2
- package/src/home/lib/meta/launch-rows.mjs +23 -0
- package/src/home/lib/meta/reply.mjs +37 -3
- package/src/home/lib/meta/run-state.mjs +3 -0
- package/src/home/lib/meta/verdict.mjs +16 -8
- package/src/home/lib/required-inputs.mjs +48 -8
- package/src/home/lib/run-codex.mjs +6 -3
- package/src/home/lib/runner/advise-carry.mjs +2 -1
- package/src/home/lib/runner/args.mjs +16 -5
- package/src/home/lib/runner/attach.mjs +21 -14
- package/src/home/lib/runner/build-evidence.mjs +29 -0
- package/src/home/lib/runner/codex-availability.mjs +50 -0
- package/src/home/lib/runner/codex-cmd.mjs +2 -19
- package/src/home/lib/runner/continuation.mjs +60 -8
- package/src/home/lib/runner/exit-codes.mjs +13 -0
- package/src/home/lib/runner/flag-baseline.mjs +105 -0
- package/src/home/lib/runner/flag-scan.mjs +165 -0
- package/src/home/lib/runner/git-state.mjs +0 -33
- package/src/home/lib/runner/launcher.mjs +233 -273
- package/src/home/lib/runner/order-claim.mjs +84 -0
- package/src/home/lib/runner/order-owner.mjs +18 -0
- package/src/home/lib/runner/pass-gate.mjs +109 -0
- package/src/home/lib/runner/preflight.mjs +22 -25
- package/src/home/lib/runner/prompts.mjs +7 -2
- package/src/home/lib/runner/question-kind.mjs +41 -0
- package/src/home/lib/runner/run-env.mjs +2 -1
- package/src/home/lib/runner/sandbox-probe.mjs +1 -1
- package/src/home/lib/runner/scope-check.mjs +29 -11
- package/src/home/lib/runner/task-input.mjs +11 -2
- package/src/home/lib/runner/worker-admission.mjs +43 -0
- package/src/home/lib/runner/worker.mjs +19 -18
- package/src/home/lib/task-header.mjs +127 -0
- package/src/home/lib/write-meta.mjs +17 -5
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads the orchestrator's metadata header at the top of a task file — the one place a grant or an
|
|
3
|
+
* advice line may stand.
|
|
4
|
+
*
|
|
5
|
+
* Plan_75 D5: on 2026-10-03 20:42 wrapped prose in a task file began a line with `continue:`, and
|
|
6
|
+
* the runner, which looked for grants on any line, refused the order over its own prose. Grants and
|
|
7
|
+
* advice had two parsers and the task hash a third rule; this module is the single reader, and a
|
|
8
|
+
* line below the header that still looks like real metadata is refused rather than silently obeyed
|
|
9
|
+
* or silently ignored.
|
|
10
|
+
*/
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { splitGrantValue } from './required-inputs.mjs';
|
|
13
|
+
|
|
14
|
+
const BARE_RUN = /^(?!\.{1,2}$)[A-Za-z0-9._-]+$/;
|
|
15
|
+
const MISPLACED_GRANT = /^[A-Za-z0-9._-]+(?:$| — | - |:)/;
|
|
16
|
+
|
|
17
|
+
function validateGrant(value) {
|
|
18
|
+
const grant = splitGrantValue(value);
|
|
19
|
+
return grant && BARE_RUN.test(grant.run)
|
|
20
|
+
? null
|
|
21
|
+
: 'grant must name a bare run folder (not . or ..) and a non-placeholder reason';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function descriptor(label, validate, misplaced) {
|
|
25
|
+
const spellings = [`${label}[ \\t]*[:=]`];
|
|
26
|
+
for (const wrapper of ['\\*', '_', '`', '\\*\\*', '__']) {
|
|
27
|
+
spellings.push(`${wrapper}${label}${wrapper}[ \\t]*[:=]`);
|
|
28
|
+
spellings.push(`${wrapper}${label}[ \\t]*[:=]${wrapper}`);
|
|
29
|
+
}
|
|
30
|
+
return Object.freeze({
|
|
31
|
+
label,
|
|
32
|
+
validate,
|
|
33
|
+
misplaced,
|
|
34
|
+
shape: new RegExp(
|
|
35
|
+
// Plan_75 D5: try a wrapper before treating its first star as a list marker.
|
|
36
|
+
`^[ \\t]*(?:[-*][ \\t]*)??(?:${spellings.join('|')})[ \\t]*(.*?)[ \\t]*$`,
|
|
37
|
+
'i',
|
|
38
|
+
),
|
|
39
|
+
repairHint: `move it into the header at the top of the file, spelled ${label}: <value>; `
|
|
40
|
+
+ 'if it is an example, quote it with > or reword it inside a sentence',
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Plan_75 D5, 2026-10-03 20:42: one registry prevents wrapped task prose becoming a grant.
|
|
45
|
+
const LABELS = Object.freeze([
|
|
46
|
+
// Plan_75 D5: empty advice closes the strict header but must still explain the missing value.
|
|
47
|
+
descriptor('advice', (value) => value.trim() && !/[\r\n]/.test(value)
|
|
48
|
+
? null : 'advice value must be non-empty and single line',
|
|
49
|
+
(value) => !value || /^\S+$/.test(value)
|
|
50
|
+
|| path.posix.isAbsolute(value) || path.win32.isAbsolute(value)),
|
|
51
|
+
descriptor('continue', validateGrant, (value) => MISPLACED_GRANT.test(value)),
|
|
52
|
+
descriptor('retry', validateGrant, (value) => MISPLACED_GRANT.test(value)),
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
const HEADER_LINE = new RegExp(`^(${LABELS.map(({ label }) => label).join('|')}):[ \\t]*(\\S.*?)[ \\t]*$`);
|
|
56
|
+
|
|
57
|
+
export function parseTaskHeader(text) {
|
|
58
|
+
const lines = text.replace(/^\uFEFF/, '').split(/\r\n|\n|\r/);
|
|
59
|
+
const fields = {};
|
|
60
|
+
const entries = [];
|
|
61
|
+
const problems = [];
|
|
62
|
+
let headerEnd = 0;
|
|
63
|
+
|
|
64
|
+
for (const line of lines) {
|
|
65
|
+
const match = line.match(HEADER_LINE);
|
|
66
|
+
if (!match) break;
|
|
67
|
+
const [, label, value] = match;
|
|
68
|
+
const entry = { label, value, lineNo: headerEnd + 1, line };
|
|
69
|
+
if (Object.hasOwn(fields, label)) {
|
|
70
|
+
problems.push({ lineNo: entry.lineNo, line, reason: `duplicate ${label} label` });
|
|
71
|
+
} else {
|
|
72
|
+
fields[label] = value;
|
|
73
|
+
}
|
|
74
|
+
entries.push(entry);
|
|
75
|
+
headerEnd++;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Plan_75 D5: conflicting authorization is a label-presence error even if a value is invalid.
|
|
79
|
+
if (Object.hasOwn(fields, 'continue') && Object.hasOwn(fields, 'retry')) {
|
|
80
|
+
const conflict = entries.find(({ label }) => label === 'retry');
|
|
81
|
+
problems.push({
|
|
82
|
+
lineNo: conflict.lineNo,
|
|
83
|
+
line: conflict.line,
|
|
84
|
+
reason: 'continue and retry cannot both be present; keep exactly one grant label',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let grant = null;
|
|
89
|
+
let advice = null;
|
|
90
|
+
for (const entry of entries) {
|
|
91
|
+
const definition = LABELS.find(({ label }) => label === entry.label);
|
|
92
|
+
const reason = definition.validate(entry.value);
|
|
93
|
+
if (reason) {
|
|
94
|
+
problems.push({ lineNo: entry.lineNo, line: entry.line, reason: `${entry.label}: ${reason}` });
|
|
95
|
+
} else if (entry.value !== fields[entry.label]) {
|
|
96
|
+
continue;
|
|
97
|
+
} else if (entry.label === 'advice') {
|
|
98
|
+
advice ??= entry.value;
|
|
99
|
+
} else {
|
|
100
|
+
grant ??= { kind: entry.label, ...splitGrantValue(entry.value) };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Plan_75 D5, 2026-10-03 20:42: fences are examples, not an escape from the misplaced guard.
|
|
105
|
+
for (let index = headerEnd; index < lines.length; index++) {
|
|
106
|
+
for (const definition of LABELS) {
|
|
107
|
+
const match = lines[index].match(definition.shape);
|
|
108
|
+
if (!match || !definition.misplaced(match[1])) continue;
|
|
109
|
+
const reason = match[1] ? `misplaced ${definition.label} metadata`
|
|
110
|
+
: `${definition.label} value must be non-empty and single line`;
|
|
111
|
+
problems.push({
|
|
112
|
+
lineNo: index + 1,
|
|
113
|
+
line: lines[index],
|
|
114
|
+
reason: `${reason}; ${definition.repairHint}`,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
problems.sort((a, b) => a.lineNo - b.lineNo);
|
|
120
|
+
return { fields, grant, advice, body: lines.slice(headerEnd).join('\n'), problems };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function taskHeaderRefusal(parsed) {
|
|
124
|
+
if (!parsed.problems.length) return null;
|
|
125
|
+
return parsed.problems.map(({ lineNo, line, reason }) => `line ${lineNo}: ${reason}: ${line}`)
|
|
126
|
+
.concat('The run folder was not created; quota was not spent.').join('\n');
|
|
127
|
+
}
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* run-state.mjs keeps status.json honest, verdict.mjs decides OK/FAIL/LIMIT, reply.mjs
|
|
20
20
|
* renders the lines. Importers name this file and nothing below it.
|
|
21
21
|
*/
|
|
22
|
+
import { EXIT } from './runner/exit-codes.mjs';
|
|
22
23
|
import fs from 'node:fs';
|
|
23
24
|
import path from 'node:path';
|
|
24
25
|
import { fileURLToPath } from 'node:url';
|
|
@@ -28,7 +29,7 @@ import { splitRunChanges } from './meta/environment.mjs';
|
|
|
28
29
|
import { readEvents } from './meta/events.mjs';
|
|
29
30
|
import { writeStatus } from './meta/run-state.mjs';
|
|
30
31
|
import { resolveStatus } from './meta/verdict.mjs';
|
|
31
|
-
import { AGENTS, failReply, limitReply, withProfileRow } from './meta/reply.mjs';
|
|
32
|
+
import { AGENTS, failReply, limitReply, unavailableReply, withProfileRow } from './meta/reply.mjs';
|
|
32
33
|
import { withLaunchRows } from './meta/launch-rows.mjs';
|
|
33
34
|
|
|
34
35
|
function readRunnerVersion() {
|
|
@@ -164,7 +165,13 @@ export function collect(runDir, agent, exitCode) {
|
|
|
164
165
|
const reply =
|
|
165
166
|
withProfileRow(
|
|
166
167
|
withLaunchRows(
|
|
167
|
-
status === 'OK'
|
|
168
|
+
status === 'OK'
|
|
169
|
+
? cfg.reply(ctx)
|
|
170
|
+
: status === 'LIMIT'
|
|
171
|
+
? limitReply(ctx, meta)
|
|
172
|
+
: status === 'UNAVAILABLE'
|
|
173
|
+
? unavailableReply(ctx, meta)
|
|
174
|
+
: failReply(ctx, meta),
|
|
168
175
|
runDir,
|
|
169
176
|
),
|
|
170
177
|
meta,
|
|
@@ -173,8 +180,13 @@ export function collect(runDir, agent, exitCode) {
|
|
|
173
180
|
return { meta, reply: reply.join('\n') };
|
|
174
181
|
}
|
|
175
182
|
|
|
176
|
-
/**
|
|
177
|
-
|
|
183
|
+
/**
|
|
184
|
+
* Exit code numbers live in runner/exit-codes.mjs.
|
|
185
|
+
* USAGE is the launcher's argument error and never comes from here.
|
|
186
|
+
*/
|
|
187
|
+
export const exitCodeFor = (status) =>
|
|
188
|
+
(status === 'OK' ? EXIT.OK : status === 'LIMIT' ? EXIT.LIMIT :
|
|
189
|
+
status === 'UNAVAILABLE' ? EXIT.UNAVAILABLE : EXIT.FAIL);
|
|
178
190
|
|
|
179
191
|
const invokedDirectly =
|
|
180
192
|
process.argv[1] && path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url));
|
|
@@ -183,7 +195,7 @@ if (invokedDirectly) {
|
|
|
183
195
|
const [runDir, agent, exitCode] = process.argv.slice(2);
|
|
184
196
|
if (!runDir || !agent) {
|
|
185
197
|
console.error('usage: node write-meta.mjs <runDir> <agent> <exitCode>');
|
|
186
|
-
process.exit(
|
|
198
|
+
process.exit(EXIT.FAIL);
|
|
187
199
|
}
|
|
188
200
|
const { meta, reply } = collect(runDir, agent, exitCode);
|
|
189
201
|
console.log(reply);
|