@lyupro/codex-bridge 0.6.14 → 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 +33 -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 +15 -14
- 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/paths.mjs +6 -1
- 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/prompts.mjs +2 -2
- 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,5 +1,6 @@
|
|
|
1
1
|
/** Checks whether this host's Codex sandbox can start a process before a run spends quota. */
|
|
2
2
|
import { performance } from 'node:perf_hooks';
|
|
3
|
+
import { orderDiagnosticMessage } from '../order-diagnostics.mjs';
|
|
3
4
|
import { sandboxModeFor } from './codex-args.mjs';
|
|
4
5
|
import { codexSpawnSpec, spawnCaptured } from './codex-cmd.mjs';
|
|
5
6
|
import { platformSandboxArgs } from './sandbox-flags.mjs';
|
|
@@ -12,15 +13,6 @@ export const SANDBOX_PROBE_MARKER = 'codex-bridge-sandbox-ok';
|
|
|
12
13
|
// on the same host after the AppArmor repair. macOS has never been observed.
|
|
13
14
|
export const PROBED_PLATFORMS = new Set(['win32', 'linux']);
|
|
14
15
|
|
|
15
|
-
// Ubuntu 23.10+ forbids unprivileged user namespaces unless an AppArmor profile allows bwrap; that
|
|
16
|
-
// is the ordinary state of a fresh server, not an accident like the corrupted Windows state file.
|
|
17
|
-
const LINUX_REPAIR = [
|
|
18
|
-
'On Ubuntu 23.10 and later this usually means AppArmor does not let bubblewrap create a user namespace.',
|
|
19
|
-
'Official repair: https://learn.chatgpt.com/docs/sandboxing — the bwrap-userns-restrict AppArmor profile',
|
|
20
|
-
'(Ubuntu 24.04) or the bubblewrap package (25.04 and later). Do not set',
|
|
21
|
-
'kernel.apparmor_restrict_unprivileged_userns=0: it lifts the restriction for every program on the host.',
|
|
22
|
-
].join(' ');
|
|
23
|
-
|
|
24
16
|
const echoArgs = (platform) => platform === 'win32'
|
|
25
17
|
? ['cmd', '/d', '/c', 'echo', SANDBOX_PROBE_MARKER]
|
|
26
18
|
: ['echo', SANDBOX_PROBE_MARKER];
|
|
@@ -28,12 +20,12 @@ const echoArgs = (platform) => platform === 'win32'
|
|
|
28
20
|
function inconclusiveReason(result, form) {
|
|
29
21
|
if (result.error) {
|
|
30
22
|
return result.error.code === 'ETIMEDOUT'
|
|
31
|
-
?
|
|
32
|
-
:
|
|
23
|
+
? orderDiagnosticMessage('sandbox-probe-reason', { variant: 'timeout', form })
|
|
24
|
+
: orderDiagnosticMessage('sandbox-probe-reason', { variant: 'incomplete', form });
|
|
33
25
|
}
|
|
34
|
-
if (result.signal) return
|
|
35
|
-
if (result.status === 2) return
|
|
36
|
-
if (result.status == null) return
|
|
26
|
+
if (result.signal) return orderDiagnosticMessage('sandbox-probe-reason', { variant: 'signal', form, signal: result.signal });
|
|
27
|
+
if (result.status === 2) return orderDiagnosticMessage('sandbox-probe-reason', { variant: 'arguments', form });
|
|
28
|
+
if (result.status == null) return orderDiagnosticMessage('sandbox-probe-reason', { variant: 'no-status', form });
|
|
37
29
|
return null;
|
|
38
30
|
}
|
|
39
31
|
|
|
@@ -43,7 +35,7 @@ export async function probeSandbox({
|
|
|
43
35
|
}) {
|
|
44
36
|
if (!PROBED_PLATFORMS.has(platform)) return { outcome: 'skipped' };
|
|
45
37
|
const sandbox = sandboxModeFor(agent);
|
|
46
|
-
if (typeof repo !== 'string' || !repo) throw new Error('sandbox
|
|
38
|
+
if (typeof repo !== 'string' || !repo) throw new Error(orderDiagnosticMessage('sandbox-probe-repository-required'));
|
|
47
39
|
const echo = echoArgs(platform);
|
|
48
40
|
const attempts = [];
|
|
49
41
|
// The 2026-10-10 TradeForge refusals showed only "setup refresh had errors"; capture the
|
|
@@ -95,43 +87,42 @@ export async function probeSandbox({
|
|
|
95
87
|
const flaggedReason = inconclusiveReason(flagged, 'flagged');
|
|
96
88
|
if (flaggedReason) return finish('inconclusive', flaggedReason);
|
|
97
89
|
if (flagged.status === 0 && flagged.marker) {
|
|
98
|
-
return finish('alive', '
|
|
90
|
+
return finish('alive', orderDiagnosticMessage('sandbox-probe-reason', { variant: 'alive' }));
|
|
99
91
|
}
|
|
100
92
|
// D16: the marker proves a process started, so a nonzero exit cannot prove a dead sandbox.
|
|
101
93
|
if (flagged.marker) {
|
|
102
|
-
return finish('inconclusive',
|
|
94
|
+
return finish('inconclusive', orderDiagnosticMessage('sandbox-probe-reason', { variant: 'marker-exit', status: flagged.status }));
|
|
103
95
|
}
|
|
104
96
|
|
|
105
97
|
const control = await attempt('control', ['sandbox', '--', ...echo]);
|
|
106
98
|
const controlReason = inconclusiveReason(control, 'control');
|
|
107
99
|
if (controlReason) return finish('inconclusive', controlReason);
|
|
108
100
|
if (control.marker) {
|
|
109
|
-
return finish('inconclusive', '
|
|
101
|
+
return finish('inconclusive', orderDiagnosticMessage('sandbox-probe-reason', { variant: 'flags' }));
|
|
110
102
|
}
|
|
111
103
|
|
|
112
104
|
const version = await attempt('version', ['--version']);
|
|
113
105
|
if (version.error || version.signal || version.status !== 0) {
|
|
114
106
|
// codexAvailabilityRefusal() owns the refusal for an unavailable CLI, earlier in the same pre-flight pass.
|
|
115
|
-
return finish('inconclusive', '
|
|
107
|
+
return finish('inconclusive', orderDiagnosticMessage('sandbox-probe-reason', { variant: 'unavailable' }));
|
|
116
108
|
}
|
|
117
|
-
return finish('dead', '
|
|
109
|
+
return finish('dead', orderDiagnosticMessage('sandbox-probe-reason', { variant: 'dead' }));
|
|
118
110
|
}
|
|
119
111
|
|
|
120
112
|
export function sandboxRefusal(result, platform = process.platform) {
|
|
121
|
-
const
|
|
113
|
+
const controlCommand = ['codex', 'sandbox', '--', ...echoArgs(platform)].join(' ');
|
|
122
114
|
const stderr = result.attempts
|
|
123
115
|
.filter(({ form }) => form === 'flagged' || form === 'control')
|
|
124
|
-
.map(({ form, stderrTail }) =>
|
|
125
|
-
`${form} stderr:\n> ${(stderrTail || '(empty)').replace(/\r\n?/g, '\n').replace(/\n/g, '\n> ')}`);
|
|
116
|
+
.map(({ form, stderrTail }) => orderDiagnosticMessage('sandbox-stderr-evidence', { form, stderrTail }));
|
|
126
117
|
// Only a dead result is refused, and its reason already is the headline; printing both said it twice.
|
|
127
118
|
return [
|
|
128
119
|
result.reason,
|
|
129
|
-
'
|
|
130
|
-
...(platform === 'linux' ? [
|
|
131
|
-
|
|
120
|
+
orderDiagnosticMessage('sandbox-refusal-repair'),
|
|
121
|
+
...(platform === 'linux' ? [orderDiagnosticMessage('sandbox-linux-repair')] : []),
|
|
122
|
+
orderDiagnosticMessage('sandbox-operator-check', { command: controlCommand }),
|
|
132
123
|
...stderr,
|
|
133
124
|
...(result.diagnosis && typeof result.diagnosis === 'object'
|
|
134
125
|
? [formatSandboxDiagnosis(result.diagnosis).join('\n')] : []),
|
|
135
|
-
'
|
|
126
|
+
orderDiagnosticMessage('run-not-created'),
|
|
136
127
|
].join('\n\n');
|
|
137
128
|
}
|
|
@@ -9,6 +9,7 @@ import path from 'node:path';
|
|
|
9
9
|
import { listRepositoryPaths } from './git-paths.mjs';
|
|
10
10
|
import { globToRegExp, normalizePath, SERVICE_RE } from '../meta/paths.mjs';
|
|
11
11
|
import { orderInputName } from '../order-schema.mjs';
|
|
12
|
+
import { orderRuleMessage } from '../order-rules.mjs';
|
|
12
13
|
|
|
13
14
|
// Directories a self-walk must never descend into. Only reached when the repository has no git:
|
|
14
15
|
// with git present the file list comes from the repository itself, which already knows what is
|
|
@@ -29,41 +30,41 @@ function structuralRefusal(repoRoot, pattern) {
|
|
|
29
30
|
// Plan_73 D7: scope lists store one pattern per line; literal line breaks cannot round-trip.
|
|
30
31
|
if (/[\n\r]/.test(pattern)) {
|
|
31
32
|
return {
|
|
32
|
-
reason: '
|
|
33
|
-
action: '
|
|
33
|
+
reason: orderRuleMessage('scope-pattern-shape', { violation: 'line-break', part: 'reason' }),
|
|
34
|
+
action: orderRuleMessage('scope-pattern-shape', { violation: 'line-break', part: 'action' }),
|
|
34
35
|
};
|
|
35
36
|
}
|
|
36
37
|
if (absolutePattern(pattern)) {
|
|
37
38
|
return {
|
|
38
|
-
reason: '
|
|
39
|
-
action: '
|
|
39
|
+
reason: orderRuleMessage('scope-pattern-shape', { violation: 'absolute', part: 'reason' }),
|
|
40
|
+
action: orderRuleMessage('scope-pattern-shape', { violation: 'absolute', part: 'action' }),
|
|
40
41
|
};
|
|
41
42
|
}
|
|
42
43
|
if (pattern.includes('\\')) {
|
|
43
44
|
return {
|
|
44
|
-
reason: '
|
|
45
|
-
action: '
|
|
45
|
+
reason: orderRuleMessage('scope-pattern-shape', { violation: 'backslash', part: 'reason' }),
|
|
46
|
+
action: orderRuleMessage('scope-pattern-shape', { violation: 'backslash', part: 'action' }),
|
|
46
47
|
};
|
|
47
48
|
}
|
|
48
49
|
if (pattern.split('/').some((segment) => segment === '..')) {
|
|
49
50
|
return {
|
|
50
|
-
reason: '
|
|
51
|
-
action: '
|
|
51
|
+
reason: orderRuleMessage('scope-pattern-shape', { violation: 'parent-directory', part: 'reason' }),
|
|
52
|
+
action: orderRuleMessage('scope-pattern-shape', { violation: 'parent-directory', part: 'action' }),
|
|
52
53
|
};
|
|
53
54
|
}
|
|
54
55
|
// Plan_59 S1: 2026-09-22_235724_plan59-d1-advisor-docs-surface spent 22 minutes on a
|
|
55
56
|
// service-directory scope the verdict could never authorise. Refuse it before a run exists.
|
|
56
57
|
if (SERVICE_RE.test(normalizePath(pattern))) {
|
|
57
58
|
return {
|
|
58
|
-
reason: '
|
|
59
|
-
action: '
|
|
59
|
+
reason: orderRuleMessage('scope-pattern-shape', { violation: 'service-directory', part: 'reason' }),
|
|
60
|
+
action: orderRuleMessage('scope-pattern-shape', { violation: 'service-directory', part: 'action' }),
|
|
60
61
|
};
|
|
61
62
|
}
|
|
62
63
|
// Plan_58 D7: 2026-09-19_191402_ports-infra spent a run on a bare scope-new directory,
|
|
63
64
|
// which the verdict could only match as a file. Refuse that intent before spending a run.
|
|
64
65
|
if (!pattern || /[*?]/.test(pattern)) return null;
|
|
65
|
-
const reason = '
|
|
66
|
-
const action =
|
|
66
|
+
const reason = orderRuleMessage('scope-pattern-shape', { violation: 'directory', part: 'reason' });
|
|
67
|
+
const action = orderRuleMessage('scope-pattern-shape', { violation: 'directory', part: 'action', pattern });
|
|
67
68
|
if (pattern.endsWith('/')) return { reason, action };
|
|
68
69
|
|
|
69
70
|
let entry;
|
|
@@ -76,7 +77,7 @@ function structuralRefusal(repoRoot, pattern) {
|
|
|
76
77
|
if (!entry && !pattern.split('/').at(-1).includes('.')) {
|
|
77
78
|
return {
|
|
78
79
|
reason,
|
|
79
|
-
action:
|
|
80
|
+
action: orderRuleMessage('scope-pattern-shape', { violation: 'new-directory', part: 'action', pattern }),
|
|
80
81
|
};
|
|
81
82
|
}
|
|
82
83
|
return null;
|
|
@@ -88,16 +89,18 @@ function noMatchRefusal(pattern, repoRoot, gitListed) {
|
|
|
88
89
|
if (gitListed === false) {
|
|
89
90
|
return {
|
|
90
91
|
pattern,
|
|
91
|
-
reason:
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
reason: orderRuleMessage('scope-pattern-match', { violation: 'not-repository', part: 'reason', repoRoot }),
|
|
93
|
+
action: orderRuleMessage('scope-pattern-match', {
|
|
94
|
+
violation: 'not-repository', part: 'action', labelName: orderInputName('repository'),
|
|
95
|
+
}),
|
|
95
96
|
};
|
|
96
97
|
}
|
|
97
98
|
return {
|
|
98
99
|
pattern,
|
|
99
|
-
reason: '
|
|
100
|
-
action:
|
|
100
|
+
reason: orderRuleMessage('scope-pattern-match', { violation: 'missing', part: 'reason' }),
|
|
101
|
+
action: orderRuleMessage('scope-pattern-match', {
|
|
102
|
+
violation: 'missing', part: 'action', labelName: orderInputName('scope new'),
|
|
103
|
+
}),
|
|
101
104
|
};
|
|
102
105
|
}
|
|
103
106
|
|
|
@@ -193,8 +196,10 @@ export function validateScope(repoRoot, patterns, scopeNewPatterns = []) {
|
|
|
193
196
|
return {
|
|
194
197
|
pattern: required[0],
|
|
195
198
|
label: 'scope',
|
|
196
|
-
reason:
|
|
197
|
-
|
|
199
|
+
reason: orderRuleMessage('scope-pattern-match', {
|
|
200
|
+
violation: 'invalid-glob', part: 'reason', message: error.message,
|
|
201
|
+
}),
|
|
202
|
+
action: orderRuleMessage('scope-pattern-match', { violation: 'invalid-glob', part: 'action' }),
|
|
198
203
|
};
|
|
199
204
|
}
|
|
200
205
|
|
|
@@ -206,8 +211,10 @@ export function validateScope(repoRoot, patterns, scopeNewPatterns = []) {
|
|
|
206
211
|
return {
|
|
207
212
|
pattern: required[0],
|
|
208
213
|
label: 'scope',
|
|
209
|
-
reason:
|
|
210
|
-
|
|
214
|
+
reason: orderRuleMessage('scope-pattern-match', {
|
|
215
|
+
violation: 'non-utf8', part: 'reason', message: error.message,
|
|
216
|
+
}),
|
|
217
|
+
action: orderRuleMessage('scope-pattern-match', { violation: 'non-utf8', part: 'action' }),
|
|
211
218
|
};
|
|
212
219
|
}
|
|
213
220
|
const missing = matchers.find((matcher) => !matches.matched.has(matcher.pattern));
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* text in the file while the headingless form preserves every existing task file.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { orderRuleMessage } from '../order-rules.mjs';
|
|
10
|
+
|
|
9
11
|
const SECTION_HEADING = /^ {0,3}(#{1,6})[ \t]+(.+?)[ \t]*$/;
|
|
10
12
|
const LIST_ITEM = /^\s*(?:[-*]|\d+\.)[ \t]+(.+?)\s*$/;
|
|
11
13
|
const SECTION_NAMES = new Map([
|
|
@@ -72,16 +74,16 @@ export function parseTaskDocument(text) {
|
|
|
72
74
|
if (!line.trim()) continue;
|
|
73
75
|
const item = LIST_ITEM.exec(line);
|
|
74
76
|
if (!item) {
|
|
75
|
-
throw new Error(
|
|
77
|
+
throw new Error(orderRuleMessage('scout-questions', { violation: 'non-list-line', line }));
|
|
76
78
|
}
|
|
77
79
|
questions.push(item[1].trim());
|
|
78
80
|
}
|
|
79
|
-
if (sawQuestions && !questions.length) throw new Error('
|
|
81
|
+
if (sawQuestions && !questions.length) throw new Error(orderRuleMessage('scout-questions', { violation: 'empty' }));
|
|
80
82
|
|
|
81
83
|
const verifyContent = verifyLines.filter((line) => line.trim());
|
|
82
|
-
if (sawVerify && !verifyContent.length) throw new Error('
|
|
84
|
+
if (sawVerify && !verifyContent.length) throw new Error(orderRuleMessage('verify-line', { violation: 'empty' }));
|
|
83
85
|
if (verifyContent.length > 1) {
|
|
84
|
-
throw new Error('
|
|
86
|
+
throw new Error(orderRuleMessage('verify-line', { violation: 'multiple-lines' }));
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
return {
|
|
@@ -10,6 +10,8 @@ import { die, readTaskDocument } from './args.mjs';
|
|
|
10
10
|
import { questionKindRefusal } from './question-kind.mjs';
|
|
11
11
|
import { orderFromHeader, orderInputName, renderOrderProblems } from '../order-schema.mjs';
|
|
12
12
|
import { orderOptions } from './order-options.mjs';
|
|
13
|
+
import { orderRuleMessage, orderLabelReason } from '../order-rules.mjs';
|
|
14
|
+
import { orderDiagnosticMessage } from '../order-diagnostics.mjs';
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* OW-061: outside git the launcher keeps `repository:` as given and the project folder takes its
|
|
@@ -21,18 +23,17 @@ import { orderOptions } from './order-options.mjs';
|
|
|
21
23
|
*/
|
|
22
24
|
export function repositoryRefusal(value) {
|
|
23
25
|
if (!path.isAbsolute(value)) {
|
|
24
|
-
return
|
|
26
|
+
return orderRuleMessage('repository-path', { violation: 'relative', value });
|
|
25
27
|
}
|
|
26
28
|
const altered = value.split(/[\\/]/).find((segment) => /[. ]$/.test(segment) && segment !== '.' && segment !== '..');
|
|
27
29
|
if (altered !== undefined) {
|
|
28
|
-
return
|
|
29
|
-
`got ${JSON.stringify(value)}.`;
|
|
30
|
+
return orderRuleMessage('repository-path', { violation: 'altered-segment', value, segment: altered });
|
|
30
31
|
}
|
|
31
32
|
let isDirectory = false;
|
|
32
33
|
try {
|
|
33
34
|
isDirectory = fs.statSync(value).isDirectory();
|
|
34
35
|
} catch {}
|
|
35
|
-
return isDirectory ? null :
|
|
36
|
+
return isDirectory ? null : orderRuleMessage('repository-path', { violation: 'not-directory', value });
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
export function settleTaskInput(opts, { cwd = process.cwd() } = {}) {
|
|
@@ -43,11 +44,13 @@ export function settleTaskInput(opts, { cwd = process.cwd() } = {}) {
|
|
|
43
44
|
}
|
|
44
45
|
const normalized = orderOptions(opts.agent, order, { cwd });
|
|
45
46
|
if (normalized.problems.length) {
|
|
46
|
-
die(normalized.problems.map(({ label, reason }) =>
|
|
47
|
+
die(normalized.problems.map(({ label, reason }) => orderLabelReason(orderInputName(label), reason)).join('\n'));
|
|
47
48
|
}
|
|
48
49
|
const repositoryReason = order.has('repository') ? repositoryRefusal(order.get('repository')) : null;
|
|
49
50
|
if (repositoryReason) {
|
|
50
|
-
die(
|
|
51
|
+
die(orderRuleMessage('repository-path', {
|
|
52
|
+
context: 'task-input', label: orderInputName('repository'), reason: repositoryReason,
|
|
53
|
+
}));
|
|
51
54
|
}
|
|
52
55
|
Object.assign(opts, normalized.options);
|
|
53
56
|
opts.continue = Boolean(header.grant);
|
|
@@ -56,23 +59,16 @@ export function settleTaskInput(opts, { cwd = process.cwd() } = {}) {
|
|
|
56
59
|
// Still before the run folder exists and before a token of someone else's quota is touched,
|
|
57
60
|
// which is where every refusal of this kind belongs.
|
|
58
61
|
if (opts.agent === 'codex-scout' && !opts.questions?.length) {
|
|
59
|
-
die(
|
|
60
|
-
'a sub-question is required for codex-scout: put one Markdown list item per sub-question ' +
|
|
61
|
-
'under a `Questions` heading in the task document. ' +
|
|
62
|
-
'The runner will not infer questions from the task text; no quota was spent.',
|
|
63
|
-
);
|
|
62
|
+
die(orderRuleMessage('scout-questions', { violation: 'missing' }));
|
|
64
63
|
}
|
|
65
64
|
if (opts.agent === 'codex-scout') {
|
|
66
65
|
const refusal = questionKindRefusal(opts.questions);
|
|
67
66
|
if (refusal) {
|
|
68
|
-
die(
|
|
69
|
-
`${refusal}; the marker is exactly [context-only] followed by a space and the question; no quota was spent.`,
|
|
70
|
-
);
|
|
67
|
+
die(orderRuleMessage('question-context-marker', { context: 'task-input', refusal }));
|
|
71
68
|
}
|
|
72
69
|
}
|
|
73
70
|
if (opts.noWait && opts.continue) {
|
|
74
|
-
die(
|
|
75
|
-
'checking an existing run must never authorize a new one.');
|
|
71
|
+
die(orderDiagnosticMessage('no-wait-grant-conflict', { grant: orderInputName(header.grant.kind) }));
|
|
76
72
|
}
|
|
77
73
|
return { task, header };
|
|
78
74
|
}
|
|
@@ -9,6 +9,9 @@ import { readJsonFileSync } from '../json-file.mjs';
|
|
|
9
9
|
import { writeStatus } from '../meta/run-state.mjs';
|
|
10
10
|
import { acquireOrderClaim } from './order-claim.mjs';
|
|
11
11
|
|
|
12
|
+
// Plan_79 B4e: admission reasons are machine-readable codes, not refusal prose.
|
|
13
|
+
const REASONS = Object.freeze({ claimTimeout: 'claim-timeout', closed: 'closed' });
|
|
14
|
+
|
|
12
15
|
export async function admitWorker({ runDir, orderId, timing, platform, createServer }) {
|
|
13
16
|
const claim = await acquireOrderClaim({
|
|
14
17
|
projectRunsRoot: path.dirname(runDir),
|
|
@@ -19,7 +22,7 @@ export async function admitWorker({ runDir, orderId, timing, platform, createSer
|
|
|
19
22
|
...(createServer !== undefined ? { createServer } : {}),
|
|
20
23
|
});
|
|
21
24
|
try {
|
|
22
|
-
if (claim.busy) return { admitted: false, reason:
|
|
25
|
+
if (claim.busy) return { admitted: false, reason: REASONS.claimTimeout, holder: claim.holder };
|
|
23
26
|
let status = null;
|
|
24
27
|
try {
|
|
25
28
|
status = readJsonFileSync(path.join(runDir, 'status.json'));
|
|
@@ -27,7 +30,7 @@ export async function admitWorker({ runDir, orderId, timing, platform, createSer
|
|
|
27
30
|
// Plan_60 A4 r4: an unreadable run cannot authorize paid work after a launcher dies.
|
|
28
31
|
}
|
|
29
32
|
if (status?.state !== 'running' || fs.existsSync(path.join(runDir, 'meta.json'))) {
|
|
30
|
-
return { admitted: false, reason:
|
|
33
|
+
return { admitted: false, reason: REASONS.closed, state: status?.state ?? null };
|
|
31
34
|
}
|
|
32
35
|
// The launcher cannot know this detached process's clock origin; takeover must record the
|
|
33
36
|
// worker's own identity before it starts producing the run's artifacts.
|
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
* Unknown host versions are not evidence for a pre-start refusal.
|
|
5
5
|
*/
|
|
6
6
|
import { modelFamily } from './model-family.mjs';
|
|
7
|
+
import { orderDiagnosticMessage } from './order-diagnostics.mjs';
|
|
7
8
|
|
|
8
9
|
// Host contract facts, not package versions or volatile model identifiers (Plan_67 D6 (b)).
|
|
9
|
-
export const FRONTMATTER_PRECEDENCE_VERSION = '2.1.251';
|
|
10
|
-
export const SUBAGENT_MODEL_FORCE_VERSION = '2.1.257';
|
|
10
|
+
export const FRONTMATTER_PRECEDENCE_VERSION = '2.1.251'; // order-guard: not-a-refusal — host version boundary compared as data
|
|
11
|
+
export const SUBAGENT_MODEL_FORCE_VERSION = '2.1.257'; // order-guard: not-a-refusal — host version boundary compared as data
|
|
11
12
|
|
|
12
13
|
function versionParts(value) {
|
|
13
14
|
if (typeof value !== 'string' || !/^\d+\.\d+\.\d+(?:\.\d+)*$/.test(value)) return null;
|
|
@@ -37,8 +38,6 @@ export function envModelProblem({ env, hostVersion, pinFamily }) {
|
|
|
37
38
|
const model = env.CLAUDE_CODE_SUBAGENT_MODEL;
|
|
38
39
|
const family = modelFamily(model);
|
|
39
40
|
if (family === null || pinFamily === null || family === pinFamily) return null;
|
|
40
|
-
return
|
|
41
|
-
|
|
42
|
-
+ `Unset CLAUDE_CODE_SUBAGENT_MODEL or set it to the pinned family ${pinFamily}. `
|
|
43
|
-
+ 'The run folder was not created; quota was not spent.';
|
|
41
|
+
return [orderDiagnosticMessage('subagent-model-env-pin', { model, pinFamily, hostVersion }),
|
|
42
|
+
orderDiagnosticMessage('run-not-created')].join(' ');
|
|
44
43
|
}
|