@fitlab-ai/agent-infra 0.11.10 → 0.11.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/agent-clients/adapter.js +20 -0
- package/dist/lib/agent-clients/adapters/codex-sandbox.js +13 -0
- package/dist/lib/agent-clients/adapters/codex.js +4 -0
- package/dist/lib/internal/manual-validation.js +2 -2
- package/dist/lib/internal/platform-pr.js +4 -5
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-create.js +15 -1
- package/dist/lib/internal/task-finalization.js +1 -3
- package/dist/lib/internal/task-operation-registry.js +4 -138
- package/dist/lib/platform/pr-summary.js +45 -8
- package/dist/lib/platform/pull-requests.js +79 -14
- package/dist/lib/platform/verification-sync.js +17 -14
- package/dist/lib/sandbox/commands/create.js +18 -2
- package/dist/lib/sandbox/commands/enter.js +47 -33
- package/dist/lib/sandbox/control/client.js +46 -12
- package/dist/lib/sandbox/control/executor.js +4 -11
- package/dist/lib/sandbox/control/host-recovery.js +218 -0
- package/dist/lib/sandbox/control/protocol.js +2 -3
- package/dist/lib/sandbox/control/server.js +42 -2
- package/dist/lib/sandbox/environment.js +150 -0
- package/dist/lib/sandbox/recovery.js +8 -0
- package/dist/lib/sandbox/workspace-identity.js +0 -14
- package/dist/lib/sandbox/workspace-view.js +33 -6
- package/dist/lib/task/artifact-command.js +7 -2
- package/dist/lib/task/artifact-lifecycle.js +2 -1
- package/dist/lib/task/capabilities.js +4 -1
- package/dist/lib/task/control-authority.js +0 -2
- package/dist/lib/task/events.js +1 -1
- package/dist/lib/task/finalization.js +37 -54
- package/dist/lib/task/frontmatter.js +24 -1
- package/dist/lib/task/ledger-intents.js +2 -1
- package/dist/lib/task/platform-operation-journal.js +12 -0
- package/dist/lib/task/platform-operation-recovery.js +14 -7
- package/dist/lib/task/resolve-ref.js +8 -3
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapter.ts +22 -0
- package/lib/agent-clients/adapters/codex-sandbox.ts +13 -0
- package/lib/agent-clients/adapters/codex.ts +4 -0
- package/lib/internal/manual-validation.ts +2 -2
- package/lib/internal/platform-pr.ts +3 -4
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-create.ts +15 -2
- package/lib/internal/task-finalization.ts +1 -3
- package/lib/internal/task-operation-registry.ts +4 -150
- package/lib/platform/pr-summary.ts +43 -7
- package/lib/platform/pull-requests.ts +74 -13
- package/lib/platform/verification-sync.ts +18 -14
- package/lib/sandbox/commands/create.ts +24 -2
- package/lib/sandbox/commands/enter.ts +42 -39
- package/lib/sandbox/control/client.ts +52 -15
- package/lib/sandbox/control/executor.ts +5 -12
- package/lib/sandbox/control/host-recovery.ts +195 -0
- package/lib/sandbox/control/protocol.ts +4 -5
- package/lib/sandbox/control/server.ts +42 -1
- package/lib/sandbox/environment.ts +162 -0
- package/lib/sandbox/recovery.ts +10 -0
- package/lib/sandbox/tool-types.ts +1 -0
- package/lib/sandbox/workspace-identity.ts +0 -26
- package/lib/sandbox/workspace-view.ts +33 -8
- package/lib/task/artifact-command.ts +9 -2
- package/lib/task/artifact-lifecycle.ts +2 -1
- package/lib/task/capabilities.ts +5 -2
- package/lib/task/control-authority.ts +0 -4
- package/lib/task/events.ts +1 -1
- package/lib/task/finalization.ts +34 -51
- package/lib/task/frontmatter.ts +25 -1
- package/lib/task/ledger-intents.ts +2 -1
- package/lib/task/platform-operation-journal.ts +29 -2
- package/lib/task/platform-operation-recovery.ts +12 -6
- package/lib/task/resolve-ref.ts +8 -3
- package/package.json +1 -1
- package/templates/.agents/rules/README.en.md +1 -1
- package/templates/.agents/rules/README.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +1 -1
- package/templates/.agents/rules/issue-sync.zh-CN.md +1 -1
- package/templates/.agents/rules/no-mid-flow-questions.en.md +19 -0
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +19 -0
- package/templates/.agents/rules/review-handshake.en.md +3 -0
- package/templates/.agents/rules/review-handshake.zh-CN.md +3 -0
- package/templates/.agents/rules/task-management.en.md +1 -1
- package/templates/.agents/rules/task-management.zh-CN.md +1 -1
- package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/close-codescan/SKILL.en.md +3 -0
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +3 -0
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +3 -0
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +3 -0
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/branch-management.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/branch-management.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +2 -0
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +2 -0
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/fix-mode.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/create-pr/SKILL.en.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/reference/branch-strategy.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/branch-strategy.zh-CN.md +2 -2
- package/templates/.agents/skills/create-task/SKILL.en.md +3 -5
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +3 -4
- package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/plan-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.en.md +4 -4
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/review-pr/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/test/SKILL.en.md +4 -1
- package/templates/.agents/skills/test/SKILL.zh-CN.md +4 -1
- package/templates/.agents/skills/test-integration/SKILL.en.md +5 -2
- package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +5 -2
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +4 -1
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +4 -1
- package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +5 -2
- package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +5 -2
- package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
- 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/dist/lib/task/finalization-handoff.js +0 -93
- package/lib/task/finalization-handoff.ts +0 -105
|
@@ -274,7 +274,6 @@ export function createSandboxExecutorExecutionContext(params) {
|
|
|
274
274
|
generation: params.generation,
|
|
275
275
|
manifestPath: absolute('manifestPath', params.manifestPath),
|
|
276
276
|
requestId: params.requestId,
|
|
277
|
-
handoffDirectory: absolute('handoffDirectory', params.handoffDirectory ?? path.join(params.worktreeRoot, '.agent-infra-handoff')),
|
|
278
277
|
...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) }),
|
|
279
278
|
...(params.lifecycleRecoveryAttestation === undefined ? {} : {
|
|
280
279
|
lifecycleRecoveryAttestation: params.lifecycleRecoveryAttestation === null
|
|
@@ -302,7 +301,6 @@ export function assertTaskControlExecutionContext(context) {
|
|
|
302
301
|
absolute('worktreeRoot', context.worktreeRoot);
|
|
303
302
|
absolute('runtimeDir', context.runtimeDir);
|
|
304
303
|
absolute('manifestPath', context.manifestPath);
|
|
305
|
-
absolute('handoffDirectory', context.handoffDirectory);
|
|
306
304
|
if (path.resolve(context.runtimeDir) !== path.join(path.dirname(path.resolve(context.manifestPath)), 'runtime')) {
|
|
307
305
|
invalidContext('runtimeDir is not bound to the manifest control root');
|
|
308
306
|
}
|
package/dist/lib/task/events.js
CHANGED
|
@@ -113,7 +113,7 @@ function validateTaskEventRequest(request) {
|
|
|
113
113
|
return { code: 'EVENT_PAYLOAD_INVALID', message: 'initiator is invalid' };
|
|
114
114
|
if (request.requestId !== undefined && (!request.requestId.trim() || /[\r\n]/.test(request.requestId)))
|
|
115
115
|
return { code: 'EVENT_PAYLOAD_INVALID', message: 'requestId must be a non-empty single line' };
|
|
116
|
-
if (request.reasonCode && !['user-request', 'new-requirement', 'upstream-fact-doubt', 'review-finding', 'retry', 'validation-rerun'].includes(request.reasonCode))
|
|
116
|
+
if (request.reasonCode && !['user-request', 'new-requirement', 'upstream-fact-doubt', 'review-finding', 'manual-review-supplement', 'retry', 'validation-rerun'].includes(request.reasonCode))
|
|
117
117
|
return { code: 'EVENT_PAYLOAD_INVALID', message: 'reasonCode is invalid' };
|
|
118
118
|
for (const [name, value] of [['sourceFinding', request.sourceFinding], ['sourceArtifact', request.sourceArtifact], ['sourceSha256', request.sourceSha256]]) {
|
|
119
119
|
if (value !== undefined && (!value.trim() || /[\r\n]/.test(value)))
|
|
@@ -10,22 +10,45 @@ import { resourceIdentityEquals } from '../platform/resource-identity.js';
|
|
|
10
10
|
import { parseTaskFrontmatter } from './frontmatter.js';
|
|
11
11
|
import { recoverPlatformOperations } from './platform-operation-recovery.js';
|
|
12
12
|
import { applyTaskLifecycle, inspectTaskLifecycleProgress, previewTaskCompletion } from './lifecycle.js';
|
|
13
|
-
import { resolveTaskRef } from './resolve-ref.js';
|
|
13
|
+
import { enumerateAllTaskDirs, resolveTaskRef } from './resolve-ref.js';
|
|
14
14
|
import { inspectShortIdRegistry } from './short-id.js';
|
|
15
15
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
16
16
|
import { verifyTaskEvent } from './verification.js';
|
|
17
17
|
import { projectFinalizationWarning } from './workflow-warning-intents.js';
|
|
18
18
|
import { mergeOperationWarnings } from './operation-outcome.js';
|
|
19
|
-
import { readTaskFinalizationHandoff } from './finalization-handoff.js';
|
|
20
19
|
import { readSandboxControlManifest } from '../sandbox/control/lifecycle.js';
|
|
21
20
|
import { validateSandboxControlIdentity } from '../sandbox/control/identity-sentinel.js';
|
|
22
21
|
const RECEIPT_VERSION = 4;
|
|
23
22
|
const FINALIZATION_STEPS = ['lifecycle', 'task-comment', 'verification', 'summary'];
|
|
24
|
-
function
|
|
25
|
-
|
|
23
|
+
export function resolveTaskFinalizationDirectory(repoRoot, taskId) {
|
|
24
|
+
const tasks = enumerateAllTaskDirs(repoRoot).filter((task) => task.taskId === taskId);
|
|
25
|
+
if (tasks.length === 0)
|
|
26
|
+
return null;
|
|
27
|
+
if (tasks.length !== 1)
|
|
28
|
+
throw new Error(`FINALIZATION_TASK_IDENTITY_AMBIGUOUS: ${taskId}`);
|
|
29
|
+
const task = tasks[0];
|
|
30
|
+
let canonicalDir;
|
|
31
|
+
try {
|
|
32
|
+
canonicalDir = fs.realpathSync.native(task.taskDir);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
throw new Error(`FINALIZATION_TASK_IDENTITY_UNAVAILABLE: ${taskId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
36
|
+
}
|
|
37
|
+
let frontmatter;
|
|
38
|
+
try {
|
|
39
|
+
frontmatter = parseTaskFrontmatter(fs.readFileSync(path.join(canonicalDir, 'task.md'), 'utf8'));
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
throw new Error(`FINALIZATION_TASK_IDENTITY_INVALID: ${taskId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
43
|
+
}
|
|
44
|
+
if (task.taskId !== taskId || frontmatter.id !== taskId) {
|
|
45
|
+
throw new Error(`FINALIZATION_TASK_IDENTITY_INVALID: task directory and task.md must identify ${taskId}`);
|
|
46
|
+
}
|
|
47
|
+
return canonicalDir;
|
|
26
48
|
}
|
|
27
49
|
function receiptPath(repoRoot, taskId) {
|
|
28
|
-
|
|
50
|
+
const taskDir = resolveTaskFinalizationDirectory(repoRoot, taskId);
|
|
51
|
+
return taskDir ? path.join(taskDir, '.task-finalization.json') : null;
|
|
29
52
|
}
|
|
30
53
|
function now() {
|
|
31
54
|
return new Date().toISOString();
|
|
@@ -89,7 +112,7 @@ function validWarning(value) {
|
|
|
89
112
|
&& (warning.status === 'open' || warning.status === 'resolved')
|
|
90
113
|
&& (warning.resolvedAt === null || typeof warning.resolvedAt === 'string');
|
|
91
114
|
}
|
|
92
|
-
function
|
|
115
|
+
export function validateTaskFinalizationReceipt(value, taskId) {
|
|
93
116
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
94
117
|
throw new Error('receipt must be an object');
|
|
95
118
|
const receipt = value;
|
|
@@ -128,18 +151,18 @@ function validateReceipt(value, taskId) {
|
|
|
128
151
|
}
|
|
129
152
|
function readReceipt(repoRoot, taskId) {
|
|
130
153
|
const file = receiptPath(repoRoot, taskId);
|
|
131
|
-
if (!fs.existsSync(file))
|
|
154
|
+
if (!file || !fs.existsSync(file))
|
|
132
155
|
return null;
|
|
133
156
|
const value = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
134
|
-
return
|
|
157
|
+
return validateTaskFinalizationReceipt(value, taskId);
|
|
135
158
|
}
|
|
136
159
|
function readTaskFinalizationReceipt(repoRoot, taskId) {
|
|
137
160
|
return readReceipt(path.resolve(repoRoot), taskId);
|
|
138
161
|
}
|
|
139
162
|
function writeReceipt(repoRoot, receipt) {
|
|
140
|
-
const directory = finalizationRoot(repoRoot);
|
|
141
|
-
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
142
163
|
const target = receiptPath(repoRoot, receipt.taskId);
|
|
164
|
+
if (!target)
|
|
165
|
+
throw new Error(`TASK_FINALIZATION_TASK_NOT_FOUND: ${receipt.taskId}`);
|
|
143
166
|
const temporary = `${target}.${process.pid}.${randomUUID()}.tmp`;
|
|
144
167
|
try {
|
|
145
168
|
fs.writeFileSync(temporary, `${JSON.stringify(receipt)}\n`, { mode: 0o600, flag: 'wx' });
|
|
@@ -670,7 +693,7 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
670
693
|
let receipt;
|
|
671
694
|
try {
|
|
672
695
|
const file = receiptPath(repoRoot, taskId);
|
|
673
|
-
const existed = fs.existsSync(file);
|
|
696
|
+
const existed = file !== null && fs.existsSync(file);
|
|
674
697
|
receipt = readReceipt(repoRoot, taskId) ?? emptyReceipt(taskId, options.controlBinding);
|
|
675
698
|
if (!existed)
|
|
676
699
|
writeReceipt(repoRoot, receipt);
|
|
@@ -969,34 +992,13 @@ async function commitPreparedTaskFinalization(request, options) {
|
|
|
969
992
|
}
|
|
970
993
|
}
|
|
971
994
|
let receipt = readReceipt(repoRoot, resolved.taskId);
|
|
972
|
-
let importedHandoff = false;
|
|
973
|
-
if (!receipt && options.controlBinding && options.handoffDirectory && request.handoffSha256) {
|
|
974
|
-
try {
|
|
975
|
-
const handoff = readTaskFinalizationHandoff(options.handoffDirectory, resolved.taskId, options.controlBinding, request.handoffSha256);
|
|
976
|
-
receipt = validateReceipt(handoff.receipt, resolved.taskId);
|
|
977
|
-
if (receipt.lifecycle !== 'pending')
|
|
978
|
-
throw new Error('handoff lifecycle must be pending');
|
|
979
|
-
if (inspectTaskLifecycleProgress(repoRoot, resolved.taskId, request.agent) === 'unknown') {
|
|
980
|
-
throw new Error('TASK_FINALIZATION_RECOVERY_PROOF_UNAVAILABLE');
|
|
981
|
-
}
|
|
982
|
-
writeReceipt(repoRoot, receipt);
|
|
983
|
-
importedHandoff = true;
|
|
984
|
-
try {
|
|
985
|
-
handoff.cleanup();
|
|
986
|
-
}
|
|
987
|
-
catch { /* keep the persisted canonical receipt usable */ }
|
|
988
|
-
}
|
|
989
|
-
catch (error) {
|
|
990
|
-
return failed(resolved.taskId, {
|
|
991
|
-
code: 'TASK_FINALIZATION_HANDOFF_INVALID',
|
|
992
|
-
message: error instanceof Error ? error.message : String(error), retryable: false
|
|
993
|
-
});
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
995
|
if (!receipt)
|
|
997
996
|
return failed(resolved.taskId, {
|
|
998
997
|
code: 'TASK_FINALIZATION_PREPARATION_REQUIRED', message: 'sandbox preparation receipt is unavailable', retryable: true
|
|
999
998
|
});
|
|
999
|
+
if (options.controlBinding && !receipt.controlBinding) {
|
|
1000
|
+
receipt = updateReceipt(repoRoot, receipt, { controlBinding: options.controlBinding });
|
|
1001
|
+
}
|
|
1000
1002
|
if (receipt.lifecycle === 'done' && receipt.verification !== 'pending' && resolved.state === 'completed') {
|
|
1001
1003
|
return terminalResult(resolved.taskId, receipt, { lifecycle: { status: 'no-op', changed: false, error: null } }, false);
|
|
1002
1004
|
}
|
|
@@ -1008,25 +1010,6 @@ async function commitPreparedTaskFinalization(request, options) {
|
|
|
1008
1010
|
message: 'task lifecycle progress cannot be verified', retryable: false
|
|
1009
1011
|
});
|
|
1010
1012
|
}
|
|
1011
|
-
if (!importedHandoff && options.handoffDirectory && request.handoffSha256) {
|
|
1012
|
-
try {
|
|
1013
|
-
const handoff = readTaskFinalizationHandoff(options.handoffDirectory, resolved.taskId, options.controlBinding, request.handoffSha256);
|
|
1014
|
-
const nextReceipt = validateReceipt(handoff.receipt, resolved.taskId);
|
|
1015
|
-
if (nextReceipt.receiptId !== receipt.receiptId || nextReceipt.intent !== receipt.intent
|
|
1016
|
-
|| nextReceipt.lifecycle !== receipt.lifecycle)
|
|
1017
|
-
throw new Error('handoff is not continuous with canonical receipt');
|
|
1018
|
-
try {
|
|
1019
|
-
handoff.cleanup();
|
|
1020
|
-
}
|
|
1021
|
-
catch { /* keep the persisted canonical receipt usable */ }
|
|
1022
|
-
}
|
|
1023
|
-
catch (error) {
|
|
1024
|
-
return failed(resolved.taskId, {
|
|
1025
|
-
code: 'TASK_FINALIZATION_HANDOFF_INVALID',
|
|
1026
|
-
message: error instanceof Error ? error.message : String(error), retryable: false
|
|
1027
|
-
});
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
1013
|
}
|
|
1031
1014
|
if (receipt.taskComment === 'pending' || receipt.summary === 'pending' || receipt.warningProjection === 'pending') {
|
|
1032
1015
|
return failed(resolved.taskId, {
|
|
@@ -53,6 +53,29 @@ function serializeScalar(value) {
|
|
|
53
53
|
}
|
|
54
54
|
return serialized;
|
|
55
55
|
}
|
|
56
|
+
function stripInlineComment(value) {
|
|
57
|
+
const first = value.search(/\S/u);
|
|
58
|
+
let activeQuote = first >= 0 && (value[first] === '"' || value[first] === "'") ? value[first] : null;
|
|
59
|
+
for (let index = activeQuote ? first + 1 : Math.max(first, 0); index < value.length; index += 1) {
|
|
60
|
+
const character = value[index];
|
|
61
|
+
if (activeQuote === '"' && character === '\\') {
|
|
62
|
+
index += 1;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (activeQuote && character === activeQuote) {
|
|
66
|
+
if (activeQuote === "'" && value[index + 1] === "'") {
|
|
67
|
+
index += 1;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
activeQuote = null;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (!activeQuote && character === '#' && (index === 0 || /\s/u.test(value[index - 1]))) {
|
|
74
|
+
return value.slice(0, index).trimEnd();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
56
79
|
function parseTaskFrontmatter(content) {
|
|
57
80
|
const result = {};
|
|
58
81
|
if (!content.startsWith('---'))
|
|
@@ -69,7 +92,7 @@ function parseTaskFrontmatter(content) {
|
|
|
69
92
|
if (colon === -1)
|
|
70
93
|
continue;
|
|
71
94
|
const key = line.slice(0, colon).trim();
|
|
72
|
-
const value = line.slice(colon + 1).trim();
|
|
95
|
+
const value = stripInlineComment(line.slice(colon + 1).trim());
|
|
73
96
|
if (key)
|
|
74
97
|
result[key] = value;
|
|
75
98
|
}
|
|
@@ -336,7 +336,8 @@ function applyLedgerIntent(intent, options = {}) {
|
|
|
336
336
|
? new Set(['confirmed', 'open', 'needs-human-decision'])
|
|
337
337
|
: before.status === 'cannot-judge' ? new Set(['open', 'needs-human-decision']) : new Set();
|
|
338
338
|
const sameRoundMinorClose = before.status === 'open' && before.severity === 'minor' && intent.status === 'closed';
|
|
339
|
-
|
|
339
|
+
const refreshOpenEvidence = before.status === 'open' && intent.status === 'open';
|
|
340
|
+
if (!sameRoundMinorClose && !refreshOpenEvidence && !allowed.has(intent.status)) {
|
|
340
341
|
return failed(intent, 'LEDGER_TRANSITION_INVALID', `finding '${intent.id}' cannot transition from ${before.status} to ${intent.status}`, resolved.taskId, intent.id);
|
|
341
342
|
}
|
|
342
343
|
after = { ...before, status: intent.status, evidence };
|
|
@@ -69,6 +69,18 @@ function parseJournal(file, taskId) {
|
|
|
69
69
|
? !item.pullRequestSummary || typeof item.pullRequestSummary.body !== 'string'
|
|
70
70
|
|| !item.pullRequestSummary.body.trim() || typeof item.pullRequestSummary.changeReportFile !== 'string'
|
|
71
71
|
|| !item.pullRequestSummary.changeReportFile.trim()
|
|
72
|
+
|| (item.pullRequestSummary.manualValidation !== undefined && (!item.pullRequestSummary.manualValidation
|
|
73
|
+
|| typeof item.pullRequestSummary.manualValidation !== 'object'
|
|
74
|
+
|| Array.isArray(item.pullRequestSummary.manualValidation)
|
|
75
|
+
|| !['pending', 'final'].includes(item.pullRequestSummary.manualValidation.phase)
|
|
76
|
+
|| typeof item.pullRequestSummary.manualValidation.transactionId !== 'string'
|
|
77
|
+
|| !item.pullRequestSummary.manualValidation.transactionId.trim()
|
|
78
|
+
|| (item.pullRequestSummary.manualValidation.receiptDigest !== undefined && !/^[a-f0-9]{64}$/u.test(item.pullRequestSummary.manualValidation.receiptDigest))
|
|
79
|
+
|| (item.pullRequestSummary.manualValidation.prHeadSha !== undefined && !/^[a-f0-9]{40}$/u.test(item.pullRequestSummary.manualValidation.prHeadSha))
|
|
80
|
+
|| (item.pullRequestSummary.manualValidation.authority !== undefined && item.pullRequestSummary.manualValidation.authority !== 'coordinator')
|
|
81
|
+
|| (item.pullRequestSummary.manualValidation.phase === 'final' && (!/^[a-f0-9]{64}$/u.test(item.pullRequestSummary.manualValidation.receiptDigest ?? '')
|
|
82
|
+
|| !/^[a-f0-9]{40}$/u.test(item.pullRequestSummary.manualValidation.prHeadSha ?? '')
|
|
83
|
+
|| item.pullRequestSummary.manualValidation.authority !== 'coordinator'))))
|
|
72
84
|
: item.pullRequestSummary !== undefined)
|
|
73
85
|
|| (item.kind === 'pull-request-review'
|
|
74
86
|
? !item.pullRequestReview || typeof item.pullRequestReview.prNumber !== 'string'
|
|
@@ -193,6 +193,16 @@ async function replayPullRequestSummary(taskId, operation, agent, cwd, client) {
|
|
|
193
193
|
catch {
|
|
194
194
|
return platformResult('failed', { error: { code: 'PLATFORM_OPERATION_PAYLOAD_INVALID', message: 'Queued pull-request summary identity is invalid', retryable: false } });
|
|
195
195
|
}
|
|
196
|
+
const summary = operation.pullRequestSummary;
|
|
197
|
+
if (!summary)
|
|
198
|
+
return platformResult('blocked', { error: {
|
|
199
|
+
code: 'PLATFORM_OPERATION_PAYLOAD_INVALID', message: 'Pull-request summary recovery content is missing', retryable: false
|
|
200
|
+
} });
|
|
201
|
+
const manualValidationHeading = /^###\s+(?:⏳\s+(?:Manual Validation Pending|人工验证待收尾)|✅\s+(?:Manual Validation Passed|人工验证已通过))\s*$/mu.test(summary.body);
|
|
202
|
+
if (manualValidationHeading && !summary.manualValidation)
|
|
203
|
+
return platformResult('blocked', { error: {
|
|
204
|
+
code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'Queued manual-validation summary has no persisted transaction context', retryable: false
|
|
205
|
+
} });
|
|
196
206
|
const resolved = await resolvePlatformProviderContext({ cwd, client });
|
|
197
207
|
if (!resolved.ok)
|
|
198
208
|
return resolved.context;
|
|
@@ -207,13 +217,9 @@ async function replayPullRequestSummary(taskId, operation, agent, cwd, client) {
|
|
|
207
217
|
return platformResult('blocked', { error: {
|
|
208
218
|
code: 'PR_SUMMARY_MARKER_AMBIGUOUS', message: 'Multiple pull-request comments contain the queued summary marker', retryable: false
|
|
209
219
|
} });
|
|
210
|
-
if (summaries.length === 1 && createHash('sha256').update(normalizeCommentContent(summaries[0].body)).digest('hex') === operation.expectedDigest) {
|
|
220
|
+
if (!summary.manualValidation && summaries.length === 1 && createHash('sha256').update(normalizeCommentContent(summaries[0].body)).digest('hex') === operation.expectedDigest) {
|
|
211
221
|
return platformResult('no-op');
|
|
212
222
|
}
|
|
213
|
-
if (!operation.pullRequestSummary)
|
|
214
|
-
return platformResult('blocked', { error: {
|
|
215
|
-
code: 'PLATFORM_OPERATION_PAYLOAD_INVALID', message: 'Pull-request summary recovery content is missing', retryable: false
|
|
216
|
-
} });
|
|
217
223
|
const bound = await inspectPlatformPullRequest(taskId, { cwd, client });
|
|
218
224
|
if (!bound.pullRequest || !resourceIdentityEquals(bound.pullRequest.identity, identity))
|
|
219
225
|
return platformResult('blocked', { error: bound.error ?? {
|
|
@@ -223,8 +229,9 @@ async function replayPullRequestSummary(taskId, operation, agent, cwd, client) {
|
|
|
223
229
|
agent,
|
|
224
230
|
cwd,
|
|
225
231
|
client,
|
|
226
|
-
body:
|
|
227
|
-
changeReportFile:
|
|
232
|
+
body: summary.body,
|
|
233
|
+
changeReportFile: summary.changeReportFile,
|
|
234
|
+
...(summary.manualValidation ? { manualValidation: summary.manualValidation } : {}),
|
|
228
235
|
primaryResult: 'no_op',
|
|
229
236
|
strict: true,
|
|
230
237
|
skipQueue: true,
|
|
@@ -98,16 +98,21 @@ function enumerateTaskDirs(repoRoot) {
|
|
|
98
98
|
*/
|
|
99
99
|
function enumerateAllTaskDirs(repoRoot) {
|
|
100
100
|
const out = [];
|
|
101
|
-
const seen = new
|
|
101
|
+
const seen = new Map();
|
|
102
102
|
const add = (taskId, taskDir, state) => {
|
|
103
103
|
if (!TASK_ID_RE.test(taskId))
|
|
104
104
|
return;
|
|
105
105
|
const stat = fs.lstatSync(taskDir);
|
|
106
106
|
if (!stat.isDirectory() || stat.isSymbolicLink() || !fs.existsSync(path.join(taskDir, 'task.md')))
|
|
107
107
|
return;
|
|
108
|
-
|
|
108
|
+
const canonicalDir = fs.realpathSync.native(taskDir);
|
|
109
|
+
const previousDir = seen.get(taskId);
|
|
110
|
+
if (previousDir !== undefined) {
|
|
111
|
+
if (previousDir === canonicalDir)
|
|
112
|
+
return;
|
|
109
113
|
throw new Error(`duplicate task identity: ${taskId}`);
|
|
110
|
-
|
|
114
|
+
}
|
|
115
|
+
seen.set(taskId, canonicalDir);
|
|
111
116
|
out.push({ taskId, taskDir, state });
|
|
112
117
|
};
|
|
113
118
|
for (const state of FLAT_WORKSPACE_DIRS) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"protocolVersion": 3,
|
|
3
|
-
"packageVersion": "0.11.
|
|
4
|
-
"sourceInputHash": "
|
|
5
|
-
"internalExecutableBuildHash": "
|
|
3
|
+
"packageVersion": "0.11.11",
|
|
4
|
+
"sourceInputHash": "576098e3f88ceca11a0a0f94573a3ceca2b14c97b29a6b934ae07849e214c889",
|
|
5
|
+
"internalExecutableBuildHash": "065375c31d3564a65236fc43bad9947395ea0873ae2531d9b05f42956ef30f1e"
|
|
6
6
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
1
2
|
import {
|
|
2
3
|
AGENT_CLIENT_CAPABILITY_IDS,
|
|
3
4
|
AGENT_CLIENT_SUPPORT_LEVELS,
|
|
@@ -216,6 +217,26 @@ function freezeTool(tool: SandboxTool): SandboxTool {
|
|
|
216
217
|
if (!tool.containerMount.startsWith('/')) {
|
|
217
218
|
throw new Error(`Sandbox tool '${tool.id}' containerMount must be absolute`);
|
|
218
219
|
}
|
|
220
|
+
if (tool.hostStateMounts !== undefined && !Array.isArray(tool.hostStateMounts)) {
|
|
221
|
+
throw new Error(`Agent Client '${tool.id}' hostStateMounts must be an array`);
|
|
222
|
+
}
|
|
223
|
+
const hostStateMounts = tool.hostStateMounts?.map((entry) => {
|
|
224
|
+
if (!entry || typeof entry.hostSubdir !== 'string' || typeof entry.containerSubpath !== 'string') {
|
|
225
|
+
throw new Error(`Agent Client '${tool.id}' hostStateMounts must contain relative path pairs`);
|
|
226
|
+
}
|
|
227
|
+
for (const value of [entry.hostSubdir, entry.containerSubpath]) {
|
|
228
|
+
const normalized = path.posix.normalize(value);
|
|
229
|
+
if (
|
|
230
|
+
value === ''
|
|
231
|
+
|| path.posix.isAbsolute(value)
|
|
232
|
+
|| normalized === '..'
|
|
233
|
+
|| normalized.startsWith('../')
|
|
234
|
+
) {
|
|
235
|
+
throw new Error(`Agent Client '${tool.id}' hostStateMounts must stay within the tool directories`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return Object.freeze({ ...entry });
|
|
239
|
+
});
|
|
219
240
|
if (!tool.versionCmd) {
|
|
220
241
|
throw new Error(`Sandbox tool '${tool.id}' requires versionCmd`);
|
|
221
242
|
}
|
|
@@ -233,6 +254,7 @@ function freezeTool(tool: SandboxTool): SandboxTool {
|
|
|
233
254
|
...(tool.hostLiveMounts ? {
|
|
234
255
|
hostLiveMounts: Object.freeze(tool.hostLiveMounts.map((entry) => Object.freeze({ ...entry })))
|
|
235
256
|
} : {}),
|
|
257
|
+
...(hostStateMounts ? { hostStateMounts: Object.freeze(hostStateMounts) } : {}),
|
|
236
258
|
...(tool.postSetupCmds ? { postSetupCmds: Object.freeze([...tool.postSetupCmds]) } : {}),
|
|
237
259
|
...(tool.tmpfs ? {
|
|
238
260
|
tmpfs: Object.freeze({
|
|
@@ -285,6 +285,19 @@ const codexRecoveryChecks: readonly AgentClientSandboxRecoveryCheck[] = [
|
|
|
285
285
|
path: '/home/devuser/.codex'
|
|
286
286
|
}
|
|
287
287
|
},
|
|
288
|
+
{
|
|
289
|
+
id: 'app-server-package-writable',
|
|
290
|
+
probe: {
|
|
291
|
+
script: 'probe="$1/.agent-infra-app-server-package-$$"; trap \'rm -f -- "$probe"\' EXIT; : > "$probe"',
|
|
292
|
+
args: ['/home/devuser/.codex/packages/app-server-daemon'],
|
|
293
|
+
user: 'devuser'
|
|
294
|
+
},
|
|
295
|
+
finding: {
|
|
296
|
+
repairKind: 'hard-failure',
|
|
297
|
+
message: 'Codex app-server package directory is not writable by devuser.',
|
|
298
|
+
path: '/home/devuser/.codex/packages/app-server-daemon'
|
|
299
|
+
}
|
|
300
|
+
},
|
|
288
301
|
{
|
|
289
302
|
id: 'prompts-link',
|
|
290
303
|
when: {
|
|
@@ -55,6 +55,10 @@ const codexAdapter = defineAgentClientAdapter({
|
|
|
55
55
|
versionCmd: 'codex --version',
|
|
56
56
|
setupHint: 'Run codex once inside the container and choose Device Code login if needed.',
|
|
57
57
|
tmpfs: { size: '512m', seed: ['config.toml', 'model-catalogs'], exec: true },
|
|
58
|
+
hostStateMounts: [{
|
|
59
|
+
hostSubdir: 'packages/app-server-daemon',
|
|
60
|
+
containerSubpath: 'packages/app-server-daemon'
|
|
61
|
+
}],
|
|
58
62
|
hostLiveMounts: [
|
|
59
63
|
{
|
|
60
64
|
hostPath: hostJoin(home, '.codex', 'auth.json'),
|
|
@@ -206,7 +206,7 @@ async function executeManualValidationTransactionLocked(
|
|
|
206
206
|
if (transaction.phase === 'prepared') {
|
|
207
207
|
const staged = await syncPullRequestSummary(taskRef, {
|
|
208
208
|
cwd, client: options.client, agent, body: pendingBody, changeReportFile: path.resolve(cwd, values.changeReportFile!), primaryResult: primaryResult as 'pr_created' | 'pr_reused' | 'no_op', strict: true,
|
|
209
|
-
manualValidation: { phase: 'pending' }, lockAlreadyHeld: true
|
|
209
|
+
manualValidation: { phase: 'pending', transactionId: transaction.transactionId }, lockAlreadyHeld: true
|
|
210
210
|
});
|
|
211
211
|
if (!['applied', 'no-op'].includes(staged.status)) transactionFailure(staged.error ?? { code: 'MANUAL_VALIDATION_TRANSACTION_FAILED', message: 'pending summary staging failed' });
|
|
212
212
|
transitionAndPersist('summary-staged');
|
|
@@ -264,7 +264,7 @@ async function executeManualValidationTransactionLocked(
|
|
|
264
264
|
try {
|
|
265
265
|
await syncPullRequestSummary(taskRef, {
|
|
266
266
|
cwd, client: options.client, agent, body: pendingBody, changeReportFile: path.resolve(cwd, values.changeReportFile!), primaryResult: primaryResult as 'pr_created' | 'pr_reused' | 'no_op', strict: true,
|
|
267
|
-
manualValidation: { phase: 'pending' }, lockAlreadyHeld: true
|
|
267
|
+
manualValidation: { phase: 'pending', transactionId: transaction.transactionId }, lockAlreadyHeld: true
|
|
268
268
|
});
|
|
269
269
|
} catch {
|
|
270
270
|
// Preserve recovery-required when pending compensation cannot be confirmed.
|
|
@@ -22,7 +22,7 @@ const USAGE = `Usage: agent-infra-internal platform-pr inspect <task-ref> [--cwd
|
|
|
22
22
|
agent-infra-internal platform-pr bind <task-ref> --pr <token> --agent <agent> [--dry-run] [--cwd <path>]
|
|
23
23
|
agent-infra-internal platform-pr skip <task-ref> --agent <agent> [--dry-run] [--cwd <path>]
|
|
24
24
|
agent-infra-internal platform-pr sync <task-ref> --agent <agent> [--metadata] [--closing-issue] --result <pr_created|pr_reused|no_op> [--dry-run] [--cwd <path>]
|
|
25
|
-
agent-infra-internal platform-pr sync-in-labels --pr <
|
|
25
|
+
agent-infra-internal platform-pr sync-in-labels --pr <token> [--dry-run] [--cwd <path>]
|
|
26
26
|
agent-infra-internal platform-pr summary-context <task-ref> [--cwd <path>]
|
|
27
27
|
agent-infra-internal platform-pr change-report <task-ref> --agent <agent> --mechanical-file <path> --precheck-file <path> [--dry-run] [--cwd <path>]
|
|
28
28
|
agent-infra-internal platform-pr summary-sync <task-ref> --agent <agent> --body-file <path|-> --change-report-file <path> --result <pr_created|pr_reused|no_op> [--manual-phase pending|final ...] [--strict] [--dry-run] [--cwd <path>]
|
|
@@ -89,10 +89,9 @@ async function platformPr(args: string[] = []): Promise<void> {
|
|
|
89
89
|
const values = parsed.values;
|
|
90
90
|
const unexpected = Object.keys(values).find((name) => !['cwd', 'pr', 'dryRun'].includes(name));
|
|
91
91
|
if (unexpected) { fail(`sync-in-labels does not accept --${unexpected}`); return; }
|
|
92
|
-
|
|
93
|
-
if (!Number.isInteger(pr) || pr <= 0) { fail('sync-in-labels requires a positive --pr'); return; }
|
|
92
|
+
if (typeof values.pr !== 'string' || !values.pr || values.pr.trim() !== values.pr) { fail('sync-in-labels requires a non-empty --pr token'); return; }
|
|
94
93
|
const cwd = path.resolve(typeof values.cwd === 'string' ? values.cwd : process.cwd());
|
|
95
|
-
finish(await syncPlatformPullRequestInLabels(pr, { cwd, dryRun: values.dryRun === true }));
|
|
94
|
+
finish(await syncPlatformPullRequestInLabels(values.pr, { cwd, dryRun: values.dryRun === true }));
|
|
96
95
|
return;
|
|
97
96
|
}
|
|
98
97
|
const taskRef = args[1];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parseArtifactCommand, executeArtifactCommand } from '../task/artifact-command.ts';
|
|
2
2
|
import { ensureInternalHandlerRoute } from './cli-route-inventory.ts';
|
|
3
3
|
|
|
4
|
-
const USAGE = `Usage: agent-infra-internal task-artifact <N | TASK-id> inspect --family <family> [--source-finding <id> --source-artifact <name> --source-sha256 <digest>]\n agent-infra-internal task-artifact <N | TASK-id> init --family <family> --artifact <artifact> [--locale <zh-CN|en>]\n agent-infra-internal task-artifact <N | TASK-id> preflight|finalize-local --family <analysis|plan|code> --artifact <artifact>\n\nInspect, initialize, preflight, or finalize workflow artifact facts.\n`;
|
|
4
|
+
const USAGE = `Usage: agent-infra-internal task-artifact <N | TASK-id> inspect --family <family> [--reason-code <reason>] [--source-finding <id> --source-artifact <name> --source-sha256 <digest>]\n agent-infra-internal task-artifact <N | TASK-id> init --family <family> --artifact <artifact> [--locale <zh-CN|en>]\n agent-infra-internal task-artifact <N | TASK-id> preflight|finalize-local --family <analysis|plan|code> --artifact <artifact>\n\nInspect, initialize, preflight, or finalize workflow artifact facts.\n`;
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
function taskArtifact(args: string[] = []): void {
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
SandboxControlClientError
|
|
8
8
|
} from '../sandbox/control/client.ts';
|
|
9
9
|
import { SANDBOX_CONTROL_MAX_BYTES } from '../sandbox/control/protocol.ts';
|
|
10
|
-
import { validateTaskCreateCandidate } from '../task/create.ts';
|
|
10
|
+
import { validateTaskCreateCandidate, type TaskCreateCandidateV1 } from '../task/create.ts';
|
|
11
11
|
import {
|
|
12
12
|
createTask,
|
|
13
13
|
parseTaskCreateResult,
|
|
@@ -82,7 +82,20 @@ async function taskCreate(args: string[]): Promise<void> {
|
|
|
82
82
|
const stat = fs.lstatSync(inputPath);
|
|
83
83
|
if (!stat.isFile() || stat.isSymbolicLink()) throw new Error('TASK_CREATE_INPUT_INVALID: input must be a regular file');
|
|
84
84
|
if (stat.size > SANDBOX_CONTROL_MAX_BYTES) throw new Error('TASK_CREATE_INPUT_TOO_LARGE: input exceeds the control limit');
|
|
85
|
-
const
|
|
85
|
+
const inputContent = fs.readFileSync(inputPath, 'utf8');
|
|
86
|
+
let candidate: TaskCreateCandidateV1;
|
|
87
|
+
try {
|
|
88
|
+
candidate = validateTaskCreateCandidate(JSON.parse(inputContent));
|
|
89
|
+
} catch (error) {
|
|
90
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
91
|
+
const code = /^([A-Z][A-Z0-9_]+)/.exec(message)?.[1] ?? 'TASK_CREATE_INPUT_INVALID';
|
|
92
|
+
output(failed(code, message, false, {
|
|
93
|
+
requestId: null,
|
|
94
|
+
accepted: false,
|
|
95
|
+
recovery: 'new-request-id'
|
|
96
|
+
}));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
86
99
|
const environment = resolveSandboxControlTransport();
|
|
87
100
|
if (environment.kind === 'fail-closed') {
|
|
88
101
|
output(failed(environment.reasonCode ?? 'TASK_CONTROL_TRANSPORT_INVALID', 'sandbox client control configuration is invalid'));
|
|
@@ -80,9 +80,7 @@ async function taskFinalization(args: string[] = []): Promise<void> {
|
|
|
80
80
|
const transport = resolveSandboxControlTransport(process.env, { localWorkflow: true });
|
|
81
81
|
if (transport.kind === 'sandbox-local') {
|
|
82
82
|
try {
|
|
83
|
-
const response = requestSandboxTaskFinalization({
|
|
84
|
-
agent: boundOperation.request.agent
|
|
85
|
-
});
|
|
83
|
+
const response = requestSandboxTaskFinalization({ agent: boundOperation.request.agent });
|
|
86
84
|
process.stdout.write(response.stdout);
|
|
87
85
|
process.stderr.write(response.stderr);
|
|
88
86
|
process.exitCode = response.exitCode ?? 1;
|