@fitlab-ai/agent-infra 0.9.7 → 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 +11 -1
- package/dist/lib/agent-clients/adapters/claude-code.js +3 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +60 -22
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +8 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +175 -11
- 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 +86 -69
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +2 -1
- package/dist/lib/decide.js +68 -14
- package/dist/lib/git/workflow.js +10 -1
- package/dist/lib/init.js +1 -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 +3 -4
- package/dist/lib/internal/sandbox-control.js +79 -2
- package/dist/lib/internal/task-activity.js +57 -7
- package/dist/lib/internal/task-event.js +57 -4
- package/dist/lib/internal/task-finalization.js +54 -0
- 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 +161 -288
- 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/pr-summary.js +53 -13
- package/dist/lib/platform/pull-requests.js +205 -41
- package/dist/lib/runtime/agent-runtime.js +56 -0
- package/dist/lib/sandbox/commands/create.js +389 -311
- package/dist/lib/sandbox/commands/rm.js +316 -55
- package/dist/lib/sandbox/control/client.js +190 -6
- package/dist/lib/sandbox/control/container-identity.js +1 -1
- package/dist/lib/sandbox/control/controller-registration.js +273 -0
- package/dist/lib/sandbox/control/executor.js +145 -5
- package/dist/lib/sandbox/control/lifecycle.js +395 -73
- package/dist/lib/sandbox/control/protocol.js +67 -6
- package/dist/lib/sandbox/control/state.js +27 -11
- package/dist/lib/sandbox/engine.js +14 -7
- package/dist/lib/sandbox/index.js +5 -3
- package/dist/lib/sandbox/managed-fs.js +5 -0
- package/dist/lib/sandbox/mounts.js +20 -5
- package/dist/lib/sandbox/recovery.js +78 -10
- package/dist/lib/sandbox/task-resolver.js +50 -6
- package/dist/lib/sandbox/workspace-identity.js +27 -0
- package/dist/lib/sandbox/workspace-view.js +62 -40
- package/dist/lib/sandbox/worktree-safety.js +215 -2
- package/dist/lib/server/process-state.js +51 -1
- package/dist/lib/task/activity-intent.js +4 -1
- 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/decision-intents.js +4 -2
- package/dist/lib/task/delegation-receipts.js +279 -17
- package/dist/lib/task/events.js +9 -5
- package/dist/lib/task/finalization.js +567 -0
- 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/lifecycle.js +69 -14
- package/dist/lib/task/operation-outcome.js +33 -0
- package/dist/lib/task/orchestration.js +199 -719
- package/dist/lib/task/review-finalization.js +3 -2
- 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 +138 -59
- package/dist/lib/task/verification.js +15 -2
- package/dist/lib/task/workflow-warning-intents.js +54 -3
- package/dist/lib/task/write.js +2 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/claude-code.ts +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +63 -20
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +12 -0
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +278 -13
- 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 +112 -93
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +2 -1
- package/lib/decide.ts +59 -14
- package/lib/git/workflow.ts +11 -2
- package/lib/init.ts +1 -1
- 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 +3 -4
- package/lib/internal/sandbox-control.ts +92 -2
- package/lib/internal/task-activity.ts +46 -6
- package/lib/internal/task-event.ts +53 -4
- package/lib/internal/task-finalization.ts +68 -0
- package/lib/internal/task-ledger.ts +48 -4
- package/lib/internal/task-lifecycle.ts +56 -12
- package/lib/internal/task-orchestration.ts +32 -107
- 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/pr-summary.ts +65 -14
- package/lib/platform/pull-requests.ts +211 -41
- package/lib/runtime/agent-runtime.ts +76 -0
- package/lib/sandbox/commands/create.ts +88 -4
- package/lib/sandbox/commands/rm.ts +413 -61
- package/lib/sandbox/config.ts +2 -1
- package/lib/sandbox/control/client.ts +255 -6
- package/lib/sandbox/control/container-identity.ts +3 -3
- package/lib/sandbox/control/controller-registration.ts +347 -0
- package/lib/sandbox/control/executor.ts +172 -6
- package/lib/sandbox/control/lifecycle.ts +465 -77
- package/lib/sandbox/control/protocol.ts +85 -9
- package/lib/sandbox/control/state.ts +35 -18
- package/lib/sandbox/engine.ts +20 -9
- package/lib/sandbox/index.ts +5 -3
- package/lib/sandbox/managed-fs.ts +5 -0
- package/lib/sandbox/mounts.ts +23 -5
- package/lib/sandbox/recovery.ts +90 -9
- package/lib/sandbox/task-resolver.ts +58 -7
- package/lib/sandbox/workspace-identity.ts +50 -2
- package/lib/sandbox/workspace-view.ts +73 -43
- package/lib/sandbox/worktree-safety.ts +236 -2
- package/lib/server/process-state.ts +48 -1
- package/lib/task/activity-intent.ts +4 -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/decision-intents.ts +4 -2
- package/lib/task/delegation-receipts.ts +304 -19
- package/lib/task/events.ts +10 -5
- package/lib/task/finalization.ts +719 -0
- 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/lifecycle.ts +71 -14
- package/lib/task/operation-outcome.ts +48 -0
- package/lib/task/orchestration.ts +239 -922
- package/lib/task/review-finalization.ts +6 -2
- package/lib/task/short-id.ts +65 -1
- package/lib/task/task-execution-lock.ts +10 -1
- package/lib/task/verification-engine.ts +145 -64
- package/lib/task/verification.ts +18 -3
- package/lib/task/workflow-warning-intents.ts +61 -4
- package/lib/task/write.ts +4 -1
- 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 +9 -24
- package/templates/.agents/rules/lifecycle-orchestration.en.md +4 -3
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +4 -3
- 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/run-task/reference/host-validation.en.md +3 -1
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +3 -1
- 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()) {
|
|
@@ -38,12 +38,39 @@ function controlFromEnvironment() {
|
|
|
38
38
|
const generation = process.env.AGENT_INFRA_CONTROL_GENERATION;
|
|
39
39
|
const channelDir = process.env.AGENT_INFRA_CONTROL_DIR;
|
|
40
40
|
const statusDir = process.env.AGENT_INFRA_CONTROL_STATUS_DIR;
|
|
41
|
-
|
|
41
|
+
const runtimeDir = process.env.AGENT_INFRA_RUNTIME_DIR;
|
|
42
|
+
if (!token || !generation || !channelDir || !statusDir || !runtimeDir) {
|
|
42
43
|
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTROL_MISSING');
|
|
43
44
|
}
|
|
44
|
-
return { token, generation, channelDir, statusDir };
|
|
45
|
+
return { token, generation, channelDir, statusDir, runtimeDir };
|
|
46
|
+
}
|
|
47
|
+
function verifyRuntime(runtimeDir) {
|
|
48
|
+
let stat;
|
|
49
|
+
try {
|
|
50
|
+
stat = fs.lstatSync(runtimeDir);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
throw new Error('CODEX_SANDBOX_CONTROLLER_RUNTIME_UNAVAILABLE');
|
|
54
|
+
}
|
|
55
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
56
|
+
throw new Error('CODEX_SANDBOX_CONTROLLER_RUNTIME_UNAVAILABLE');
|
|
57
|
+
}
|
|
58
|
+
const probe = path.join(runtimeDir, `.controller-${process.pid}-${crypto.randomUUID()}.probe`);
|
|
59
|
+
try {
|
|
60
|
+
fs.writeFileSync(probe, 'runtime-probe\n', { encoding: 'utf8', flag: 'wx', mode: 0o600 });
|
|
61
|
+
if (fs.readFileSync(probe, 'utf8') !== 'runtime-probe\n') {
|
|
62
|
+
throw new Error('probe mismatch');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
throw new Error('CODEX_SANDBOX_CONTROLLER_RUNTIME_UNAVAILABLE');
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
fs.rmSync(probe, { force: true });
|
|
70
|
+
}
|
|
45
71
|
}
|
|
46
72
|
function verifyControl(taskId, control) {
|
|
73
|
+
verifyRuntime(control.runtimeDir);
|
|
47
74
|
let status = null;
|
|
48
75
|
let lastError = null;
|
|
49
76
|
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
@@ -81,6 +108,26 @@ function verifyControl(taskId, control) {
|
|
|
81
108
|
if (payload.taskId !== taskId)
|
|
82
109
|
throw new Error('CODEX_SANDBOX_CONTROLLER_TASK_BINDING_INVALID');
|
|
83
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
|
+
}
|
|
84
131
|
function detectedCodexVersion() {
|
|
85
132
|
const result = spawnSync('codex', ['--version'], { encoding: 'utf8' });
|
|
86
133
|
const version = /codex-cli\s+(\d+\.\d+\.\d+)/u.exec(result.stdout ?? '')?.[1];
|
|
@@ -153,6 +200,7 @@ function isolatedEnvironment(home, shimDir, contextPath, taskId, control, provid
|
|
|
153
200
|
AGENT_INFRA_CONTROL_GENERATION: control.generation,
|
|
154
201
|
AGENT_INFRA_CONTROL_DIR: control.channelDir,
|
|
155
202
|
AGENT_INFRA_CONTROL_STATUS_DIR: control.statusDir,
|
|
203
|
+
AGENT_INFRA_RUNTIME_DIR: control.runtimeDir,
|
|
156
204
|
AGENT_INFRA_CODEX_CONTROLLER_CONTEXT: contextPath
|
|
157
205
|
};
|
|
158
206
|
}
|
|
@@ -163,7 +211,6 @@ function prepareCodexSandboxController(input, options = {}) {
|
|
|
163
211
|
const repoRoot = path.resolve(options.repoRoot ?? process.cwd());
|
|
164
212
|
const sourceHome = path.resolve(options.codexHome ?? process.env.CODEX_HOME ?? path.join(os.homedir(), '.codex'));
|
|
165
213
|
const control = options.control ?? controlFromEnvironment();
|
|
166
|
-
(options.verifyTaskBinding ?? verifyControl)(input.taskId, control);
|
|
167
214
|
const version = (options.codexVersion ?? detectedCodexVersion)();
|
|
168
215
|
if (!semver.gte(version, '0.147.0'))
|
|
169
216
|
throw new Error('CODEX_SANDBOX_CONTROLLER_CODEX_UNSUPPORTED');
|
|
@@ -172,48 +219,36 @@ function prepareCodexSandboxController(input, options = {}) {
|
|
|
172
219
|
fs.mkdirSync(runtimeRoot, { recursive: true, mode: 0o700 });
|
|
173
220
|
fs.chmodSync(runtimeRoot, 0o700);
|
|
174
221
|
const key = crypto.createHash('sha256')
|
|
175
|
-
.update(`${
|
|
222
|
+
.update(`${process.pid}\0${control.generation}`)
|
|
176
223
|
.digest('hex');
|
|
177
|
-
const leasePath = path.join(runtimeRoot, `${key}.lease.json`);
|
|
178
|
-
if (fs.existsSync(leasePath)) {
|
|
179
|
-
const lease = JSON.parse(fs.readFileSync(leasePath, 'utf8'));
|
|
180
|
-
if (Number.isSafeInteger(lease.pid) && typeof lease.startTime === 'number'
|
|
181
|
-
&& getProcessStartTime(lease.pid) === lease.startTime) {
|
|
182
|
-
throw new Error('CODEX_SANDBOX_CONTROLLER_BUSY');
|
|
183
|
-
}
|
|
184
|
-
fs.unlinkSync(leasePath);
|
|
185
|
-
}
|
|
186
224
|
const parentStartTime = getProcessStartTime(process.pid);
|
|
187
225
|
if (!parentStartTime)
|
|
188
226
|
throw new Error('CODEX_SANDBOX_CONTROLLER_PROCESS_IDENTITY_INVALID');
|
|
189
|
-
const instanceDigest = crypto.randomBytes(32).toString('hex');
|
|
190
|
-
fs.writeFileSync(leasePath, `${JSON.stringify({
|
|
191
|
-
version: 1,
|
|
192
|
-
pid: process.pid,
|
|
193
|
-
startTime: parentStartTime,
|
|
194
|
-
taskId: input.taskId,
|
|
195
|
-
generation: control.generation,
|
|
196
|
-
instanceDigest
|
|
197
|
-
})}\n`, { mode: 0o600, flag: 'wx' });
|
|
198
227
|
const home = fs.mkdtempSync(path.join(runtimeRoot, `${key}-`));
|
|
199
228
|
fs.chmodSync(home, 0o700);
|
|
200
229
|
let cleaned = false;
|
|
230
|
+
let context = null;
|
|
201
231
|
const cleanup = () => {
|
|
202
232
|
if (cleaned)
|
|
203
233
|
return;
|
|
204
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
|
+
}
|
|
205
248
|
const relative = path.relative(runtimeRoot, home);
|
|
206
249
|
if (!relative.startsWith('..') && !path.isAbsolute(relative)) {
|
|
207
250
|
fs.rmSync(home, { recursive: true, force: true });
|
|
208
251
|
}
|
|
209
|
-
try {
|
|
210
|
-
const lease = JSON.parse(fs.readFileSync(leasePath, 'utf8'));
|
|
211
|
-
if (lease.instanceDigest === instanceDigest)
|
|
212
|
-
fs.unlinkSync(leasePath);
|
|
213
|
-
}
|
|
214
|
-
catch {
|
|
215
|
-
// A missing or replaced lease is owned by another cleanup path.
|
|
216
|
-
}
|
|
217
252
|
};
|
|
218
253
|
try {
|
|
219
254
|
const auth = path.join(sourceHome, 'auth.json');
|
|
@@ -235,22 +270,25 @@ function prepareCodexSandboxController(input, options = {}) {
|
|
|
235
270
|
throw new Error('CODEX_SANDBOX_CONTROLLER_BUNDLE_MISMATCH');
|
|
236
271
|
}
|
|
237
272
|
const buildIdentity = computeLifecycleBuildIdentity(repoRoot);
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
parentStartTime,
|
|
246
|
-
issuedAt: now,
|
|
247
|
-
expiresAt: now + CONTEXT_TTL_MS,
|
|
248
|
-
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, {
|
|
249
280
|
hookDefinitionHash: crypto.createHash('sha256').update(fs.readFileSync(hooks)).digest('hex'),
|
|
250
281
|
lifecycleProfilesHash: digestFiles([executor, reviewer])
|
|
251
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);
|
|
252
290
|
const contextPath = path.join(home, 'controller-context.json');
|
|
253
|
-
|
|
291
|
+
writeCodexSandboxControllerContext(contextPath, context);
|
|
254
292
|
const shimDir = path.join(home, 'bin');
|
|
255
293
|
fs.mkdirSync(shimDir, { mode: 0o700 });
|
|
256
294
|
const internalCli = path.resolve(process.argv[1] ?? path.join(repoRoot, 'bin', 'internal-cli.ts'));
|
|
@@ -264,7 +302,7 @@ function prepareCodexSandboxController(input, options = {}) {
|
|
|
264
302
|
input.reviewerModel ? `--reviewer-model ${input.reviewerModel}` : '',
|
|
265
303
|
input.reviewerReasoningEffort ? `--reviewer-reasoning-effort ${input.reviewerReasoningEffort}` : ''
|
|
266
304
|
].filter(Boolean).join(' ');
|
|
267
|
-
const prompt = `$run-task ${
|
|
305
|
+
const prompt = `$run-task ${taskId}${policy ? ` ${policy}` : ''}`;
|
|
268
306
|
const args = Object.freeze([
|
|
269
307
|
'exec',
|
|
270
308
|
'--enable', 'hooks',
|
|
@@ -275,7 +313,7 @@ function prepareCodexSandboxController(input, options = {}) {
|
|
|
275
313
|
'-C', repoRoot,
|
|
276
314
|
prompt
|
|
277
315
|
]);
|
|
278
|
-
const env = isolatedEnvironment(home, shimDir, contextPath,
|
|
316
|
+
const env = isolatedEnvironment(home, shimDir, contextPath, taskId, control, providerEnvironment, options.environment ?? process.env);
|
|
279
317
|
return Object.freeze({
|
|
280
318
|
command: 'codex',
|
|
281
319
|
args,
|
|
@@ -291,27 +329,6 @@ function prepareCodexSandboxController(input, options = {}) {
|
|
|
291
329
|
throw error;
|
|
292
330
|
}
|
|
293
331
|
}
|
|
294
|
-
function verifyCodexSandboxControllerContext(contextPath, taskId, options = {}) {
|
|
295
|
-
const stat = fs.lstatSync(contextPath);
|
|
296
|
-
if (!stat.isFile() || stat.isSymbolicLink() || (stat.mode & 0o077) !== 0) {
|
|
297
|
-
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_INVALID');
|
|
298
|
-
}
|
|
299
|
-
const context = JSON.parse(fs.readFileSync(contextPath, 'utf8'));
|
|
300
|
-
const generation = options.generation ?? process.env.AGENT_INFRA_CONTROL_GENERATION;
|
|
301
|
-
if (context.version !== 1
|
|
302
|
-
|| context.taskId !== taskId
|
|
303
|
-
|| context.controlGeneration !== generation
|
|
304
|
-
|| context.expiresAt < (options.now ?? Date.now())
|
|
305
|
-
|| getProcessStartTime(context.parentPid) !== context.parentStartTime
|
|
306
|
-
|| !isProcessAlive(context.parentPid)) {
|
|
307
|
-
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_INVALID');
|
|
308
|
-
}
|
|
309
|
-
const current = computeLifecycleBuildIdentity(options.repoRoot ?? process.cwd());
|
|
310
|
-
if (JSON.stringify(current) !== JSON.stringify(context.buildIdentity)) {
|
|
311
|
-
throw new Error('CODEX_SANDBOX_CONTROLLER_BUNDLE_MISMATCH');
|
|
312
|
-
}
|
|
313
|
-
return Object.freeze(context);
|
|
314
|
-
}
|
|
315
332
|
async function runCodexSandboxController(input, options = {}) {
|
|
316
333
|
const prepared = prepareCodexSandboxController(input, options);
|
|
317
334
|
try {
|
|
@@ -366,5 +383,5 @@ async function runCodexSandboxController(input, options = {}) {
|
|
|
366
383
|
prepared.cleanup();
|
|
367
384
|
}
|
|
368
385
|
}
|
|
369
|
-
export { prepareCodexSandboxController, runCodexSandboxController, verifyCodexSandboxControllerContext };
|
|
386
|
+
export { prepareCodexSandboxController, runCodexSandboxController, verifyCodexSandboxControllerContext, controllerProofFromContext };
|
|
370
387
|
//# sourceMappingURL=sandbox-controller.js.map
|
|
@@ -2,6 +2,7 @@ import crypto from 'node:crypto';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { createCodexLifecycleState, expireCodexLifecycleState, reduceCodexLifecycleEvent } from './evidence.js';
|
|
5
|
+
import { resolveAgentRuntimeStoreRoot } from '../../../runtime/agent-runtime.js';
|
|
5
6
|
const LOCK_RETRY_MS = 10;
|
|
6
7
|
const LOCK_TIMEOUT_MS = 1_000;
|
|
7
8
|
const LOCK_STALE_MS = 30_000;
|
|
@@ -58,7 +59,7 @@ function writeRecord(file, record, expectedRevision) {
|
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
function createCodexLifecycleStore(options) {
|
|
61
|
-
const root = options.root ??
|
|
62
|
+
const root = options.root ?? resolveAgentRuntimeStoreRoot({ store: 'lifecycle' });
|
|
62
63
|
const now = options.now ?? (() => new Date().toISOString());
|
|
63
64
|
fs.mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
64
65
|
fs.chmodSync(root, 0o700);
|
package/dist/lib/decide.js
CHANGED
|
@@ -2,25 +2,44 @@ import { VERSION } from './version.js';
|
|
|
2
2
|
import { parseTaskScope } from './task/command-options.js';
|
|
3
3
|
import { applyHumanDecision } from './task/decision-intents.js';
|
|
4
4
|
import { canonicalTimestamp } from './task/write.js';
|
|
5
|
+
import { consumeHumanOverride, failureId } from './task/human-override.js';
|
|
6
|
+
import { resolveTaskRef } from './task/resolve-ref.js';
|
|
7
|
+
import { TaskExecutionLockError, withTaskExecutionLock } from './task/task-execution-lock.js';
|
|
5
8
|
function parseDecisionParts(parts) {
|
|
6
9
|
const decision = [];
|
|
7
10
|
let needsImplementation;
|
|
11
|
+
let overrideTicket;
|
|
12
|
+
let overrideTarget;
|
|
13
|
+
let overrideScope;
|
|
8
14
|
for (let index = 0; index < parts.length; index += 1) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
15
|
+
const part = parts[index];
|
|
16
|
+
if (part === '--needs-implementation') {
|
|
17
|
+
if (needsImplementation !== undefined)
|
|
18
|
+
throw new Error("duplicate option '--needs-implementation'");
|
|
19
|
+
const value = parts[++index];
|
|
20
|
+
if (value !== 'true' && value !== 'false') {
|
|
21
|
+
throw new Error("--needs-implementation must be 'true' or 'false'");
|
|
22
|
+
}
|
|
23
|
+
needsImplementation = value === 'true';
|
|
12
24
|
}
|
|
13
|
-
if (
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
25
|
+
else if (part === '--override-ticket' || part === '--override-target' || part === '--override-scope') {
|
|
26
|
+
const value = parts[++index];
|
|
27
|
+
if (!value)
|
|
28
|
+
throw new Error(`${part} requires a value`);
|
|
29
|
+
if (part === '--override-ticket')
|
|
30
|
+
overrideTicket = value;
|
|
31
|
+
else if (part === '--override-target')
|
|
32
|
+
overrideTarget = value;
|
|
33
|
+
else
|
|
34
|
+
overrideScope = value;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
decision.push(parts[index]);
|
|
18
38
|
}
|
|
19
|
-
needsImplementation = value === 'true';
|
|
20
39
|
}
|
|
21
40
|
if (decision.length === 0)
|
|
22
41
|
throw new Error('decision content is required');
|
|
23
|
-
return { decision: decision.join(' '), needsImplementation };
|
|
42
|
+
return { decision: decision.join(' '), needsImplementation, overrideTicket, overrideTarget, overrideScope };
|
|
24
43
|
}
|
|
25
44
|
export async function decide(args, options = {}) {
|
|
26
45
|
let scope;
|
|
@@ -79,19 +98,54 @@ export async function decide(args, options = {}) {
|
|
|
79
98
|
try {
|
|
80
99
|
const parsedDecision = parseDecisionParts(decisionParts);
|
|
81
100
|
const now = (options.now ?? canonicalTimestamp)();
|
|
82
|
-
const
|
|
101
|
+
const request = {
|
|
83
102
|
taskRef, selector, decision: parsedDecision.decision,
|
|
84
103
|
needsImplementation: parsedDecision.needsImplementation
|
|
85
|
-
}
|
|
104
|
+
};
|
|
105
|
+
const writeOptions = {
|
|
86
106
|
repoRoot: options.repoRoot,
|
|
87
107
|
metadataProvider: () => ({ timestamp: now, agentInfraVersion: options.version ?? VERSION })
|
|
88
|
-
}
|
|
108
|
+
};
|
|
109
|
+
const resolved = taskRef ? resolveTaskRef(taskRef, { repoRoot: options.repoRoot }) : null;
|
|
110
|
+
let result;
|
|
111
|
+
const execute = () => {
|
|
112
|
+
let current = applyHumanDecision(request, writeOptions);
|
|
113
|
+
if (current.status !== 'failed' || !parsedDecision.overrideTicket)
|
|
114
|
+
return current;
|
|
115
|
+
if (!parsedDecision.overrideTarget || !parsedDecision.overrideScope)
|
|
116
|
+
throw new Error('override ticket requires --override-target and --override-scope');
|
|
117
|
+
const consumed = consumeHumanOverride({
|
|
118
|
+
taskRef: resolved?.ok ? resolved.taskId : String(taskRef ?? ''),
|
|
119
|
+
ticketId: parsedDecision.overrideTicket,
|
|
120
|
+
failureId: failureId('decision-intent', current.error?.code ?? 'TASK_STATE_MISMATCH'),
|
|
121
|
+
target: parsedDecision.overrideTarget,
|
|
122
|
+
scope: parsedDecision.overrideScope
|
|
123
|
+
}, {
|
|
124
|
+
...writeOptions,
|
|
125
|
+
effectExecutor: (capability) => {
|
|
126
|
+
const retried = applyHumanDecision(request, { ...writeOptions, manualOverride: capability });
|
|
127
|
+
current = retried;
|
|
128
|
+
return retried.status === 'failed' || retried.status === 'planned'
|
|
129
|
+
? { code: 'OVERRIDE_EFFECT_FAILED', message: retried.status === 'planned' ? 'producer returned planned; no decision effect was committed' : `${retried.error?.code ?? 'DECISION_FAILED'}: ${retried.error?.message ?? 'manual decision effect failed'}` }
|
|
130
|
+
: null;
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
if (consumed.status === 'failed')
|
|
134
|
+
throw new Error(consumed.error.message);
|
|
135
|
+
return current;
|
|
136
|
+
};
|
|
137
|
+
result = resolved?.ok
|
|
138
|
+
? withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-decision', execute)
|
|
139
|
+
: execute();
|
|
89
140
|
if (result.error)
|
|
90
141
|
throw new Error(result.error.message);
|
|
91
142
|
return 0;
|
|
92
143
|
}
|
|
93
144
|
catch (error) {
|
|
94
|
-
|
|
145
|
+
const message = error instanceof TaskExecutionLockError
|
|
146
|
+
? `${error.code}: ${error.message}`
|
|
147
|
+
: error instanceof Error ? error.message : String(error);
|
|
148
|
+
process.stderr.write(`Error: ${message}\n`);
|
|
95
149
|
return 1;
|
|
96
150
|
}
|
|
97
151
|
}
|
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 };
|
package/dist/lib/init.js
CHANGED
|
@@ -180,7 +180,7 @@ async function cmdInit() {
|
|
|
180
180
|
agentClients: serializeAgentClients(defaultState)
|
|
181
181
|
};
|
|
182
182
|
if (sandboxEngine)
|
|
183
|
-
config.sandbox.engine = sandboxEngine;
|
|
183
|
+
config.sandbox.engine = { [currentPlatform]: sandboxEngine };
|
|
184
184
|
if (templateSources.length > 0)
|
|
185
185
|
config.templates = { sources: templateSources };
|
|
186
186
|
if (skillSources.length > 0)
|
|
@@ -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,20 +10,19 @@ 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',
|
|
22
23
|
'lib/render.ts',
|
|
23
24
|
'lib/builtin-tuis.ts',
|
|
24
|
-
'lib/sandbox/engines/'
|
|
25
|
-
'src/sync-templates.js',
|
|
26
|
-
'templates/'
|
|
25
|
+
'lib/sandbox/engines/'
|
|
27
26
|
];
|
|
28
27
|
const DEMO_DIGEST_PATH = 'assets/demo-init.inputs.sha256';
|
|
29
28
|
const DEMO_OUTPUT_PATH = 'assets/demo-init.gif';
|