@fitlab-ai/agent-infra 0.9.8 → 0.9.9
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/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/bin/internal-cli.js +6 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +58 -21
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +8 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +173 -10
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-context.js +147 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +14 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +56 -67
- package/dist/lib/git/workflow.js +10 -1
- package/dist/lib/internal/codex-lifecycle.js +19 -5
- package/dist/lib/internal/codex-sandbox-controller.js +6 -13
- package/dist/lib/internal/git-workflow.js +5 -7
- package/dist/lib/internal/platform-pr.js +20 -7
- package/dist/lib/internal/release-workflow.js +2 -1
- package/dist/lib/internal/sandbox-control.js +79 -2
- package/dist/lib/internal/task-finalization.js +54 -0
- package/dist/lib/internal/task-orchestration.js +156 -291
- package/dist/lib/platform/pr-summary.js +48 -10
- package/dist/lib/platform/pull-requests.js +205 -41
- package/dist/lib/sandbox/commands/rm.js +316 -55
- package/dist/lib/sandbox/control/client.js +190 -6
- package/dist/lib/sandbox/control/controller-registration.js +273 -0
- package/dist/lib/sandbox/control/executor.js +137 -5
- package/dist/lib/sandbox/control/lifecycle.js +3 -1
- package/dist/lib/sandbox/control/protocol.js +67 -6
- package/dist/lib/sandbox/index.js +5 -3
- package/dist/lib/sandbox/managed-fs.js +5 -0
- package/dist/lib/sandbox/task-resolver.js +50 -6
- package/dist/lib/sandbox/workspace-identity.js +27 -0
- package/dist/lib/sandbox/worktree-safety.js +215 -2
- package/dist/lib/task/codex-orchestration.js +158 -33
- package/dist/lib/task/commands/status.js +59 -1
- package/dist/lib/task/commit-operation.js +335 -0
- package/dist/lib/task/commit-policy.js +18 -0
- package/dist/lib/task/delegation-receipts.js +243 -2
- package/dist/lib/task/finalization.js +567 -0
- package/dist/lib/task/operation-outcome.js +33 -0
- package/dist/lib/task/orchestration.js +187 -761
- package/dist/lib/task/short-id.js +59 -1
- package/dist/lib/task/task-execution-lock.js +6 -1
- package/dist/lib/task/verification-engine.js +113 -60
- package/dist/lib/task/verification.js +2 -1
- package/dist/lib/task/workflow-warning-intents.js +50 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +61 -19
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +12 -0
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +276 -12
- package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +180 -0
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +19 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +9 -1
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +83 -91
- package/lib/git/workflow.ts +11 -2
- package/lib/internal/codex-lifecycle.ts +18 -5
- package/lib/internal/codex-sandbox-controller.ts +6 -12
- package/lib/internal/git-workflow.ts +5 -6
- package/lib/internal/platform-pr.ts +14 -7
- package/lib/internal/release-workflow.ts +2 -1
- package/lib/internal/sandbox-control.ts +92 -2
- package/lib/internal/task-finalization.ts +68 -0
- package/lib/internal/task-orchestration.ts +20 -103
- package/lib/platform/pr-summary.ts +55 -11
- package/lib/platform/pull-requests.ts +211 -41
- package/lib/sandbox/commands/rm.ts +413 -61
- package/lib/sandbox/control/client.ts +255 -6
- package/lib/sandbox/control/controller-registration.ts +347 -0
- package/lib/sandbox/control/executor.ts +163 -5
- package/lib/sandbox/control/lifecycle.ts +2 -1
- package/lib/sandbox/control/protocol.ts +80 -7
- package/lib/sandbox/index.ts +5 -3
- package/lib/sandbox/managed-fs.ts +5 -0
- package/lib/sandbox/task-resolver.ts +58 -7
- package/lib/sandbox/workspace-identity.ts +50 -2
- package/lib/sandbox/workspace-view.ts +3 -3
- package/lib/sandbox/worktree-safety.ts +236 -2
- package/lib/task/codex-orchestration.ts +167 -33
- package/lib/task/commands/status.ts +78 -1
- package/lib/task/commit-operation.ts +430 -0
- package/lib/task/commit-policy.ts +30 -0
- package/lib/task/delegation-receipts.ts +258 -1
- package/lib/task/finalization.ts +719 -0
- package/lib/task/operation-outcome.ts +48 -0
- package/lib/task/orchestration.ts +217 -982
- package/lib/task/short-id.ts +65 -1
- package/lib/task/task-execution-lock.ts +10 -1
- package/lib/task/verification-engine.ts +118 -63
- package/lib/task/verification.ts +3 -2
- package/lib/task/workflow-warning-intents.ts +57 -2
- package/package.json +2 -2
- package/templates/.agents/QUICKSTART.en.md +2 -2
- package/templates/.agents/QUICKSTART.zh-CN.md +2 -2
- package/templates/.agents/hooks/lifecycle-delegation.js +1 -21
- package/templates/.agents/rules/lifecycle-orchestration.en.md +1 -1
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +1 -1
- package/templates/.agents/rules/task-management.en.md +2 -2
- package/templates/.agents/rules/task-management.zh-CN.md +2 -2
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -4
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -4
- package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/close-codescan/SKILL.en.md +6 -2
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +6 -2
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/commit/SKILL.en.md +41 -125
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +52 -123
- package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -1
- package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +14 -43
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +14 -49
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +2 -35
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +2 -35
- package/templates/.agents/skills/complete-task/SKILL.en.md +31 -38
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +31 -38
- package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +6 -4
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +6 -4
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +2 -2
- package/templates/.agents/skills/run-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +1 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +1 -1
- package/templates/.codex/hooks.json +4 -4
- package/dist/lib/task/commit-finalization.js +0 -305
- package/dist/lib/task/commit-intent.js +0 -165
- package/lib/task/commit-finalization.ts +0 -419
- package/lib/task/commit-intent.ts +0 -230
|
@@ -5,11 +5,11 @@ import os from 'node:os';
|
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import semver from 'semver';
|
|
7
7
|
import * as toml from 'smol-toml';
|
|
8
|
-
import { requestSandboxControl } from '../../../sandbox/control/client.js';
|
|
8
|
+
import { requestCodexControllerClose, requestCodexControllerOpen, requestCodexControllerVerify, requestSandboxControl } from '../../../sandbox/control/client.js';
|
|
9
9
|
import { readSandboxControlStatus } from '../../../sandbox/control/state.js';
|
|
10
|
-
import { getProcessStartTime
|
|
10
|
+
import { getProcessStartTime } from '../../../server/process-state.js';
|
|
11
11
|
import { computeLifecycleBuildIdentity } from './build-identity.js';
|
|
12
|
-
|
|
12
|
+
import { contextFromControllerLease, controllerProofFromContext, verifyCodexSandboxControllerContext as verifyContextFile, writeCodexSandboxControllerContext } from './controller-context.js';
|
|
13
13
|
function digestFiles(files) {
|
|
14
14
|
const hash = crypto.createHash('sha256');
|
|
15
15
|
for (const file of [...files].sort()) {
|
|
@@ -108,6 +108,26 @@ function verifyControl(taskId, control) {
|
|
|
108
108
|
if (payload.taskId !== taskId)
|
|
109
109
|
throw new Error('CODEX_SANDBOX_CONTROLLER_TASK_BINDING_INVALID');
|
|
110
110
|
}
|
|
111
|
+
function verifyCodexSandboxControllerContext(contextPath, options = {}) {
|
|
112
|
+
const control = options.control ?? controlFromEnvironment();
|
|
113
|
+
const context = verifyContextFile(contextPath, {
|
|
114
|
+
repoRoot: options.repoRoot,
|
|
115
|
+
now: options.now,
|
|
116
|
+
generation: control.generation,
|
|
117
|
+
probeProcess: options.probeProcess
|
|
118
|
+
});
|
|
119
|
+
const verified = (options.requestControllerVerify ?? requestCodexControllerVerify)({
|
|
120
|
+
controllerProof: controllerProofFromContext(context),
|
|
121
|
+
...control,
|
|
122
|
+
timeoutMs: 30_000
|
|
123
|
+
});
|
|
124
|
+
if (verified.binding.taskId !== context.taskId
|
|
125
|
+
|| verified.binding.controlGeneration !== context.controlGeneration
|
|
126
|
+
|| verified.binding.controllerInstanceDigest !== context.controllerInstanceDigest) {
|
|
127
|
+
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_INVALID');
|
|
128
|
+
}
|
|
129
|
+
return context;
|
|
130
|
+
}
|
|
111
131
|
function detectedCodexVersion() {
|
|
112
132
|
const result = spawnSync('codex', ['--version'], { encoding: 'utf8' });
|
|
113
133
|
const version = /codex-cli\s+(\d+\.\d+\.\d+)/u.exec(result.stdout ?? '')?.[1];
|
|
@@ -191,7 +211,6 @@ function prepareCodexSandboxController(input, options = {}) {
|
|
|
191
211
|
const repoRoot = path.resolve(options.repoRoot ?? process.cwd());
|
|
192
212
|
const sourceHome = path.resolve(options.codexHome ?? process.env.CODEX_HOME ?? path.join(os.homedir(), '.codex'));
|
|
193
213
|
const control = options.control ?? controlFromEnvironment();
|
|
194
|
-
(options.verifyTaskBinding ?? verifyControl)(input.taskId, control);
|
|
195
214
|
const version = (options.codexVersion ?? detectedCodexVersion)();
|
|
196
215
|
if (!semver.gte(version, '0.147.0'))
|
|
197
216
|
throw new Error('CODEX_SANDBOX_CONTROLLER_CODEX_UNSUPPORTED');
|
|
@@ -200,48 +219,36 @@ function prepareCodexSandboxController(input, options = {}) {
|
|
|
200
219
|
fs.mkdirSync(runtimeRoot, { recursive: true, mode: 0o700 });
|
|
201
220
|
fs.chmodSync(runtimeRoot, 0o700);
|
|
202
221
|
const key = crypto.createHash('sha256')
|
|
203
|
-
.update(`${
|
|
222
|
+
.update(`${process.pid}\0${control.generation}`)
|
|
204
223
|
.digest('hex');
|
|
205
|
-
const leasePath = path.join(runtimeRoot, `${key}.lease.json`);
|
|
206
|
-
if (fs.existsSync(leasePath)) {
|
|
207
|
-
const lease = JSON.parse(fs.readFileSync(leasePath, 'utf8'));
|
|
208
|
-
if (Number.isSafeInteger(lease.pid) && typeof lease.startTime === 'number'
|
|
209
|
-
&& getProcessStartTime(lease.pid) === lease.startTime) {
|
|
210
|
-
throw new Error('CODEX_SANDBOX_CONTROLLER_BUSY');
|
|
211
|
-
}
|
|
212
|
-
fs.unlinkSync(leasePath);
|
|
213
|
-
}
|
|
214
224
|
const parentStartTime = getProcessStartTime(process.pid);
|
|
215
225
|
if (!parentStartTime)
|
|
216
226
|
throw new Error('CODEX_SANDBOX_CONTROLLER_PROCESS_IDENTITY_INVALID');
|
|
217
|
-
const instanceDigest = crypto.randomBytes(32).toString('hex');
|
|
218
|
-
fs.writeFileSync(leasePath, `${JSON.stringify({
|
|
219
|
-
version: 1,
|
|
220
|
-
pid: process.pid,
|
|
221
|
-
startTime: parentStartTime,
|
|
222
|
-
taskId: input.taskId,
|
|
223
|
-
generation: control.generation,
|
|
224
|
-
instanceDigest
|
|
225
|
-
})}\n`, { mode: 0o600, flag: 'wx' });
|
|
226
227
|
const home = fs.mkdtempSync(path.join(runtimeRoot, `${key}-`));
|
|
227
228
|
fs.chmodSync(home, 0o700);
|
|
228
229
|
let cleaned = false;
|
|
230
|
+
let context = null;
|
|
229
231
|
const cleanup = () => {
|
|
230
232
|
if (cleaned)
|
|
231
233
|
return;
|
|
232
234
|
cleaned = true;
|
|
235
|
+
if (context) {
|
|
236
|
+
try {
|
|
237
|
+
(options.closeController ?? requestCodexControllerClose)({
|
|
238
|
+
controllerProcess: context.controllerProcess,
|
|
239
|
+
controllerProof: controllerProofFromContext(context),
|
|
240
|
+
...control,
|
|
241
|
+
timeoutMs: 30_000
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
// The wrapper is exiting; dead-process recovery safely handles an unknown close result.
|
|
246
|
+
}
|
|
247
|
+
}
|
|
233
248
|
const relative = path.relative(runtimeRoot, home);
|
|
234
249
|
if (!relative.startsWith('..') && !path.isAbsolute(relative)) {
|
|
235
250
|
fs.rmSync(home, { recursive: true, force: true });
|
|
236
251
|
}
|
|
237
|
-
try {
|
|
238
|
-
const lease = JSON.parse(fs.readFileSync(leasePath, 'utf8'));
|
|
239
|
-
if (lease.instanceDigest === instanceDigest)
|
|
240
|
-
fs.unlinkSync(leasePath);
|
|
241
|
-
}
|
|
242
|
-
catch {
|
|
243
|
-
// A missing or replaced lease is owned by another cleanup path.
|
|
244
|
-
}
|
|
245
252
|
};
|
|
246
253
|
try {
|
|
247
254
|
const auth = path.join(sourceHome, 'auth.json');
|
|
@@ -263,22 +270,25 @@ function prepareCodexSandboxController(input, options = {}) {
|
|
|
263
270
|
throw new Error('CODEX_SANDBOX_CONTROLLER_BUNDLE_MISMATCH');
|
|
264
271
|
}
|
|
265
272
|
const buildIdentity = computeLifecycleBuildIdentity(repoRoot);
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
parentStartTime,
|
|
274
|
-
issuedAt: now,
|
|
275
|
-
expiresAt: now + CONTEXT_TTL_MS,
|
|
276
|
-
buildIdentity,
|
|
273
|
+
const opened = (options.openController ?? requestCodexControllerOpen)({
|
|
274
|
+
controllerProcess: { pid: process.pid, startTime: parentStartTime },
|
|
275
|
+
...control,
|
|
276
|
+
timeoutMs: 30_000
|
|
277
|
+
});
|
|
278
|
+
const taskId = opened.lease.taskId;
|
|
279
|
+
context = contextFromControllerLease(opened.lease, {
|
|
277
280
|
hookDefinitionHash: crypto.createHash('sha256').update(fs.readFileSync(hooks)).digest('hex'),
|
|
278
281
|
lifecycleProfilesHash: digestFiles([executor, reviewer])
|
|
279
282
|
});
|
|
283
|
+
if (opened.lease.controlGeneration !== control.generation
|
|
284
|
+
|| opened.lease.controllerProcess.pid !== process.pid
|
|
285
|
+
|| opened.lease.controllerProcess.startTime !== parentStartTime
|
|
286
|
+
|| JSON.stringify(opened.lease.buildIdentity) !== JSON.stringify(buildIdentity)) {
|
|
287
|
+
throw new Error('SANDBOX_CONTROL_RESULT_INVALID');
|
|
288
|
+
}
|
|
289
|
+
(options.verifyTaskBinding ?? verifyControl)(taskId, control);
|
|
280
290
|
const contextPath = path.join(home, 'controller-context.json');
|
|
281
|
-
|
|
291
|
+
writeCodexSandboxControllerContext(contextPath, context);
|
|
282
292
|
const shimDir = path.join(home, 'bin');
|
|
283
293
|
fs.mkdirSync(shimDir, { mode: 0o700 });
|
|
284
294
|
const internalCli = path.resolve(process.argv[1] ?? path.join(repoRoot, 'bin', 'internal-cli.ts'));
|
|
@@ -292,7 +302,7 @@ function prepareCodexSandboxController(input, options = {}) {
|
|
|
292
302
|
input.reviewerModel ? `--reviewer-model ${input.reviewerModel}` : '',
|
|
293
303
|
input.reviewerReasoningEffort ? `--reviewer-reasoning-effort ${input.reviewerReasoningEffort}` : ''
|
|
294
304
|
].filter(Boolean).join(' ');
|
|
295
|
-
const prompt = `$run-task ${
|
|
305
|
+
const prompt = `$run-task ${taskId}${policy ? ` ${policy}` : ''}`;
|
|
296
306
|
const args = Object.freeze([
|
|
297
307
|
'exec',
|
|
298
308
|
'--enable', 'hooks',
|
|
@@ -303,7 +313,7 @@ function prepareCodexSandboxController(input, options = {}) {
|
|
|
303
313
|
'-C', repoRoot,
|
|
304
314
|
prompt
|
|
305
315
|
]);
|
|
306
|
-
const env = isolatedEnvironment(home, shimDir, contextPath,
|
|
316
|
+
const env = isolatedEnvironment(home, shimDir, contextPath, taskId, control, providerEnvironment, options.environment ?? process.env);
|
|
307
317
|
return Object.freeze({
|
|
308
318
|
command: 'codex',
|
|
309
319
|
args,
|
|
@@ -319,27 +329,6 @@ function prepareCodexSandboxController(input, options = {}) {
|
|
|
319
329
|
throw error;
|
|
320
330
|
}
|
|
321
331
|
}
|
|
322
|
-
function verifyCodexSandboxControllerContext(contextPath, taskId, options = {}) {
|
|
323
|
-
const stat = fs.lstatSync(contextPath);
|
|
324
|
-
if (!stat.isFile() || stat.isSymbolicLink() || (stat.mode & 0o077) !== 0) {
|
|
325
|
-
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_INVALID');
|
|
326
|
-
}
|
|
327
|
-
const context = JSON.parse(fs.readFileSync(contextPath, 'utf8'));
|
|
328
|
-
const generation = options.generation ?? process.env.AGENT_INFRA_CONTROL_GENERATION;
|
|
329
|
-
if (context.version !== 1
|
|
330
|
-
|| context.taskId !== taskId
|
|
331
|
-
|| context.controlGeneration !== generation
|
|
332
|
-
|| context.expiresAt < (options.now ?? Date.now())
|
|
333
|
-
|| getProcessStartTime(context.parentPid) !== context.parentStartTime
|
|
334
|
-
|| !isProcessAlive(context.parentPid)) {
|
|
335
|
-
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_INVALID');
|
|
336
|
-
}
|
|
337
|
-
const current = computeLifecycleBuildIdentity(options.repoRoot ?? process.cwd());
|
|
338
|
-
if (JSON.stringify(current) !== JSON.stringify(context.buildIdentity)) {
|
|
339
|
-
throw new Error('CODEX_SANDBOX_CONTROLLER_BUNDLE_MISMATCH');
|
|
340
|
-
}
|
|
341
|
-
return Object.freeze(context);
|
|
342
|
-
}
|
|
343
332
|
async function runCodexSandboxController(input, options = {}) {
|
|
344
333
|
const prepared = prepareCodexSandboxController(input, options);
|
|
345
334
|
try {
|
|
@@ -394,5 +383,5 @@ async function runCodexSandboxController(input, options = {}) {
|
|
|
394
383
|
prepared.cleanup();
|
|
395
384
|
}
|
|
396
385
|
}
|
|
397
|
-
export { prepareCodexSandboxController, runCodexSandboxController, verifyCodexSandboxControllerContext };
|
|
386
|
+
export { prepareCodexSandboxController, runCodexSandboxController, verifyCodexSandboxControllerContext, controllerProofFromContext };
|
|
398
387
|
//# sourceMappingURL=sandbox-controller.js.map
|
package/dist/lib/git/workflow.js
CHANGED
|
@@ -124,7 +124,16 @@ function pushGitRefs(input, runner = defaultRunner) {
|
|
|
124
124
|
}
|
|
125
125
|
const canonical = ref.startsWith('refs/') ? ref : `refs/heads/${ref}`;
|
|
126
126
|
const remoteFact = run(runner, input.cwd, ['ls-remote', '--exit-code', input.remote, canonical]);
|
|
127
|
-
|
|
127
|
+
const remoteSha = remoteFact.status === 0 ? remoteFact.stdout.trim().split(/\s+/)[0] ?? null : null;
|
|
128
|
+
const verified = remoteFact.status === 0 && (!input.expectedSha || remoteSha === input.expectedSha);
|
|
129
|
+
operations.push({
|
|
130
|
+
name: 'push',
|
|
131
|
+
ref,
|
|
132
|
+
status: verified ? 'applied' : 'failed',
|
|
133
|
+
message: verified ? undefined : input.expectedSha
|
|
134
|
+
? `Remote ref verification failed: expected ${input.expectedSha}, received ${remoteSha ?? 'unavailable'}`
|
|
135
|
+
: 'Remote ref verification failed'
|
|
136
|
+
});
|
|
128
137
|
}
|
|
129
138
|
const failures = operations.filter((item) => item.status === 'failed').length;
|
|
130
139
|
return { status: failures === 0 ? 'applied' : failures === operations.length ? 'failed' : 'degraded', changed: failures < operations.length, snapshot: inspectGitWorkflow(input.cwd, runner).snapshot, operations, error: failures ? { code: 'GIT_PUSH_PARTIAL', message: `${failures} ref push operation(s) failed` } : null };
|
|
@@ -8,6 +8,7 @@ import { createCodexCapabilityStore } from '../agent-clients/adapters/codex-life
|
|
|
8
8
|
import { computeLifecycleBuildIdentity } from '../agent-clients/adapters/codex-lifecycle/build-identity.js';
|
|
9
9
|
import { verifyCodexSandboxControllerContext } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.js';
|
|
10
10
|
import { resolveTaskRef } from '../task/resolve-ref.js';
|
|
11
|
+
import { hasSealableOrchestrationDelegation } from '../task/orchestration.js';
|
|
11
12
|
import { activateCodexOrchestrationDelegation, activateCodexSpawnDelegation, reconcileCodexOrchestrationDelegation, sealCodexOrchestrationDelegation, sealCodexParentDelegation } from '../task/codex-orchestration.js';
|
|
12
13
|
const USAGE = 'Usage: agent-infra-internal codex-lifecycle <capability-arm|hook-event|resolve-start|resolve-stop|preflight|consume> [options]\n';
|
|
13
14
|
const MANAGED_AGENT = /^agent-infra-lifecycle-(executor|reviewer)$/;
|
|
@@ -80,7 +81,7 @@ function controllerBinding(taskId) {
|
|
|
80
81
|
const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
|
|
81
82
|
if (!contextPath)
|
|
82
83
|
return undefined;
|
|
83
|
-
const context = verifyCodexSandboxControllerContext(contextPath,
|
|
84
|
+
const context = verifyCodexSandboxControllerContext(contextPath, { repoRoot: process.cwd() });
|
|
84
85
|
return {
|
|
85
86
|
instanceDigest: context.controllerInstanceDigest,
|
|
86
87
|
controlGeneration: context.controlGeneration
|
|
@@ -121,7 +122,8 @@ function recordFromPayload(phase, payload) {
|
|
|
121
122
|
sessionId: text('sessionId'),
|
|
122
123
|
turnId: text('turnId'),
|
|
123
124
|
childThreadId: text('childThreadId'),
|
|
124
|
-
nativeAgent: text('nativeAgent')
|
|
125
|
+
nativeAgent: text('nativeAgent'),
|
|
126
|
+
source: 'hook'
|
|
125
127
|
};
|
|
126
128
|
if (phase === 'subagent-stop')
|
|
127
129
|
return {
|
|
@@ -314,8 +316,17 @@ async function codexLifecycle(args = []) {
|
|
|
314
316
|
}
|
|
315
317
|
const result = store.apply(event);
|
|
316
318
|
if (parsed.values['--bridge'] === 'true' && event.type === 'hook-stop') {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
+
if (!hasSealableOrchestrationDelegation('codex', event.childThreadId, { repoRoot: process.cwd() })) {
|
|
320
|
+
output({ status: 'ignored', changed: false, evidence: null, diagnostics: [], error: null });
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
output({
|
|
324
|
+
status: result.state.status,
|
|
325
|
+
changed: true,
|
|
326
|
+
evidence: result.state,
|
|
327
|
+
diagnostics: [],
|
|
328
|
+
error: result.state.error
|
|
329
|
+
});
|
|
319
330
|
return;
|
|
320
331
|
}
|
|
321
332
|
output({ status: result.state.status, changed: true, evidence: result.state, diagnostics: [], error: result.state.error });
|
|
@@ -341,7 +352,10 @@ async function codexLifecycle(args = []) {
|
|
|
341
352
|
return;
|
|
342
353
|
}
|
|
343
354
|
if (parsed.operation === 'resolve-stop') {
|
|
344
|
-
const
|
|
355
|
+
const stopTurnId = store.read(childThreadId).state.stop?.turnId;
|
|
356
|
+
if (!stopTurnId)
|
|
357
|
+
throw new Error('Codex lifecycle stop hook is not available');
|
|
358
|
+
const terminal = await resolveCodexTerminal(childThreadId, stopTurnId);
|
|
345
359
|
const latest = store.apply(terminal);
|
|
346
360
|
if (latest.state.status !== 'stop-ready') {
|
|
347
361
|
throw new Error(`Codex lifecycle stop evidence is not ready (status=${latest.state.status})`);
|
|
@@ -20,8 +20,6 @@ function parse(args) {
|
|
|
20
20
|
return null;
|
|
21
21
|
}
|
|
22
22
|
const allowed = new Set([
|
|
23
|
-
'--task-id',
|
|
24
|
-
'--task-ref',
|
|
25
23
|
'--context',
|
|
26
24
|
'--executor-model',
|
|
27
25
|
'--executor-reasoning-effort',
|
|
@@ -31,6 +29,11 @@ function parse(args) {
|
|
|
31
29
|
const values = {};
|
|
32
30
|
for (let index = 1; index < args.length; index += 1) {
|
|
33
31
|
const flag = args[index];
|
|
32
|
+
if (flag === '--task-id' || flag.startsWith('--task-id=')
|
|
33
|
+
|| flag === '--task-ref' || flag.startsWith('--task-ref=')) {
|
|
34
|
+
fail('CODEX_SANDBOX_CONTROLLER_IDENTITY_OPTION_UNSUPPORTED', 'controller task identity options are not supported', 2);
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
34
37
|
const value = args[++index];
|
|
35
38
|
if (!allowed.has(flag) || Object.hasOwn(values, flag) || !value || value.startsWith('--')) {
|
|
36
39
|
fail('CODEX_SANDBOX_CONTROLLER_PAYLOAD_INVALID', `invalid option '${flag}'`, 2);
|
|
@@ -44,18 +47,13 @@ async function codexSandboxController(args = []) {
|
|
|
44
47
|
const parsed = parse(args);
|
|
45
48
|
if (!parsed || process.exitCode)
|
|
46
49
|
return;
|
|
47
|
-
const taskId = parsed.values['--task-id'];
|
|
48
|
-
if (!taskId) {
|
|
49
|
-
fail('CODEX_SANDBOX_CONTROLLER_PAYLOAD_INVALID', '--task-id is required', 2);
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
50
|
try {
|
|
53
51
|
if (parsed.operation === 'verify-context') {
|
|
54
52
|
const contextPath = parsed.values['--context']
|
|
55
53
|
?? process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
|
|
56
54
|
if (!contextPath)
|
|
57
55
|
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_MISSING');
|
|
58
|
-
const context = verifyCodexSandboxControllerContext(contextPath
|
|
56
|
+
const context = verifyCodexSandboxControllerContext(contextPath);
|
|
59
57
|
process.stdout.write(`${JSON.stringify({
|
|
60
58
|
status: 'ready',
|
|
61
59
|
changed: false,
|
|
@@ -72,9 +70,6 @@ async function codexSandboxController(args = []) {
|
|
|
72
70
|
})}\n`);
|
|
73
71
|
return;
|
|
74
72
|
}
|
|
75
|
-
const taskRef = parsed.values['--task-ref'];
|
|
76
|
-
if (!taskRef)
|
|
77
|
-
throw new Error('CODEX_SANDBOX_CONTROLLER_TASK_REF_REQUIRED');
|
|
78
73
|
const policy = [
|
|
79
74
|
'--executor-model',
|
|
80
75
|
'--executor-reasoning-effort',
|
|
@@ -86,8 +81,6 @@ async function codexSandboxController(args = []) {
|
|
|
86
81
|
throw new Error('CODEX_SANDBOX_CONTROLLER_MODEL_POLICY_INVALID');
|
|
87
82
|
}
|
|
88
83
|
const exitCode = await runCodexSandboxController({
|
|
89
|
-
taskId,
|
|
90
|
-
taskRef,
|
|
91
84
|
executorModel: parsed.values['--executor-model'],
|
|
92
85
|
executorReasoningEffort: parsed.values['--executor-reasoning-effort'],
|
|
93
86
|
reviewerModel: parsed.values['--reviewer-model'],
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import {
|
|
2
|
+
import { inspectGitWorkflow, pushRebasedBranch } from '../git/workflow.js';
|
|
3
3
|
import { compareReviewTrees, snapshotReview } from '../git/review-snapshot.js';
|
|
4
4
|
import { resolvePostReviewGlobs } from '../task/review-fingerprint.js';
|
|
5
|
+
import { executeCommitOperation } from '../task/commit-operation.js';
|
|
5
6
|
function output(value) { process.stdout.write(`${JSON.stringify(value)}\n`); }
|
|
6
7
|
function gitWorkflow(args = []) {
|
|
7
8
|
const [action, ...rest] = args;
|
|
@@ -16,12 +17,9 @@ function gitWorkflow(args = []) {
|
|
|
16
17
|
else if (action === 'commit') {
|
|
17
18
|
const inputIndex = rest.indexOf('--input');
|
|
18
19
|
const input = inputIndex >= 0 && rest[inputIndex + 1] ? JSON.parse(fs.readFileSync(rest[inputIndex + 1], 'utf8')) : null;
|
|
19
|
-
result = input
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const inputIndex = rest.indexOf('--input');
|
|
23
|
-
const input = inputIndex >= 0 && rest[inputIndex + 1] ? JSON.parse(fs.readFileSync(rest[inputIndex + 1], 'utf8')) : null;
|
|
24
|
-
result = input ? pushGitRefs({ cwd: cwd ?? process.cwd(), ...input }) : { status: 'failed', changed: false, error: { code: 'GIT_INPUT_REQUIRED', message: '--input JSON file is required' } };
|
|
20
|
+
result = input
|
|
21
|
+
? executeCommitOperation({ cwd: cwd ?? process.cwd(), ...input })
|
|
22
|
+
: { status: 'failed', changed: false, error: { code: 'GIT_INPUT_REQUIRED', message: '--input JSON file is required' } };
|
|
25
23
|
}
|
|
26
24
|
else if (action === 'push-rebased') {
|
|
27
25
|
const inputIndex = rest.indexOf('--input');
|
|
@@ -7,12 +7,12 @@ const USAGE = `Usage: agent-infra-internal platform-pr inspect <task-ref> [--cwd
|
|
|
7
7
|
agent-infra-internal platform-pr resolve-external <task-ref> --agent <agent> [--pr <N>] [--dry-run] [--cwd <path>]
|
|
8
8
|
agent-infra-internal platform-pr create <task-ref> --agent <agent> --base <branch> --head <branch> --title-file <path|-> --body-file <path|-> [--draft] [--dry-run] [--cwd <path>]
|
|
9
9
|
agent-infra-internal platform-pr bind <task-ref> --pr <N> --agent <agent> [--dry-run] [--cwd <path>]
|
|
10
|
-
agent-infra-internal platform-pr sync <task-ref> --agent <agent> [--metadata] [--closing-issue] [--dry-run] [--cwd <path>]
|
|
10
|
+
agent-infra-internal platform-pr sync <task-ref> --agent <agent> [--metadata] [--closing-issue] --result <pr_created|pr_reused|no_op> [--dry-run] [--cwd <path>]
|
|
11
11
|
agent-infra-internal platform-pr summary-context <task-ref> [--cwd <path>]
|
|
12
|
-
agent-infra-internal platform-pr summary-sync <task-ref> --agent <agent> --body-file <path|-> [--dry-run] [--cwd <path>]
|
|
12
|
+
agent-infra-internal platform-pr summary-sync <task-ref> --agent <agent> --body-file <path|-> --result <pr_created|pr_reused|no_op> [--dry-run] [--cwd <path>]
|
|
13
13
|
`;
|
|
14
14
|
const BOOLEAN_FLAGS = new Set(['--draft', '--dry-run', '--metadata', '--closing-issue']);
|
|
15
|
-
const VALUE_FLAGS = new Set(['--cwd', '--agent', '--base', '--head', '--title-file', '--body-file', '--pr']);
|
|
15
|
+
const VALUE_FLAGS = new Set(['--cwd', '--agent', '--base', '--head', '--title-file', '--body-file', '--pr', '--result']);
|
|
16
16
|
function key(flag) {
|
|
17
17
|
return flag.slice(2).replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
18
18
|
}
|
|
@@ -79,9 +79,9 @@ function platformPr(args = []) {
|
|
|
79
79
|
'resolve-external': ['cwd', 'agent', 'pr', 'dryRun'],
|
|
80
80
|
create: ['cwd', 'agent', 'base', 'head', 'titleFile', 'bodyFile', 'draft', 'dryRun'],
|
|
81
81
|
bind: ['cwd', 'agent', 'pr', 'dryRun'],
|
|
82
|
-
sync: ['cwd', 'agent', 'metadata', 'closingIssue', 'dryRun'],
|
|
82
|
+
sync: ['cwd', 'agent', 'metadata', 'closingIssue', 'result', 'dryRun'],
|
|
83
83
|
'summary-context': ['cwd'],
|
|
84
|
-
'summary-sync': ['cwd', 'agent', 'bodyFile', 'dryRun']
|
|
84
|
+
'summary-sync': ['cwd', 'agent', 'bodyFile', 'result', 'dryRun']
|
|
85
85
|
};
|
|
86
86
|
const unexpected = Object.keys(values).find((name) => !allowed[operation].includes(name));
|
|
87
87
|
if (unexpected) {
|
|
@@ -106,6 +106,11 @@ function platformPr(args = []) {
|
|
|
106
106
|
return;
|
|
107
107
|
}
|
|
108
108
|
values.agent = agent;
|
|
109
|
+
const primaryResult = values.result === undefined ? undefined : values.result;
|
|
110
|
+
if (primaryResult !== undefined && !['pr_created', 'pr_reused', 'no_op'].includes(primaryResult)) {
|
|
111
|
+
fail('--result must be pr_created, pr_reused, or no_op');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
109
114
|
if (operation === 'resolve-external') {
|
|
110
115
|
const pr = values.pr === undefined ? undefined : Number(values.pr);
|
|
111
116
|
if (pr !== undefined && (!Number.isInteger(pr) || pr <= 0)) {
|
|
@@ -129,7 +134,11 @@ function platformPr(args = []) {
|
|
|
129
134
|
fail('sync requires --metadata or --closing-issue');
|
|
130
135
|
return;
|
|
131
136
|
}
|
|
132
|
-
|
|
137
|
+
if (!primaryResult) {
|
|
138
|
+
fail('sync requires --result pr_created, pr_reused, or no_op');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
finish(syncPlatformPullRequest(taskRef, { cwd, agent: values.agent, metadata: values.metadata === true, closingIssue: values.closingIssue === true, primaryResult: primaryResult, dryRun: values.dryRun === true }));
|
|
133
142
|
return;
|
|
134
143
|
}
|
|
135
144
|
if (operation === 'summary-sync') {
|
|
@@ -137,8 +146,12 @@ function platformPr(args = []) {
|
|
|
137
146
|
fail('summary-sync requires --body-file');
|
|
138
147
|
return;
|
|
139
148
|
}
|
|
149
|
+
if (!primaryResult) {
|
|
150
|
+
fail('summary-sync requires --result pr_created, pr_reused, or no_op');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
140
153
|
try {
|
|
141
|
-
finish(syncPullRequestSummary(taskRef, { cwd, agent: values.agent, body: readFile(values.bodyFile, cwd), dryRun: values.dryRun === true }));
|
|
154
|
+
finish(syncPullRequestSummary(taskRef, { cwd, agent: values.agent, body: readFile(values.bodyFile, cwd), primaryResult: primaryResult, dryRun: values.dryRun === true }));
|
|
142
155
|
}
|
|
143
156
|
catch (error) {
|
|
144
157
|
fail(`unable to read body file: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -10,12 +10,13 @@ import { releaseSnapshot } from '../release/workflow.js';
|
|
|
10
10
|
function command(cwd, executable, args) {
|
|
11
11
|
return spawnSync(executable, args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
12
12
|
}
|
|
13
|
+
// The tape is the canonical init interaction contract. Do not hash all of
|
|
14
|
+
// lib/init.ts: non-visual config serialization changes must not re-record it.
|
|
13
15
|
const DEMO_INPUT_PATHS = [
|
|
14
16
|
'assets/demo-init.tape',
|
|
15
17
|
'scripts/demo-regen.sh',
|
|
16
18
|
'scripts/normalize-gif-duration.py',
|
|
17
19
|
'bin/cli.ts',
|
|
18
|
-
'lib/init.ts',
|
|
19
20
|
'lib/log.ts',
|
|
20
21
|
'lib/prompt.ts',
|
|
21
22
|
'lib/paths.ts',
|
|
@@ -1,6 +1,70 @@
|
|
|
1
|
-
import { requestSandboxControl, SandboxControlClientError } from '../sandbox/control/client.js';
|
|
1
|
+
import { requestSandboxControl, requestSandboxTaskFinalization, requestSandboxTaskControl, SandboxControlClientError } from '../sandbox/control/client.js';
|
|
2
|
+
import { normalizeAgentToken, AGENT_USAGE_HINT } from '../agent-clients/tokens.js';
|
|
2
3
|
import { serveSandboxControl } from '../sandbox/control/server.js';
|
|
3
4
|
import { runSandboxControlExecutor } from '../sandbox/control/executor.js';
|
|
5
|
+
import { controllerProofFromContext, verifyCodexSandboxControllerContext } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.js';
|
|
6
|
+
function isCanonicalCodexPrepare(args) {
|
|
7
|
+
if (args[1] !== 'prepare')
|
|
8
|
+
return false;
|
|
9
|
+
const clients = [];
|
|
10
|
+
for (let index = 2; index < args.length; index += 1) {
|
|
11
|
+
if (args[index] === '--client')
|
|
12
|
+
clients.push(args[index + 1] ?? '');
|
|
13
|
+
else if (args[index]?.startsWith('--client='))
|
|
14
|
+
clients.push(args[index].slice('--client='.length));
|
|
15
|
+
}
|
|
16
|
+
return clients.length === 1 && clients[0] === 'codex';
|
|
17
|
+
}
|
|
18
|
+
function writeFinalizationEnvelope(status, accepted, error, changed = false, result = null) {
|
|
19
|
+
process.stdout.write(`${JSON.stringify({ version: 1, status, changed, accepted, result, error })}\n`);
|
|
20
|
+
}
|
|
21
|
+
function finalizationErrorStatus(error) {
|
|
22
|
+
if (error.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN')
|
|
23
|
+
return 'unknown';
|
|
24
|
+
return error.retryable ? 'blocked' : 'failed';
|
|
25
|
+
}
|
|
26
|
+
function sandboxFinalizationClient(args) {
|
|
27
|
+
if (args.length !== 4 || !args[0] || args[1] !== 'complete' || args[2] !== '--agent' || !args[3]) {
|
|
28
|
+
const error = { code: 'TASK_FINALIZATION_PAYLOAD_INVALID', message: 'task ref, complete intent, and --agent are required', retryable: false };
|
|
29
|
+
writeFinalizationEnvelope('failed', false, error);
|
|
30
|
+
process.stderr.write('Usage: agent-infra-internal task-finalization <N | TASK-id> complete --agent <agent>\n');
|
|
31
|
+
process.exitCode = 1;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const agent = normalizeAgentToken(args[3]);
|
|
35
|
+
if (!agent) {
|
|
36
|
+
const error = { code: 'TASK_FINALIZATION_PAYLOAD_INVALID', message: `invalid --agent: ${AGENT_USAGE_HINT}`, retryable: false };
|
|
37
|
+
writeFinalizationEnvelope('failed', false, error);
|
|
38
|
+
process.exitCode = 1;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
let response;
|
|
42
|
+
try {
|
|
43
|
+
response = requestSandboxTaskFinalization({ agent });
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (!(error instanceof SandboxControlClientError))
|
|
47
|
+
throw error;
|
|
48
|
+
writeFinalizationEnvelope(finalizationErrorStatus(error.detail), error.accepted, error.detail);
|
|
49
|
+
process.stderr.write(`${error.detail.message}\n`);
|
|
50
|
+
process.exitCode = error.detail.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN' ? 1 : error.detail.retryable ? 2 : 1;
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (response.phase === 'rejected') {
|
|
54
|
+
const error = response.error ?? {
|
|
55
|
+
code: 'SANDBOX_CONTROL_REJECTED',
|
|
56
|
+
message: 'sandbox control rejected the finalization request',
|
|
57
|
+
retryable: false
|
|
58
|
+
};
|
|
59
|
+
writeFinalizationEnvelope(finalizationErrorStatus(error), error.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN', error);
|
|
60
|
+
process.stderr.write(`${error.message}\n`);
|
|
61
|
+
process.exitCode = error.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN' ? 1 : error.retryable ? 2 : 1;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
process.stdout.write(response.stdout);
|
|
65
|
+
process.stderr.write(response.stderr);
|
|
66
|
+
process.exitCode = response.exitCode ?? 1;
|
|
67
|
+
}
|
|
4
68
|
async function sandboxControl(args) {
|
|
5
69
|
const [operation, ...rest] = args;
|
|
6
70
|
if (operation === 'serve') {
|
|
@@ -33,9 +97,22 @@ async function sandboxControl(args) {
|
|
|
33
97
|
}
|
|
34
98
|
if (operation === 'client') {
|
|
35
99
|
const [family = '', ...commandArgs] = rest;
|
|
100
|
+
if (family === 'task-finalization') {
|
|
101
|
+
sandboxFinalizationClient(commandArgs);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
36
104
|
let response;
|
|
37
105
|
try {
|
|
38
|
-
|
|
106
|
+
if (family === 'task-orchestration' && isCanonicalCodexPrepare(commandArgs)) {
|
|
107
|
+
const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
|
|
108
|
+
const proof = contextPath
|
|
109
|
+
? controllerProofFromContext(verifyCodexSandboxControllerContext(contextPath))
|
|
110
|
+
: null;
|
|
111
|
+
response = requestSandboxTaskControl({ family, args: commandArgs, controllerProof: proof });
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
response = requestSandboxControl({ family, args: commandArgs });
|
|
115
|
+
}
|
|
39
116
|
}
|
|
40
117
|
catch (error) {
|
|
41
118
|
if (!(error instanceof SandboxControlClientError))
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { normalizeAgentToken, AGENT_USAGE_HINT } from '../agent-clients/tokens.js';
|
|
2
|
+
import { applyTaskFinalization } from '../task/finalization.js';
|
|
3
|
+
import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.js';
|
|
4
|
+
import { verifyTaskEvent } from '../task/verification.js';
|
|
5
|
+
const USAGE = 'Usage: agent-infra-internal task-finalization <N | TASK-id> complete --agent <agent>\n';
|
|
6
|
+
function envelope(status, changed, accepted, result, error) {
|
|
7
|
+
return `${JSON.stringify({ version: 1, status, changed, accepted, result, error })}\n`;
|
|
8
|
+
}
|
|
9
|
+
function exitCode(status) {
|
|
10
|
+
return status === 'completed' ? 0 : status === 'blocked' ? 2 : 1;
|
|
11
|
+
}
|
|
12
|
+
function fail(message) {
|
|
13
|
+
const error = { code: 'TASK_FINALIZATION_PAYLOAD_INVALID', message, retryable: false };
|
|
14
|
+
process.stdout.write(envelope('failed', false, false, null, error));
|
|
15
|
+
process.stderr.write(USAGE);
|
|
16
|
+
process.exitCode = 1;
|
|
17
|
+
}
|
|
18
|
+
function taskFinalization(args = []) {
|
|
19
|
+
if (args[0] === '--help' || args[0] === '-h') {
|
|
20
|
+
process.stdout.write(USAGE);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (args.length !== 4 || !args[0] || args[1] !== 'complete' || args[2] !== '--agent' || !args[3]) {
|
|
24
|
+
fail('task ref, complete intent, and --agent are required');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const agent = normalizeAgentToken(args[3]);
|
|
28
|
+
if (!agent) {
|
|
29
|
+
fail(`invalid --agent: ${AGENT_USAGE_HINT}`);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
let repoRoot;
|
|
33
|
+
try {
|
|
34
|
+
repoRoot = detectRepoRoot();
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
const detail = { code: 'REPO_ROOT_NOT_FOUND', message: error instanceof Error ? error.message : String(error), retryable: false };
|
|
38
|
+
process.stdout.write(envelope('failed', false, true, null, detail));
|
|
39
|
+
process.exitCode = 1;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const resolved = resolveTaskRef(args[0], { repoRoot });
|
|
43
|
+
if (!resolved.ok) {
|
|
44
|
+
const detail = { code: resolved.code, message: resolved.message, retryable: false };
|
|
45
|
+
process.stdout.write(envelope('failed', false, true, null, detail));
|
|
46
|
+
process.exitCode = 1;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const result = applyTaskFinalization({ taskRef: resolved.taskId, intent: 'complete', agent }, { repoRoot, preflight: (request, options) => verifyTaskEvent({ ...request, event: 'complete-task.hard-preflight' }, options) });
|
|
50
|
+
process.stdout.write(envelope(result.status, result.changed, true, result, result.error));
|
|
51
|
+
process.exitCode = exitCode(result.status);
|
|
52
|
+
}
|
|
53
|
+
export { taskFinalization };
|
|
54
|
+
//# sourceMappingURL=task-finalization.js.map
|