@fitlab-ai/agent-infra 0.9.6 → 0.9.8
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/bin/internal-cli.js +10 -0
- package/dist/lib/agent-clients/adapters/claude-code.js +3 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +47 -5
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +162 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +245 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +398 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +23 -6
- package/dist/lib/decide.js +68 -14
- package/dist/lib/defaults.json +10 -2
- package/dist/lib/git/workflow.js +30 -3
- package/dist/lib/init.js +1 -1
- package/dist/lib/internal/codex-lifecycle.js +69 -1
- package/dist/lib/internal/codex-sandbox-controller.js +106 -0
- package/dist/lib/internal/release-workflow.js +1 -3
- package/dist/lib/internal/task-activity.js +57 -7
- package/dist/lib/internal/task-event.js +57 -4
- package/dist/lib/internal/task-ledger.js +59 -5
- package/dist/lib/internal/task-lifecycle.js +59 -8
- package/dist/lib/internal/task-orchestration.js +48 -8
- package/dist/lib/internal/task-override.js +131 -0
- package/dist/lib/internal/task-review.js +64 -6
- package/dist/lib/internal/task-verify.js +65 -3
- package/dist/lib/internal/task-warning.js +57 -4
- package/dist/lib/platform/context.js +6 -4
- package/dist/lib/platform/github-client.js +55 -2
- package/dist/lib/platform/pr-summary.js +5 -3
- package/dist/lib/process-data/checkpoint.js +319 -0
- package/dist/lib/process-data/index.js +128 -60
- package/dist/lib/process-data/materialize.js +84 -0
- package/dist/lib/process-data/normalize.js +34 -1
- package/dist/lib/process-data/reconcile.js +55 -1
- package/dist/lib/process-data/sources.js +422 -1
- package/dist/lib/process-data/store.js +341 -11
- package/dist/lib/runtime/agent-runtime.js +56 -0
- package/dist/lib/sandbox/commands/create.js +394 -264
- package/dist/lib/sandbox/commands/rm.js +64 -15
- package/dist/lib/sandbox/commands/start.js +4 -3
- package/dist/lib/sandbox/control/container-identity.js +64 -0
- package/dist/lib/sandbox/control/executor.js +23 -9
- package/dist/lib/sandbox/control/lifecycle.js +590 -61
- package/dist/lib/sandbox/control/protocol.js +7 -0
- package/dist/lib/sandbox/control/server.js +202 -26
- package/dist/lib/sandbox/control/state.js +51 -25
- package/dist/lib/sandbox/control/timing.js +8 -0
- package/dist/lib/sandbox/engine.js +14 -7
- package/dist/lib/sandbox/mounts.js +7 -0
- package/dist/lib/sandbox/recovery.js +135 -50
- package/dist/lib/sandbox/workspace-view.js +59 -20
- package/dist/lib/server/daemon.js +1 -1
- package/dist/lib/server/process-control.js +11 -1
- package/dist/lib/server/process-state.js +107 -20
- package/dist/lib/task/activity-intent.js +4 -1
- package/dist/lib/task/artifact-lifecycle.js +82 -11
- package/dist/lib/task/artifact-receipts.js +128 -0
- package/dist/lib/task/codex-orchestration.js +81 -6
- package/dist/lib/task/commands/validate.js +1 -1
- package/dist/lib/task/create.js +10 -2
- package/dist/lib/task/decision-intents.js +4 -2
- package/dist/lib/task/delegation-receipts.js +120 -17
- package/dist/lib/task/events.js +148 -7
- package/dist/lib/task/guard-override.js +31 -0
- package/dist/lib/task/human-override.js +1240 -0
- package/dist/lib/task/ledger-intents.js +6 -3
- package/dist/lib/task/ledger.js +13 -1
- package/dist/lib/task/lifecycle.js +69 -14
- package/dist/lib/task/orchestration.js +293 -18
- package/dist/lib/task/review-finalization.js +3 -2
- package/dist/lib/task/sections.js +5 -1
- package/dist/lib/task/task-execution-lock.js +5 -4
- package/dist/lib/task/verification-engine.js +80 -22
- package/dist/lib/task/verification.js +13 -1
- package/dist/lib/task/workflow-warning-intents.js +4 -2
- package/dist/lib/task/write.js +2 -1
- package/dist/lifecycle-build-manifest.json +6 -0
- package/lib/agent-clients/adapters/claude-code.ts +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +62 -4
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +227 -0
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +322 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +26 -0
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +480 -0
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +35 -5
- package/lib/decide.ts +59 -14
- package/lib/defaults.json +10 -2
- package/lib/git/workflow.ts +25 -2
- package/lib/init.ts +1 -1
- package/lib/internal/codex-lifecycle.ts +68 -1
- package/lib/internal/codex-sandbox-controller.ts +109 -0
- package/lib/internal/release-workflow.ts +1 -3
- package/lib/internal/task-activity.ts +46 -6
- package/lib/internal/task-event.ts +53 -4
- package/lib/internal/task-ledger.ts +48 -4
- package/lib/internal/task-lifecycle.ts +56 -12
- package/lib/internal/task-orchestration.ts +43 -8
- package/lib/internal/task-override.ts +138 -0
- package/lib/internal/task-review.ts +51 -6
- package/lib/internal/task-verify.ts +52 -3
- package/lib/internal/task-warning.ts +44 -3
- package/lib/platform/context.ts +8 -5
- package/lib/platform/github-client.ts +61 -3
- package/lib/platform/pr-summary.ts +10 -3
- package/lib/process-data/checkpoint.ts +335 -0
- package/lib/process-data/index.ts +112 -60
- package/lib/process-data/materialize.ts +83 -0
- package/lib/process-data/normalize.ts +29 -1
- package/lib/process-data/reconcile.ts +64 -2
- package/lib/process-data/sources.ts +449 -2
- package/lib/process-data/store.ts +304 -11
- package/lib/process-data/types.ts +124 -2
- package/lib/runtime/agent-runtime.ts +76 -0
- package/lib/sandbox/commands/create.ts +139 -6
- package/lib/sandbox/commands/rm.ts +64 -14
- package/lib/sandbox/commands/start.ts +4 -3
- package/lib/sandbox/config.ts +2 -1
- package/lib/sandbox/control/container-identity.ts +93 -0
- package/lib/sandbox/control/executor.ts +24 -12
- package/lib/sandbox/control/lifecycle.ts +685 -67
- package/lib/sandbox/control/protocol.ts +35 -6
- package/lib/sandbox/control/server.ts +184 -24
- package/lib/sandbox/control/state.ts +67 -33
- package/lib/sandbox/control/timing.ts +12 -0
- package/lib/sandbox/engine.ts +20 -9
- package/lib/sandbox/mounts.ts +8 -0
- package/lib/sandbox/recovery.ts +159 -49
- package/lib/sandbox/workspace-view.ts +73 -22
- package/lib/server/daemon.ts +1 -1
- package/lib/server/process-control.ts +13 -3
- package/lib/server/process-state.ts +129 -27
- package/lib/task/activity-intent.ts +4 -2
- package/lib/task/artifact-lifecycle.ts +73 -10
- package/lib/task/artifact-receipts.ts +161 -0
- package/lib/task/codex-orchestration.ts +91 -5
- package/lib/task/commands/validate.ts +1 -1
- package/lib/task/create.ts +11 -2
- package/lib/task/decision-intents.ts +4 -2
- package/lib/task/delegation-receipts.ts +205 -25
- package/lib/task/events.ts +132 -6
- package/lib/task/guard-override.ts +50 -0
- package/lib/task/human-override.ts +1503 -0
- package/lib/task/ledger-intents.ts +6 -3
- package/lib/task/ledger.ts +16 -1
- package/lib/task/lifecycle.ts +71 -14
- package/lib/task/orchestration.ts +443 -22
- package/lib/task/review-finalization.ts +6 -2
- package/lib/task/sections.ts +6 -2
- package/lib/task/task-execution-lock.ts +11 -9
- package/lib/task/verification-engine.ts +93 -32
- package/lib/task/verification.ts +15 -1
- package/lib/task/workflow-warning-intents.ts +4 -2
- package/lib/task/write.ts +4 -1
- package/package.json +1 -1
- package/templates/.agents/README.en.md +7 -0
- package/templates/.agents/README.zh-CN.md +5 -0
- package/templates/.agents/hooks/lifecycle-delegation.js +33 -4
- package/templates/.agents/rules/lifecycle-orchestration.en.md +10 -3
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +10 -3
- package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
- package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +1 -1
- package/templates/.agents/rules/review-handshake.en.md +3 -1
- package/templates/.agents/rules/review-handshake.zh-CN.md +3 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +3 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +3 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +6 -6
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +14 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +14 -3
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +67 -0
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +67 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +25 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +25 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +9 -7
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -7
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +5 -1
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +5 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +75 -3
- package/templates/.agents/workspace/README.en.md +0 -26
- package/templates/.agents/workspace/README.zh-CN.md +0 -26
|
@@ -11,7 +11,10 @@ export function parseLinuxProcessStat(raw) {
|
|
|
11
11
|
if (typeof state !== 'string' || state.length !== 1 || !/^\d+$/.test(startTime ?? '')) {
|
|
12
12
|
return null;
|
|
13
13
|
}
|
|
14
|
-
|
|
14
|
+
const numericStartTime = Number(startTime);
|
|
15
|
+
if (!Number.isSafeInteger(numericStartTime))
|
|
16
|
+
return null;
|
|
17
|
+
return { state, startTime: numericStartTime };
|
|
15
18
|
}
|
|
16
19
|
export function buildProcessStartTimeQuery(pid, platform) {
|
|
17
20
|
if (platform === 'darwin') {
|
|
@@ -47,6 +50,17 @@ export function isProcessAlive(pid, platform = process.platform) {
|
|
|
47
50
|
return error.code === 'EPERM';
|
|
48
51
|
}
|
|
49
52
|
}
|
|
53
|
+
function parseDarwinStartTime(output) {
|
|
54
|
+
const match = output.trim().match(/^(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat) ([A-Z][a-z]{2}) (\d{1,2}) (\d{2}):(\d{2}):(\d{2}) (\d{4})$/);
|
|
55
|
+
if (!match)
|
|
56
|
+
return null;
|
|
57
|
+
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
58
|
+
const month = months.indexOf(match[1]);
|
|
59
|
+
if (month < 0)
|
|
60
|
+
return null;
|
|
61
|
+
const timestamp = Date.UTC(Number(match[6]), month, Number(match[2]), Number(match[3]), Number(match[4]), Number(match[5]));
|
|
62
|
+
return Number.isNaN(timestamp) ? null : timestamp;
|
|
63
|
+
}
|
|
50
64
|
export function getProcessStartTime(pid, platform = process.platform) {
|
|
51
65
|
if (!Number.isInteger(pid) || pid <= 0)
|
|
52
66
|
return null;
|
|
@@ -65,43 +79,114 @@ export function getProcessStartTime(pid, platform = process.platform) {
|
|
|
65
79
|
if (query === null)
|
|
66
80
|
return null;
|
|
67
81
|
try {
|
|
68
|
-
const output = execFileSync(query.command, query.args, {
|
|
82
|
+
const output = execFileSync(query.command, query.args, {
|
|
83
|
+
encoding: 'utf8',
|
|
84
|
+
env: platform === 'darwin' ? { ...process.env, LC_ALL: 'C', LANG: 'C', TZ: 'UTC' } : process.env
|
|
85
|
+
}).trim();
|
|
69
86
|
if (output.length === 0)
|
|
70
87
|
return null;
|
|
71
88
|
if (platform === 'win32') {
|
|
72
89
|
const timestamp = new Date(output);
|
|
73
|
-
return Number.isNaN(timestamp.getTime()) ? null : timestamp.
|
|
90
|
+
return Number.isNaN(timestamp.getTime()) ? null : timestamp.getTime();
|
|
74
91
|
}
|
|
75
|
-
|
|
92
|
+
if (platform === 'darwin')
|
|
93
|
+
return parseDarwinStartTime(output);
|
|
94
|
+
return null;
|
|
76
95
|
}
|
|
77
96
|
catch {
|
|
78
97
|
return null;
|
|
79
98
|
}
|
|
80
99
|
}
|
|
100
|
+
export function getProcessIdentityState(identity, platform = process.platform) {
|
|
101
|
+
if (!Number.isInteger(identity.pid) || identity.pid <= 0 || !Number.isSafeInteger(identity.startTime)) {
|
|
102
|
+
return 'dead';
|
|
103
|
+
}
|
|
104
|
+
if (platform === 'linux') {
|
|
105
|
+
try {
|
|
106
|
+
const stat = parseLinuxProcessStat(fs.readFileSync(`/proc/${identity.pid}/stat`, 'utf8'));
|
|
107
|
+
if (stat === null)
|
|
108
|
+
return 'unknown';
|
|
109
|
+
if (stat.state === 'Z')
|
|
110
|
+
return 'dead';
|
|
111
|
+
return stat.startTime === identity.startTime ? 'alive' : 'dead';
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
return error.code === 'ENOENT' ? 'dead' : 'unknown';
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
process.kill(identity.pid, 0);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
const code = error.code;
|
|
122
|
+
if (code === 'ESRCH')
|
|
123
|
+
return 'dead';
|
|
124
|
+
if (code !== 'EPERM')
|
|
125
|
+
return 'unknown';
|
|
126
|
+
}
|
|
127
|
+
const query = buildProcessStartTimeQuery(identity.pid, platform);
|
|
128
|
+
if (query === null)
|
|
129
|
+
return 'unknown';
|
|
130
|
+
try {
|
|
131
|
+
const output = execFileSync(query.command, query.args, {
|
|
132
|
+
encoding: 'utf8',
|
|
133
|
+
env: platform === 'darwin' ? { ...process.env, LC_ALL: 'C', LANG: 'C', TZ: 'UTC' } : process.env
|
|
134
|
+
}).trim();
|
|
135
|
+
if (output.length === 0)
|
|
136
|
+
return 'unknown';
|
|
137
|
+
const startTime = platform === 'win32'
|
|
138
|
+
? new Date(output).getTime()
|
|
139
|
+
: platform === 'darwin'
|
|
140
|
+
? parseDarwinStartTime(output)
|
|
141
|
+
: null;
|
|
142
|
+
if (startTime === null || Number.isNaN(startTime))
|
|
143
|
+
return 'unknown';
|
|
144
|
+
return startTime === identity.startTime ? 'alive' : 'dead';
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return 'unknown';
|
|
148
|
+
}
|
|
149
|
+
}
|
|
81
150
|
function parsePidRecord(raw) {
|
|
82
151
|
const trimmed = raw.trim();
|
|
83
152
|
if (/^[1-9]\d*$/.test(trimmed)) {
|
|
84
153
|
const pid = Number(trimmed);
|
|
85
|
-
return Number.isSafeInteger(pid)
|
|
154
|
+
return Number.isSafeInteger(pid)
|
|
155
|
+
? { kind: 'legacy-pid-only', legacy: { pid, raw, format: 'pid-only' } }
|
|
156
|
+
: { kind: 'legacy-unknown', legacy: { raw, format: 'malformed' }, reason: 'pid is not a safe integer' };
|
|
86
157
|
}
|
|
87
158
|
try {
|
|
88
159
|
const value = JSON.parse(trimmed);
|
|
160
|
+
const pidValue = value?.pid;
|
|
161
|
+
const startTimeValue = value?.startTime;
|
|
89
162
|
if (value !== null &&
|
|
90
|
-
value.version ===
|
|
91
|
-
|
|
92
|
-
(
|
|
93
|
-
|
|
94
|
-
|
|
163
|
+
value.version === 2 &&
|
|
164
|
+
typeof pidValue === 'number' &&
|
|
165
|
+
Number.isSafeInteger(pidValue) &&
|
|
166
|
+
pidValue > 0 &&
|
|
167
|
+
typeof startTimeValue === 'number' &&
|
|
168
|
+
Number.isSafeInteger(startTimeValue) &&
|
|
169
|
+
startTimeValue >= 0) {
|
|
170
|
+
const startTime = startTimeValue;
|
|
95
171
|
return {
|
|
96
172
|
kind: 'record',
|
|
97
|
-
record: { version:
|
|
173
|
+
record: { version: 2, pid: pidValue, startTime }
|
|
98
174
|
};
|
|
99
175
|
}
|
|
176
|
+
if (value !== null &&
|
|
177
|
+
value.version === 1 &&
|
|
178
|
+
typeof pidValue === 'number' &&
|
|
179
|
+
Number.isSafeInteger(pidValue) &&
|
|
180
|
+
pidValue > 0 &&
|
|
181
|
+
typeof value.startTime === 'string' &&
|
|
182
|
+
value.startTime.length > 0) {
|
|
183
|
+
return { kind: 'legacy-json', legacy: { pid: pidValue, raw, format: 'json-v1' } };
|
|
184
|
+
}
|
|
185
|
+
return { kind: 'legacy-unknown', legacy: { raw, format: 'malformed' }, reason: 'unsupported PID record schema' };
|
|
100
186
|
}
|
|
101
187
|
catch {
|
|
102
|
-
|
|
188
|
+
return { kind: 'legacy-unknown', legacy: { raw, format: 'malformed' }, reason: 'invalid PID record JSON' };
|
|
103
189
|
}
|
|
104
|
-
return { kind: 'invalid' };
|
|
105
190
|
}
|
|
106
191
|
export function readProcessState(pidFile) {
|
|
107
192
|
let snapshot;
|
|
@@ -111,13 +196,15 @@ export function readProcessState(pidFile) {
|
|
|
111
196
|
catch (error) {
|
|
112
197
|
if (error.code === 'ENOENT')
|
|
113
198
|
return { kind: 'missing', snapshot: null };
|
|
114
|
-
return { kind: '
|
|
199
|
+
return { kind: 'legacy-unknown', snapshot: '', legacy: { raw: '', format: 'malformed' }, reason: 'PID record cannot be read' };
|
|
115
200
|
}
|
|
116
201
|
const parsed = parsePidRecord(snapshot);
|
|
117
|
-
if (parsed.kind === '
|
|
118
|
-
return { kind:
|
|
119
|
-
if (parsed.kind === 'legacy')
|
|
120
|
-
return { kind:
|
|
202
|
+
if (parsed.kind === 'legacy-json')
|
|
203
|
+
return { kind: parsed.kind, snapshot, legacy: parsed.legacy };
|
|
204
|
+
if (parsed.kind === 'legacy-pid-only')
|
|
205
|
+
return { kind: parsed.kind, snapshot, legacy: parsed.legacy };
|
|
206
|
+
if (parsed.kind === 'legacy-unknown')
|
|
207
|
+
return { kind: parsed.kind, snapshot, legacy: parsed.legacy, reason: parsed.reason };
|
|
121
208
|
const currentStartTime = getProcessStartTime(parsed.record.pid);
|
|
122
209
|
if (currentStartTime === null || currentStartTime !== parsed.record.startTime) {
|
|
123
210
|
return { kind: 'stale', snapshot, record: parsed.record };
|
|
@@ -162,7 +249,7 @@ export function removePidRecordIfMatches(pidFile, expected) {
|
|
|
162
249
|
return false;
|
|
163
250
|
return removePidFileIfMatches(pidFile, state.snapshot);
|
|
164
251
|
}
|
|
165
|
-
export function processIdentityMatches(
|
|
166
|
-
return
|
|
252
|
+
export function processIdentityMatches(identity) {
|
|
253
|
+
return getProcessIdentityState(identity) === 'alive';
|
|
167
254
|
}
|
|
168
255
|
//# sourceMappingURL=process-state.js.map
|
|
@@ -5,6 +5,7 @@ import { locateActivityLog, appendActivityEntry } from './activity-log.js';
|
|
|
5
5
|
import { assertWritableInventory, buildArtifactLinkSection, inspectTaskArtifacts, parseArtifactName } from './artifact-lifecycle.js';
|
|
6
6
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
7
7
|
import { captureTaskWriteMetadata, writeTask } from './write.js';
|
|
8
|
+
import { allowsManualOverride } from './guard-override.js';
|
|
8
9
|
const HEAD_RE = /^[0-9a-f]{40}$/;
|
|
9
10
|
const REVIEW_ACTION_RE = /^Review PR \(Round ([1-9]\d*)\)( \[started\])?$/;
|
|
10
11
|
const VERDICTS = new Set(['approved', 'changes-requested', 'commented']);
|
|
@@ -292,9 +293,11 @@ export function applyPrReviewActivityIntent(intent, options = {}) {
|
|
|
292
293
|
const resolved = resolveTaskRef(intent.taskRef, { repoRoot: options.repoRoot });
|
|
293
294
|
if (!resolved.ok)
|
|
294
295
|
return failed(intent, resolved.code, resolved.message, resolved.taskId);
|
|
295
|
-
if (resolved.state !== 'active') {
|
|
296
|
+
if (resolved.state !== 'active' && !allowsManualOverride(options.manualOverride, 'activity-intent', 'TASK_STATE_MISMATCH')) {
|
|
296
297
|
return failed(intent, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
|
|
297
298
|
}
|
|
299
|
+
if (options.lockAlreadyHeld)
|
|
300
|
+
return applyLocked(intent, validated.agent, { ...options, repoRoot: resolved.repoRoot });
|
|
298
301
|
try {
|
|
299
302
|
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-activity:${intent.kind}`, () => applyLocked(intent, validated.agent, { ...options, repoRoot: resolved.repoRoot }), options.lockOptions);
|
|
300
303
|
}
|
|
@@ -5,6 +5,7 @@ import { locateActivityLog } from './activity-log.js';
|
|
|
5
5
|
import { parseImplementationInputs, selectPendingImplementationInput } from './implementation-inputs.js';
|
|
6
6
|
import { parseVerdict } from './review-artifacts.js';
|
|
7
7
|
import { extractSection, findSectionHeading } from './sections.js';
|
|
8
|
+
import { receiptForOutput, sha256File } from './artifact-receipts.js';
|
|
8
9
|
const artifactFamilyCatalog = [
|
|
9
10
|
{ family: 'analysis', sectionAliases: ['分析', 'Analysis'], heading: '分析', labels: ['需求分析报告', 'Requirements Analysis'] },
|
|
10
11
|
{ family: 'review-analysis', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['需求分析审查', 'Analysis Review'] },
|
|
@@ -151,11 +152,7 @@ function resolveReviewedInput(taskDir, review, expectedFamily, diagnostics) {
|
|
|
151
152
|
diagnostics.push(diagnostic('BROKEN_REFERENCE', review.family, review.name, String(error)));
|
|
152
153
|
return null;
|
|
153
154
|
}
|
|
154
|
-
const
|
|
155
|
-
const header = lines.findIndex((line) => /\*\*(?:审查输入|Review Input)\*\*[::]/.test(line));
|
|
156
|
-
const referenceBlock = header >= 0 ? lines.slice(header, header + 12).join('\n') : '';
|
|
157
|
-
const candidates = [...referenceBlock.matchAll(/`([^`]+\.md)`/g)].map((match) => match[1]);
|
|
158
|
-
const parsed = candidates.map((name) => parseArtifactName(name)).find((item) => item?.family === expectedFamily);
|
|
155
|
+
const parsed = parseReviewedInputReference(content, expectedFamily);
|
|
159
156
|
if (!parsed) {
|
|
160
157
|
diagnostics.push(diagnostic('BROKEN_REFERENCE', review.family, review.name, `${review.name} does not reference a canonical ${expectedFamily} artifact`));
|
|
161
158
|
return null;
|
|
@@ -165,9 +162,17 @@ function resolveReviewedInput(taskDir, review, expectedFamily, diagnostics) {
|
|
|
165
162
|
const stat = fs.lstatSync(abs);
|
|
166
163
|
if (!stat.isFile() || stat.isSymbolicLink())
|
|
167
164
|
throw new Error('referenced input is not a regular file');
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
165
|
+
const taskContent = fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8');
|
|
166
|
+
const receipt = receiptForOutput(taskContent, review.name);
|
|
167
|
+
if (!receipt)
|
|
168
|
+
throw new Error(`receipt for ${review.name} is missing`);
|
|
169
|
+
const expectedEvent = reviewEventName(review.family);
|
|
170
|
+
if (receipt.event !== expectedEvent || receipt.input !== parsed.name) {
|
|
171
|
+
throw new Error(`receipt for ${review.name} does not match ${parsed.name}`);
|
|
172
|
+
}
|
|
173
|
+
const actualSha256 = sha256File(abs);
|
|
174
|
+
if (actualSha256 !== receipt.inputSha256) {
|
|
175
|
+
throw new Error(`${parsed.name} content digest does not match receipt`);
|
|
171
176
|
}
|
|
172
177
|
return { ...parsed, path: abs, size: stat.size, mtimeMs: stat.mtimeMs };
|
|
173
178
|
}
|
|
@@ -176,6 +181,64 @@ function resolveReviewedInput(taskDir, review, expectedFamily, diagnostics) {
|
|
|
176
181
|
return null;
|
|
177
182
|
}
|
|
178
183
|
}
|
|
184
|
+
function parseReviewedInputReference(content, expectedFamily) {
|
|
185
|
+
const lines = content.split(/\r?\n/);
|
|
186
|
+
const header = lines.findIndex((line) => /\*\*(?:审查输入|Review Input)\*\*[::]/.test(line));
|
|
187
|
+
const referenceBlock = header >= 0 ? lines.slice(header, header + 12).join('\n') : '';
|
|
188
|
+
const candidates = [...referenceBlock.matchAll(/`([^`]+\.md)`/g)].map((match) => match[1]);
|
|
189
|
+
return candidates
|
|
190
|
+
.map((name) => parseArtifactName(name))
|
|
191
|
+
.find((item) => item?.family === expectedFamily) ?? null;
|
|
192
|
+
}
|
|
193
|
+
function parseCodePlanInputReference(content) {
|
|
194
|
+
const lines = content.split(/\r?\n/);
|
|
195
|
+
const sectionStart = lines.findIndex((line) => /^##\s+(?:实现输入|Implementation Input)\s*$/.test(line.trim()));
|
|
196
|
+
if (sectionStart < 0)
|
|
197
|
+
return null;
|
|
198
|
+
const bodyLines = lines.slice(sectionStart + 1);
|
|
199
|
+
const nextSection = bodyLines.findIndex((line) => /^##\s+/.test(line.trim()));
|
|
200
|
+
const body = (nextSection < 0 ? bodyLines : bodyLines.slice(0, nextSection)).join('\n');
|
|
201
|
+
const match = body.match(/\*\*(?:方案输入|Plan Input)\*\*[::]\s*`([^`]+\.md)`/);
|
|
202
|
+
if (!match)
|
|
203
|
+
return null;
|
|
204
|
+
const parsed = parseArtifactName(match[1]);
|
|
205
|
+
return parsed?.family === 'plan' ? { ...parsed, family: 'plan' } : null;
|
|
206
|
+
}
|
|
207
|
+
function reviewEventName(family) {
|
|
208
|
+
if (family === 'review-analysis')
|
|
209
|
+
return 'review-analysis.completed';
|
|
210
|
+
if (family === 'review-plan')
|
|
211
|
+
return 'review-plan.completed';
|
|
212
|
+
if (family === 'review-code')
|
|
213
|
+
return 'review-code.completed';
|
|
214
|
+
throw new Error(`unsupported review family '${family}'`);
|
|
215
|
+
}
|
|
216
|
+
function resolveCodeInputReceipt(taskDir, code) {
|
|
217
|
+
let taskContent;
|
|
218
|
+
try {
|
|
219
|
+
taskContent = fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8');
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
let receipt;
|
|
225
|
+
try {
|
|
226
|
+
receipt = receiptForOutput(taskContent, code.name);
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
if (!receipt || receipt.event !== 'code.completed')
|
|
232
|
+
return null;
|
|
233
|
+
const plan = inspectArtifactDirectory(taskDir, 'plan');
|
|
234
|
+
if (plan.status !== 'ready')
|
|
235
|
+
return null;
|
|
236
|
+
const input = plan.artifacts.find((artifact) => artifact.name === receipt.input);
|
|
237
|
+
return input ? { input, inputSha256: receipt.inputSha256 } : null;
|
|
238
|
+
}
|
|
239
|
+
function resolveCodePlanInput(taskDir, code) {
|
|
240
|
+
return resolveCodeInputReceipt(taskDir, code);
|
|
241
|
+
}
|
|
179
242
|
function assertWritableInventory(inventory) {
|
|
180
243
|
if (inventory.status === 'failed')
|
|
181
244
|
return inventory.error;
|
|
@@ -248,10 +311,18 @@ function resolveCodeContext(inventory, options) {
|
|
|
248
311
|
}
|
|
249
312
|
return withCodeMode(inventory, inputs, 'ready', 'init', codeMax, reviewMax, null, null, 'No prior code artifact. Starting initial implementation (round 1 -> code.md).');
|
|
250
313
|
}
|
|
314
|
+
const codePlanInput = resolveCodePlanInput(inventory.taskDir, latestCode);
|
|
315
|
+
if (!codePlanInput) {
|
|
316
|
+
return contextFailure(inventory, 'ARTIFACT_REFERENCE_INVALID', `code completion receipt for ${latestCode.name} is missing or does not match the current plan`, latestCode.name);
|
|
317
|
+
}
|
|
318
|
+
if (reviewPlan.latest && !reviewPlan.reviewedInput) {
|
|
319
|
+
return contextFailure(inventory, 'ARTIFACT_REFERENCE_INVALID', `${reviewPlan.latest.name} has no valid reviewed input`, reviewPlan.latest.name);
|
|
320
|
+
}
|
|
251
321
|
if (reviewPlan.latest && reviewPlan.reviewedInput?.name === plan.latest.name) {
|
|
252
322
|
const verdict = parseVerdict(reviewPlan.latest.path);
|
|
253
|
-
|
|
254
|
-
|
|
323
|
+
const reviewedPlanSha256 = sha256File(reviewPlan.reviewedInput.path);
|
|
324
|
+
if (verdict.ok && verdict.verdict === 'Approved' && (codePlanInput.input.name !== plan.latest.name || codePlanInput.inputSha256 !== reviewedPlanSha256)) {
|
|
325
|
+
return withCodeMode(inventory, inputs, 'ready', 'init', codeMax, reviewMax, verdict.verdict, reviewPlan.latest.name, `Latest ${reviewPlan.latest.name} approves plan content not captured by the latest code input receipt. Entering replan-driven init.`);
|
|
255
326
|
}
|
|
256
327
|
}
|
|
257
328
|
if (reviewMax < codeMax) {
|
|
@@ -377,5 +448,5 @@ function buildArtifactLinkSection(content, artifact) {
|
|
|
377
448
|
body = lines.join('\n').replace(/^\n+|\n+$/g, '');
|
|
378
449
|
return { aliases, heading, body };
|
|
379
450
|
}
|
|
380
|
-
export { artifactFamilyCatalog, artifactName, parseArtifactName, familySpec, inspectTaskArtifacts, inspectArtifactDirectory, resolveArtifactContext, assertWritableInventory, validateCompletedArtifact, buildArtifactLinkSection };
|
|
451
|
+
export { artifactFamilyCatalog, artifactName, parseArtifactName, familySpec, inspectTaskArtifacts, inspectArtifactDirectory, resolveArtifactContext, parseReviewedInputReference, parseCodePlanInputReference, resolveCodePlanInput, reviewEventName, assertWritableInventory, validateCompletedArtifact, buildArtifactLinkSection };
|
|
381
452
|
//# sourceMappingURL=artifact-lifecycle.js.map
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { extractSection, findSectionHeading, parseTable } from './sections.js';
|
|
4
|
+
const RECEIPT_SECTION_ALIASES = ['产物生命周期收据', 'Artifact Lifecycle Receipts'];
|
|
5
|
+
const RECEIPT_COLUMNS = ['event', 'output', 'input', 'input_sha256', 'completed_at'];
|
|
6
|
+
const RECEIPT_EVENTS = new Set([
|
|
7
|
+
'review-analysis.completed',
|
|
8
|
+
'review-plan.completed',
|
|
9
|
+
'review-code.completed',
|
|
10
|
+
'code.completed'
|
|
11
|
+
]);
|
|
12
|
+
const SHA256_RE = /^[a-f0-9]{64}$/;
|
|
13
|
+
const ARTIFACT_NAME_RE = /^(analysis|review-analysis|plan|review-plan|code|review-code|manual-validation|validation-run|pr-review)(?:-r([2-9]|[1-9]\d+))?\.md$/;
|
|
14
|
+
const RECEIPT_SHAPES = {
|
|
15
|
+
'review-analysis.completed': { output: 'review-analysis', input: 'analysis' },
|
|
16
|
+
'review-plan.completed': { output: 'review-plan', input: 'plan' },
|
|
17
|
+
'review-code.completed': { output: 'review-code', input: 'code' },
|
|
18
|
+
'code.completed': { output: 'code', input: 'plan' }
|
|
19
|
+
};
|
|
20
|
+
class ArtifactReceiptError extends Error {
|
|
21
|
+
code = 'ARTIFACT_RECEIPT_INVALID';
|
|
22
|
+
constructor(message) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = 'ArtifactReceiptError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function sha256Bytes(value) {
|
|
28
|
+
return createHash('sha256').update(value).digest('hex');
|
|
29
|
+
}
|
|
30
|
+
function sha256File(filePath) {
|
|
31
|
+
return sha256Bytes(fs.readFileSync(filePath));
|
|
32
|
+
}
|
|
33
|
+
function parseReceiptArtifactName(name) {
|
|
34
|
+
const match = ARTIFACT_NAME_RE.exec(name);
|
|
35
|
+
if (!match)
|
|
36
|
+
return null;
|
|
37
|
+
const roundText = match[2];
|
|
38
|
+
if (roundText !== undefined) {
|
|
39
|
+
const round = Number(roundText);
|
|
40
|
+
if (!Number.isSafeInteger(round) || String(round) !== roundText)
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return { family: match[1] };
|
|
44
|
+
}
|
|
45
|
+
function validateReceiptShape(event, output, input) {
|
|
46
|
+
const outputIdentity = parseReceiptArtifactName(output);
|
|
47
|
+
const inputIdentity = parseReceiptArtifactName(input);
|
|
48
|
+
if (!outputIdentity || !inputIdentity)
|
|
49
|
+
throw new ArtifactReceiptError(`receipt artifact identity is invalid: ${output} -> ${input}`);
|
|
50
|
+
const shape = RECEIPT_SHAPES[event];
|
|
51
|
+
if (outputIdentity.family !== shape.output || inputIdentity.family !== shape.input) {
|
|
52
|
+
throw new ArtifactReceiptError(`receipt event '${event}' does not match ${output} -> ${input}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function parseArtifactReceipts(content) {
|
|
56
|
+
const section = extractSection(content, [...RECEIPT_SECTION_ALIASES]);
|
|
57
|
+
if (!section)
|
|
58
|
+
return { present: false, rows: [] };
|
|
59
|
+
let table;
|
|
60
|
+
try {
|
|
61
|
+
table = parseTable(content, {
|
|
62
|
+
sectionAliases: [...RECEIPT_SECTION_ALIASES],
|
|
63
|
+
columns: [...RECEIPT_COLUMNS],
|
|
64
|
+
keyColumn: 'output'
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
throw new ArtifactReceiptError(error instanceof Error ? error.message : String(error));
|
|
69
|
+
}
|
|
70
|
+
if (!table)
|
|
71
|
+
throw new ArtifactReceiptError('receipt section has no receipt table');
|
|
72
|
+
const rows = table.rows.map((row) => {
|
|
73
|
+
const event = row.values.event ?? '';
|
|
74
|
+
const output = row.values.output ?? '';
|
|
75
|
+
const input = row.values.input ?? '';
|
|
76
|
+
const inputSha256 = row.values.input_sha256 ?? '';
|
|
77
|
+
const completedAt = row.values.completed_at ?? '';
|
|
78
|
+
if (!RECEIPT_EVENTS.has(event))
|
|
79
|
+
throw new ArtifactReceiptError(`unknown receipt event '${event}'`);
|
|
80
|
+
if (!output || !input)
|
|
81
|
+
throw new ArtifactReceiptError('receipt output and input are required');
|
|
82
|
+
validateReceiptShape(event, output, input);
|
|
83
|
+
if (!SHA256_RE.test(inputSha256))
|
|
84
|
+
throw new ArtifactReceiptError(`receipt digest for '${output}' is invalid`);
|
|
85
|
+
if (!completedAt || Number.isNaN(Date.parse(completedAt.replace(' ', 'T')))) {
|
|
86
|
+
throw new ArtifactReceiptError(`receipt completion time for '${output}' is invalid`);
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
event: event,
|
|
90
|
+
output,
|
|
91
|
+
input,
|
|
92
|
+
inputSha256,
|
|
93
|
+
completedAt
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
return { present: true, rows };
|
|
97
|
+
}
|
|
98
|
+
function receiptForOutput(content, output) {
|
|
99
|
+
const parsed = parseArtifactReceipts(content);
|
|
100
|
+
return parsed.rows.find((row) => row.output === output) ?? null;
|
|
101
|
+
}
|
|
102
|
+
function upsertArtifactReceipt(content, receipt) {
|
|
103
|
+
if (!RECEIPT_EVENTS.has(receipt.event))
|
|
104
|
+
throw new ArtifactReceiptError(`unknown receipt event '${receipt.event}'`);
|
|
105
|
+
if (!receipt.output || !receipt.input)
|
|
106
|
+
throw new ArtifactReceiptError('receipt output and input are required');
|
|
107
|
+
validateReceiptShape(receipt.event, receipt.output, receipt.input);
|
|
108
|
+
if (!SHA256_RE.test(receipt.inputSha256))
|
|
109
|
+
throw new ArtifactReceiptError(`receipt digest for '${receipt.output}' is invalid`);
|
|
110
|
+
if (!receipt.completedAt || Number.isNaN(Date.parse(receipt.completedAt.replace(' ', 'T')))) {
|
|
111
|
+
throw new ArtifactReceiptError(`receipt completion time for '${receipt.output}' is invalid`);
|
|
112
|
+
}
|
|
113
|
+
const existing = parseArtifactReceipts(content).rows;
|
|
114
|
+
const previous = existing.find((row) => row.output === receipt.output);
|
|
115
|
+
if (previous && JSON.stringify(previous) !== JSON.stringify(receipt)) {
|
|
116
|
+
throw new ArtifactReceiptError(`receipt for '${receipt.output}' already exists with different evidence`);
|
|
117
|
+
}
|
|
118
|
+
const rows = previous ? existing : [...existing, receipt];
|
|
119
|
+
const heading = findSectionHeading(content, [...RECEIPT_SECTION_ALIASES]);
|
|
120
|
+
const body = [
|
|
121
|
+
`| ${RECEIPT_COLUMNS.join(' | ')} |`,
|
|
122
|
+
`| ${RECEIPT_COLUMNS.map(() => '---').join(' | ')} |`,
|
|
123
|
+
...rows.map((row) => `| ${row.event} | ${row.output} | ${row.input} | ${row.inputSha256} | ${row.completedAt} |`)
|
|
124
|
+
].join('\n');
|
|
125
|
+
return { aliases: [...RECEIPT_SECTION_ALIASES], heading, body };
|
|
126
|
+
}
|
|
127
|
+
export { ArtifactReceiptError, RECEIPT_SECTION_ALIASES, parseArtifactReceipts, receiptForOutput, sha256Bytes, sha256File, upsertArtifactReceipt };
|
|
128
|
+
//# sourceMappingURL=artifact-receipts.js.map
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
1
2
|
import { preflightCodexLifecycleEvidence, resolveCodexSpawnedChild, resolveCodexTerminal, resolveCodexThread } from '../agent-clients/adapters/codex-lifecycle/app-server.js';
|
|
2
3
|
import { createCodexLifecycleStore } from '../agent-clients/adapters/codex-lifecycle/store.js';
|
|
3
|
-
import {
|
|
4
|
+
import { createCodexCapabilityStore } from '../agent-clients/adapters/codex-lifecycle/capability-store.js';
|
|
5
|
+
import { computeLifecycleBuildIdentity } from '../agent-clients/adapters/codex-lifecycle/build-identity.js';
|
|
6
|
+
import { verifyCodexSandboxControllerContext } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.js';
|
|
7
|
+
import { resolveTaskRef } from './resolve-ref.js';
|
|
8
|
+
import { activateMatchingOrchestrationDelegation, hasActivatableOrchestrationDelegation, hasSealableOrchestrationDelegation, pauseMatchingOrchestrationDelegation, prepareOrchestrationDelegation, reconcileMatchingOrchestrationDelegation, sealMatchingOrchestrationDelegationWithHostEvidence } from './orchestration.js';
|
|
4
9
|
function coreOptions(options) {
|
|
5
10
|
return { ...options.orchestrationOptions, repoRoot: options.repoRoot ?? options.orchestrationOptions?.repoRoot };
|
|
6
11
|
}
|
|
@@ -21,19 +26,61 @@ function requiredStore(options) {
|
|
|
21
26
|
throw new Error('Codex lifecycle store is required for orchestration bridge events');
|
|
22
27
|
return options.store;
|
|
23
28
|
}
|
|
29
|
+
function controllerContext(taskId, repoRoot) {
|
|
30
|
+
const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
|
|
31
|
+
return contextPath
|
|
32
|
+
? verifyCodexSandboxControllerContext(contextPath, taskId, { repoRoot })
|
|
33
|
+
: null;
|
|
34
|
+
}
|
|
24
35
|
async function prepareCodexOrchestrationDelegation(taskRef, input, options = {}) {
|
|
25
36
|
if (input.client !== 'codex')
|
|
26
37
|
return prepareOrchestrationDelegation(taskRef, input, coreOptions(options));
|
|
27
38
|
try {
|
|
28
|
-
|
|
39
|
+
const repoRoot = options.repoRoot ?? process.cwd();
|
|
40
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot });
|
|
41
|
+
if (!resolved.ok)
|
|
42
|
+
return bridgeFailure(resolved.code, resolved.message);
|
|
43
|
+
const preflight = await (options.preflight ?? preflightCodexLifecycleEvidence)(repoRoot);
|
|
44
|
+
if (!input.capabilityToken) {
|
|
45
|
+
return bridgeFailure('ORCHESTRATION_CODEX_CAPABILITY_REQUIRED', 'Codex prepare requires a current-session capability token');
|
|
46
|
+
}
|
|
47
|
+
const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(repoRoot);
|
|
48
|
+
const controller = controllerContext(resolved.taskId, repoRoot);
|
|
49
|
+
const capabilityStore = options.capabilityStore ?? createCodexCapabilityStore();
|
|
50
|
+
const consumed = capabilityStore.consume(input.capabilityToken, {
|
|
51
|
+
taskId: resolved.taskId,
|
|
52
|
+
hookDefinitionHash: preflight.hookDefinitionHash,
|
|
53
|
+
buildIdentity,
|
|
54
|
+
...(controller ? { controller: {
|
|
55
|
+
instanceDigest: controller.controllerInstanceDigest,
|
|
56
|
+
controlGeneration: controller.controlGeneration
|
|
57
|
+
} } : {})
|
|
58
|
+
});
|
|
59
|
+
return prepareOrchestrationDelegation(taskRef, {
|
|
60
|
+
...input,
|
|
61
|
+
lifecycleProvenance: {
|
|
62
|
+
...consumed.buildIdentity,
|
|
63
|
+
hookDefinitionHash: preflight.hookDefinitionHash,
|
|
64
|
+
...preflight.hookProvenance,
|
|
65
|
+
capabilitySessionId: consumed.sessionId,
|
|
66
|
+
capabilityTurnId: consumed.turnId,
|
|
67
|
+
capabilityToolUseId: consumed.toolUseId,
|
|
68
|
+
controllerInstanceDigest: controller?.controllerInstanceDigest ?? null,
|
|
69
|
+
controlGeneration: controller?.controlGeneration ?? null
|
|
70
|
+
}
|
|
71
|
+
}, coreOptions(options));
|
|
29
72
|
}
|
|
30
73
|
catch (error) {
|
|
31
|
-
return bridgeFailure(
|
|
74
|
+
return bridgeFailure(error instanceof Error && error.name.startsWith('CODEX_CAPABILITY_')
|
|
75
|
+
? error.name
|
|
76
|
+
: 'ORCHESTRATION_CLIENT_PREFLIGHT_FAILED', error instanceof Error ? error.message : String(error));
|
|
32
77
|
}
|
|
33
|
-
return prepareOrchestrationDelegation(taskRef, input, coreOptions(options));
|
|
34
78
|
}
|
|
35
79
|
async function activateCodexOrchestrationDelegation(childThreadId, options = {}) {
|
|
36
80
|
try {
|
|
81
|
+
if (!hasActivatableOrchestrationDelegation('codex', childThreadId, coreOptions(options))) {
|
|
82
|
+
return bridgeFailure('ORCHESTRATION_DELEGATION_MISSING', 'No matching Codex delegation is active');
|
|
83
|
+
}
|
|
37
84
|
const store = requiredStore(options);
|
|
38
85
|
const resolved = await (options.resolveThread ?? resolveCodexThread)(childThreadId);
|
|
39
86
|
store.apply(resolved.resolution.thread);
|
|
@@ -45,6 +92,23 @@ async function activateCodexOrchestrationDelegation(childThreadId, options = {})
|
|
|
45
92
|
if (record.state.status !== 'start-ready' || !evidence) {
|
|
46
93
|
return pauseBridge('ORCHESTRATION_CODEX_START_EVIDENCE_INVALID', 'Codex lifecycle start evidence is not ready', options);
|
|
47
94
|
}
|
|
95
|
+
const repoRoot = options.repoRoot ?? process.cwd();
|
|
96
|
+
const preflight = await (options.preflight ?? preflightCodexLifecycleEvidence)(repoRoot, {
|
|
97
|
+
sessionId: evidence.parentThreadId,
|
|
98
|
+
turnId: evidence.parentTurnId,
|
|
99
|
+
toolUseId: evidence.spawnToolUseId
|
|
100
|
+
});
|
|
101
|
+
const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(repoRoot);
|
|
102
|
+
const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
|
|
103
|
+
const contextTaskId = contextPath
|
|
104
|
+
? JSON.parse(fs.readFileSync(contextPath, 'utf8')).taskId
|
|
105
|
+
: null;
|
|
106
|
+
if (contextPath && typeof contextTaskId !== 'string') {
|
|
107
|
+
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_INVALID');
|
|
108
|
+
}
|
|
109
|
+
const controller = contextPath
|
|
110
|
+
? verifyCodexSandboxControllerContext(contextPath, contextTaskId, { repoRoot })
|
|
111
|
+
: null;
|
|
48
112
|
return activateMatchingOrchestrationDelegation('codex', {
|
|
49
113
|
nativeAgent: evidence.nativeAgent,
|
|
50
114
|
childId: evidence.childThreadId,
|
|
@@ -57,9 +121,17 @@ async function activateCodexOrchestrationDelegation(childThreadId, options = {})
|
|
|
57
121
|
? { reasoningEffortFallbackReason: evidence.reasoningEffortFallbackReason }
|
|
58
122
|
: {}),
|
|
59
123
|
hostEvidence: {
|
|
60
|
-
kind: 'codex-lifecycle-
|
|
124
|
+
kind: 'codex-lifecycle-v2',
|
|
61
125
|
hookDefinitionHash: evidence.hookDefinitionHash,
|
|
62
|
-
startRevision: record.revision
|
|
126
|
+
startRevision: record.revision,
|
|
127
|
+
...buildIdentity,
|
|
128
|
+
...preflight.hookProvenance,
|
|
129
|
+
capabilitySessionId: evidence.parentThreadId,
|
|
130
|
+
capabilityTurnId: evidence.parentTurnId,
|
|
131
|
+
spawnToolUseId: evidence.spawnToolUseId,
|
|
132
|
+
spawnObservedAt: record.spawnObservedAt ?? undefined,
|
|
133
|
+
controllerInstanceDigest: controller?.controllerInstanceDigest ?? null,
|
|
134
|
+
controlGeneration: controller?.controlGeneration ?? null
|
|
63
135
|
}
|
|
64
136
|
}, coreOptions(options));
|
|
65
137
|
}
|
|
@@ -93,6 +165,9 @@ async function activateCodexSpawnDelegation(spawn, options = {}) {
|
|
|
93
165
|
}
|
|
94
166
|
async function sealCodexOrchestrationDelegation(childThreadId, options = {}) {
|
|
95
167
|
try {
|
|
168
|
+
if (!hasSealableOrchestrationDelegation('codex', childThreadId, coreOptions(options))) {
|
|
169
|
+
return bridgeFailure('ORCHESTRATION_DELEGATION_MISSING', 'No matching Codex delegation is active');
|
|
170
|
+
}
|
|
96
171
|
const store = requiredStore(options);
|
|
97
172
|
const existing = store.read(childThreadId);
|
|
98
173
|
if (!existing.consumer) {
|
|
@@ -168,7 +168,7 @@ function inplaceValidation(config, target, options) {
|
|
|
168
168
|
const nonce = randomUUID();
|
|
169
169
|
const issuedAt = Date.now();
|
|
170
170
|
const lease = {
|
|
171
|
-
version:
|
|
171
|
+
version: 2, generation: manifest.generation, nonce,
|
|
172
172
|
owner: { pid: process.pid, startTime }, issuedAt,
|
|
173
173
|
expiresAt: issuedAt + Math.min(options.timeoutMs + RECOVERY_GRACE_MS, MAX_TIMEOUT_MS + RECOVERY_GRACE_MS),
|
|
174
174
|
taskId: target.workspace.mode === 'task-bound' ? target.workspace.taskId : null,
|
package/dist/lib/task/create.js
CHANGED
|
@@ -181,6 +181,13 @@ function assertRealDirectory(directory, code) {
|
|
|
181
181
|
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
182
182
|
throw new Error(code);
|
|
183
183
|
}
|
|
184
|
+
function ensureRealDirectory(directory, code) {
|
|
185
|
+
if (fs.existsSync(directory)) {
|
|
186
|
+
assertRealDirectory(directory, code);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
fs.mkdirSync(directory, { mode: 0o700 });
|
|
190
|
+
}
|
|
184
191
|
function withCreateLock(repoRoot, workspaceRoot, operation) {
|
|
185
192
|
const lockRoot = path.join(workspaceRoot, '.task-create.lock');
|
|
186
193
|
fs.mkdirSync(lockRoot, { recursive: true, mode: 0o700 });
|
|
@@ -254,8 +261,9 @@ function createLocalTask(value, options) {
|
|
|
254
261
|
const repoRoot = fs.realpathSync.native(options.repoRoot);
|
|
255
262
|
const workspaceRoot = path.join(repoRoot, '.agents', 'workspace');
|
|
256
263
|
const activeRoot = path.join(workspaceRoot, 'active');
|
|
257
|
-
|
|
258
|
-
|
|
264
|
+
ensureRealDirectory(path.join(repoRoot, '.agents'), 'TASK_CREATE_WORKSPACE_INVALID');
|
|
265
|
+
ensureRealDirectory(workspaceRoot, 'TASK_CREATE_WORKSPACE_INVALID');
|
|
266
|
+
ensureRealDirectory(activeRoot, 'TASK_CREATE_WORKSPACE_INVALID');
|
|
259
267
|
const project = readProject(repoRoot);
|
|
260
268
|
const canonical = canonicalTaskCreateCandidate(candidate);
|
|
261
269
|
const candidateDigest = sha256(canonical);
|