@fitlab-ai/agent-infra 0.11.3 → 0.11.5
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 +17 -8
- package/README.zh-CN.md +17 -8
- package/dist/bin/internal-cli.js +1 -6
- package/dist/lib/decide.js +4 -41
- package/dist/lib/git/branch-worktree.js +45 -0
- package/dist/lib/internal/cli-route-inventory.js +1 -4
- package/dist/lib/internal/task-activity.js +7 -40
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-event.js +4 -37
- package/dist/lib/internal/task-finalization.js +48 -1
- package/dist/lib/internal/task-ledger.js +14 -44
- package/dist/lib/internal/task-lifecycle.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +2 -4
- package/dist/lib/internal/task-review.js +5 -37
- package/dist/lib/internal/task-verify.js +3 -47
- package/dist/lib/internal/task-warning.js +4 -40
- package/dist/lib/platform/completion-backfill.js +63 -17
- package/dist/lib/platform/pr-summary.js +4 -23
- package/dist/lib/platform/verification-sync.js +12 -49
- package/dist/lib/sandbox/control/client.js +4 -1
- package/dist/lib/sandbox/control/executor.js +12 -4
- package/dist/lib/sandbox/control/protocol.js +2 -1
- package/dist/lib/sandbox/control/server.js +26 -143
- package/dist/lib/sandbox/removal.js +15 -8
- package/dist/lib/sandbox/task-cleanup.js +13 -20
- package/dist/lib/task/activity-intent.js +1 -2
- package/dist/lib/task/artifact-command.js +11 -4
- package/dist/lib/task/artifact-lifecycle.js +217 -85
- package/dist/lib/task/artifact-operations.js +37 -28
- package/dist/lib/task/artifact-receipts.js +4 -1
- package/dist/lib/task/artifact-schema.js +8 -7
- package/dist/lib/task/artifact-selection.js +25 -0
- package/dist/lib/task/capabilities.js +158 -39
- package/dist/lib/task/commands/status.js +9 -2
- package/dist/lib/task/control-authority.js +12 -168
- package/dist/lib/task/decision-intents.js +14 -4
- package/dist/lib/task/delivery.js +1 -43
- package/dist/lib/task/events.js +68 -73
- package/dist/lib/task/finalization-handoff.js +92 -0
- package/dist/lib/task/finalization.js +423 -154
- package/dist/lib/task/intermediate-cleanup.js +9 -148
- package/dist/lib/task/ledger-intents.js +159 -20
- package/dist/lib/task/lifecycle-path.js +75 -0
- package/dist/lib/task/lifecycle.js +60 -11
- package/dist/lib/task/local-artifact-finalization.js +13 -47
- package/dist/lib/task/orchestration.js +17 -7
- package/dist/lib/task/review-command.js +1 -2
- package/dist/lib/task/review-finalization.js +4 -18
- package/dist/lib/task/rework-intent.js +70 -4
- package/dist/lib/task/snapshot.js +6 -6
- package/dist/lib/task/verification-engine.js +25 -30
- package/dist/lib/task/verification.js +2 -13
- package/dist/lib/task/workflow-warning-intents.js +23 -10
- package/dist/lib/task/workspace-snapshot.js +7 -2
- package/dist/lib/task/write.js +1 -2
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/decide.ts +5 -35
- package/lib/git/branch-worktree.ts +41 -0
- package/lib/internal/cli-route-inventory.ts +1 -3
- package/lib/internal/task-activity.ts +6 -34
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-event.ts +4 -37
- package/lib/internal/task-finalization.ts +47 -1
- package/lib/internal/task-ledger.ts +15 -41
- package/lib/internal/task-lifecycle.ts +2 -2
- package/lib/internal/task-operation-registry.ts +2 -4
- package/lib/internal/task-review.ts +5 -31
- package/lib/internal/task-verify.ts +3 -39
- package/lib/internal/task-warning.ts +3 -32
- package/lib/platform/completion-backfill.ts +78 -16
- package/lib/platform/pr-summary.ts +4 -25
- package/lib/platform/verification-sync.ts +13 -53
- package/lib/sandbox/control/client.ts +5 -1
- package/lib/sandbox/control/executor.ts +13 -4
- package/lib/sandbox/control/protocol.ts +3 -2
- package/lib/sandbox/control/server.ts +21 -146
- package/lib/sandbox/removal.ts +23 -7
- package/lib/sandbox/task-cleanup.ts +12 -20
- package/lib/task/activity-intent.ts +1 -2
- package/lib/task/artifact-command.ts +14 -4
- package/lib/task/artifact-lifecycle.ts +236 -85
- package/lib/task/artifact-operations.ts +59 -28
- package/lib/task/artifact-receipts.ts +4 -1
- package/lib/task/artifact-schema.ts +10 -7
- package/lib/task/artifact-selection.ts +53 -0
- package/lib/task/capabilities.ts +154 -40
- package/lib/task/commands/status.ts +11 -2
- package/lib/task/control-authority.ts +17 -189
- package/lib/task/decision-intents.ts +11 -4
- package/lib/task/delivery.ts +1 -40
- package/lib/task/events.ts +73 -74
- package/lib/task/finalization-handoff.ts +104 -0
- package/lib/task/finalization.ts +388 -122
- package/lib/task/intermediate-cleanup.ts +9 -125
- package/lib/task/ledger-intents.ts +157 -20
- package/lib/task/lifecycle-path.ts +98 -0
- package/lib/task/lifecycle.ts +58 -13
- package/lib/task/local-artifact-finalization.ts +14 -56
- package/lib/task/orchestration.ts +26 -8
- package/lib/task/review-command.ts +2 -5
- package/lib/task/review-finalization.ts +4 -20
- package/lib/task/rework-intent.ts +75 -5
- package/lib/task/snapshot.ts +6 -6
- package/lib/task/verification-engine.ts +29 -30
- package/lib/task/verification.ts +3 -16
- package/lib/task/workflow-warning-intents.ts +20 -10
- package/lib/task/workspace-snapshot.ts +3 -2
- package/lib/task/write.ts +1 -4
- package/package.json +1 -1
- package/templates/.agents/rules/review-handshake.en.md +4 -4
- package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
- package/templates/.agents/skills/analyze-task/SKILL.en.md +21 -5
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +21 -5
- package/templates/.agents/skills/code-task/SKILL.en.md +14 -8
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +18 -13
- package/templates/.agents/skills/code-task/reference/code-rules.en.md +2 -2
- package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +4 -4
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +4 -4
- package/templates/.agents/skills/code-task/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +7 -7
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +2 -2
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +1 -3
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +1 -3
- 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/plan-task/SKILL.en.md +13 -10
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +13 -10
- package/templates/.agents/skills/review-analysis/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/review-code/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/review-code/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/review-plan/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/review-pr/config/verify.en.json +3 -1
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +3 -1
- package/templates/.agents/skills/review-pr/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +2 -2
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -2
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -2
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +18 -2
- package/templates/.agents/templates/task.en.md +2 -2
- package/templates/.agents/templates/task.zh-CN.md +2 -2
- package/templates/.agents/workflows/bug-fix.en.yaml +11 -6
- package/templates/.agents/workflows/bug-fix.zh-CN.yaml +11 -6
- package/templates/.agents/workflows/feature-development.en.yaml +11 -6
- package/templates/.agents/workflows/feature-development.zh-CN.yaml +11 -6
- package/templates/.agents/workflows/refactoring.en.yaml +11 -6
- package/templates/.agents/workflows/refactoring.zh-CN.yaml +11 -6
- package/dist/lib/internal/task-override.js +0 -138
- package/dist/lib/sandbox/control/finalization-response.js +0 -33
- package/dist/lib/task/artifact-recovery.js +0 -476
- package/dist/lib/task/artifact-repair-intent.js +0 -181
- package/dist/lib/task/guard-override.js +0 -31
- package/dist/lib/task/human-override.js +0 -1259
- package/lib/internal/task-override.ts +0 -144
- package/lib/sandbox/control/finalization-response.ts +0 -35
- package/lib/task/artifact-recovery.ts +0 -572
- package/lib/task/artifact-repair-intent.ts +0 -227
- package/lib/task/guard-override.ts +0 -50
- package/lib/task/human-override.ts +0 -1522
|
@@ -2,7 +2,7 @@ import { createDirectHostExecutionContext, dispatchTaskControlOperation, parseTa
|
|
|
2
2
|
import { lifecycleIntentCatalog } from '../task/lifecycle.js';
|
|
3
3
|
import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.js';
|
|
4
4
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
|
|
5
|
-
const USAGE = `Usage: agent-infra-internal task-lifecycle <N | TASK-id> <intent> --agent <agent> [intent flags] [--dry-run]\n\nIntents: ${lifecycleIntentCatalog.join(', ')}\nrecover-started: --auto\
|
|
5
|
+
const USAGE = `Usage: agent-infra-internal task-lifecycle <N | TASK-id> <intent> --agent <agent> [intent flags] [--dry-run]\n\nIntents: ${lifecycleIntentCatalog.join(', ')}\nrecover-started: --auto\n`;
|
|
6
6
|
function usageFailure(message) {
|
|
7
7
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'LIFECYCLE_PAYLOAD_INVALID', message } })}\n`);
|
|
8
8
|
process.stderr.write(USAGE);
|
|
@@ -85,6 +85,7 @@ function internalTaskRoutes() {
|
|
|
85
85
|
descriptor('internal', 'task-ledger', 'finding-review', 'task-bound', 'progress'),
|
|
86
86
|
descriptor('internal', 'task-ledger', 'decision-upsert', 'task-bound', 'progress'),
|
|
87
87
|
descriptor('internal', 'task-ledger', 'rework-intent-upsert', 'task-bound', 'progress'),
|
|
88
|
+
descriptor('internal', 'task-ledger', 'rework-intent-rebuild', 'task-bound', 'progress'),
|
|
88
89
|
descriptor('internal', 'task-warning', 'list', 'task-bound', 'diagnostic'),
|
|
89
90
|
descriptor('internal', 'task-warning', 'add', 'task-bound', 'progress'),
|
|
90
91
|
descriptor('internal', 'task-warning', 'set-status', 'task-bound', 'progress'),
|
|
@@ -102,9 +103,6 @@ function internalTaskRoutes() {
|
|
|
102
103
|
descriptor('internal', 'task-review', 'finalize-summary', 'task-bound', 'progress'),
|
|
103
104
|
descriptor('internal', 'task-event', 'event', 'task-bound', 'progress'),
|
|
104
105
|
descriptor('internal', 'task-invalidation', 'reconcile', 'task-bound', 'progress'),
|
|
105
|
-
descriptor('internal', 'task-override', 'diagnose', 'task-bound', 'diagnostic'),
|
|
106
|
-
descriptor('internal', 'task-override', 'issue', 'task-bound', 'progress'),
|
|
107
|
-
descriptor('internal', 'task-override', 'consume', 'task-bound', 'progress'),
|
|
108
106
|
descriptor('internal', 'task-lifecycle', 'intent', 'task-bound', 'progress'),
|
|
109
107
|
descriptor('internal', 'task-finalization', 'complete', 'task-bound', 'progress'),
|
|
110
108
|
descriptor('internal', 'task-short-id', 'list', 'conditional', 'diagnostic'),
|
|
@@ -180,7 +178,7 @@ export const INTERNAL_DISPATCHER_ROUTES = Object.freeze([
|
|
|
180
178
|
'platform-comment', 'platform-issue', 'platform-pr', 'platform-pr-review', 'pr-review-grade',
|
|
181
179
|
'platform-checks', 'task-context', 'task-ledger', 'task-warning', 'task-activity', 'task-artifact',
|
|
182
180
|
'task-orchestration', 'task-review', 'task-event', 'task-invalidation', 'task-lifecycle',
|
|
183
|
-
'task-finalization', 'task-
|
|
181
|
+
'task-finalization', 'task-short-id', 'task-snapshot', 'task-verify', 'task-validate', 'manual-validation'
|
|
184
182
|
]);
|
|
185
183
|
export const PUBLIC_DISPATCHER_ROUTES = Object.freeze([
|
|
186
184
|
'agent-client', 'cp', 'data', 'decide', 'help', 'init', 'merge', 'run', 'sandbox', 'server', 'task', 'sync', 'update', 'version'
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { parseReviewCommand } from '../task/review-command.js';
|
|
2
2
|
import { finalizeReviewSummary, preflightReviewSummary } from '../task/review-finalization.js';
|
|
3
|
-
import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.js';
|
|
4
3
|
import { resolveTaskRef } from '../task/resolve-ref.js';
|
|
5
4
|
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
|
|
6
5
|
import { ensureInternalHandlerRoute } from './cli-route-inventory.js';
|
|
7
|
-
const USAGE = 'Usage: agent-infra-internal task-review <task-ref> <preflight|finalize-summary> --stage <analysis|plan|code> --artifact <review-*.md> [--orchestrated] [--dry-run]
|
|
6
|
+
const USAGE = 'Usage: agent-infra-internal task-review <task-ref> <preflight|finalize-summary> --stage <analysis|plan|code> --artifact <review-*.md> [--orchestrated] [--dry-run]\n';
|
|
8
7
|
function failUsage(message) {
|
|
9
8
|
process.stdout.write(`${JSON.stringify({
|
|
10
9
|
status: 'failed',
|
|
@@ -30,48 +29,19 @@ async function taskReview(args = []) {
|
|
|
30
29
|
failUsage(error instanceof Error ? error.message : String(error));
|
|
31
30
|
return;
|
|
32
31
|
}
|
|
33
|
-
const { stage, artifact, dryRun, orchestrated
|
|
32
|
+
const { stage, artifact, dryRun, orchestrated } = request;
|
|
34
33
|
const operation = args[1];
|
|
35
|
-
const dryRunConflict = overrideDryRunConflict({ dryRun, overrideTicket, overrideTarget, overrideScope });
|
|
36
|
-
if (dryRunConflict) {
|
|
37
|
-
failUsage(dryRunConflict.message);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
34
|
const resolved = resolveTaskRef(args[0]);
|
|
41
35
|
if (!resolved.ok) {
|
|
42
36
|
failUsage(resolved.message);
|
|
43
37
|
return;
|
|
44
38
|
}
|
|
45
39
|
let result;
|
|
46
|
-
let humanOverride = null;
|
|
47
40
|
try {
|
|
48
41
|
result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-review.${operation}`, async () => {
|
|
49
|
-
|
|
42
|
+
return operation === 'preflight'
|
|
50
43
|
? preflightReviewSummary({ taskRef: args[0], stage, artifact, orchestrated, dryRun }, { lockAlreadyHeld: true })
|
|
51
44
|
: finalizeReviewSummary({ taskRef: args[0], stage, artifact, orchestrated, dryRun }, { lockAlreadyHeld: true });
|
|
52
|
-
if (operation === 'preflight')
|
|
53
|
-
return current;
|
|
54
|
-
if (current.status !== 'failed' || !overrideTicket)
|
|
55
|
-
return current;
|
|
56
|
-
if (!overrideTarget || !overrideScope) {
|
|
57
|
-
failUsage('override ticket requires target and scope');
|
|
58
|
-
return current;
|
|
59
|
-
}
|
|
60
|
-
const consumed = await consumeHumanOverride({
|
|
61
|
-
taskRef: args[0], ticketId: overrideTicket,
|
|
62
|
-
failureId: failureId('review-finalization', current.error?.code ?? 'TASK_STATE_MISMATCH'),
|
|
63
|
-
target: overrideTarget, scope: overrideScope
|
|
64
|
-
}, {
|
|
65
|
-
effectExecutor: (capability) => {
|
|
66
|
-
const retried = finalizeReviewSummary({ taskRef: args[0], stage, artifact, orchestrated, dryRun }, { lockAlreadyHeld: true, manualOverride: capability });
|
|
67
|
-
current = retried;
|
|
68
|
-
return retried.status === 'failed' || retried.status === 'planned'
|
|
69
|
-
? { code: 'OVERRIDE_EFFECT_FAILED', message: retried.status === 'planned' ? 'producer returned planned; no review effect was committed' : `${retried.error?.code ?? 'REVIEW_FAILED'}: ${retried.error?.message ?? 'manual review effect failed'}` }
|
|
70
|
-
: null;
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
humanOverride = consumed;
|
|
74
|
-
return current;
|
|
75
45
|
});
|
|
76
46
|
}
|
|
77
47
|
catch (error) {
|
|
@@ -81,10 +51,8 @@ async function taskReview(args = []) {
|
|
|
81
51
|
process.exitCode = 1;
|
|
82
52
|
return;
|
|
83
53
|
}
|
|
84
|
-
process.stdout.write(`${JSON.stringify(
|
|
85
|
-
|
|
86
|
-
humanOverride.status === 'failed');
|
|
87
|
-
if (result.status === 'failed' || overrideFailed)
|
|
54
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
55
|
+
if (result.status === 'failed')
|
|
88
56
|
process.exitCode = 1;
|
|
89
57
|
}
|
|
90
58
|
export { taskReview };
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { renderTaskVerification, verifyTaskEvent } from '../task/verification.js';
|
|
2
|
-
import { consumeHumanOverride, failureId } from '../task/human-override.js';
|
|
3
2
|
import { resolveTaskRef } from '../task/resolve-ref.js';
|
|
4
3
|
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
|
|
5
4
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
|
|
6
|
-
const USAGE = 'Usage: agent-infra-internal task-verify <N | TASK-id> <verification-event> [--artifact <canonical.md>] [--format json|text]
|
|
5
|
+
const USAGE = 'Usage: agent-infra-internal task-verify <N | TASK-id> <verification-event> [--artifact <canonical.md>] [--format json|text]\n';
|
|
7
6
|
function fail(message) {
|
|
8
7
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'VERIFY_PAYLOAD_INVALID', message } })}\n`);
|
|
9
8
|
process.stderr.write(USAGE);
|
|
@@ -25,13 +24,10 @@ async function taskVerify(args = []) {
|
|
|
25
24
|
const event = args[1];
|
|
26
25
|
let artifact;
|
|
27
26
|
let format = 'json';
|
|
28
|
-
let overrideTicket;
|
|
29
|
-
let overrideTarget;
|
|
30
|
-
let overrideScope;
|
|
31
27
|
const seen = new Set();
|
|
32
28
|
for (let index = 2; index < args.length; index += 1) {
|
|
33
29
|
const flag = args[index];
|
|
34
|
-
if (!['--artifact', '--format'
|
|
30
|
+
if (!['--artifact', '--format'].includes(flag)) {
|
|
35
31
|
fail(`unknown option '${flag}'`);
|
|
36
32
|
return;
|
|
37
33
|
}
|
|
@@ -47,12 +43,6 @@ async function taskVerify(args = []) {
|
|
|
47
43
|
seen.add(flag);
|
|
48
44
|
if (flag === '--artifact')
|
|
49
45
|
artifact = value;
|
|
50
|
-
else if (flag === '--override-ticket')
|
|
51
|
-
overrideTicket = value;
|
|
52
|
-
else if (flag === '--override-target')
|
|
53
|
-
overrideTarget = value;
|
|
54
|
-
else if (flag === '--override-scope')
|
|
55
|
-
overrideScope = value;
|
|
56
46
|
else if (value === 'json' || value === 'text')
|
|
57
47
|
format = value;
|
|
58
48
|
else {
|
|
@@ -66,40 +56,8 @@ async function taskVerify(args = []) {
|
|
|
66
56
|
return;
|
|
67
57
|
}
|
|
68
58
|
let result;
|
|
69
|
-
let humanOverride = null;
|
|
70
59
|
try {
|
|
71
|
-
result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-verify.${event}`, async () => {
|
|
72
|
-
let current = await verifyTaskEvent({ taskRef, event, ...(artifact ? { artifact } : {}) });
|
|
73
|
-
if (current.status === 'pass' || !overrideTicket)
|
|
74
|
-
return current;
|
|
75
|
-
if (!overrideTarget || !overrideScope) {
|
|
76
|
-
fail('override ticket requires target and scope');
|
|
77
|
-
return current;
|
|
78
|
-
}
|
|
79
|
-
const failureProducer = current.status === 'blocked' || current.status === 'fail'
|
|
80
|
-
? 'verification-engine'
|
|
81
|
-
: 'task-verify';
|
|
82
|
-
const failureCode = current.status === 'blocked'
|
|
83
|
-
? 'CHECK_BLOCKED'
|
|
84
|
-
: current.status === 'fail'
|
|
85
|
-
? 'CHECK_FAILED'
|
|
86
|
-
: current.error?.code ?? 'VERIFY_TASK_STATE_MISMATCH';
|
|
87
|
-
const consumed = await consumeHumanOverride({
|
|
88
|
-
taskRef, ticketId: overrideTicket,
|
|
89
|
-
failureId: failureId(failureProducer, failureCode),
|
|
90
|
-
target: overrideTarget, scope: overrideScope
|
|
91
|
-
}, {
|
|
92
|
-
effectExecutor: async (capability) => {
|
|
93
|
-
const retried = await verifyTaskEvent({ taskRef, event, ...(artifact ? { artifact } : {}) }, { manualOverride: capability });
|
|
94
|
-
current = retried;
|
|
95
|
-
return retried.status !== 'pass'
|
|
96
|
-
? { code: 'OVERRIDE_EFFECT_FAILED', message: `${retried.error?.code ?? 'VERIFY_FAILED'}: ${retried.error?.message ?? 'manual verification effect failed'}` }
|
|
97
|
-
: null;
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
humanOverride = consumed;
|
|
101
|
-
return current;
|
|
102
|
-
});
|
|
60
|
+
result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-verify.${event}`, async () => verifyTaskEvent({ taskRef, event, ...(artifact ? { artifact } : {}) }));
|
|
103
61
|
}
|
|
104
62
|
catch (error) {
|
|
105
63
|
if (!(error instanceof TaskExecutionLockError))
|
|
@@ -107,8 +65,6 @@ async function taskVerify(args = []) {
|
|
|
107
65
|
fail(`${error.code}: ${error.message}`);
|
|
108
66
|
return;
|
|
109
67
|
}
|
|
110
|
-
if (humanOverride)
|
|
111
|
-
result = { ...result, humanOverride };
|
|
112
68
|
process.stdout.write(format === 'text' ? renderTaskVerification(result) : `${JSON.stringify(result)}\n`);
|
|
113
69
|
process.exitCode = result.status === 'pass' ? 0 : result.status === 'blocked' ? 2 : 1;
|
|
114
70
|
}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { applyWorkflowWarningIntent } from '../task/workflow-warning-intents.js';
|
|
2
|
-
import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.js';
|
|
3
2
|
import { resolveTaskRef } from '../task/resolve-ref.js';
|
|
4
3
|
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
|
|
5
4
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
|
|
6
5
|
const USAGE = `Usage: agent-infra-internal task-warning <task-ref> <add|set-status|list> [intent flags] [--dry-run]\n`;
|
|
7
6
|
const FLAGS = {
|
|
8
7
|
'--step': 'step', '--severity': 'severity', '--code': 'code', '--target': 'target',
|
|
9
|
-
'--message': 'message', '--action': 'action', '--id': 'id', '--status': 'status', '--resolution': 'resolution'
|
|
10
|
-
'--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
|
|
8
|
+
'--message': 'message', '--action': 'action', '--id': 'id', '--status': 'status', '--resolution': 'resolution'
|
|
11
9
|
};
|
|
12
10
|
function usageFailure(message) {
|
|
13
11
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'WARNING_PAYLOAD_INVALID', message } })}\n`);
|
|
@@ -66,9 +64,6 @@ async function taskWarning(args = []) {
|
|
|
66
64
|
};
|
|
67
65
|
const optional = kind === 'list' ? ['status'] : [];
|
|
68
66
|
const allowed = new Set(['kind', 'taskRef', 'dryRun', ...required[kind], ...optional]);
|
|
69
|
-
allowed.add('overrideTicket');
|
|
70
|
-
allowed.add('overrideTarget');
|
|
71
|
-
allowed.add('overrideScope');
|
|
72
67
|
const unexpected = Object.keys(values).find((key) => !allowed.has(key));
|
|
73
68
|
const missing = required[kind].find((key) => values[key] === undefined);
|
|
74
69
|
if (unexpected) {
|
|
@@ -79,43 +74,14 @@ async function taskWarning(args = []) {
|
|
|
79
74
|
usageFailure(`${kind} requires '${missing}'`);
|
|
80
75
|
return;
|
|
81
76
|
}
|
|
82
|
-
const dryRunConflict = overrideDryRunConflict(values);
|
|
83
|
-
if (dryRunConflict) {
|
|
84
|
-
usageFailure(dryRunConflict.message);
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
77
|
const resolved = resolveTaskRef(taskRef);
|
|
88
78
|
if (!resolved.ok) {
|
|
89
79
|
usageFailure(resolved.message);
|
|
90
80
|
return;
|
|
91
81
|
}
|
|
92
82
|
let result;
|
|
93
|
-
let humanOverride = null;
|
|
94
83
|
try {
|
|
95
|
-
result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-warning.${kind}`, async () =>
|
|
96
|
-
let current = applyWorkflowWarningIntent(values);
|
|
97
|
-
if (current.status !== 'failed' || !values.overrideTicket)
|
|
98
|
-
return current;
|
|
99
|
-
if (!values.overrideTarget || !values.overrideScope) {
|
|
100
|
-
usageFailure('override ticket requires target and scope');
|
|
101
|
-
return current;
|
|
102
|
-
}
|
|
103
|
-
const consumed = await consumeHumanOverride({
|
|
104
|
-
taskRef, ticketId: String(values.overrideTicket),
|
|
105
|
-
failureId: failureId('workflow-warning', current.error?.code ?? 'TASK_STATE_MISMATCH'),
|
|
106
|
-
target: String(values.overrideTarget), scope: String(values.overrideScope)
|
|
107
|
-
}, {
|
|
108
|
-
effectExecutor: (capability) => {
|
|
109
|
-
const retried = applyWorkflowWarningIntent(values, { manualOverride: capability });
|
|
110
|
-
current = retried;
|
|
111
|
-
return retried.status === 'failed' || retried.status === 'planned'
|
|
112
|
-
? { code: 'OVERRIDE_EFFECT_FAILED', message: retried.status === 'planned' ? 'producer returned planned; no workflow warning effect was committed' : `${retried.error?.code ?? 'WARNING_FAILED'}: ${retried.error?.message ?? 'manual warning effect failed'}` }
|
|
113
|
-
: null;
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
humanOverride = consumed;
|
|
117
|
-
return current;
|
|
118
|
-
});
|
|
84
|
+
result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-warning.${kind}`, async () => applyWorkflowWarningIntent(values));
|
|
119
85
|
}
|
|
120
86
|
catch (error) {
|
|
121
87
|
if (!(error instanceof TaskExecutionLockError))
|
|
@@ -124,10 +90,8 @@ async function taskWarning(args = []) {
|
|
|
124
90
|
process.exitCode = 1;
|
|
125
91
|
return;
|
|
126
92
|
}
|
|
127
|
-
process.stdout.write(`${JSON.stringify(
|
|
128
|
-
|
|
129
|
-
humanOverride.status === 'failed');
|
|
130
|
-
if (result.status === 'failed' || overrideFailed)
|
|
93
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
94
|
+
if (result.status === 'failed')
|
|
131
95
|
process.exitCode = 1;
|
|
132
96
|
}
|
|
133
97
|
export { taskWarning };
|
|
@@ -13,21 +13,70 @@ import { taskIssueIdentity } from './task-identities.js';
|
|
|
13
13
|
function result(base, artifacts, warnings) {
|
|
14
14
|
return { ...base, artifacts, warnings };
|
|
15
15
|
}
|
|
16
|
-
function
|
|
17
|
-
const names = [...message.matchAll(/(?:^|[^A-Za-z0-9_-])(
|
|
16
|
+
function referencedBackfillArtifact(taskDir, message) {
|
|
17
|
+
const names = [...message.matchAll(/(?:^|[^A-Za-z0-9_-])([a-z][a-z-]*(?:-r[1-9]\d*)?\.md)(?=$|[^A-Za-z0-9_.-])/g)]
|
|
18
18
|
.map((match) => match[1]);
|
|
19
19
|
if (names.length !== 1)
|
|
20
|
-
return null;
|
|
20
|
+
return { status: 'ignored', artifact: null, error: null };
|
|
21
21
|
const parsed = parseArtifactName(names[0]);
|
|
22
|
-
if (!parsed || parsed.family !== 'pr-review')
|
|
23
|
-
return null;
|
|
22
|
+
if (!parsed || (parsed.family !== 'pr-review' && !COMPLETION_BACKFILL_FAMILIES.includes(parsed.family))) {
|
|
23
|
+
return { status: 'ignored', artifact: null, error: null };
|
|
24
|
+
}
|
|
24
25
|
const artifactPath = path.join(taskDir, parsed.name);
|
|
25
26
|
try {
|
|
26
27
|
const stat = fs.lstatSync(artifactPath);
|
|
27
|
-
|
|
28
|
+
if (stat.isSymbolicLink())
|
|
29
|
+
return {
|
|
30
|
+
status: 'failed', artifact: null,
|
|
31
|
+
error: { code: 'ARTIFACT_TOPOLOGY_CONFLICT', message: `SYMBOLIC_LINK: symbolic links are not workflow artifacts: ${parsed.name}`, retryable: false }
|
|
32
|
+
};
|
|
33
|
+
if (!stat.isFile())
|
|
34
|
+
return {
|
|
35
|
+
status: 'failed', artifact: null,
|
|
36
|
+
error: { code: 'ARTIFACT_TOPOLOGY_CONFLICT', message: `NON_REGULAR_FILE: artifact is not a regular file: ${parsed.name}`, retryable: false }
|
|
37
|
+
};
|
|
38
|
+
fs.accessSync(artifactPath, fs.constants.R_OK);
|
|
39
|
+
return { status: 'ready', artifact: parsed.name, error: null };
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
return {
|
|
43
|
+
status: 'failed', artifact: null,
|
|
44
|
+
error: { code: 'ARTIFACT_READ_FAILED', message: `Unable to inspect referenced artifact '${parsed.name}': ${error instanceof Error ? error.message : String(error)}`, retryable: false }
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function matchingBackfillWarnings(taskDir, content) {
|
|
49
|
+
const matches = [];
|
|
50
|
+
for (const warning of getOpenWorkflowWarnings(content)) {
|
|
51
|
+
if (warning.step !== 'complete-task' || warning.code !== 'COMMENT_SYNC_FAILED' || warning.target !== 'artifact')
|
|
52
|
+
continue;
|
|
53
|
+
const reference = referencedBackfillArtifact(taskDir, warning.message);
|
|
54
|
+
if (reference.status === 'failed')
|
|
55
|
+
return { status: 'failed', matches: [], error: reference.error };
|
|
56
|
+
if (reference.status === 'ready')
|
|
57
|
+
matches.push({ warning, artifact: reference.artifact });
|
|
58
|
+
}
|
|
59
|
+
return { status: 'ready', matches, error: null };
|
|
60
|
+
}
|
|
61
|
+
function inspectCompletionBackfillEligibility(taskRef, options = {}) {
|
|
62
|
+
const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
|
|
63
|
+
if (!resolved.ok)
|
|
64
|
+
return {
|
|
65
|
+
status: 'failed', eligible: false,
|
|
66
|
+
error: { code: resolved.code, message: resolved.message, retryable: false }
|
|
67
|
+
};
|
|
68
|
+
try {
|
|
69
|
+
const content = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
70
|
+
const matching = matchingBackfillWarnings(resolved.taskDir, content);
|
|
71
|
+
if (matching.status === 'failed')
|
|
72
|
+
return { status: 'failed', eligible: false, error: matching.error };
|
|
73
|
+
return { status: 'ready', eligible: matching.matches.length > 0, error: null };
|
|
28
74
|
}
|
|
29
|
-
catch {
|
|
30
|
-
return
|
|
75
|
+
catch (error) {
|
|
76
|
+
return {
|
|
77
|
+
status: 'failed', eligible: false,
|
|
78
|
+
error: { code: 'TASK_READ_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false }
|
|
79
|
+
};
|
|
31
80
|
}
|
|
32
81
|
}
|
|
33
82
|
async function backfillCompletionComments(taskRef, options) {
|
|
@@ -46,6 +95,9 @@ async function backfillCompletionComments(taskRef, options) {
|
|
|
46
95
|
}
|
|
47
96
|
}), [], []);
|
|
48
97
|
const initialContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
98
|
+
const matching = matchingBackfillWarnings(resolved.taskDir, initialContent);
|
|
99
|
+
if (matching.status === 'failed')
|
|
100
|
+
return result(platformResult('failed', { error: matching.error }), [], []);
|
|
49
101
|
const issueIdentity = taskIssueIdentity(parseTaskFrontmatter(initialContent));
|
|
50
102
|
if (!issueIdentity)
|
|
51
103
|
return result(platformResult('no-op', {
|
|
@@ -79,18 +131,12 @@ async function backfillCompletionComments(taskRef, options) {
|
|
|
79
131
|
error: latest.error
|
|
80
132
|
}), artifacts, []);
|
|
81
133
|
}
|
|
82
|
-
const matching = getOpenWorkflowWarnings(initialContent).flatMap((warning) => {
|
|
83
|
-
if (warning.step !== 'complete-task' || warning.code !== 'COMMENT_SYNC_FAILED' || warning.target !== 'artifact')
|
|
84
|
-
return [];
|
|
85
|
-
const excluded = excludedPrReview(resolved.taskDir, warning.message);
|
|
86
|
-
return excluded ? [{ warning, excluded }] : [];
|
|
87
|
-
});
|
|
88
134
|
const warnings = [];
|
|
89
135
|
let metadata = null;
|
|
90
|
-
for (const { warning,
|
|
136
|
+
for (const { warning, artifact } of matching.matches) {
|
|
91
137
|
metadata ??= captureTaskWriteMetadata();
|
|
92
138
|
const resolution = [
|
|
93
|
-
`
|
|
139
|
+
`artifact=${artifact}`,
|
|
94
140
|
`completionFamilies=${COMPLETION_BACKFILL_FAMILIES.join(',')}`,
|
|
95
141
|
`synchronized=${inventory.artifacts.map((artifact) => artifact.name).join(',') || 'none'}`,
|
|
96
142
|
`completedAt=${metadata.timestamp}`
|
|
@@ -119,5 +165,5 @@ async function backfillCompletionComments(taskRef, options) {
|
|
|
119
165
|
error: null
|
|
120
166
|
}), artifacts, warnings);
|
|
121
167
|
}
|
|
122
|
-
export { backfillCompletionComments };
|
|
168
|
+
export { backfillCompletionComments, inspectCompletionBackfillEligibility };
|
|
123
169
|
//# sourceMappingURL=completion-backfill.js.map
|
|
@@ -2,13 +2,12 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { enumerateArtifacts } from '../task/artifacts.js';
|
|
4
4
|
import { parseTypedTaskFrontmatter } from '../task/frontmatter.js';
|
|
5
|
-
import { renderHumanOverrideAudit } from '../task/human-override.js';
|
|
6
5
|
import { resolveTaskRef } from '../task/resolve-ref.js';
|
|
7
6
|
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
|
|
8
7
|
import { readPrDeliveryFact } from '../task/pr-delivery-fact.js';
|
|
9
8
|
import { resolvePlatformProviderContext } from './context.js';
|
|
10
9
|
import { normalizeCommentContent } from './issue-comments.js';
|
|
11
|
-
import { CONTROL_MARKER_PATTERN,
|
|
10
|
+
import { CONTROL_MARKER_PATTERN, sanitizeMarkdownDocument, splitDocumentPlaceholder } from './comment-safety.js';
|
|
12
11
|
import { platformResult } from './types.js';
|
|
13
12
|
import { providerError, providerOperationContext, providerStatus, providerResourceToken, resourceIdentityNumber, unsupportedProviderOperation } from './provider-bridge.js';
|
|
14
13
|
import { resourceIdentityEquals } from './resource-identity.js';
|
|
@@ -18,23 +17,6 @@ import { readManualValidationCompletion } from '../task/manual-validation-comple
|
|
|
18
17
|
function summaryMarker(taskId) {
|
|
19
18
|
return `<!-- sync-pr:${taskId}:summary -->`;
|
|
20
19
|
}
|
|
21
|
-
function escapeSummaryAuditText(value) {
|
|
22
|
-
return escapeHtmlText(value).replace(/\r\n?/g, '\n');
|
|
23
|
-
}
|
|
24
|
-
function renderSummaryAuditLine(value) {
|
|
25
|
-
const safe = escapeSummaryAuditText(value);
|
|
26
|
-
const longestRun = Math.max(0, ...(safe.match(/`+/g) || []).map((run) => run.length));
|
|
27
|
-
const delimiter = '`'.repeat(longestRun + 1);
|
|
28
|
-
return `${delimiter} ${safe} ${delimiter}`;
|
|
29
|
-
}
|
|
30
|
-
function renderSafeHumanOverrideAudit(value) {
|
|
31
|
-
const normalized = value.trim().replace(/\r\n?/g, '\n');
|
|
32
|
-
if (!normalized)
|
|
33
|
-
return '';
|
|
34
|
-
const [heading, ...rows] = normalized.split('\n');
|
|
35
|
-
const safeHeading = heading === '## Human Override Audit' ? heading : renderSummaryAuditLine(heading || '');
|
|
36
|
-
return [safeHeading, ...rows.map((row) => row ? renderSummaryAuditLine(row) : '')].join('\n');
|
|
37
|
-
}
|
|
38
20
|
function isSafeSummaryEnvelope(value, taskId) {
|
|
39
21
|
return value.includes(summaryMarker(taskId))
|
|
40
22
|
&& (value.match(/<!--\s*sync-pr:/gi) || []).length === 1
|
|
@@ -71,9 +53,8 @@ function warningResultForPrimary(primaryResult) {
|
|
|
71
53
|
function platformError(error) {
|
|
72
54
|
return { code: error.code, message: error.message, retryable: error.retryable ?? false };
|
|
73
55
|
}
|
|
74
|
-
function buildPullRequestSummary(taskId, body, headSha
|
|
75
|
-
|
|
76
|
-
return normalizeCommentContent(`${summaryMarker(taskId)}\n<!-- last-commit: ${headSha} -->\n\n${sections}\n`);
|
|
56
|
+
function buildPullRequestSummary(taskId, body, headSha) {
|
|
57
|
+
return normalizeCommentContent(`${summaryMarker(taskId)}\n<!-- last-commit: ${headSha} -->\n\n${body.replace(/\s+$/, '')}\n`);
|
|
77
58
|
}
|
|
78
59
|
function reconcileSummaryComment(comments, taskId, desired) {
|
|
79
60
|
const marker = summaryMarker(taskId);
|
|
@@ -586,7 +567,7 @@ async function syncPullRequestSummary(taskRef, options) {
|
|
|
586
567
|
const renderedBody = manual?.phase === 'final'
|
|
587
568
|
? replaced.value.replace(MANUAL_VALIDATION_RECEIPT_PLACEHOLDER, `<!-- manual-validation-receipt: transaction=${manual.transactionId}; receipt=${manual.receiptDigest}; evidence=${manualEvidenceDigest}; head=${manual.prHeadSha} -->`)
|
|
588
569
|
: replaced.value;
|
|
589
|
-
const desired = buildPullRequestSummary(resolved.taskId, renderedBody, initial.value.head.sha
|
|
570
|
+
const desired = buildPullRequestSummary(resolved.taskId, renderedBody, initial.value.head.sha);
|
|
590
571
|
if (!isSafeSummaryEnvelope(desired, resolved.taskId))
|
|
591
572
|
return fail('failed', context, {
|
|
592
573
|
code: 'PR_SUMMARY_RENDER_INVALID',
|
|
@@ -3,6 +3,7 @@ import fs from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import process from "node:process";
|
|
5
5
|
import { execFileSync } from "node:child_process";
|
|
6
|
+
import { resolveBranchWorktree } from "../git/branch-worktree.js";
|
|
6
7
|
import { resolvePlatformProviderContext } from "./context.js";
|
|
7
8
|
import { hasCheckedRequirement, resolveRequirementSection } from "./issue-metadata.js";
|
|
8
9
|
import { requirementSectionAnchors } from "./issues.js";
|
|
@@ -807,7 +808,10 @@ function computeExpectedInLabels(taskDir, repositoryLabels, mappingOverride, sha
|
|
|
807
808
|
return { ok: false, type: "check_failed", message: "Task has no delivery_base_ref for in-label evidence" };
|
|
808
809
|
}
|
|
809
810
|
const branch = typeof task.metadata?.branch === "string" ? task.metadata.branch.trim() : "";
|
|
810
|
-
const gitCwd = branch ?
|
|
811
|
+
const gitCwd = branch ? resolveBranchWorktree(shared.repoRoot, branch) : taskDir;
|
|
812
|
+
if (!gitCwd) {
|
|
813
|
+
return { ok: false, type: "check_failed", message: `Task branch '${branch}' is not checked out in a usable worktree` };
|
|
814
|
+
}
|
|
811
815
|
const changedFilesResult = gitText(["diff", `${baseRef}...HEAD`, "--name-only"], gitCwd);
|
|
812
816
|
if (!changedFilesResult.ok) {
|
|
813
817
|
return { ...changedFilesResult, type: "network_error" };
|
|
@@ -831,26 +835,6 @@ function computeExpectedInLabels(taskDir, repositoryLabels, mappingOverride, sha
|
|
|
831
835
|
}
|
|
832
836
|
return { ok: true, labels: planned.target, mode: "mapped" };
|
|
833
837
|
}
|
|
834
|
-
function worktreeForBranch(repositoryRoot, branch) {
|
|
835
|
-
try {
|
|
836
|
-
const records = execFileSync("git", ["worktree", "list", "--porcelain"], {
|
|
837
|
-
cwd: repositoryRoot,
|
|
838
|
-
encoding: "utf8",
|
|
839
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
840
|
-
}).trim().split(/\r?\n\r?\n/);
|
|
841
|
-
const expectedRef = `refs/heads/${branch}`;
|
|
842
|
-
for (const record of records) {
|
|
843
|
-
const worktree = /^worktree (.+)$/m.exec(record)?.[1];
|
|
844
|
-
const branchRef = /^branch (.+)$/m.exec(record)?.[1];
|
|
845
|
-
if (worktree && branchRef === expectedRef)
|
|
846
|
-
return worktree;
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
catch {
|
|
850
|
-
// Fall back to taskDir so ordinary in-repository task workspaces retain their behavior.
|
|
851
|
-
}
|
|
852
|
-
return null;
|
|
853
|
-
}
|
|
854
838
|
function loadInLabelMapping(shared) {
|
|
855
839
|
const configPath = path.join(shared.repoRoot, ".agents", ".airc.json");
|
|
856
840
|
if (!fs.existsSync(configPath)) {
|
|
@@ -888,36 +872,15 @@ function resolvePrHeadSha(context) {
|
|
|
888
872
|
if (!branch) {
|
|
889
873
|
return fallback();
|
|
890
874
|
}
|
|
891
|
-
const
|
|
892
|
-
if (!worktreeList.ok) {
|
|
893
|
-
return fallback();
|
|
894
|
-
}
|
|
895
|
-
const matchedWorktree = findWorktreeForBranch(worktreeList.value, branch);
|
|
875
|
+
const matchedWorktree = resolveBranchWorktree(context.taskDir, branch);
|
|
896
876
|
if (!matchedWorktree) {
|
|
897
|
-
return
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
}
|
|
903
|
-
return headInWorktree;
|
|
904
|
-
}
|
|
905
|
-
function findWorktreeForBranch(porcelainOutput, branch) {
|
|
906
|
-
let currentWorktree = "";
|
|
907
|
-
for (const rawLine of String(porcelainOutput || "").split("\n")) {
|
|
908
|
-
const line = rawLine.trimEnd();
|
|
909
|
-
if (line.startsWith("worktree ")) {
|
|
910
|
-
currentWorktree = line.slice("worktree ".length).trim();
|
|
911
|
-
continue;
|
|
912
|
-
}
|
|
913
|
-
if (line.startsWith("branch refs/heads/")) {
|
|
914
|
-
const usedBranch = line.slice("branch refs/heads/".length).trim();
|
|
915
|
-
if (usedBranch === branch && currentWorktree) {
|
|
916
|
-
return currentWorktree;
|
|
917
|
-
}
|
|
918
|
-
}
|
|
877
|
+
return {
|
|
878
|
+
ok: false,
|
|
879
|
+
type: "check_failed",
|
|
880
|
+
message: `Task branch '${branch}' is not checked out in a usable worktree`
|
|
881
|
+
};
|
|
919
882
|
}
|
|
920
|
-
return
|
|
883
|
+
return gitText(["rev-parse", "HEAD"], matchedWorktree);
|
|
921
884
|
}
|
|
922
885
|
function interpolate(template, taskDir, artifactFile) {
|
|
923
886
|
const artifactStem = artifactFile ? path.basename(artifactFile, path.extname(artifactFile)) : "";
|
|
@@ -340,15 +340,18 @@ export function requestSandboxTaskFinalization(params) {
|
|
|
340
340
|
clientError('SANDBOX_CONTROL_REQUEST_INVALID', 'task-finalization agent is invalid', false);
|
|
341
341
|
const auth = authority(params);
|
|
342
342
|
const issuedAt = Date.now();
|
|
343
|
+
const id = randomUUID();
|
|
344
|
+
const handoffSha256 = (params.prepareHandoff ?? (() => '0'.repeat(64)))({ generation: auth.generation, requestId: id });
|
|
343
345
|
const request = {
|
|
344
346
|
version: 3,
|
|
345
|
-
id
|
|
347
|
+
id,
|
|
346
348
|
...auth,
|
|
347
349
|
issuedAt,
|
|
348
350
|
expiresAt: issuedAt + SANDBOX_CONTROL_ADMISSION_WINDOW_MS,
|
|
349
351
|
family: 'task-finalization',
|
|
350
352
|
operation: 'complete',
|
|
351
353
|
agent,
|
|
354
|
+
handoffSha256,
|
|
352
355
|
args: [],
|
|
353
356
|
controllerProcess: null,
|
|
354
357
|
controllerProof: null
|
|
@@ -2,6 +2,7 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
5
6
|
import { getProcessStartTime } from '../../server/process-state.js';
|
|
6
7
|
import { createTask, projectTaskCreateResult, taskCreateExitCode, taskCreateFailure } from '../../task/create-service.js';
|
|
7
8
|
import { applyTaskFinalization } from '../../task/finalization.js';
|
|
@@ -13,6 +14,10 @@ import { createSandboxExecutorExecutionContext, dispatchTaskControlOperation, pa
|
|
|
13
14
|
import { assertSandboxControlBrokerOwner, readSandboxControlManifest } from './lifecycle.js';
|
|
14
15
|
import { computeLifecycleBuildIdentity } from '../../agent-clients/adapters/codex-lifecycle/build-identity.js';
|
|
15
16
|
import { closeCodexControllerRegistration, CodexControllerRegistrationError, openCodexControllerRegistration, resolveCodexControllerBinding } from './controller-registration.js';
|
|
17
|
+
import { shareBranchDir } from '../constants.js';
|
|
18
|
+
function handoffDirectory(manifest) {
|
|
19
|
+
return shareBranchDir({ shareBase: path.join(homedir(), '.agent-infra', 'share', manifest.project) }, manifest.branch);
|
|
20
|
+
}
|
|
16
21
|
function sameBrokerOwner(left, right) {
|
|
17
22
|
return left.version === right.version
|
|
18
23
|
&& left.pid === right.pid
|
|
@@ -295,9 +300,10 @@ async function executeRequestInner(manifest, manifestPath, request, options = {}
|
|
|
295
300
|
}
|
|
296
301
|
}
|
|
297
302
|
if (request.family === 'task-finalization') {
|
|
298
|
-
const
|
|
299
|
-
if (
|
|
303
|
+
const parsed = parseTaskControlOperation('task-finalization', [manifest.taskId, 'complete', '--agent', request.agent]);
|
|
304
|
+
if (parsed.family !== 'task-finalization')
|
|
300
305
|
throw new Error('SANDBOX_CONTROL_FINALIZATION_OPERATION_INVALID');
|
|
306
|
+
const operation = { ...parsed, request: { ...parsed.request, handoffSha256: request.handoffSha256 } };
|
|
301
307
|
const result = dispatchTaskControlOperation(createSandboxExecutorExecutionContext({
|
|
302
308
|
repoRoot: manifest.repoRoot,
|
|
303
309
|
worktreeRoot: manifest.worktreeRoot,
|
|
@@ -305,7 +311,8 @@ async function executeRequestInner(manifest, manifestPath, request, options = {}
|
|
|
305
311
|
taskId: manifest.taskId,
|
|
306
312
|
generation: manifest.generation,
|
|
307
313
|
manifestPath,
|
|
308
|
-
requestId: request.id
|
|
314
|
+
requestId: request.id,
|
|
315
|
+
handoffDirectory: handoffDirectory(manifest)
|
|
309
316
|
}), operation);
|
|
310
317
|
return finalizationResult(await result);
|
|
311
318
|
}
|
|
@@ -326,7 +333,8 @@ async function executeRequestInner(manifest, manifestPath, request, options = {}
|
|
|
326
333
|
taskId: manifest.taskId,
|
|
327
334
|
generation: manifest.generation,
|
|
328
335
|
manifestPath,
|
|
329
|
-
requestId: request.id
|
|
336
|
+
requestId: request.id,
|
|
337
|
+
handoffDirectory: handoffDirectory(manifest)
|
|
330
338
|
});
|
|
331
339
|
const format = (value) => {
|
|
332
340
|
const result = value;
|
|
@@ -158,11 +158,12 @@ export function validateSandboxControlRequest(value, manifest, options = {}) {
|
|
|
158
158
|
return request;
|
|
159
159
|
}
|
|
160
160
|
if (request.family === 'task-finalization') {
|
|
161
|
-
const expected = ['agent', 'args', 'controllerProcess', 'controllerProof', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'operation', 'token', 'version'];
|
|
161
|
+
const expected = ['agent', 'args', 'controllerProcess', 'controllerProof', 'expiresAt', 'family', 'generation', 'handoffSha256', 'id', 'issuedAt', 'operation', 'token', 'version'];
|
|
162
162
|
if (Object.keys(request).sort().join(',') !== expected.sort().join(',')
|
|
163
163
|
|| request.operation !== 'complete'
|
|
164
164
|
|| !Array.isArray(request.args) || request.args.length !== 0
|
|
165
165
|
|| typeof request.agent !== 'string' || normalizeAgentToken(request.agent) !== request.agent
|
|
166
|
+
|| typeof request.handoffSha256 !== 'string' || !/^[a-f0-9]{64}$/u.test(request.handoffSha256)
|
|
166
167
|
|| request.controllerProcess !== null || request.controllerProof !== null) {
|
|
167
168
|
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'task-finalization request schema or authorization is invalid');
|
|
168
169
|
}
|