@fitlab-ai/agent-infra 0.9.15 → 0.11.0
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 +2 -6
- package/README.zh-CN.md +2 -6
- package/bin/internal-cli.sh +0 -13
- package/dist/bin/internal-cli.js +11 -49
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +6 -2
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +43 -3
- package/dist/lib/fs/durable-write.js +2 -0
- package/dist/lib/internal/cli-route-inventory.js +3 -4
- package/dist/lib/internal/manual-validation.js +17 -8
- package/dist/lib/internal/platform-comment.js +60 -2
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-finalization.js +2 -2
- package/dist/lib/internal/task-lifecycle.js +4 -4
- package/dist/lib/internal/task-operation-registry.js +22 -13
- package/dist/lib/internal/task-orchestration.js +2 -2
- package/dist/lib/internal/task-review.js +12 -7
- package/dist/lib/platform/activity-recovery.js +159 -0
- package/dist/lib/platform/issue-comments.js +89 -31
- package/dist/lib/platform/issues.js +1 -1
- package/dist/lib/platform/pr-change-report.js +5 -4
- package/dist/lib/platform/pr-summary.js +64 -32
- package/dist/lib/platform/pull-requests.js +3 -3
- package/dist/lib/platform/task-comment-projection.js +70 -0
- package/dist/lib/platform/task-identities.js +2 -9
- package/dist/lib/platform/task-recovery.js +55 -0
- package/dist/lib/platform/verification-sync.js +45 -7
- package/dist/lib/pr-review/evidence-grading.js +4 -8
- package/dist/lib/sandbox/commands/rm.js +21 -2
- package/dist/lib/sandbox/control/executor.js +4 -2
- package/dist/lib/sandbox/control/server.js +29 -9
- package/dist/lib/sandbox/control/state.js +7 -1
- package/dist/lib/sandbox/control/task-workflow.js +1 -1
- package/dist/lib/sandbox/control/workflow-executor.js +87 -35
- package/dist/lib/sandbox/index.js +1 -2
- package/dist/lib/sandbox/removal.js +116 -1011
- package/dist/lib/sandbox/workspace-identity.js +17 -1
- package/dist/lib/server/process-state.js +17 -5
- package/dist/lib/task/artifact-command.js +11 -30
- package/dist/lib/task/artifact-operations.js +17 -231
- package/dist/lib/task/artifact-recovery.js +476 -0
- package/dist/lib/task/artifact-repair-intent.js +90 -39
- package/dist/lib/task/commit-operation.js +31 -4
- package/dist/lib/task/control-authority.js +39 -8
- package/dist/lib/task/control-recovery.js +14 -2
- package/dist/lib/task/create-service.js +1 -8
- package/dist/lib/task/delegation-receipts.js +64 -3
- package/dist/lib/task/events.js +165 -23
- package/dist/lib/task/human-override.js +5 -8
- package/dist/lib/task/intermediate-cleanup.js +91 -8
- package/dist/lib/task/lifecycle-recovery.js +549 -0
- package/dist/lib/task/lifecycle.js +12 -3
- package/dist/lib/task/local-artifact-finalization.js +212 -123
- package/dist/lib/task/manual-validation-receipt.js +5 -1
- package/dist/lib/task/orchestration.js +52 -2
- package/dist/lib/task/pr-delivery-fact.js +12 -56
- package/dist/lib/task/recovery-warning.js +19 -0
- package/dist/lib/task/review-command.js +3 -2
- package/dist/lib/task/review-finalization.js +294 -74
- package/dist/lib/{host-control → task}/secure-fs.js +8 -19
- package/dist/lib/task/short-id.js +15 -7
- package/dist/lib/task/task-execution-lock.js +1 -1
- package/dist/lib/task/verification-engine.js +3 -14
- package/dist/lib/task/workflow-command.js +2 -1
- package/dist/lib/task/workflow-dispatch.js +161 -0
- package/dist/lib/version.js +1 -11
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +7 -2
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +55 -3
- package/lib/fs/durable-write.ts +1 -0
- package/lib/internal/cli-route-inventory.ts +3 -4
- package/lib/internal/manual-validation.ts +17 -8
- package/lib/internal/platform-comment.ts +53 -2
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-finalization.ts +1 -1
- package/lib/internal/task-lifecycle.ts +5 -4
- package/lib/internal/task-operation-registry.ts +22 -13
- package/lib/internal/task-orchestration.ts +1 -1
- package/lib/internal/task-review.ts +11 -7
- package/lib/platform/activity-recovery.ts +179 -0
- package/lib/platform/issue-comments.ts +105 -30
- package/lib/platform/issues.ts +1 -1
- package/lib/platform/pr-change-report.ts +7 -5
- package/lib/platform/pr-summary.ts +64 -32
- package/lib/platform/pull-requests.ts +3 -3
- package/lib/platform/task-comment-projection.ts +79 -0
- package/lib/platform/task-identities.ts +3 -14
- package/lib/platform/task-recovery.ts +54 -0
- package/lib/platform/verification-sync.ts +48 -11
- package/lib/pr-review/evidence-grading.ts +4 -9
- package/lib/sandbox/commands/rm.ts +34 -2
- package/lib/sandbox/control/executor.ts +4 -2
- package/lib/sandbox/control/protocol.ts +6 -1
- package/lib/sandbox/control/server.ts +30 -10
- package/lib/sandbox/control/state.ts +9 -1
- package/lib/sandbox/control/task-workflow.ts +1 -1
- package/lib/sandbox/control/workflow-executor.ts +106 -35
- package/lib/sandbox/index.ts +1 -2
- package/lib/sandbox/removal.ts +126 -1128
- package/lib/sandbox/workspace-identity.ts +19 -3
- package/lib/server/process-state.ts +26 -5
- package/lib/task/artifact-command.ts +12 -29
- package/lib/task/artifact-operations.ts +22 -252
- package/lib/task/artifact-recovery.ts +572 -0
- package/lib/task/artifact-repair-intent.ts +126 -51
- package/lib/task/commit-operation.ts +35 -4
- package/lib/task/control-authority.ts +48 -15
- package/lib/task/control-recovery.ts +13 -1
- package/lib/task/create-service.ts +1 -10
- package/lib/task/delegation-receipts.ts +73 -2
- package/lib/task/events.ts +188 -24
- package/lib/task/human-override.ts +5 -8
- package/lib/task/intermediate-cleanup.ts +66 -12
- package/lib/task/lifecycle-recovery.ts +691 -0
- package/lib/task/lifecycle.ts +13 -4
- package/lib/task/local-artifact-finalization.ts +249 -131
- package/lib/task/manual-validation-receipt.ts +5 -1
- package/lib/task/orchestration.ts +65 -0
- package/lib/task/pr-delivery-fact.ts +15 -62
- package/lib/task/recovery-warning.ts +21 -0
- package/lib/task/review-command.ts +2 -1
- package/lib/task/review-finalization.ts +298 -84
- package/lib/{host-control → task}/secure-fs.ts +10 -32
- package/lib/task/short-id.ts +27 -5
- package/lib/task/task-execution-lock.ts +1 -1
- package/lib/task/verification-engine.ts +3 -17
- package/lib/task/verification-types.ts +0 -2
- package/lib/task/workflow-command.ts +2 -1
- package/lib/task/workflow-dispatch.ts +171 -0
- package/lib/version.ts +1 -15
- package/package.json +1 -1
- package/templates/.agents/rules/create-issue.en.md +1 -1
- package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +12 -2
- package/templates/.agents/rules/issue-sync.zh-CN.md +13 -5
- package/templates/.agents/rules/local-artifact-repair.en.md +47 -39
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +45 -36
- package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/block-task/config/verify.json +1 -1
- 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/cancel-task/config/verify.json +1 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +7 -7
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/code-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
- 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 +2 -2
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/create-pr/config/verify.json +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +2 -2
- package/templates/.agents/skills/create-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/create-task/config/verify.json +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +8 -8
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/import-issue/config/verify.json +2 -2
- package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/restore-task/SKILL.en.md +11 -6
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +11 -6
- package/templates/.agents/skills/restore-task/config/verify.json +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +6 -6
- package/templates/.agents/skills/review-code/SKILL.en.md +5 -4
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -4
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -12
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -12
- package/templates/.agents/skills/review-plan/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -7
- package/templates/.agents/skills/review-pr/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +3 -3
- package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/templates/task.en.md +1 -1
- package/templates/.agents/templates/task.zh-CN.md +1 -1
- package/templates/.claude/commands/import-issue.en.md +1 -1
- package/templates/.claude/commands/import-issue.zh-CN.md +1 -1
- package/templates/.claude/commands/restore-task.en.md +1 -1
- package/templates/.claude/commands/restore-task.zh-CN.md +1 -1
- package/dist/lib/host-control/audit.js +0 -19
- package/dist/lib/host-control/client.js +0 -142
- package/dist/lib/host-control/command.js +0 -49
- package/dist/lib/host-control/path.js +0 -160
- package/dist/lib/host-control/server.js +0 -159
- package/dist/lib/host-control/service.js +0 -112
- package/dist/lib/internal/host-control.js +0 -28
- package/lib/host-control/audit.ts +0 -22
- package/lib/host-control/client.ts +0 -172
- package/lib/host-control/command.ts +0 -60
- package/lib/host-control/path.ts +0 -167
- package/lib/host-control/server.ts +0 -180
- package/lib/host-control/service.ts +0 -133
- package/lib/internal/host-control.ts +0 -28
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
1
2
|
import fs from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
3
|
-
import { createHash } from 'node:crypto';
|
|
4
4
|
const NO_FOLLOW = fs.constants.O_NOFOLLOW ?? 0;
|
|
5
5
|
const DIRECTORY = fs.constants.O_DIRECTORY ?? 0;
|
|
6
6
|
export class SecureFileError extends Error {
|
|
@@ -15,11 +15,8 @@ function conflict(message) {
|
|
|
15
15
|
throw new SecureFileError('TASK_ARTIFACT_WRITE_CONFLICT', message);
|
|
16
16
|
}
|
|
17
17
|
function sameStat(left, right) {
|
|
18
|
-
return left.dev === right.dev
|
|
19
|
-
&& left.
|
|
20
|
-
&& left.size === right.size
|
|
21
|
-
&& left.mtimeNs === right.mtimeNs
|
|
22
|
-
&& left.ctimeNs === right.ctimeNs;
|
|
18
|
+
return left.dev === right.dev && left.ino === right.ino && left.size === right.size
|
|
19
|
+
&& left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs;
|
|
23
20
|
}
|
|
24
21
|
function assertNoSymlinkAncestors(candidate) {
|
|
25
22
|
let current = path.dirname(candidate);
|
|
@@ -36,15 +33,12 @@ function assertNoSymlinkAncestors(candidate) {
|
|
|
36
33
|
export async function readStableFile(candidate, options) {
|
|
37
34
|
return readStableFileSync(candidate, options);
|
|
38
35
|
}
|
|
39
|
-
/** Shared bounded reader for synchronous domain commands and async workflow callers. */
|
|
40
36
|
export function readStableFileSync(candidate, options) {
|
|
41
|
-
if (!path.isAbsolute(candidate))
|
|
37
|
+
if (!path.isAbsolute(candidate))
|
|
42
38
|
conflict('candidate path must be absolute and terminal');
|
|
43
|
-
}
|
|
44
39
|
assertNoSymlinkAncestors(candidate);
|
|
45
40
|
let descriptor;
|
|
46
41
|
try {
|
|
47
|
-
// O_NONBLOCK lets fstat reject a FIFO even when it has no writer.
|
|
48
42
|
descriptor = fs.openSync(candidate, fs.constants.O_RDONLY | NO_FOLLOW | (fs.constants.O_NONBLOCK ?? 0));
|
|
49
43
|
const before = fs.fstatSync(descriptor, { bigint: true });
|
|
50
44
|
if (!before.isFile())
|
|
@@ -63,18 +57,16 @@ export function readStableFileSync(candidate, options) {
|
|
|
63
57
|
if (!sameStat(before, after))
|
|
64
58
|
conflict('candidate identity or metadata changed during read');
|
|
65
59
|
const sha256 = createHash('sha256').update(bytes).digest('hex');
|
|
66
|
-
if (options.expectedSha256 !== undefined && sha256 !== options.expectedSha256)
|
|
60
|
+
if (options.expectedSha256 !== undefined && sha256 !== options.expectedSha256)
|
|
67
61
|
conflict('candidate digest does not match the expected digest');
|
|
68
|
-
}
|
|
69
62
|
return { bytes, sha256, stat: after };
|
|
70
63
|
}
|
|
71
64
|
catch (error) {
|
|
72
65
|
if (error instanceof SecureFileError)
|
|
73
66
|
throw error;
|
|
74
67
|
const code = error.code;
|
|
75
|
-
if (code === 'ELOOP' || code === 'ENOTSUP' || code === 'EINVAL')
|
|
68
|
+
if (code === 'ELOOP' || code === 'ENOTSUP' || code === 'EINVAL')
|
|
76
69
|
conflict('candidate terminal is not a stable non-symlink file');
|
|
77
|
-
}
|
|
78
70
|
throw new SecureFileError('TASK_ARTIFACT_WRITE_CONFLICT', error instanceof Error ? error.message : String(error));
|
|
79
71
|
}
|
|
80
72
|
finally {
|
|
@@ -93,9 +85,8 @@ export async function writeAtomicFile(target, bytes, mode = 0o600, expectedSha25
|
|
|
93
85
|
catch {
|
|
94
86
|
throw new SecureFileError('TASK_ARTIFACT_WRITE_CONFLICT', 'authoritative parent directory is missing');
|
|
95
87
|
}
|
|
96
|
-
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
88
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
97
89
|
throw new SecureFileError('TASK_ARTIFACT_WRITE_CONFLICT', 'authoritative parent directory is not a real directory');
|
|
98
|
-
}
|
|
99
90
|
const next = path.dirname(current);
|
|
100
91
|
if (next === current)
|
|
101
92
|
break;
|
|
@@ -103,9 +94,8 @@ export async function writeAtomicFile(target, bytes, mode = 0o600, expectedSha25
|
|
|
103
94
|
}
|
|
104
95
|
try {
|
|
105
96
|
const existing = await fs.promises.lstat(target);
|
|
106
|
-
if (existing.isSymbolicLink() || !existing.isFile())
|
|
97
|
+
if (existing.isSymbolicLink() || !existing.isFile())
|
|
107
98
|
throw new SecureFileError('TASK_ARTIFACT_WRITE_CONFLICT', 'authoritative target is not a regular file');
|
|
108
|
-
}
|
|
109
99
|
}
|
|
110
100
|
catch (error) {
|
|
111
101
|
if (error.code !== 'ENOENT')
|
|
@@ -120,7 +110,6 @@ export async function writeAtomicFile(target, bytes, mode = 0o600, expectedSha25
|
|
|
120
110
|
await handle.chmod(mode);
|
|
121
111
|
await handle.close();
|
|
122
112
|
handle = undefined;
|
|
123
|
-
// Check after all asynchronous preparation; keep the final check/rename adjacent.
|
|
124
113
|
if (expectedSha256 !== undefined)
|
|
125
114
|
readStableFileSync(target, { maxBytes: 1024 * 1024, expectedSha256 });
|
|
126
115
|
fs.renameSync(temporary, target);
|
|
@@ -199,14 +199,16 @@ function mutateShortIdRegistry(repoRoot, taskId, requested) {
|
|
|
199
199
|
const activeDir = path.join(repoRoot, '.agents', 'workspace', 'active');
|
|
200
200
|
return mutateShortIdRegistryAt(activeDir, configuredShortIdLength(repoRoot), taskId, requested);
|
|
201
201
|
}
|
|
202
|
-
function mutateShortIdRegistryAt(activeDir, width, taskId, requested) {
|
|
202
|
+
function mutateShortIdRegistryAt(activeDir, width, taskId, requested, onlyReleaseWhenInactive = false, pruneOrphans = true) {
|
|
203
203
|
const registryPath = path.join(activeDir, REGISTRY_NAME);
|
|
204
204
|
return withRegistryLock(activeDir, () => {
|
|
205
205
|
const registry = readRegistryStrict(registryPath, width);
|
|
206
206
|
const original = JSON.stringify(registry.ids);
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
207
|
+
if (pruneOrphans) {
|
|
208
|
+
for (const [key, candidate] of Object.entries(registry.ids)) {
|
|
209
|
+
if (candidate !== taskId && !fs.existsSync(path.join(activeDir, candidate, 'task.md')))
|
|
210
|
+
delete registry.ids[key];
|
|
211
|
+
}
|
|
210
212
|
}
|
|
211
213
|
const existing = Object.entries(registry.ids).find(([, candidate]) => candidate === taskId);
|
|
212
214
|
let result;
|
|
@@ -232,8 +234,10 @@ function mutateShortIdRegistryAt(activeDir, width, taskId, requested) {
|
|
|
232
234
|
}
|
|
233
235
|
}
|
|
234
236
|
else if (requested === 'release') {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
+
const taskIsActive = fs.existsSync(path.join(activeDir, taskId, 'task.md'));
|
|
238
|
+
if (!existing || (onlyReleaseWhenInactive && taskIsActive)) {
|
|
239
|
+
result = { effect: 'unchanged', shortId: existing?.[0] ?? null, changed: false };
|
|
240
|
+
}
|
|
237
241
|
else {
|
|
238
242
|
delete registry.ids[existing[0]];
|
|
239
243
|
result = { effect: 'released', shortId: existing[0], changed: true };
|
|
@@ -247,6 +251,10 @@ function mutateShortIdRegistryAt(activeDir, width, taskId, requested) {
|
|
|
247
251
|
return { ...result, changed };
|
|
248
252
|
});
|
|
249
253
|
}
|
|
254
|
+
function releaseStaleShortIdRegistry(repoRoot, taskId) {
|
|
255
|
+
const activeDir = path.join(repoRoot, '.agents', 'workspace', 'active');
|
|
256
|
+
return mutateShortIdRegistryAt(activeDir, configuredShortIdLength(repoRoot), taskId, 'release', true, false);
|
|
257
|
+
}
|
|
250
258
|
function executeShortIdCommand(request) {
|
|
251
259
|
try {
|
|
252
260
|
if (!Number.isInteger(request.shortIdLength) || request.shortIdLength < 1) {
|
|
@@ -429,5 +437,5 @@ function lookupShortIdByBranch(branch, repoRoot, _opts) {
|
|
|
429
437
|
}
|
|
430
438
|
return matches[0];
|
|
431
439
|
}
|
|
432
|
-
export { normalizeShortIdInput, isRemovedHashShortIdInput, resolveShortIdReadOnly, lookupShortIdByBranch, loadShortIdByTaskId, inspectShortIdRegistry, configuredShortIdLength, mutateShortIdRegistry, executeShortIdCommand };
|
|
440
|
+
export { normalizeShortIdInput, isRemovedHashShortIdInput, resolveShortIdReadOnly, lookupShortIdByBranch, loadShortIdByTaskId, inspectShortIdRegistry, configuredShortIdLength, mutateShortIdRegistry, releaseStaleShortIdRegistry, executeShortIdCommand };
|
|
433
441
|
//# sourceMappingURL=short-id.js.map
|
|
@@ -231,7 +231,7 @@ function withTaskExecutionLock(repoRoot, taskId, ownerName, callback, options =
|
|
|
231
231
|
catch (error) {
|
|
232
232
|
if (error instanceof TaskExecutionLockError)
|
|
233
233
|
throw error;
|
|
234
|
-
if (error instanceof Error && error.name
|
|
234
|
+
if (error instanceof Error && ['OrchestrationStateError', 'ArtifactRecoveryError'].includes(error.name))
|
|
235
235
|
throw error;
|
|
236
236
|
throw new TaskExecutionLockError('ORCHESTRATION_LOCK_FAILED', 'lifecycle task lock operation failed', { operation: ownsFixed ? 'callback' : 'acquire', errno: errno(error), key });
|
|
237
237
|
}
|
|
@@ -33,6 +33,7 @@ import { manualValidationFinalSummaryProjectionMatches } from "./manual-validati
|
|
|
33
33
|
import { readManualValidationCompletion } from "./manual-validation-completion.js";
|
|
34
34
|
import { sha256File } from "./artifact-receipts.js";
|
|
35
35
|
import { summaryCommentState } from "../platform/pr-summary.js";
|
|
36
|
+
import { taskIssueIdentity } from "../platform/task-identities.js";
|
|
36
37
|
const TASK_ENUMS = {
|
|
37
38
|
type: ["feature", "bugfix", "refactor", "docs", "chore"],
|
|
38
39
|
workflow: ["feature-development", "bug-fix", "refactoring"],
|
|
@@ -78,8 +79,6 @@ const sharedUtils = {
|
|
|
78
79
|
failResult,
|
|
79
80
|
blockedResult,
|
|
80
81
|
safeStat,
|
|
81
|
-
parseIssueNumber,
|
|
82
|
-
parsePrNumber
|
|
83
82
|
};
|
|
84
83
|
async function runCheck(type, context, shared) {
|
|
85
84
|
switch (type) {
|
|
@@ -314,8 +313,8 @@ function checkTaskMeta({ taskDir, config, repositoryRoot }) {
|
|
|
314
313
|
if (expectedStatus && metadata.status !== expectedStatus) {
|
|
315
314
|
return failResult("task-meta", `Expected status '${expectedStatus}', got '${metadata.status || "(empty)"}'`);
|
|
316
315
|
}
|
|
317
|
-
if (config.
|
|
318
|
-
return failResult("task-meta", "Expected a valid
|
|
316
|
+
if (config.require_platform_issue_identity && !taskIssueIdentity(metadata)) {
|
|
317
|
+
return failResult("task-meta", "Expected a valid platform_issue_identity in task metadata");
|
|
319
318
|
}
|
|
320
319
|
if (config.require_completed_at && isBlank(metadata.completed_at)) {
|
|
321
320
|
return failResult("task-meta", "Expected completed_at to be present");
|
|
@@ -1185,16 +1184,6 @@ function getCheckedRequirements(content) {
|
|
|
1185
1184
|
.filter(Boolean)
|
|
1186
1185
|
.map((match) => match[1].trim());
|
|
1187
1186
|
}
|
|
1188
|
-
function parseIssueNumber(value) {
|
|
1189
|
-
if (isBlank(value) || value === "N/A") {
|
|
1190
|
-
return null;
|
|
1191
|
-
}
|
|
1192
|
-
const match = String(value).match(/\d+/);
|
|
1193
|
-
return match ? Number(match[0]) : null;
|
|
1194
|
-
}
|
|
1195
|
-
function parsePrNumber(value) {
|
|
1196
|
-
return parseIssueNumber(value);
|
|
1197
|
-
}
|
|
1198
1187
|
// === Utilities ===
|
|
1199
1188
|
function normalizeContent(text) {
|
|
1200
1189
|
return String(text || "")
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export const TASK_WORKFLOW_COMMANDS = Object.freeze({
|
|
2
2
|
'artifact-inspect': ['task-artifact', 'inspect'],
|
|
3
3
|
'artifact-init': ['task-artifact', 'init'],
|
|
4
|
-
'artifact-
|
|
4
|
+
'artifact-preflight': ['task-artifact', 'preflight'],
|
|
5
5
|
'artifact-finalize-local': ['task-artifact', 'finalize-local'],
|
|
6
|
+
'review-preflight': ['task-review', 'preflight'],
|
|
6
7
|
'review-finalize-summary': ['task-review', 'finalize-summary'],
|
|
7
8
|
event: ['task-event', null],
|
|
8
9
|
'ledger-finding-response': ['task-ledger', 'finding-respond'],
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { reconcileTaskInvalidation } from './invalidation-command.js';
|
|
2
|
+
import { applyLedgerIntent } from './ledger-intents.js';
|
|
3
|
+
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
4
|
+
import { applyWorkflowWarningIntent } from './workflow-warning-intents.js';
|
|
5
|
+
function failed(code, message) {
|
|
6
|
+
return { status: 'failed', changed: false, error: { code, message } };
|
|
7
|
+
}
|
|
8
|
+
function isFailure(value) {
|
|
9
|
+
return Boolean(value) && typeof value === 'object' && value.status === 'failed';
|
|
10
|
+
}
|
|
11
|
+
function options(args, allowed) {
|
|
12
|
+
const values = {};
|
|
13
|
+
for (let index = 2; index < args.length; index += 1) {
|
|
14
|
+
const flag = args[index];
|
|
15
|
+
if (flag === '--dry-run') {
|
|
16
|
+
if (!Object.hasOwn(allowed, flag) || values[flag] !== undefined)
|
|
17
|
+
return failed('TASK_WORKFLOW_PAYLOAD_INVALID', `invalid option '${flag}'`);
|
|
18
|
+
values[flag] = true;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const key = allowed[flag];
|
|
22
|
+
if (!key || values[flag] !== undefined)
|
|
23
|
+
return failed('TASK_WORKFLOW_PAYLOAD_INVALID', `invalid option '${flag}'`);
|
|
24
|
+
const value = args[++index];
|
|
25
|
+
if (value === undefined || value.startsWith('--'))
|
|
26
|
+
return failed('TASK_WORKFLOW_PAYLOAD_INVALID', `option '${flag}' requires a value`);
|
|
27
|
+
if (flag === '--needs-implementation' && value !== 'true' && value !== 'false') {
|
|
28
|
+
return failed('TASK_WORKFLOW_PAYLOAD_INVALID', "option '--needs-implementation' must be true or false");
|
|
29
|
+
}
|
|
30
|
+
values[flag] = value;
|
|
31
|
+
}
|
|
32
|
+
return values;
|
|
33
|
+
}
|
|
34
|
+
function value(values, flag) {
|
|
35
|
+
const candidate = values[flag];
|
|
36
|
+
return typeof candidate === 'string' ? candidate : undefined;
|
|
37
|
+
}
|
|
38
|
+
function required(values, flags) {
|
|
39
|
+
const missing = flags.find((flag) => value(values, flag) === undefined);
|
|
40
|
+
return missing ? failed('TASK_WORKFLOW_PAYLOAD_INVALID', `option '${missing}' is required`) : null;
|
|
41
|
+
}
|
|
42
|
+
function resultForLock(error) {
|
|
43
|
+
if (error instanceof TaskExecutionLockError)
|
|
44
|
+
return failed(error.code, error.message);
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
function ledgerIntent(operation, args) {
|
|
48
|
+
const [taskRef, kind] = args;
|
|
49
|
+
const mappings = {
|
|
50
|
+
'ledger-finding-response': {
|
|
51
|
+
kind: 'finding-respond', required: ['--id', '--round', '--status', '--evidence'],
|
|
52
|
+
flags: { '--id': 'id', '--round': 'round', '--status': 'status', '--evidence': 'evidence', '--dry-run': 'dryRun' }
|
|
53
|
+
},
|
|
54
|
+
'ledger-finding-review': {
|
|
55
|
+
kind: 'finding-review', required: ['--id', '--status', '--evidence'],
|
|
56
|
+
flags: { '--id': 'id', '--status': 'status', '--evidence': 'evidence', '--needs-implementation': 'needsImplementation', '--dry-run': 'dryRun' }
|
|
57
|
+
},
|
|
58
|
+
'ledger-finding-upsert': {
|
|
59
|
+
kind: 'finding-upsert', required: ['--stage', '--review-artifact', '--ordinal', '--severity', '--evidence'],
|
|
60
|
+
flags: { '--stage': 'stage', '--review-artifact': 'reviewArtifact', '--ordinal': 'ordinal', '--severity': 'severity', '--evidence': 'evidence', '--dry-run': 'dryRun' }
|
|
61
|
+
},
|
|
62
|
+
'decision-next-id': { kind: 'decision-next-id', required: [], flags: {} },
|
|
63
|
+
'decision-upsert': {
|
|
64
|
+
kind: 'decision-upsert', required: ['--id', '--stage', '--artifact'],
|
|
65
|
+
flags: { '--id': 'id', '--stage': 'stage', '--artifact': 'artifact', '--needs-implementation': 'needsImplementation', '--dry-run': 'dryRun' }
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const mapping = mappings[operation];
|
|
69
|
+
if (!mapping || !taskRef || kind === undefined)
|
|
70
|
+
return failed('TASK_WORKFLOW_PAYLOAD_INVALID', 'task workflow ledger request is invalid');
|
|
71
|
+
const values = options(args, mapping.flags);
|
|
72
|
+
if (isFailure(values))
|
|
73
|
+
return values;
|
|
74
|
+
const missing = required(values, mapping.required);
|
|
75
|
+
if (missing)
|
|
76
|
+
return missing;
|
|
77
|
+
const dryRun = values['--dry-run'] === true;
|
|
78
|
+
if (mapping.kind === 'decision-next-id')
|
|
79
|
+
return { kind: mapping.kind, taskRef };
|
|
80
|
+
if (mapping.kind === 'finding-respond')
|
|
81
|
+
return {
|
|
82
|
+
kind: mapping.kind, taskRef, id: value(values, '--id'), round: Number(value(values, '--round')),
|
|
83
|
+
status: value(values, '--status'), evidence: value(values, '--evidence'), ...(dryRun ? { dryRun } : {})
|
|
84
|
+
};
|
|
85
|
+
if (mapping.kind === 'finding-review')
|
|
86
|
+
return {
|
|
87
|
+
kind: mapping.kind, taskRef, id: value(values, '--id'), status: value(values, '--status'),
|
|
88
|
+
evidence: value(values, '--evidence'), ...(value(values, '--needs-implementation') === 'true' ? { needsImplementation: true } : {}),
|
|
89
|
+
...(dryRun ? { dryRun } : {})
|
|
90
|
+
};
|
|
91
|
+
if (mapping.kind === 'finding-upsert')
|
|
92
|
+
return {
|
|
93
|
+
kind: mapping.kind, taskRef, stage: value(values, '--stage'), reviewArtifact: value(values, '--review-artifact'),
|
|
94
|
+
ordinal: Number(value(values, '--ordinal')), severity: value(values, '--severity'), evidence: value(values, '--evidence'),
|
|
95
|
+
...(dryRun ? { dryRun } : {})
|
|
96
|
+
};
|
|
97
|
+
return {
|
|
98
|
+
kind: mapping.kind, taskRef, id: value(values, '--id'), stage: value(values, '--stage'),
|
|
99
|
+
artifact: value(values, '--artifact'), ...(value(values, '--needs-implementation') === 'true' ? { needsImplementation: true } : {}),
|
|
100
|
+
...(dryRun ? { dryRun } : {})
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function warningIntent(args) {
|
|
104
|
+
const [taskRef, kind] = args;
|
|
105
|
+
if (!taskRef || kind !== 'add')
|
|
106
|
+
return failed('TASK_WORKFLOW_PAYLOAD_INVALID', 'task workflow warning request is invalid');
|
|
107
|
+
const values = options(args, {
|
|
108
|
+
'--step': 'step', '--severity': 'severity', '--code': 'code', '--target': 'target',
|
|
109
|
+
'--message': 'message', '--action': 'action', '--dry-run': 'dryRun'
|
|
110
|
+
});
|
|
111
|
+
if (isFailure(values))
|
|
112
|
+
return values;
|
|
113
|
+
const missing = required(values, ['--step', '--severity', '--code', '--target', '--message', '--action']);
|
|
114
|
+
if (missing)
|
|
115
|
+
return missing;
|
|
116
|
+
return {
|
|
117
|
+
kind: 'add', taskRef, step: value(values, '--step'), severity: value(values, '--severity'),
|
|
118
|
+
code: value(values, '--code'), target: value(values, '--target'), message: value(values, '--message'),
|
|
119
|
+
action: value(values, '--action'), ...(values['--dry-run'] === true ? { dryRun: true } : {})
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* The caller has already selected and authorized the execution boundary.
|
|
124
|
+
* This function deliberately invokes task domains in that process: starting a
|
|
125
|
+
* second CLI process would discard the broker binding and could misclassify a
|
|
126
|
+
* sandbox process with cleared environment variables as direct-host.
|
|
127
|
+
*/
|
|
128
|
+
export function dispatchWorkflowCommand(repoRoot, operation, args) {
|
|
129
|
+
const taskRef = args[0];
|
|
130
|
+
if (!taskRef)
|
|
131
|
+
return failed('TASK_WORKFLOW_PAYLOAD_INVALID', 'task ref is required');
|
|
132
|
+
try {
|
|
133
|
+
if (operation === 'invalidation-reconcile') {
|
|
134
|
+
const values = options(args, { '--max-targets': 'maxTargets', '--dry-run': 'dryRun' });
|
|
135
|
+
if (isFailure(values))
|
|
136
|
+
return values;
|
|
137
|
+
const maxTargets = value(values, '--max-targets');
|
|
138
|
+
if (maxTargets !== undefined && (!Number.isSafeInteger(Number(maxTargets)) || Number(maxTargets) < 1)) {
|
|
139
|
+
return failed('TASK_WORKFLOW_PAYLOAD_INVALID', '--max-targets must be a positive integer');
|
|
140
|
+
}
|
|
141
|
+
return withTaskExecutionLock(repoRoot, taskRef, 'task-invalidation.reconcile', () => reconcileTaskInvalidation(taskRef, {
|
|
142
|
+
repoRoot, ...(maxTargets === undefined ? {} : { maxTargets: Number(maxTargets) }),
|
|
143
|
+
...(values['--dry-run'] === true ? { dryRun: true } : {})
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
if (operation === 'warning-add') {
|
|
147
|
+
const intent = warningIntent(args);
|
|
148
|
+
if (isFailure(intent))
|
|
149
|
+
return intent;
|
|
150
|
+
return withTaskExecutionLock(repoRoot, taskRef, 'task-warning.add', () => applyWorkflowWarningIntent(intent, { repoRoot }));
|
|
151
|
+
}
|
|
152
|
+
const intent = ledgerIntent(operation, args);
|
|
153
|
+
if (isFailure(intent))
|
|
154
|
+
return intent;
|
|
155
|
+
return withTaskExecutionLock(repoRoot, taskRef, `task-ledger.${intent.kind}`, () => applyLedgerIntent(intent, { repoRoot }));
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
return resultForLock(error);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=workflow-dispatch.js.map
|
package/dist/lib/version.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
-
import semver from 'semver';
|
|
3
2
|
const packageJsonUrl = [
|
|
4
3
|
new URL('../package.json', import.meta.url),
|
|
5
4
|
new URL('../../package.json', import.meta.url),
|
|
@@ -10,17 +9,8 @@ if (!packageJsonUrl) {
|
|
|
10
9
|
const { version } = JSON.parse(readFileSync(packageJsonUrl, 'utf8'));
|
|
11
10
|
const VERSION = `v${version}`;
|
|
12
11
|
const AGENT_INFRA_VERSION_PATTERN = /^v\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
13
|
-
const LEGACY_COMPATIBILITY_CUTOFF_VERSION = '1.0.0';
|
|
14
12
|
function isValidAgentInfraVersion(value) {
|
|
15
13
|
return typeof value === 'string' && AGENT_INFRA_VERSION_PATTERN.test(value);
|
|
16
14
|
}
|
|
17
|
-
|
|
18
|
-
const normalized = runtimeVersion.startsWith('v') ? runtimeVersion.slice(1) : runtimeVersion;
|
|
19
|
-
const parsed = semver.valid(normalized);
|
|
20
|
-
return parsed !== null && semver.lt(parsed, LEGACY_COMPATIBILITY_CUTOFF_VERSION);
|
|
21
|
-
}
|
|
22
|
-
function legacyCompatibilityError(resource, runtimeVersion = VERSION) {
|
|
23
|
-
return Object.assign(new Error(`PLATFORM_IDENTITY_LEGACY_UNSUPPORTED: ${resource} requires migration to the current schema before v${LEGACY_COMPATIBILITY_CUTOFF_VERSION} runtime ${runtimeVersion}`), { code: 'PLATFORM_IDENTITY_LEGACY_UNSUPPORTED' });
|
|
24
|
-
}
|
|
25
|
-
export { LEGACY_COMPATIBILITY_CUTOFF_VERSION, VERSION, isLegacyCompatibilityEnabled, isValidAgentInfraVersion, legacyCompatibilityError };
|
|
15
|
+
export { VERSION, isValidAgentInfraVersion };
|
|
26
16
|
//# sourceMappingURL=version.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"protocolVersion": 3,
|
|
3
|
-
"packageVersion": "0.
|
|
4
|
-
"sourceInputHash": "
|
|
5
|
-
"internalExecutableBuildHash": "
|
|
3
|
+
"packageVersion": "0.11.0",
|
|
4
|
+
"sourceInputHash": "c67e66a46040d5246d67c751af605b796cd61913b98add01ff327829c01ce3db",
|
|
5
|
+
"internalExecutableBuildHash": "f6b4be06df12111b3a7614d335c9e802049c3cea194c6e7c040f3251188c694f"
|
|
6
6
|
}
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
type ControllerControl = Readonly<{
|
|
27
27
|
token: string;
|
|
28
28
|
generation: string;
|
|
29
|
+
rootId?: string;
|
|
29
30
|
channelDir: string;
|
|
30
31
|
statusDir: string;
|
|
31
32
|
runtimeDir: string;
|
|
@@ -83,13 +84,14 @@ function copyRegular(source: string, destination: string, mode: number): void {
|
|
|
83
84
|
function controlFromEnvironment(): ControllerControl {
|
|
84
85
|
const token = process.env.AGENT_INFRA_CONTROL_TOKEN;
|
|
85
86
|
const generation = process.env.AGENT_INFRA_CONTROL_GENERATION;
|
|
87
|
+
const rootId = process.env.AGENT_INFRA_CONTROL_ROOT_ID;
|
|
86
88
|
const channelDir = process.env.AGENT_INFRA_CONTROL_DIR;
|
|
87
89
|
const statusDir = process.env.AGENT_INFRA_CONTROL_STATUS_DIR;
|
|
88
90
|
const runtimeDir = process.env.AGENT_INFRA_RUNTIME_DIR;
|
|
89
|
-
if (!token || !generation || !channelDir || !statusDir || !runtimeDir) {
|
|
91
|
+
if (!token || !generation || !rootId || !channelDir || !statusDir || !runtimeDir) {
|
|
90
92
|
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTROL_MISSING');
|
|
91
93
|
}
|
|
92
|
-
return { token, generation, channelDir, statusDir, runtimeDir };
|
|
94
|
+
return { token, generation, rootId, channelDir, statusDir, runtimeDir };
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
function verifyRuntime(runtimeDir: string): void {
|
|
@@ -255,6 +257,9 @@ function isolatedEnvironment(
|
|
|
255
257
|
AGENT_INFRA_TASK_ID: taskId,
|
|
256
258
|
AGENT_INFRA_CONTROL_TOKEN: control.token,
|
|
257
259
|
AGENT_INFRA_CONTROL_GENERATION: control.generation,
|
|
260
|
+
...(control.rootId || sourceEnvironment.AGENT_INFRA_CONTROL_ROOT_ID
|
|
261
|
+
? { AGENT_INFRA_CONTROL_ROOT_ID: control.rootId ?? sourceEnvironment.AGENT_INFRA_CONTROL_ROOT_ID }
|
|
262
|
+
: {}),
|
|
258
263
|
AGENT_INFRA_CONTROL_DIR: control.channelDir,
|
|
259
264
|
AGENT_INFRA_CONTROL_STATUS_DIR: control.statusDir,
|
|
260
265
|
AGENT_INFRA_RUNTIME_DIR: control.runtimeDir,
|
|
@@ -43,11 +43,23 @@ type ActiveCodexLifecycleEvidenceQuery = Readonly<{
|
|
|
43
43
|
const LOCK_RETRY_MS = 10;
|
|
44
44
|
const LOCK_TIMEOUT_MS = 1_000;
|
|
45
45
|
const LOCK_STALE_MS = 30_000;
|
|
46
|
+
const RECOVERY_CONSUMER_RE = /^lifecycle-recovery:([^:\r\n]+):([^:\r\n]+)$/u;
|
|
46
47
|
|
|
47
48
|
function digest(value: string): string {
|
|
48
49
|
return crypto.createHash('sha256').update(value).digest('hex');
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
function isRecoveryConsumer(value: string): boolean {
|
|
53
|
+
return RECOVERY_CONSUMER_RE.test(value);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function recoveryConsumer(taskId: string, receiptId: string): string {
|
|
57
|
+
if (!/^[^:\r\n]+$/u.test(taskId) || !/^[^:\r\n]+$/u.test(receiptId)) {
|
|
58
|
+
throw new Error('Codex lifecycle recovery claim identity is invalid');
|
|
59
|
+
}
|
|
60
|
+
return `lifecycle-recovery:${taskId}:${receiptId}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
51
63
|
function readRecord(file: string): StoredCodexLifecycle {
|
|
52
64
|
const value = JSON.parse(fs.readFileSync(file, 'utf8')) as StoredCodexLifecycle;
|
|
53
65
|
if (
|
|
@@ -249,11 +261,12 @@ function createCodexLifecycleStore(options: CodexLifecycleStoreOptions) {
|
|
|
249
261
|
|
|
250
262
|
function read(childThreadId: string): StoredCodexLifecycle {
|
|
251
263
|
const matches = findByChild(childThreadId);
|
|
252
|
-
if (matches.length
|
|
264
|
+
if (matches.length === 0) throw new Error(`Codex lifecycle child '${childThreadId}' was not found uniquely`);
|
|
265
|
+
if (matches.length > 1) throw new Error(`Codex lifecycle child '${childThreadId}' is ambiguous`);
|
|
253
266
|
return readRecord(matches[0]!);
|
|
254
267
|
}
|
|
255
268
|
|
|
256
|
-
function
|
|
269
|
+
function consumeInternal(
|
|
257
270
|
childThreadId: string,
|
|
258
271
|
consumer: string,
|
|
259
272
|
expectedHookDefinitionHash?: string
|
|
@@ -291,12 +304,51 @@ function createCodexLifecycleStore(options: CodexLifecycleStoreOptions) {
|
|
|
291
304
|
});
|
|
292
305
|
}
|
|
293
306
|
|
|
307
|
+
function consume(
|
|
308
|
+
childThreadId: string,
|
|
309
|
+
consumer: string,
|
|
310
|
+
expectedHookDefinitionHash?: string
|
|
311
|
+
): StoredCodexLifecycle {
|
|
312
|
+
if (!consumer.trim()) throw new Error('Codex lifecycle consumer is required');
|
|
313
|
+
if (consumer.startsWith('lifecycle-recovery:')) {
|
|
314
|
+
throw new Error('Codex lifecycle recovery claims require claimRecovery');
|
|
315
|
+
}
|
|
316
|
+
return consumeInternal(childThreadId, consumer, expectedHookDefinitionHash);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function claimRecovery(
|
|
320
|
+
childThreadId: string,
|
|
321
|
+
taskId: string,
|
|
322
|
+
receiptId: string,
|
|
323
|
+
expectedHookDefinitionHash?: string
|
|
324
|
+
): StoredCodexLifecycle {
|
|
325
|
+
return consumeInternal(
|
|
326
|
+
childThreadId,
|
|
327
|
+
recoveryConsumer(taskId, receiptId),
|
|
328
|
+
expectedHookDefinitionHash
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function releaseRecovery(childThreadId: string, consumer: string): boolean {
|
|
333
|
+
if (!/^lifecycle-recovery:[^:\r\n]+:[^:\r\n]+$/.test(consumer)) return false;
|
|
334
|
+
return withWriteLock(() => {
|
|
335
|
+
const matches = findByChild(childThreadId);
|
|
336
|
+
if (matches.length !== 1) return false;
|
|
337
|
+
const file = matches[0]!;
|
|
338
|
+
const current = readRecord(file);
|
|
339
|
+
if (current.state.status !== 'stop-ready' || current.consumer !== consumer || !current.consumedAt) return false;
|
|
340
|
+
fs.unlinkSync(file);
|
|
341
|
+
return true;
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
294
345
|
function expireBefore(cutoff: string): number {
|
|
295
346
|
return withWriteLock(() => {
|
|
296
347
|
let changed = 0;
|
|
297
348
|
for (const file of recordFiles(root)) {
|
|
298
349
|
const current = readRecord(file);
|
|
299
350
|
if (current.updatedAt >= cutoff) continue;
|
|
351
|
+
if (current.consumer && isRecoveryConsumer(current.consumer)) continue;
|
|
300
352
|
if (current.consumer || ['invalid', 'expired', 'stop-ready'].includes(current.state.status)) {
|
|
301
353
|
fs.unlinkSync(file);
|
|
302
354
|
changed += 1;
|
|
@@ -316,7 +368,7 @@ function createCodexLifecycleStore(options: CodexLifecycleStoreOptions) {
|
|
|
316
368
|
});
|
|
317
369
|
}
|
|
318
370
|
|
|
319
|
-
return Object.freeze({ root, apply, applyToSpawn, consume, expireBefore, findByParent, read });
|
|
371
|
+
return Object.freeze({ root, apply, applyToSpawn, consume, claimRecovery, releaseRecovery, expireBefore, findByParent, read });
|
|
320
372
|
}
|
|
321
373
|
|
|
322
374
|
export { createCodexLifecycleStore, hasActiveCodexLifecycleEvidence };
|
package/lib/fs/durable-write.ts
CHANGED
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
|
|
5
5
|
export function fsyncDirectory(directory: string): void {
|
|
6
|
+
if (process.platform === 'win32') return;
|
|
6
7
|
const descriptor = fs.openSync(directory, 'r');
|
|
7
8
|
try { fs.fsyncSync(descriptor); } finally { fs.closeSync(descriptor); }
|
|
8
9
|
}
|
|
@@ -36,7 +36,6 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
|
|
|
36
36
|
'task-create': ['input'],
|
|
37
37
|
'task-qualification': ['proposal', 'confirm', 'supersede', 'revoke'],
|
|
38
38
|
'sandbox-control': ['serve', 'execute', 'recover', 'client'],
|
|
39
|
-
'host-control': ['serve', 'status', 'install', 'uninstall'],
|
|
40
39
|
'agent-client': ['next-steps', 'model-selection'],
|
|
41
40
|
'codex-lifecycle': ['preflight', 'capability-arm', 'hook-event', 'resolve-start', 'resolve-stop', 'consume'],
|
|
42
41
|
'codex-sandbox-controller': ['verify-context', 'run'],
|
|
@@ -47,7 +46,7 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
|
|
|
47
46
|
'platform-security': ['read', 'dismiss'],
|
|
48
47
|
'platform-metadata': ['init-labels', 'init-milestones'],
|
|
49
48
|
'platform-context': ['resolve'],
|
|
50
|
-
'platform-comment': ['list', 'owner', 'backfill', 'sync'],
|
|
49
|
+
'platform-comment': ['list', 'recover', 'owner', 'backfill', 'sync'],
|
|
51
50
|
'platform-issue': ['inspect', 'create', 'bind', 'sync'],
|
|
52
51
|
'platform-pr': ['inspect', 'summary-context', 'resolve-external', 'create', 'bind', 'skip', 'sync', 'change-report', 'summary-sync', 'sync-in-labels'],
|
|
53
52
|
'platform-pr-review': ['inspect', 'list', 'publish-pr', 'publish-task'],
|
|
@@ -57,9 +56,9 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
|
|
|
57
56
|
'task-ledger': ['decision-next-id', 'stage-status', 'finding-upsert', 'finding-respond', 'finding-review', 'decision-upsert', 'rework-intent-upsert'],
|
|
58
57
|
'task-warning': ['list', 'add', 'set-status'],
|
|
59
58
|
'task-activity': ['pr-review-inspect', 'pr-review-start', 'pr-review-complete', 'pr-review-terminate'],
|
|
60
|
-
'task-artifact': ['inspect', 'init', '
|
|
59
|
+
'task-artifact': ['inspect', 'init', 'preflight', 'finalize-local'],
|
|
61
60
|
'task-orchestration': ['status', 'progress'],
|
|
62
|
-
'task-review': ['finalize-summary'],
|
|
61
|
+
'task-review': ['preflight', 'finalize-summary'],
|
|
63
62
|
'task-event': ['event'],
|
|
64
63
|
'task-invalidation': ['reconcile'],
|
|
65
64
|
'task-override': ['diagnose', 'issue', 'consume'],
|
|
@@ -43,15 +43,24 @@ function printFailure(format: OutputFormat, error: { code: string; message: stri
|
|
|
43
43
|
process.exitCode = 1;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
const MANUAL_VALIDATION_STATUS_SECTION = /^###\s+(?:⚠️\s+(?:需人工校验|Manual Validation Required)|✅\s+(?:人工验证已通过|无需人工校验|Manual Validation Passed|No Manual Validation Required)|⏳\s+(?:人工验证待收尾|Manual Validation Pending))\s*$[\s\S]*?(?=^#{1,3}\s|(?![\s\S]))/gmu;
|
|
47
|
+
|
|
46
48
|
function manualSummaryBody(body: string, phase: 'pending' | 'final', transactionId: string, receiptDigest = '', evidenceDigest = '', prHeadSha = ''): string {
|
|
47
|
-
const
|
|
48
|
-
.replace(/^###\s+✅\s+(?:Manual Validation Passed|人工验证已通过)\s*$/gmu, '')
|
|
49
|
-
.replace(/^###\s+⏳\s+(?:Manual Validation Pending|人工验证待收尾)\s*$/gmu, '')
|
|
50
|
-
.replace(/Manual validation passed(?:\s+→)?/giu, 'Manual validation pending');
|
|
49
|
+
const chinese = /###\s+(?:⚠️\s+需人工校验|✅\s+(?:人工验证已通过|无需人工校验)|⏳\s+人工验证待收尾)\s*$/mu.test(body);
|
|
51
50
|
const section = phase === 'pending'
|
|
52
|
-
?
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
? chinese
|
|
52
|
+
? `### ⏳ 人工验证待收尾\n\n人工验证证据已暂存;事务 ${transactionId} 正在等待最终完成。`
|
|
53
|
+
: `### ⏳ Manual Validation Pending\n\nManual validation evidence is staged; transaction ${transactionId} is awaiting final promotion.`
|
|
54
|
+
: chinese
|
|
55
|
+
? `### ✅ 人工验证已通过\n\n人工验证已通过;transaction=${transactionId}; receipt=${receiptDigest}; evidence=${evidenceDigest}; head=${prHeadSha}.`
|
|
56
|
+
: `### ✅ Manual Validation Passed\n\nManual validation passed; transaction=${transactionId}; receipt=${receiptDigest}; evidence=${evidenceDigest}; head=${prHeadSha}.`;
|
|
57
|
+
let inserted = false;
|
|
58
|
+
const updated = body.replace(MANUAL_VALIDATION_STATUS_SECTION, () => {
|
|
59
|
+
if (inserted) return '';
|
|
60
|
+
inserted = true;
|
|
61
|
+
return `${section}\n\n`;
|
|
62
|
+
});
|
|
63
|
+
return inserted ? `${updated.replace(/\s+$/u, '')}\n` : `${body.replace(/\s+$/u, '')}\n\n${section}\n`;
|
|
55
64
|
}
|
|
56
65
|
|
|
57
66
|
function openManualValidationStarted(taskMdPath: string): { present: boolean; transactionId: string | null } {
|
|
@@ -246,7 +255,7 @@ async function executeManualValidationTransactionLocked(
|
|
|
246
255
|
});
|
|
247
256
|
if (!['applied', 'no-op'].includes(promoted.status)) transactionFailure(promoted.error ?? { code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: 'final summary promotion failed' });
|
|
248
257
|
const postWrite = await summaryCommentState(taskRef, { cwd, client: options.client });
|
|
249
|
-
if (
|
|
258
|
+
if (!/^###\s+✅\s+(?:Manual Validation Passed|人工验证已通过)\s*$/mu.test(postWrite.comment?.body ?? '') || postWrite.pullRequest?.head.sha !== receipt.prHeadSha || !manualValidationFinalSummaryProjectionMatches(postWrite.comment?.body ?? '', receipt)) transactionFailure({ code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: 'final summary post-write verification failed' });
|
|
250
259
|
transitionAndPersist('committed', { postWriteVerified: true });
|
|
251
260
|
return result('applied', null, { transaction, receipt, idempotent: false });
|
|
252
261
|
} catch (error) {
|