@fitlab-ai/agent-infra 0.11.4 → 0.11.6
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/bin/internal-cli.js +0 -5
- package/dist/lib/decide.js +4 -41
- package/dist/lib/git/branch-worktree.js +45 -0
- package/dist/lib/internal/cli-route-inventory.js +0 -3
- package/dist/lib/internal/task-activity.js +7 -40
- package/dist/lib/internal/task-event.js +4 -37
- package/dist/lib/internal/task-finalization.js +9 -2
- package/dist/lib/internal/task-ledger.js +5 -43
- package/dist/lib/internal/task-lifecycle.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +1 -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/merge.js +62 -10
- package/dist/lib/platform/github-provider.js +9 -14
- package/dist/lib/platform/github-release-notes.js +80 -11
- package/dist/lib/platform/none-provider.js +1 -1
- package/dist/lib/platform/pr-summary.js +4 -23
- package/dist/lib/platform/provider-contract.d.ts +39 -16
- package/dist/lib/platform/provider-contract.js +1 -1
- package/dist/lib/platform/provider-validation.js +83 -13
- package/dist/lib/platform/release-notes.js +7 -8
- package/dist/lib/platform/verification-sync.js +12 -49
- package/dist/lib/sandbox/capture.js +1 -1
- package/dist/lib/sandbox/commands/create.js +46 -1
- package/dist/lib/sandbox/commands/enter.js +1 -1
- package/dist/lib/sandbox/commands/start.js +1 -1
- package/dist/lib/sandbox/config.js +10 -0
- package/dist/lib/sandbox/control/client.js +4 -1
- package/dist/lib/sandbox/control/executor.js +12 -4
- package/dist/lib/sandbox/control/lifecycle.js +18 -13
- package/dist/lib/sandbox/control/protocol.js +2 -1
- package/dist/lib/sandbox/removal.js +15 -8
- package/dist/lib/sandbox/task-resolver.js +62 -2
- package/dist/lib/task/activity-intent.js +1 -2
- package/dist/lib/task/artifact-operations.js +36 -2
- package/dist/lib/task/artifact-schema.js +7 -7
- package/dist/lib/task/control-authority.js +9 -166
- package/dist/lib/task/decision-intents.js +2 -4
- package/dist/lib/task/events.js +7 -28
- package/dist/lib/task/finalization-handoff.js +92 -0
- package/dist/lib/task/finalization.js +123 -12
- package/dist/lib/task/intermediate-cleanup.js +9 -148
- package/dist/lib/task/ledger-intents.js +3 -6
- package/dist/lib/task/lifecycle.js +50 -11
- package/dist/lib/task/local-artifact-finalization.js +13 -28
- package/dist/lib/task/resolve-ref.js +3 -3
- package/dist/lib/task/review-command.js +1 -2
- package/dist/lib/task/review-finalization.js +1 -2
- package/dist/lib/task/snapshot.js +6 -6
- package/dist/lib/task/verification-engine.js +16 -11
- package/dist/lib/task/verification.js +1 -13
- package/dist/lib/task/workflow-warning-intents.js +2 -4
- package/dist/lib/task/write.js +122 -3
- 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 +0 -2
- package/lib/internal/task-activity.ts +6 -34
- package/lib/internal/task-event.ts +4 -37
- package/lib/internal/task-finalization.ts +9 -2
- package/lib/internal/task-ledger.ts +5 -38
- package/lib/internal/task-lifecycle.ts +2 -2
- package/lib/internal/task-operation-registry.ts +1 -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/merge.ts +59 -10
- package/lib/platform/github-provider.ts +11 -18
- package/lib/platform/github-release-notes.ts +89 -14
- package/lib/platform/none-provider.ts +1 -1
- package/lib/platform/pr-summary.ts +4 -25
- package/lib/platform/provider-contract.ts +38 -12
- package/lib/platform/provider-validation.ts +83 -12
- package/lib/platform/release-notes.ts +11 -12
- package/lib/platform/verification-sync.ts +13 -53
- package/lib/sandbox/capture.ts +1 -1
- package/lib/sandbox/commands/create.ts +52 -1
- package/lib/sandbox/commands/enter.ts +1 -1
- package/lib/sandbox/commands/start.ts +1 -1
- package/lib/sandbox/config.ts +12 -0
- package/lib/sandbox/control/client.ts +5 -1
- package/lib/sandbox/control/executor.ts +13 -4
- package/lib/sandbox/control/lifecycle.ts +22 -13
- package/lib/sandbox/control/protocol.ts +3 -2
- package/lib/sandbox/removal.ts +23 -7
- package/lib/sandbox/task-resolver.ts +51 -2
- package/lib/task/activity-intent.ts +1 -2
- package/lib/task/artifact-operations.ts +58 -2
- package/lib/task/artifact-schema.ts +9 -7
- package/lib/task/control-authority.ts +14 -188
- package/lib/task/decision-intents.ts +2 -4
- package/lib/task/events.ts +8 -27
- package/lib/task/finalization-handoff.ts +104 -0
- package/lib/task/finalization.ts +120 -12
- package/lib/task/intermediate-cleanup.ts +9 -125
- package/lib/task/ledger-intents.ts +3 -6
- package/lib/task/lifecycle.ts +45 -13
- package/lib/task/local-artifact-finalization.ts +16 -35
- package/lib/task/resolve-ref.ts +3 -3
- package/lib/task/review-command.ts +2 -5
- package/lib/task/review-finalization.ts +1 -4
- package/lib/task/snapshot.ts +6 -6
- package/lib/task/verification-engine.ts +20 -10
- package/lib/task/verification.ts +1 -15
- package/lib/task/workflow-warning-intents.ts +2 -4
- package/lib/task/write.ts +116 -5
- package/package.json +1 -1
- package/templates/.agents/rules/evidence-reporting.en.md +1 -2
- package/templates/.agents/rules/evidence-reporting.zh-CN.md +1 -2
- package/templates/.agents/skills/analyze-task/SKILL.en.md +11 -2
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +11 -2
- package/templates/.agents/skills/archive-tasks/SKILL.en.md +2 -2
- package/templates/.agents/skills/archive-tasks/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/archive-tasks/scripts/archive-tasks.sh +109 -6
- package/templates/.agents/skills/code-task/reference/report-template.en.md +6 -6
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +6 -6
- 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/config/verify.en.json +2 -2
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/create-release-note/SKILL.en.md +17 -11
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +17 -11
- 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/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/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/dist/lib/internal/task-override.js +0 -138
- 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/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
|
@@ -36,7 +36,9 @@ function stripQuotes(value: string): string {
|
|
|
36
36
|
|
|
37
37
|
function readTaskContent(repoRoot: string, taskId: string): { content: string; state: TaskWorkspaceState; taskMd: string } {
|
|
38
38
|
for (const state of TASK_WORKSPACE_STATES) {
|
|
39
|
-
const taskPath =
|
|
39
|
+
const taskPath = state === 'archive'
|
|
40
|
+
? path.join(repoRoot, '.agents', 'workspace', state, ...findArchiveTaskParts(repoRoot, taskId), 'local', 'task.md')
|
|
41
|
+
: path.join(repoRoot, '.agents', 'workspace', state, taskId, 'task.md');
|
|
40
42
|
if (fs.existsSync(taskPath)) {
|
|
41
43
|
return { content: fs.readFileSync(taskPath, 'utf8'), state, taskMd: taskPath };
|
|
42
44
|
}
|
|
@@ -45,7 +47,7 @@ function readTaskContent(repoRoot: string, taskId: string): { content: string; s
|
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
function taskWorkspaceCandidates(repoRoot: string, taskId: string): TaskWorkspace[] {
|
|
48
|
-
|
|
50
|
+
const hot = TASK_WORKSPACE_STATES.filter((state) => state !== 'archive').flatMap((state) => {
|
|
49
51
|
const taskMd = path.join(repoRoot, '.agents', 'workspace', state, taskId, 'task.md');
|
|
50
52
|
if (!fs.existsSync(taskMd)) return [];
|
|
51
53
|
const content = fs.readFileSync(taskMd, 'utf8');
|
|
@@ -56,6 +58,32 @@ function taskWorkspaceCandidates(repoRoot: string, taskId: string): TaskWorkspac
|
|
|
56
58
|
taskMd
|
|
57
59
|
}];
|
|
58
60
|
});
|
|
61
|
+
const parts = findArchiveTaskParts(repoRoot, taskId);
|
|
62
|
+
if (parts.length === 0) return hot;
|
|
63
|
+
const taskMd = path.join(repoRoot, '.agents', 'workspace', 'archive', ...parts, 'local', 'task.md');
|
|
64
|
+
if (!fs.existsSync(taskMd)) return hot;
|
|
65
|
+
const content = fs.readFileSync(taskMd, 'utf8');
|
|
66
|
+
return [...hot, {
|
|
67
|
+
taskId,
|
|
68
|
+
branch: resolveBranchFromTaskContent(content, taskId),
|
|
69
|
+
state: 'archive',
|
|
70
|
+
taskMd
|
|
71
|
+
}];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function findArchiveTaskParts(repoRoot: string, taskId: string): string[] {
|
|
75
|
+
const archiveRoot = path.join(repoRoot, '.agents', 'workspace', 'archive');
|
|
76
|
+
if (!fs.existsSync(archiveRoot)) return [];
|
|
77
|
+
for (const year of fs.readdirSync(archiveRoot).sort().reverse()) {
|
|
78
|
+
if (!/^\d{4}$/.test(year)) continue;
|
|
79
|
+
for (const month of fs.readdirSync(path.join(archiveRoot, year)).sort().reverse()) {
|
|
80
|
+
if (!/^\d{2}$/.test(month)) continue;
|
|
81
|
+
for (const day of fs.readdirSync(path.join(archiveRoot, year, month)).sort().reverse()) {
|
|
82
|
+
if (/^\d{2}$/.test(day) && fs.existsSync(path.join(archiveRoot, year, month, day, taskId, 'local', 'task.md'))) return [year, month, day, taskId];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return [];
|
|
59
87
|
}
|
|
60
88
|
|
|
61
89
|
function resolveBranchFromTaskContent(content: string, taskId: string): string {
|
|
@@ -102,6 +130,27 @@ export function resolveTaskWorkspace(taskId: string, repoRoot: string): TaskWork
|
|
|
102
130
|
|
|
103
131
|
export function listTaskWorkspaces(repoRoot: string, state: TaskWorkspaceState): TaskWorkspace[] {
|
|
104
132
|
const stateRoot = path.join(repoRoot, '.agents', 'workspace', state);
|
|
133
|
+
if (state === 'archive') {
|
|
134
|
+
if (!fs.existsSync(stateRoot)) return [];
|
|
135
|
+
const rows: TaskWorkspace[] = [];
|
|
136
|
+
for (const year of fs.readdirSync(stateRoot).sort()) {
|
|
137
|
+
if (!/^\d{4}$/.test(year)) continue;
|
|
138
|
+
for (const month of fs.readdirSync(path.join(stateRoot, year)).sort()) {
|
|
139
|
+
if (!/^\d{2}$/.test(month)) continue;
|
|
140
|
+
for (const day of fs.readdirSync(path.join(stateRoot, year, month)).sort()) {
|
|
141
|
+
if (!/^\d{2}$/.test(day)) continue;
|
|
142
|
+
for (const taskId of fs.readdirSync(path.join(stateRoot, year, month, day)).sort()) {
|
|
143
|
+
if (!TASK_ID_RE.test(taskId)) continue;
|
|
144
|
+
const taskMd = path.join(stateRoot, year, month, day, taskId, 'local', 'task.md');
|
|
145
|
+
if (!fs.existsSync(taskMd)) continue;
|
|
146
|
+
const content = fs.readFileSync(taskMd, 'utf8');
|
|
147
|
+
rows.push({ taskId, branch: resolveBranchFromTaskContent(content, taskId), state, taskMd });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return rows;
|
|
153
|
+
}
|
|
105
154
|
if (!fs.existsSync(stateRoot)) return [];
|
|
106
155
|
return fs.readdirSync(stateRoot, { withFileTypes: true })
|
|
107
156
|
.filter((entry) => entry.isDirectory() && TASK_ID_RE.test(entry.name))
|
|
@@ -14,7 +14,6 @@ import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-
|
|
|
14
14
|
import type { TaskExecutionLockOptions } from './task-execution-lock.ts';
|
|
15
15
|
import { captureTaskWriteMetadata, writeTask } from './write.ts';
|
|
16
16
|
import type { TaskMutation, TaskOperationSummary, TaskWriteOptions } from './write.ts';
|
|
17
|
-
import { allowsManualOverride } from './guard-override.ts';
|
|
18
17
|
|
|
19
18
|
type PrReviewVerdict = 'approved' | 'changes-requested' | 'commented';
|
|
20
19
|
type PrReviewOutcome = 'aborted' | 'superseded';
|
|
@@ -398,7 +397,7 @@ export function applyPrReviewActivityIntent(intent: PrReviewActivityIntent, opti
|
|
|
398
397
|
if ('code' in validated) return failed(intent, validated.code, validated.message);
|
|
399
398
|
const resolved = resolveTaskRef(intent.taskRef, { repoRoot: options.repoRoot });
|
|
400
399
|
if (!resolved.ok) return failed(intent, resolved.code, resolved.message, resolved.taskId);
|
|
401
|
-
if (resolved.state !== 'active'
|
|
400
|
+
if (resolved.state !== 'active') {
|
|
402
401
|
return failed(intent, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
|
|
403
402
|
}
|
|
404
403
|
if (options.lockAlreadyHeld) return applyLocked(intent, validated.agent, { ...options, repoRoot: resolved.repoRoot });
|
|
@@ -27,6 +27,7 @@ type ArtifactStructuralDiagnosticCode =
|
|
|
27
27
|
| 'ARTIFACT_EMPTY_SECTION'
|
|
28
28
|
| 'ARTIFACT_SECTION_ORDER_INVALID'
|
|
29
29
|
| 'ARTIFACT_HEADING_TRAILING_PUNCTUATION'
|
|
30
|
+
| 'ARTIFACT_STATE_CHECK_PATTERN_MISSING'
|
|
30
31
|
| 'ARTIFACT_REQUIRED_PATTERN_MISSING';
|
|
31
32
|
|
|
32
33
|
type ArtifactStructuralDiagnostic = Readonly<{
|
|
@@ -254,13 +255,68 @@ function inspectArtifactPatterns(
|
|
|
254
255
|
};
|
|
255
256
|
}
|
|
256
257
|
|
|
258
|
+
function inspectArtifactStateCheck(
|
|
259
|
+
content: string,
|
|
260
|
+
schema: ArtifactSchema,
|
|
261
|
+
scopeToStateCheckSection: boolean
|
|
262
|
+
): ArtifactStructureResult {
|
|
263
|
+
const stateCheck = schema.sections.find((section) => section.id === 'state-check');
|
|
264
|
+
if (!stateCheck || schema.stateCheckPatterns.length === 0) {
|
|
265
|
+
return { ok: true, family: schema.family, semanticDigest: canonicalSemanticDigest(content), diagnostics: [] };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (!scopeToStateCheckSection) {
|
|
269
|
+
const diagnostics = schema.stateCheckPatterns
|
|
270
|
+
.filter((pattern) => !new RegExp(pattern, 'm').test(content))
|
|
271
|
+
.map((pattern) => diagnostic(
|
|
272
|
+
'ARTIFACT_STATE_CHECK_PATTERN_MISSING',
|
|
273
|
+
`artifact is missing required state-check command '${pattern}'`,
|
|
274
|
+
stateCheck.id,
|
|
275
|
+
null
|
|
276
|
+
));
|
|
277
|
+
return {
|
|
278
|
+
ok: diagnostics.length === 0,
|
|
279
|
+
family: schema.family,
|
|
280
|
+
semanticDigest: canonicalSemanticDigest(content),
|
|
281
|
+
diagnostics
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const scanned = scanVisibleMarkdown(content);
|
|
286
|
+
const headings = scanned.headings.filter((heading) => (
|
|
287
|
+
heading.level === 2 && sectionHeadings(schema, stateCheck).includes(heading.text)
|
|
288
|
+
));
|
|
289
|
+
if (headings.length !== 1) {
|
|
290
|
+
return { ok: true, family: schema.family, semanticDigest: canonicalSemanticDigest(content), diagnostics: [] };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const bodyBounds = sectionBodyBounds(content, headings[0]!, scanned);
|
|
294
|
+
const body = content.slice(bodyBounds.start, bodyBounds.end);
|
|
295
|
+
const diagnostics = schema.stateCheckPatterns
|
|
296
|
+
.filter((pattern) => !new RegExp(pattern, 'm').test(body))
|
|
297
|
+
.map((pattern) => diagnostic(
|
|
298
|
+
'ARTIFACT_STATE_CHECK_PATTERN_MISSING',
|
|
299
|
+
`state-check section is missing required command '${pattern}'`,
|
|
300
|
+
stateCheck.id,
|
|
301
|
+
lineNumber(content, headings[0]!.start)
|
|
302
|
+
));
|
|
303
|
+
return {
|
|
304
|
+
ok: diagnostics.length === 0,
|
|
305
|
+
family: schema.family,
|
|
306
|
+
semanticDigest: canonicalSemanticDigest(content),
|
|
307
|
+
diagnostics
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
257
311
|
function inspectArtifactContract(
|
|
258
312
|
content: string,
|
|
259
|
-
schema: ArtifactSchema
|
|
313
|
+
schema: ArtifactSchema,
|
|
314
|
+
options: { scopeStateCheckSection?: boolean } = {}
|
|
260
315
|
): ArtifactStructureResult {
|
|
261
316
|
const structure = inspectArtifactStructure(content, schema);
|
|
317
|
+
const stateCheck = inspectArtifactStateCheck(content, schema, options.scopeStateCheckSection === true);
|
|
262
318
|
const patterns = inspectArtifactPatterns(content, schema);
|
|
263
|
-
const diagnostics = [...structure.diagnostics, ...patterns.diagnostics];
|
|
319
|
+
const diagnostics = [...structure.diagnostics, ...stateCheck.diagnostics, ...patterns.diagnostics];
|
|
264
320
|
return {
|
|
265
321
|
ok: diagnostics.length === 0,
|
|
266
322
|
family: schema.family,
|
|
@@ -13,6 +13,7 @@ type ArtifactSchema = Readonly<{
|
|
|
13
13
|
family: ArtifactSchemaFamily;
|
|
14
14
|
title: Readonly<{ zh: string; en: string }>;
|
|
15
15
|
sections: readonly ArtifactSection[];
|
|
16
|
+
stateCheckPatterns: readonly string[];
|
|
16
17
|
requiredPatterns: readonly string[];
|
|
17
18
|
}>;
|
|
18
19
|
type ArtifactSkeletonInput = Readonly<{
|
|
@@ -106,21 +107,22 @@ function buildSections(family: ArtifactSchemaFamily): readonly ArtifactSection[]
|
|
|
106
107
|
}));
|
|
107
108
|
}
|
|
108
109
|
|
|
110
|
+
const TASK_SNAPSHOT_PATTERN = 'agent-infra-internal[ \\t]+task-snapshot\\b';
|
|
111
|
+
|
|
109
112
|
const ARTIFACT_SCHEMAS: readonly ArtifactSchema[] = [
|
|
110
|
-
{ family: 'analysis', title: { zh: '需求分析报告', en: 'Requirements Analysis' }, sections: buildSections('analysis'), requiredPatterns: [
|
|
111
|
-
{ family: 'review-analysis', title: { zh: '需求分析审查报告', en: 'Analysis Review Report' }, sections: buildSections('review-analysis'), requiredPatterns: ['^### (?:审查决定|Approval Decision)$'
|
|
112
|
-
{ family: 'plan', title: { zh: '技术方案', en: 'Technical Plan' }, sections: buildSections('plan'), requiredPatterns: [
|
|
113
|
-
{ family: 'review-plan', title: { zh: '技术方案审查报告', en: 'Plan Review Report' }, sections: buildSections('review-plan'), requiredPatterns: ['^### (?:审查决定|Approval Decision)$'
|
|
114
|
-
{ family: 'code', title: { zh: '实现报告', en: 'Implementation Report' }, sections: buildSections('code'), requiredPatterns: [
|
|
113
|
+
{ family: 'analysis', title: { zh: '需求分析报告', en: 'Requirements Analysis' }, sections: buildSections('analysis'), stateCheckPatterns: [TASK_SNAPSHOT_PATTERN], requiredPatterns: [] },
|
|
114
|
+
{ family: 'review-analysis', title: { zh: '需求分析审查报告', en: 'Analysis Review Report' }, sections: buildSections('review-analysis'), stateCheckPatterns: [TASK_SNAPSHOT_PATTERN], requiredPatterns: ['^### (?:审查决定|Approval Decision)$'] },
|
|
115
|
+
{ family: 'plan', title: { zh: '技术方案', en: 'Technical Plan' }, sections: buildSections('plan'), stateCheckPatterns: [TASK_SNAPSHOT_PATTERN], requiredPatterns: [] },
|
|
116
|
+
{ family: 'review-plan', title: { zh: '技术方案审查报告', en: 'Plan Review Report' }, sections: buildSections('review-plan'), stateCheckPatterns: [TASK_SNAPSHOT_PATTERN], requiredPatterns: ['^### (?:审查决定|Approval Decision)$'] },
|
|
117
|
+
{ family: 'code', title: { zh: '实现报告', en: 'Implementation Report' }, sections: buildSections('code'), stateCheckPatterns: [TASK_SNAPSHOT_PATTERN], requiredPatterns: [] },
|
|
115
118
|
{ family: 'review-code', title: { zh: '代码审查报告', en: 'Code Review Report' }, sections: buildSections('review-code'), requiredPatterns: [
|
|
116
119
|
'^### (?:审查决定|Approval Decision)$',
|
|
117
|
-
'^\\$ ',
|
|
118
120
|
'^- \\*\\*(?:总体结论|Overall Verdict)\\*\\*[::]\\s*(?:通过|需要修改|拒绝|Approved|Changes Requested|Rejected)\\s*$',
|
|
119
121
|
'^- \\*\\*(?:审查基线提交|Review Baseline Commit)\\*\\*[::]\\s*\\S',
|
|
120
122
|
'^- \\*\\*(?:审查差异基线|Reviewed Diff Base)\\*\\*[::]\\s*[0-9a-f]{40,64}\\s*$',
|
|
121
123
|
'^- \\*\\*(?:审查差异指纹|Reviewed Diff Fingerprint)\\*\\*[::]\\s*sha256:[0-9a-f]{64}\\s*$',
|
|
122
124
|
'^- \\*\\*(?:审查快照树|Reviewed Snapshot Tree)\\*\\*[::]\\s*[0-9a-f]{40,64}\\s*$'
|
|
123
|
-
] }
|
|
125
|
+
], stateCheckPatterns: [TASK_SNAPSHOT_PATTERN] }
|
|
124
126
|
];
|
|
125
127
|
|
|
126
128
|
function getArtifactSchema(family: string): ArtifactSchema | null {
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import crypto from 'node:crypto';
|
|
3
|
-
import fs from 'node:fs';
|
|
4
3
|
|
|
5
4
|
import { normalizeAgentToken } from '../agent-clients/tokens.ts';
|
|
6
5
|
import { isAgentClientId } from '../agent-clients/types.ts';
|
|
7
6
|
import type { AgentClientId } from '../agent-clients/types.ts';
|
|
8
7
|
import { getAgentClientAdapter } from '../agent-clients/registry.ts';
|
|
9
|
-
import { consumeHumanOverride, failureId, overrideDryRunConflict } from './human-override.ts';
|
|
10
8
|
import {
|
|
11
9
|
activateMatchingOrchestrationDelegation,
|
|
12
10
|
activateOrchestrationDelegation,
|
|
@@ -40,19 +38,12 @@ import type {
|
|
|
40
38
|
AgentClientLifecycleRecoveryRequest as LifecycleRecoveryRequest,
|
|
41
39
|
AgentClientLifecycleRecoveryResult as LifecycleRecoveryResult
|
|
42
40
|
} from '../agent-clients/adapter.ts';
|
|
43
|
-
import { locateActivityLog } from './activity-log.ts';
|
|
44
41
|
import { resolveTaskRef, TASK_ID_RE } from './resolve-ref.ts';
|
|
45
42
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
46
43
|
import { verifyTaskEvent } from './verification.ts';
|
|
47
44
|
import { createCodexCapabilityStore } from '../agent-clients/adapters/codex-lifecycle/capability-store.ts';
|
|
48
|
-
import {
|
|
49
|
-
findArtifactRecoveryIntentsByOperation,
|
|
50
|
-
writeArtifactRecoveryIntent,
|
|
51
|
-
type ArtifactRecoveryIntent
|
|
52
|
-
} from './artifact-repair-intent.ts';
|
|
53
45
|
import {
|
|
54
46
|
computeLifecycleBuildIdentity,
|
|
55
|
-
verifyLifecycleBuildIdentity,
|
|
56
47
|
type LifecycleBuildIdentity
|
|
57
48
|
} from '../agent-clients/adapters/codex-lifecycle/build-identity.ts';
|
|
58
49
|
|
|
@@ -324,7 +315,6 @@ export function consumeLifecycleRecoveryAttestation(
|
|
|
324
315
|
export function queryLifecycleRecoveryOperation(
|
|
325
316
|
operationId: string,
|
|
326
317
|
options: Readonly<{
|
|
327
|
-
repoRoot?: string;
|
|
328
318
|
capabilityStore?: ReturnType<typeof createCodexCapabilityStore>;
|
|
329
319
|
}> = {}
|
|
330
320
|
): LifecycleRecoveryOperationQueryV1 {
|
|
@@ -334,7 +324,6 @@ export function queryLifecycleRecoveryOperation(
|
|
|
334
324
|
const store = options.capabilityStore ?? createCodexCapabilityStore();
|
|
335
325
|
const registry = authorityOperationMap.get(store);
|
|
336
326
|
const known = [...(registry?.values() ?? [])].filter((value) => value.operationId === operationId);
|
|
337
|
-
const intents = options.repoRoot ? findArtifactRecoveryIntentsByOperation(options.repoRoot, operationId) : [];
|
|
338
327
|
const capabilities = store.findByRecoveryOperation(operationId);
|
|
339
328
|
const phaseMap = new Map<string, LifecycleRecoveryOperationQueryV1['phases'][number]>();
|
|
340
329
|
for (const capability of capabilities) {
|
|
@@ -358,21 +347,10 @@ export function queryLifecycleRecoveryOperation(
|
|
|
358
347
|
? 'consumed' : 'issued'
|
|
359
348
|
});
|
|
360
349
|
}
|
|
361
|
-
for (const intent of intents) {
|
|
362
|
-
if (!intent.phase) continue;
|
|
363
|
-
const key = `${intent.phase}\0${intent.requestId}`;
|
|
364
|
-
if (phaseMap.has(key)) continue;
|
|
365
|
-
phaseMap.set(key, {
|
|
366
|
-
phase: intent.phase,
|
|
367
|
-
requestId: intent.requestId,
|
|
368
|
-
attestationId: null,
|
|
369
|
-
state: intent.state === 'consumed' ? 'consumed' : 'observed'
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
350
|
const capabilityState = capabilities.some((record) => record.recoveryState === 'consumed')
|
|
373
351
|
? 'consumed'
|
|
374
352
|
: capabilities.some((record) => record.recoveryState === 'reserved') ? 'reserved' : 'unknown';
|
|
375
|
-
const committed = capabilityState === 'consumed'
|
|
353
|
+
const committed = capabilityState === 'consumed';
|
|
376
354
|
return {
|
|
377
355
|
version: 1,
|
|
378
356
|
operationId,
|
|
@@ -382,133 +360,6 @@ export function queryLifecycleRecoveryOperation(
|
|
|
382
360
|
};
|
|
383
361
|
}
|
|
384
362
|
|
|
385
|
-
type LifecycleRecoveryCompensationOptions = Readonly<{
|
|
386
|
-
repoRoot: string;
|
|
387
|
-
controllerBinding: TaskControlControllerBinding;
|
|
388
|
-
capabilityStore?: ReturnType<typeof createCodexCapabilityStore>;
|
|
389
|
-
buildIdentity?: LifecycleBuildIdentity;
|
|
390
|
-
now?: () => number;
|
|
391
|
-
}>;
|
|
392
|
-
|
|
393
|
-
function lifecycleRecoveryCompensationError(code: string, message: string): Error {
|
|
394
|
-
const error = new Error(`${code}: ${message}`);
|
|
395
|
-
error.name = code;
|
|
396
|
-
return error;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
function lifecycleRecoveryEventCommitted(
|
|
400
|
-
selector: LifecycleAuthorityRequestV1,
|
|
401
|
-
repoRoot: string
|
|
402
|
-
): boolean {
|
|
403
|
-
const resolved = resolveTaskRef(selector.taskId, { repoRoot });
|
|
404
|
-
if (!resolved.ok || resolved.taskId !== selector.taskId) return false;
|
|
405
|
-
let content: string;
|
|
406
|
-
try { content = fs.readFileSync(resolved.taskMdPath, 'utf8'); }
|
|
407
|
-
catch { return false; }
|
|
408
|
-
const section = locateActivityLog(content);
|
|
409
|
-
if (!section) return false;
|
|
410
|
-
const labels = { analysis: 'Analyze Task', plan: 'Plan Task', code: 'Code Task' } as const;
|
|
411
|
-
const prefix = `${labels[selector.family]} (Round ${selector.round}`;
|
|
412
|
-
return section.entries.some((entry) => {
|
|
413
|
-
if (entry.step.endsWith(' [started]') || !entry.step.startsWith(prefix)) return false;
|
|
414
|
-
const delimiter = entry.step[prefix.length];
|
|
415
|
-
return (delimiter === ',' || delimiter === ')') && entry.note.endsWith(`→ ${selector.artifact}`);
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
function lifecycleRecoveryIntentMatches(
|
|
420
|
-
intent: ArtifactRecoveryIntent,
|
|
421
|
-
selector: LifecycleAuthorityRequestV1
|
|
422
|
-
): boolean {
|
|
423
|
-
return intent.taskId === selector.taskId
|
|
424
|
-
&& intent.family === selector.family
|
|
425
|
-
&& intent.artifact === selector.artifact
|
|
426
|
-
&& (intent.state === 'commit-started' || intent.state === 'consumption-started' || intent.state === 'consumed');
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
/**
|
|
430
|
-
* Completes only the durable tail of a committed task event after a process
|
|
431
|
-
* restart. The task event itself is the commit boundary and is never replayed.
|
|
432
|
-
*/
|
|
433
|
-
export function recoverLifecycleRecoveryOperation(
|
|
434
|
-
input: LifecycleAuthorityRequestV1,
|
|
435
|
-
options: LifecycleRecoveryCompensationOptions
|
|
436
|
-
): LifecycleRecoveryOperationQueryV1 {
|
|
437
|
-
const selector = validateLifecycleAuthorityRequest(input);
|
|
438
|
-
if (selector.phase !== 'task-event.completed') {
|
|
439
|
-
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PHASE_UNSUPPORTED', 'only task-event.completed supports durable compensation');
|
|
440
|
-
}
|
|
441
|
-
const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(options.repoRoot);
|
|
442
|
-
if (selector.expectedBuildIdentityDigest !== lifecycleAuthorityBuildDigest(buildIdentity)) {
|
|
443
|
-
throw lifecycleRecoveryCompensationError('LIFECYCLE_AUTHORITY_BUILD_MISMATCH', 'current lifecycle build does not match the recovery request');
|
|
444
|
-
}
|
|
445
|
-
if (selector.expectedControlGeneration !== options.controllerBinding.controlGeneration
|
|
446
|
-
|| selector.expectedControllerInstanceDigest !== options.controllerBinding.instanceDigest) {
|
|
447
|
-
throw lifecycleRecoveryCompensationError('LIFECYCLE_AUTHORITY_CONTROLLER_MISMATCH', 'current controller binding does not match the recovery request');
|
|
448
|
-
}
|
|
449
|
-
const store = options.capabilityStore ?? createCodexCapabilityStore();
|
|
450
|
-
const expected = {
|
|
451
|
-
taskId: selector.taskId,
|
|
452
|
-
hookDefinitionHash: selector.expectedHookDefinitionHash,
|
|
453
|
-
buildIdentity,
|
|
454
|
-
controller: options.controllerBinding
|
|
455
|
-
};
|
|
456
|
-
const capabilities = store.findByRecoveryOperation(selector.operationId);
|
|
457
|
-
if (capabilities.length !== 1 || capabilities[0]!.capabilityRefDigest !== lifecycleAuthorityDigest(selector.authorityRef)) {
|
|
458
|
-
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PROVENANCE_INVALID', 'durable capability provenance does not identify this operation');
|
|
459
|
-
}
|
|
460
|
-
const capability = capabilities[0]!;
|
|
461
|
-
const build = verifyLifecycleBuildIdentity(capability.buildIdentity, buildIdentity);
|
|
462
|
-
const expiredReserved = capability.status === 'expired' && capability.recoveryState === 'reserved';
|
|
463
|
-
if (!build.ok
|
|
464
|
-
|| capability.taskId !== selector.taskId
|
|
465
|
-
|| capability.hookDefinitionHash !== selector.expectedHookDefinitionHash
|
|
466
|
-
|| capability.controller?.controlGeneration !== options.controllerBinding.controlGeneration
|
|
467
|
-
|| capability.controller?.instanceDigest !== options.controllerBinding.instanceDigest
|
|
468
|
-
|| capability.recoveryState === 'unreserved'
|
|
469
|
-
|| capability.recoveryOperationId !== selector.operationId
|
|
470
|
-
|| (!['attested', 'consumed'].includes(capability.status) && !expiredReserved)) {
|
|
471
|
-
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PROVENANCE_INVALID', 'durable capability provenance does not match the recovery request');
|
|
472
|
-
}
|
|
473
|
-
const phase = capability.recoveryPhases.find((entry) => entry.phase === selector.phase);
|
|
474
|
-
if (!phase) {
|
|
475
|
-
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PHASE_MISSING', 'durable task completion phase is missing');
|
|
476
|
-
}
|
|
477
|
-
const intents = findArtifactRecoveryIntentsByOperation(options.repoRoot, selector.operationId)
|
|
478
|
-
.filter((intent) => intent.taskId === selector.taskId
|
|
479
|
-
&& intent.family === selector.family
|
|
480
|
-
&& intent.artifact === selector.artifact);
|
|
481
|
-
if (intents.length !== 1) {
|
|
482
|
-
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_INTENT_INVALID', 'durable artifact recovery intent does not identify this task event');
|
|
483
|
-
}
|
|
484
|
-
const intent = intents[0]!;
|
|
485
|
-
if (!lifecycleRecoveryIntentMatches(intent, selector)
|
|
486
|
-
|| intent.phase !== selector.phase
|
|
487
|
-
|| intent.requestId !== selector.lifecycleRequestId
|
|
488
|
-
|| intent.authorityDigest === null) {
|
|
489
|
-
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_INTENT_INVALID', 'durable artifact recovery intent does not match the recovery request');
|
|
490
|
-
}
|
|
491
|
-
if (!lifecycleRecoveryEventCommitted(selector, options.repoRoot)) {
|
|
492
|
-
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_COMMIT_UNCONFIRMED', 'task completion is not durably present; compensation is not allowed');
|
|
493
|
-
}
|
|
494
|
-
if (capability.recoveryState === 'reserved') {
|
|
495
|
-
if (phase.state === 'issued') {
|
|
496
|
-
store.consumeRecoveryPhase(selector.authorityRef, selector.operationId, selector.phase, phase.requestId, expected, { allowExpiredReserved: true });
|
|
497
|
-
}
|
|
498
|
-
store.consumeReference(selector.authorityRef, selector.operationId, expected, { allowExpiredReserved: true });
|
|
499
|
-
} else if (phase.state !== 'consumed') {
|
|
500
|
-
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_STATE_INVALID', 'consumed capability has an unconsumed completion phase');
|
|
501
|
-
}
|
|
502
|
-
if (intent.state !== 'consumed') {
|
|
503
|
-
writeArtifactRecoveryIntent(options.repoRoot, {
|
|
504
|
-
...intent,
|
|
505
|
-
state: 'consumed',
|
|
506
|
-
updatedAt: (options.now ?? Date.now)()
|
|
507
|
-
}, { expected: intent });
|
|
508
|
-
}
|
|
509
|
-
return queryLifecycleRecoveryOperation(selector.operationId, { repoRoot: options.repoRoot, capabilityStore: store });
|
|
510
|
-
}
|
|
511
|
-
|
|
512
363
|
function authorityRejected(request: LifecycleAuthorityRequestV1, code: string, message: string): LifecycleAuthorityResponseV1 {
|
|
513
364
|
return { version: 1, requestId: request.requestId, operationId: request.operationId, phase: request.phase, status: 'rejected', attestation: null, error: { code, message } };
|
|
514
365
|
}
|
|
@@ -537,6 +388,7 @@ export type TaskControlExecutionContext =
|
|
|
537
388
|
generation: string;
|
|
538
389
|
manifestPath: string;
|
|
539
390
|
requestId: string;
|
|
391
|
+
handoffDirectory: string;
|
|
540
392
|
controllerBinding?: TaskControlControllerBinding | null;
|
|
541
393
|
lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
|
|
542
394
|
}>;
|
|
@@ -575,11 +427,7 @@ export function taskControlOperationKey(operation: TaskControlOperation): string
|
|
|
575
427
|
: operation.intent;
|
|
576
428
|
}
|
|
577
429
|
|
|
578
|
-
export type TaskLifecycleControlRequest = TaskLifecycleRequest
|
|
579
|
-
overrideTicket?: string;
|
|
580
|
-
overrideTarget?: string;
|
|
581
|
-
overrideScope?: string;
|
|
582
|
-
}>;
|
|
430
|
+
export type TaskLifecycleControlRequest = TaskLifecycleRequest;
|
|
583
431
|
|
|
584
432
|
function invalidContext(message: string): never {
|
|
585
433
|
throw new Error(`TASK_CONTROL_CONTEXT_INVALID: ${message}`);
|
|
@@ -625,6 +473,7 @@ export function createSandboxExecutorExecutionContext(params: Readonly<{
|
|
|
625
473
|
generation: string;
|
|
626
474
|
manifestPath: string;
|
|
627
475
|
requestId: string;
|
|
476
|
+
handoffDirectory?: string;
|
|
628
477
|
controllerBinding?: TaskControlControllerBinding | null;
|
|
629
478
|
lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
|
|
630
479
|
}>): TaskControlExecutionContext {
|
|
@@ -645,6 +494,7 @@ export function createSandboxExecutorExecutionContext(params: Readonly<{
|
|
|
645
494
|
generation: params.generation,
|
|
646
495
|
manifestPath: absolute('manifestPath', params.manifestPath),
|
|
647
496
|
requestId: params.requestId,
|
|
497
|
+
handoffDirectory: absolute('handoffDirectory', params.handoffDirectory ?? path.join(params.worktreeRoot, '.agent-infra-handoff')),
|
|
648
498
|
...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) }),
|
|
649
499
|
...(params.lifecycleRecoveryAttestation === undefined ? {} : {
|
|
650
500
|
lifecycleRecoveryAttestation: params.lifecycleRecoveryAttestation === null
|
|
@@ -670,6 +520,7 @@ export function assertTaskControlExecutionContext(context: TaskControlExecutionC
|
|
|
670
520
|
absolute('worktreeRoot', context.worktreeRoot);
|
|
671
521
|
absolute('runtimeDir', context.runtimeDir);
|
|
672
522
|
absolute('manifestPath', context.manifestPath);
|
|
523
|
+
absolute('handoffDirectory', context.handoffDirectory);
|
|
673
524
|
if (path.resolve(context.runtimeDir) !== path.join(path.dirname(path.resolve(context.manifestPath)), 'runtime')) {
|
|
674
525
|
invalidContext('runtimeDir is not bound to the manifest control root');
|
|
675
526
|
}
|
|
@@ -706,12 +557,8 @@ function lifecycleTaskId(request: TaskLifecycleControlRequest, repoRoot: string)
|
|
|
706
557
|
async function applyLifecycleWithAuthority(
|
|
707
558
|
context: TaskControlExecutionContext,
|
|
708
559
|
request: TaskLifecycleControlRequest
|
|
709
|
-
): Promise<
|
|
710
|
-
const
|
|
711
|
-
if (conflict) {
|
|
712
|
-
return taskLifecycleFailure(request, { code: 'LIFECYCLE_PAYLOAD_INVALID', message: conflict.message });
|
|
713
|
-
}
|
|
714
|
-
const execute = async (): Promise<(TaskLifecycleResult | LifecycleRecoveryResult) & { humanOverride?: unknown }> => {
|
|
560
|
+
): Promise<TaskLifecycleResult | LifecycleRecoveryResult> {
|
|
561
|
+
const execute = async (): Promise<TaskLifecycleResult | LifecycleRecoveryResult> => {
|
|
715
562
|
if (request.intent === 'recover-started') {
|
|
716
563
|
const adapter = isAgentClientId(request.agent)
|
|
717
564
|
? getAgentClientAdapter(request.agent).orchestrationAdapter
|
|
@@ -725,27 +572,7 @@ async function applyLifecycleWithAuthority(
|
|
|
725
572
|
}
|
|
726
573
|
return adapter.recoverStarted(request as LifecycleRecoveryRequest, { repoRoot: context.repoRoot });
|
|
727
574
|
}
|
|
728
|
-
|
|
729
|
-
if (lifecycleResult.status !== 'failed' || !request.overrideTicket) return lifecycleResult;
|
|
730
|
-
if (!request.overrideTarget || !request.overrideScope) {
|
|
731
|
-
return {
|
|
732
|
-
...lifecycleResult,
|
|
733
|
-
error: { code: 'OVERRIDE_PAYLOAD_INVALID', message: 'override ticket requires --override-target and --override-scope' }
|
|
734
|
-
};
|
|
735
|
-
}
|
|
736
|
-
const override = await consumeHumanOverride({
|
|
737
|
-
taskRef: request.taskRef,
|
|
738
|
-
ticketId: request.overrideTicket,
|
|
739
|
-
failureId: failureId('lifecycle.apply', lifecycleResult.error?.code ?? 'LIFECYCLE_FAILED'),
|
|
740
|
-
target: request.overrideTarget,
|
|
741
|
-
scope: request.overrideScope,
|
|
742
|
-
intent: request.intent,
|
|
743
|
-
...('alertNumber' in request && request.alertNumber ? { alertNumber: request.alertNumber } : {}),
|
|
744
|
-
...('issueNumber' in request && request.issueNumber ? { issueNumber: request.issueNumber } : {}),
|
|
745
|
-
...('stagingDir' in request && request.stagingDir ? { stagingDir: request.stagingDir } : {})
|
|
746
|
-
}, { repoRoot: context.repoRoot });
|
|
747
|
-
if (override.status === 'failed') return { ...lifecycleResult, humanOverride: override };
|
|
748
|
-
return { ...lifecycleResult, ...override, humanOverride: override, error: null };
|
|
575
|
+
return applyTaskLifecycle(request, { repoRoot: context.repoRoot });
|
|
749
576
|
};
|
|
750
577
|
const taskId = lifecycleTaskId(request, context.repoRoot);
|
|
751
578
|
if (!taskId) return await execute();
|
|
@@ -840,7 +667,7 @@ function orchestrationFailure(error: unknown): OrchestrationResult {
|
|
|
840
667
|
export function dispatchTaskControlOperation(
|
|
841
668
|
context: TaskControlExecutionContext,
|
|
842
669
|
operation: Extract<TaskControlOperation, { family: 'task-lifecycle' }>
|
|
843
|
-
): Promise<
|
|
670
|
+
): Promise<TaskLifecycleResult | LifecycleRecoveryResult>;
|
|
844
671
|
export function dispatchTaskControlOperation(
|
|
845
672
|
context: TaskControlExecutionContext,
|
|
846
673
|
operation: Extract<TaskControlOperation, { family: 'task-finalization' }>
|
|
@@ -865,7 +692,9 @@ export function dispatchTaskControlOperation(
|
|
|
865
692
|
return finalize(operation.request, {
|
|
866
693
|
repoRoot: context.repoRoot,
|
|
867
694
|
...(context.source === 'sandbox-executor' ? {
|
|
868
|
-
controlBinding: { generation: context.generation, requestId: context.requestId }
|
|
695
|
+
controlBinding: { generation: context.generation, requestId: context.requestId },
|
|
696
|
+
handoffDirectory: context.handoffDirectory,
|
|
697
|
+
manifestPath: context.manifestPath
|
|
869
698
|
} : {}),
|
|
870
699
|
preflight: (request, options) => verifyTaskEvent(
|
|
871
700
|
{ ...request, event: 'complete-task.hard-preflight' }, options
|
|
@@ -904,7 +733,7 @@ function operationInvalid(message: string): never {
|
|
|
904
733
|
|
|
905
734
|
const LIFECYCLE_FLAGS = new Set([
|
|
906
735
|
'--agent', '--reason', '--unblock-condition', '--note', '--alert-number', '--staging-dir', '--issue-number',
|
|
907
|
-
'--auto', '--
|
|
736
|
+
'--auto', '--dry-run'
|
|
908
737
|
]);
|
|
909
738
|
|
|
910
739
|
const FINALIZATION_FLAGS = new Set(['--agent']);
|
|
@@ -979,9 +808,6 @@ export function parseTaskControlOperation(
|
|
|
979
808
|
...(value(values, '--unblock-condition') ? { unblockCondition: value(values, '--unblock-condition') } : {}),
|
|
980
809
|
...(value(values, '--note') ? { note: value(values, '--note') } : {}),
|
|
981
810
|
...(value(values, '--staging-dir') ? { stagingDir: value(values, '--staging-dir') } : {}),
|
|
982
|
-
...(value(values, '--override-ticket') ? { overrideTicket: value(values, '--override-ticket') } : {}),
|
|
983
|
-
...(value(values, '--override-target') ? { overrideTarget: value(values, '--override-target') } : {}),
|
|
984
|
-
...(value(values, '--override-scope') ? { overrideScope: value(values, '--override-scope') } : {}),
|
|
985
811
|
...(value(values, '--alert-number') ? { alertNumber: Number(value(values, '--alert-number')) } : {}),
|
|
986
812
|
...(value(values, '--issue-number') ? { issueNumber: Number(value(values, '--issue-number')) } : {}),
|
|
987
813
|
...(values['--auto'] === true ? { auto: true } : {}),
|
|
@@ -16,7 +16,6 @@ import { resolveTaskContext } from './resolve-ref.ts';
|
|
|
16
16
|
import { extractSection, extractSubSection, findSectionHeading } from './sections.ts';
|
|
17
17
|
import { captureTaskWriteMetadata, writeTask } from './write.ts';
|
|
18
18
|
import type { TaskMutation, TaskOperationSummary, TaskWriteOptions } from './write.ts';
|
|
19
|
-
import { allowsManualOverride } from './guard-override.ts';
|
|
20
19
|
import { consumeHumanDecisionReworkIntents, parseReworkIntentDocument, reworkIntentMutation } from './rework-intent.ts';
|
|
21
20
|
|
|
22
21
|
type HumanDecisionRequest = {
|
|
@@ -87,8 +86,7 @@ function applyHumanDecision(request: HumanDecisionRequest, options: TaskWriteOpt
|
|
|
87
86
|
if (!request.selector || !request.decision.trim()) return failed('DECISION_PAYLOAD_INVALID', 'selector and decision are required');
|
|
88
87
|
const resolved = resolveTaskContext(request.taskRef, { repoRoot: options.repoRoot });
|
|
89
88
|
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
90
|
-
|
|
91
|
-
if (resolved.state !== 'active' && !stateOverride) return failed('TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
|
|
89
|
+
if (resolved.state !== 'active') return failed('TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
|
|
92
90
|
let content: string;
|
|
93
91
|
let rows: LedgerRow[];
|
|
94
92
|
try {
|
|
@@ -186,7 +184,7 @@ function applyHumanDecision(request: HumanDecisionRequest, options: TaskWriteOpt
|
|
|
186
184
|
} catch (error) {
|
|
187
185
|
return failed('DECISION_DOCUMENT_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId, row.id);
|
|
188
186
|
}
|
|
189
|
-
const writeResult = writeTask({ taskRef: resolved.taskId, expectedState:
|
|
187
|
+
const writeResult = writeTask({ taskRef: resolved.taskId, expectedState: 'active', mutations, dryRun: request.dryRun }, {
|
|
190
188
|
...options,
|
|
191
189
|
taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state },
|
|
192
190
|
metadataProvider: () => metadata
|