@lyupro/codex-bridge 0.6.15 → 0.6.16
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 +23 -0
- package/README.md +1 -1
- package/cli/command-registry.mjs +1 -1
- package/cli/guidance-render.mjs +57 -0
- package/cli/guidance-topics.mjs +8 -0
- package/cli/guidance.mjs +9 -10
- package/package.json +2 -2
- package/scripts/home-write-inventory.mjs +2 -0
- package/scripts/run-tests.mjs +44 -0
- package/src/claude/rules/core.md +5 -4
- package/src/home/guidance/order.md +12 -13
- package/src/home/guidance/records.md +3 -1
- package/src/home/hooks/dispatcher-gate.mjs +8 -2
- package/src/home/hooks/order-gate.mjs +16 -15
- package/src/home/lib/dispatcher-call.mjs +27 -24
- package/src/home/lib/dispatcher-command.mjs +12 -6
- package/src/home/lib/dispatcher-gate.mjs +20 -16
- package/src/home/lib/dispatcher-model.mjs +7 -8
- package/src/home/lib/dispatcher-pin.mjs +7 -6
- package/src/home/lib/dispatcher-protocol.mjs +76 -4
- package/src/home/lib/dispatcher-state.mjs +4 -3
- package/src/home/lib/install-owner-roots.mjs +57 -55
- package/src/home/lib/meta/advisor-task.mjs +21 -12
- package/src/home/lib/meta/reply.mjs +1 -10
- package/src/home/lib/meta/unavailable-rows.mjs +11 -0
- package/src/home/lib/order-diagnostics/cli-flags.mjs +56 -0
- package/src/home/lib/order-diagnostics/filesystem.mjs +18 -0
- package/src/home/lib/order-diagnostics/host-state.mjs +263 -0
- package/src/home/lib/order-diagnostics/internal.mjs +82 -0
- package/src/home/lib/order-diagnostics/run-state.mjs +58 -0
- package/src/home/lib/order-diagnostics.mjs +48 -0
- package/src/home/lib/order-rules/call.mjs +130 -0
- package/src/home/lib/order-rules/header-grants.mjs +175 -0
- package/src/home/lib/order-rules/header.mjs +206 -0
- package/src/home/lib/order-rules/task-document.mjs +80 -0
- package/src/home/lib/order-rules.mjs +77 -0
- package/src/home/lib/order-schema.mjs +206 -200
- package/src/home/lib/order-text-record.mjs +104 -0
- package/src/home/lib/order-values.mjs +46 -46
- package/src/home/lib/runner/advise-carry.mjs +9 -7
- package/src/home/lib/runner/args.mjs +23 -26
- package/src/home/lib/runner/codex-availability.mjs +10 -6
- package/src/home/lib/runner/continuation.mjs +158 -174
- package/src/home/lib/runner/launcher.mjs +19 -17
- package/src/home/lib/runner/order-claim.mjs +15 -9
- package/src/home/lib/runner/order-options.mjs +3 -2
- package/src/home/lib/runner/order-owner.mjs +9 -7
- package/src/home/lib/runner/pass-gate.mjs +14 -18
- package/src/home/lib/runner/preflight.mjs +25 -35
- package/src/home/lib/runner/question-kind.mjs +4 -2
- package/src/home/lib/runner/retired-roots.mjs +18 -9
- package/src/home/lib/runner/runs-root.mjs +3 -3
- package/src/home/lib/runner/sandbox-diagnosis.mjs +17 -17
- package/src/home/lib/runner/sandbox-probe.mjs +18 -27
- package/src/home/lib/runner/scope-check.mjs +30 -23
- package/src/home/lib/runner/task-file.mjs +6 -4
- package/src/home/lib/runner/task-input.mjs +12 -16
- package/src/home/lib/runner/worker-admission.mjs +5 -2
- package/src/home/lib/subagent-model-env.mjs +5 -6
- package/src/home/lib/task-header.mjs +159 -158
|
@@ -1,158 +1,159 @@
|
|
|
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 './order-values.mjs';
|
|
13
|
-
import { ALL_ORDER_LABELS } from './order-schema.mjs';
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Plan_75 D5, 2026-10-03 20:42: one registry prevents wrapped task prose becoming a grant.
|
|
46
|
-
const LABELS = Object.freeze([
|
|
47
|
-
// Plan_75 D5: empty advice closes the strict header but must still explain the missing value.
|
|
48
|
-
descriptor('advice', (value) => value.trim() && !/[\r\n]/.test(value)
|
|
49
|
-
? null : 'advice
|
|
50
|
-
(value) => !value || /^\S+$/.test(value)
|
|
51
|
-
|| path.posix.isAbsolute(value) || path.win32.isAbsolute(value)),
|
|
52
|
-
descriptor('continue', validateGrant, (value) => MISPLACED_GRANT.test(value)),
|
|
53
|
-
descriptor('retry', validateGrant, (value) => MISPLACED_GRANT.test(value)),
|
|
54
|
-
]);
|
|
55
|
-
|
|
56
|
-
const HEADER_LABELS = Object.freeze([...ALL_ORDER_LABELS, 'advice']);
|
|
57
|
-
const GUARDED_LABELS = Object.freeze(LABELS.map(({ label }) => label));
|
|
58
|
-
const ORDER_ONLY_LABELS = Object.freeze(HEADER_LABELS.filter((label) => !GUARDED_LABELS.includes(label)));
|
|
59
|
-
const HEADER_LINE = new RegExp(`^(${HEADER_LABELS.join('|')}):[ \\t]*(\\S.*?)[ \\t]*$`);
|
|
60
|
-
|
|
61
|
-
function normalizedHeaderLabel(line) {
|
|
62
|
-
const colon = line.indexOf(':');
|
|
63
|
-
if (colon === -1) return null;
|
|
64
|
-
return line.slice(0, colon).trim().toLowerCase()
|
|
65
|
-
.replace(/^[-*][ \t]+/, '')
|
|
66
|
-
.replace(/^[*`_]+|[*`_]+$/g, '')
|
|
67
|
-
.replace(/[-_]/g, ' ').replace(/\s+/g, ' ').trim();
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function parseTaskHeader(text) {
|
|
71
|
-
const lines = text.replace(/^\uFEFF/, '').split(/\r\n|\n|\r/);
|
|
72
|
-
const fields = {};
|
|
73
|
-
const entries = [];
|
|
74
|
-
const problems = [];
|
|
75
|
-
let headerEnd = 0;
|
|
76
|
-
|
|
77
|
-
for (const line of lines) {
|
|
78
|
-
const match = line.match(HEADER_LINE);
|
|
79
|
-
if (!match) {
|
|
80
|
-
// Plan_63 D1/D3: a misspelled or unknown order line must not silently turn into task prose. The
|
|
81
|
-
// header still closes here (Plan_75 D5), and grant/advice spellings stay with the misplaced guard.
|
|
82
|
-
const label = normalizedHeaderLabel(line);
|
|
83
|
-
const rawLabel = line.slice(0, line.indexOf(':')).trim();
|
|
84
|
-
let reason = null;
|
|
85
|
-
if (ORDER_ONLY_LABELS.includes(label)) {
|
|
86
|
-
reason =
|
|
87
|
-
:
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
let
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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 './order-values.mjs';
|
|
13
|
+
import { ALL_ORDER_LABELS } from './order-schema.mjs';
|
|
14
|
+
import { orderRuleMessage, orderRefusalAt, orderProblemLine } from './order-rules.mjs';
|
|
15
|
+
import { orderDiagnosticMessage } from './order-diagnostics.mjs';
|
|
16
|
+
|
|
17
|
+
const BARE_RUN = /^(?!\.{1,2}$)[A-Za-z0-9._-]+$/;
|
|
18
|
+
const MISPLACED_GRANT = /^[A-Za-z0-9._-]+(?:$| — | - |:)/;
|
|
19
|
+
|
|
20
|
+
function validateGrant(value) {
|
|
21
|
+
const grant = splitGrantValue(value);
|
|
22
|
+
return grant && BARE_RUN.test(grant.run)
|
|
23
|
+
? null
|
|
24
|
+
: orderRuleMessage('grant-value-shape');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function descriptor(label, validate, misplaced) {
|
|
28
|
+
const spellings = [`${label}[ \\t]*[:=]`];
|
|
29
|
+
for (const wrapper of ['\\*', '_', '`', '\\*\\*', '__']) {
|
|
30
|
+
spellings.push(`${wrapper}${label}${wrapper}[ \\t]*[:=]`);
|
|
31
|
+
spellings.push(`${wrapper}${label}[ \\t]*[:=]${wrapper}`);
|
|
32
|
+
}
|
|
33
|
+
return Object.freeze({
|
|
34
|
+
label,
|
|
35
|
+
validate,
|
|
36
|
+
misplaced,
|
|
37
|
+
shape: new RegExp(
|
|
38
|
+
// Plan_75 D5: try a wrapper before treating its first star as a list marker.
|
|
39
|
+
`^[ \\t]*(?:[-*][ \\t]*)??(?:${spellings.join('|')})[ \\t]*(.*?)[ \\t]*$`,
|
|
40
|
+
'i',
|
|
41
|
+
),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Plan_75 D5, 2026-10-03 20:42: one registry prevents wrapped task prose becoming a grant.
|
|
46
|
+
const LABELS = Object.freeze([
|
|
47
|
+
// Plan_75 D5: empty advice closes the strict header but must still explain the missing value.
|
|
48
|
+
descriptor('advice', (value) => value.trim() && !/[\r\n]/.test(value)
|
|
49
|
+
? null : orderRuleMessage('advice-value-shape'),
|
|
50
|
+
(value) => !value || /^\S+$/.test(value)
|
|
51
|
+
|| path.posix.isAbsolute(value) || path.win32.isAbsolute(value)),
|
|
52
|
+
descriptor('continue', validateGrant, (value) => MISPLACED_GRANT.test(value)),
|
|
53
|
+
descriptor('retry', validateGrant, (value) => MISPLACED_GRANT.test(value)),
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
const HEADER_LABELS = Object.freeze([...ALL_ORDER_LABELS, 'advice']);
|
|
57
|
+
const GUARDED_LABELS = Object.freeze(LABELS.map(({ label }) => label));
|
|
58
|
+
const ORDER_ONLY_LABELS = Object.freeze(HEADER_LABELS.filter((label) => !GUARDED_LABELS.includes(label)));
|
|
59
|
+
const HEADER_LINE = new RegExp(`^(${HEADER_LABELS.join('|')}):[ \\t]*(\\S.*?)[ \\t]*$`);
|
|
60
|
+
|
|
61
|
+
function normalizedHeaderLabel(line) {
|
|
62
|
+
const colon = line.indexOf(':');
|
|
63
|
+
if (colon === -1) return null;
|
|
64
|
+
return line.slice(0, colon).trim().toLowerCase()
|
|
65
|
+
.replace(/^[-*][ \t]+/, '')
|
|
66
|
+
.replace(/^[*`_]+|[*`_]+$/g, '')
|
|
67
|
+
.replace(/[-_]/g, ' ').replace(/\s+/g, ' ').trim();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function parseTaskHeader(text) {
|
|
71
|
+
const lines = text.replace(/^\uFEFF/, '').split(/\r\n|\n|\r/);
|
|
72
|
+
const fields = {};
|
|
73
|
+
const entries = [];
|
|
74
|
+
const problems = [];
|
|
75
|
+
let headerEnd = 0;
|
|
76
|
+
|
|
77
|
+
for (const line of lines) {
|
|
78
|
+
const match = line.match(HEADER_LINE);
|
|
79
|
+
if (!match) {
|
|
80
|
+
// Plan_63 D1/D3: a misspelled or unknown order line must not silently turn into task prose. The
|
|
81
|
+
// header still closes here (Plan_75 D5), and grant/advice spellings stay with the misplaced guard.
|
|
82
|
+
const label = normalizedHeaderLabel(line);
|
|
83
|
+
const rawLabel = line.slice(0, line.indexOf(':')).trim();
|
|
84
|
+
let reason = null;
|
|
85
|
+
if (ORDER_ONLY_LABELS.includes(label)) {
|
|
86
|
+
reason = orderRuleMessage('order-header-label-shape', {
|
|
87
|
+
label, violation: line.trimEnd() === `${label}:` ? 'empty' : 'spelling',
|
|
88
|
+
});
|
|
89
|
+
} else if (!GUARDED_LABELS.includes(label) && entries.length && /^[a-z][a-z _-]*:[ \t]*\S/.test(line)) {
|
|
90
|
+
reason = orderRuleMessage('unknown-header-label', { label: rawLabel, knownLabels: HEADER_LABELS });
|
|
91
|
+
}
|
|
92
|
+
if (reason) problems.push({ lineNo: headerEnd + 1, line, reason });
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
const [, label, value] = match;
|
|
96
|
+
const entry = { label, value, lineNo: headerEnd + 1, line };
|
|
97
|
+
if (Object.hasOwn(fields, label)) {
|
|
98
|
+
problems.push({ lineNo: entry.lineNo, line, reason: orderRuleMessage('duplicate-order-label', { label }) });
|
|
99
|
+
} else {
|
|
100
|
+
fields[label] = value;
|
|
101
|
+
}
|
|
102
|
+
entries.push(entry);
|
|
103
|
+
headerEnd++;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Plan_75 D5: conflicting authorization is a label-presence error even if a value is invalid.
|
|
107
|
+
if (Object.hasOwn(fields, 'continue') && Object.hasOwn(fields, 'retry')) {
|
|
108
|
+
const conflict = entries.find(({ label }) => label === 'retry');
|
|
109
|
+
problems.push({
|
|
110
|
+
lineNo: conflict.lineNo,
|
|
111
|
+
line: conflict.line,
|
|
112
|
+
reason: orderRuleMessage('grant-label-exclusive'),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let grant = null;
|
|
117
|
+
let advice = null;
|
|
118
|
+
for (const entry of entries) {
|
|
119
|
+
const definition = LABELS.find(({ label }) => label === entry.label);
|
|
120
|
+
if (!definition) continue;
|
|
121
|
+
const reason = definition.validate(entry.value);
|
|
122
|
+
if (reason) {
|
|
123
|
+
problems.push({ lineNo: entry.lineNo, line: entry.line, reason: orderRefusalAt(entry.label, reason) });
|
|
124
|
+
} else if (entry.value !== fields[entry.label]) {
|
|
125
|
+
continue;
|
|
126
|
+
} else if (entry.label === 'advice') {
|
|
127
|
+
advice ??= entry.value;
|
|
128
|
+
} else {
|
|
129
|
+
grant ??= { kind: entry.label, ...splitGrantValue(entry.value) };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Plan_75 D5, 2026-10-03 20:42: fences are examples, not an escape from the misplaced guard.
|
|
134
|
+
for (let index = headerEnd; index < lines.length; index++) {
|
|
135
|
+
for (const definition of LABELS) {
|
|
136
|
+
const match = lines[index].match(definition.shape);
|
|
137
|
+
if (!match || !definition.misplaced(match[1])) continue;
|
|
138
|
+
problems.push({
|
|
139
|
+
lineNo: index + 1,
|
|
140
|
+
line: lines[index],
|
|
141
|
+
reason: orderRuleMessage('header-metadata-placement', {
|
|
142
|
+
label: definition.label, violation: match[1] ? 'misplaced' : 'empty',
|
|
143
|
+
}),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
problems.sort((a, b) => a.lineNo - b.lineNo);
|
|
149
|
+
const parsed = { fields, grant, advice, body: lines.slice(headerEnd).join('\n'), problems };
|
|
150
|
+
// Plan_63 C1: retain source locations for schema diagnostics without changing serialized results.
|
|
151
|
+
Object.defineProperty(parsed, 'entries', { value: entries });
|
|
152
|
+
return parsed;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function taskHeaderRefusal(parsed) {
|
|
156
|
+
if (!parsed.problems.length) return null;
|
|
157
|
+
return parsed.problems.map(orderProblemLine)
|
|
158
|
+
.concat(orderDiagnosticMessage('run-not-created')).join('\n');
|
|
159
|
+
}
|