@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
|
@@ -17,6 +17,7 @@ import { acquireSandboxResourceLock } from './control/native-file-lock.js';
|
|
|
17
17
|
import { toolConfigDirCandidates, toolProjectDirCandidates } from './tools.js';
|
|
18
18
|
import { getProcessStartTime } from '../server/process-state.js';
|
|
19
19
|
import { releaseStaleShortIdRegistry } from '../task/short-id.js';
|
|
20
|
+
import { withRepositoryMutationLock } from '../task/task-execution-lock.js';
|
|
20
21
|
import { fetchSandboxRows } from './commands/list-running.js';
|
|
21
22
|
import { formatIntermediateCleanupReport, cleanupIntermediateUnderRemovalCoordinator, protectIntermediateCleanupReport, mergeIntermediateCleanupReports, scanIntermediateCleanup } from '../task/intermediate-cleanup.js';
|
|
22
23
|
import { createSandboxControlBindingEvidence } from './task-cleanup.js';
|
|
@@ -670,7 +671,7 @@ async function runRmOneUnderRepositoryLock(config, tools, branch, options = {})
|
|
|
670
671
|
return rmOneCore(config, tools, branch, { ...options, target });
|
|
671
672
|
}
|
|
672
673
|
async function rmOne(config, tools, branch, options = {}) {
|
|
673
|
-
await runRmOneUnderRepositoryLock(config, tools, branch, options);
|
|
674
|
+
await withRepositoryMutationLock(config.repoRoot, () => runRmOneUnderRepositoryLock(config, tools, branch, options));
|
|
674
675
|
}
|
|
675
676
|
async function removeUncheckedSandbox(config, target, options) {
|
|
676
677
|
const confirm = options.prompt?.confirm ?? p.confirm;
|
|
@@ -703,8 +704,12 @@ async function removeUncheckedSandbox(config, target, options) {
|
|
|
703
704
|
p.outro('Cancelled');
|
|
704
705
|
return null;
|
|
705
706
|
}
|
|
707
|
+
const worktreePermits = options.permits ?? await authorizeWorktrees(existingWorktrees, { allowDirtyDiscard: true, assumeYes: Boolean(options.assumeYes) }, { ...options.prompt, interactive: options.interactive });
|
|
706
708
|
for (const container of matchedContainers)
|
|
707
709
|
runSafeEngine(engine, 'docker', ['rm', '-f', container]);
|
|
710
|
+
if (!sandboxContainersRemoved(engine, matchedContainers)) {
|
|
711
|
+
throw new Error(`SANDBOX_REMOVAL_CONTAINER_STILL_PRESENT: ${matchedContainers.join(', ')}`);
|
|
712
|
+
}
|
|
708
713
|
for (const root of controlRoots) {
|
|
709
714
|
fs.rmSync(root, { recursive: true, force: true });
|
|
710
715
|
removeEmptyManagedParent(path.join(config.controlBase, config.project), root);
|
|
@@ -720,14 +725,16 @@ async function removeUncheckedSandbox(config, target, options) {
|
|
|
720
725
|
if (shouldRemoveShare)
|
|
721
726
|
fs.rmSync(sharePath, { recursive: true, force: true });
|
|
722
727
|
if (shouldRemoveWorktree) {
|
|
723
|
-
for (const worktree of existingWorktrees)
|
|
724
|
-
|
|
725
|
-
|
|
728
|
+
for (const worktree of existingWorktrees) {
|
|
729
|
+
const permit = worktreePermits.get(path.resolve(worktree));
|
|
730
|
+
if (!permit)
|
|
731
|
+
throw new Error(`SANDBOX_WORKTREE_REMOVAL_PERMIT_MISSING: ${worktree}`);
|
|
732
|
+
removeWorktreeDir(config.repoRoot, config.worktreeBase, worktree, permit);
|
|
733
|
+
}
|
|
726
734
|
}
|
|
727
|
-
if (shouldDeleteBranch)
|
|
735
|
+
if (shouldDeleteBranch) {
|
|
736
|
+
assertBranchRemovalIdentity(config, effectiveBranch, worktreePermits);
|
|
728
737
|
runSafe('git', ['-C', config.repoRoot, 'branch', '-D', effectiveBranch]);
|
|
729
|
-
if (!sandboxContainersRemoved(engine, matchedContainers)) {
|
|
730
|
-
throw new Error(`SANDBOX_REMOVAL_CONTAINER_STILL_PRESENT: ${matchedContainers.join(', ')}`);
|
|
731
738
|
}
|
|
732
739
|
if (target.workspace.mode === 'task-bound') {
|
|
733
740
|
releaseStaleShortIdRegistry(config.repoRoot, target.workspace.taskId);
|
|
@@ -741,7 +748,7 @@ async function rmOneCore(config, tools, branch, options = {}) {
|
|
|
741
748
|
return removeUncheckedSandbox(config, target, options);
|
|
742
749
|
}
|
|
743
750
|
async function rmPurge(config, tools, prompt = {}) {
|
|
744
|
-
return rmPurgeCore(config, tools, prompt);
|
|
751
|
+
return withRepositoryMutationLock(config.repoRoot, () => rmPurgeCore(config, tools, prompt));
|
|
745
752
|
}
|
|
746
753
|
async function rmPurgeCore(config, tools, prompt = {}) {
|
|
747
754
|
const engine = detectEngine(config);
|
|
@@ -21,7 +21,9 @@ function stripQuotes(value) {
|
|
|
21
21
|
}
|
|
22
22
|
function readTaskContent(repoRoot, taskId) {
|
|
23
23
|
for (const state of TASK_WORKSPACE_STATES) {
|
|
24
|
-
const taskPath =
|
|
24
|
+
const taskPath = state === 'archive'
|
|
25
|
+
? path.join(repoRoot, '.agents', 'workspace', state, ...findArchiveTaskParts(repoRoot, taskId), 'local', 'task.md')
|
|
26
|
+
: path.join(repoRoot, '.agents', 'workspace', state, taskId, 'task.md');
|
|
25
27
|
if (fs.existsSync(taskPath)) {
|
|
26
28
|
return { content: fs.readFileSync(taskPath, 'utf8'), state, taskMd: taskPath };
|
|
27
29
|
}
|
|
@@ -29,7 +31,7 @@ function readTaskContent(repoRoot, taskId) {
|
|
|
29
31
|
throw new Error(`Task not found: ${taskId}`);
|
|
30
32
|
}
|
|
31
33
|
function taskWorkspaceCandidates(repoRoot, taskId) {
|
|
32
|
-
|
|
34
|
+
const hot = TASK_WORKSPACE_STATES.filter((state) => state !== 'archive').flatMap((state) => {
|
|
33
35
|
const taskMd = path.join(repoRoot, '.agents', 'workspace', state, taskId, 'task.md');
|
|
34
36
|
if (!fs.existsSync(taskMd))
|
|
35
37
|
return [];
|
|
@@ -41,6 +43,37 @@ function taskWorkspaceCandidates(repoRoot, taskId) {
|
|
|
41
43
|
taskMd
|
|
42
44
|
}];
|
|
43
45
|
});
|
|
46
|
+
const parts = findArchiveTaskParts(repoRoot, taskId);
|
|
47
|
+
if (parts.length === 0)
|
|
48
|
+
return hot;
|
|
49
|
+
const taskMd = path.join(repoRoot, '.agents', 'workspace', 'archive', ...parts, 'local', 'task.md');
|
|
50
|
+
if (!fs.existsSync(taskMd))
|
|
51
|
+
return hot;
|
|
52
|
+
const content = fs.readFileSync(taskMd, 'utf8');
|
|
53
|
+
return [...hot, {
|
|
54
|
+
taskId,
|
|
55
|
+
branch: resolveBranchFromTaskContent(content, taskId),
|
|
56
|
+
state: 'archive',
|
|
57
|
+
taskMd
|
|
58
|
+
}];
|
|
59
|
+
}
|
|
60
|
+
function findArchiveTaskParts(repoRoot, taskId) {
|
|
61
|
+
const archiveRoot = path.join(repoRoot, '.agents', 'workspace', 'archive');
|
|
62
|
+
if (!fs.existsSync(archiveRoot))
|
|
63
|
+
return [];
|
|
64
|
+
for (const year of fs.readdirSync(archiveRoot).sort().reverse()) {
|
|
65
|
+
if (!/^\d{4}$/.test(year))
|
|
66
|
+
continue;
|
|
67
|
+
for (const month of fs.readdirSync(path.join(archiveRoot, year)).sort().reverse()) {
|
|
68
|
+
if (!/^\d{2}$/.test(month))
|
|
69
|
+
continue;
|
|
70
|
+
for (const day of fs.readdirSync(path.join(archiveRoot, year, month)).sort().reverse()) {
|
|
71
|
+
if (/^\d{2}$/.test(day) && fs.existsSync(path.join(archiveRoot, year, month, day, taskId, 'local', 'task.md')))
|
|
72
|
+
return [year, month, day, taskId];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return [];
|
|
44
77
|
}
|
|
45
78
|
function resolveBranchFromTaskContent(content, taskId) {
|
|
46
79
|
const frontmatterBranch = content.match(/^branch:\s*(.+)$/m);
|
|
@@ -81,6 +114,33 @@ export function resolveTaskWorkspace(taskId, repoRoot) {
|
|
|
81
114
|
}
|
|
82
115
|
export function listTaskWorkspaces(repoRoot, state) {
|
|
83
116
|
const stateRoot = path.join(repoRoot, '.agents', 'workspace', state);
|
|
117
|
+
if (state === 'archive') {
|
|
118
|
+
if (!fs.existsSync(stateRoot))
|
|
119
|
+
return [];
|
|
120
|
+
const rows = [];
|
|
121
|
+
for (const year of fs.readdirSync(stateRoot).sort()) {
|
|
122
|
+
if (!/^\d{4}$/.test(year))
|
|
123
|
+
continue;
|
|
124
|
+
for (const month of fs.readdirSync(path.join(stateRoot, year)).sort()) {
|
|
125
|
+
if (!/^\d{2}$/.test(month))
|
|
126
|
+
continue;
|
|
127
|
+
for (const day of fs.readdirSync(path.join(stateRoot, year, month)).sort()) {
|
|
128
|
+
if (!/^\d{2}$/.test(day))
|
|
129
|
+
continue;
|
|
130
|
+
for (const taskId of fs.readdirSync(path.join(stateRoot, year, month, day)).sort()) {
|
|
131
|
+
if (!TASK_ID_RE.test(taskId))
|
|
132
|
+
continue;
|
|
133
|
+
const taskMd = path.join(stateRoot, year, month, day, taskId, 'local', 'task.md');
|
|
134
|
+
if (!fs.existsSync(taskMd))
|
|
135
|
+
continue;
|
|
136
|
+
const content = fs.readFileSync(taskMd, 'utf8');
|
|
137
|
+
rows.push({ taskId, branch: resolveBranchFromTaskContent(content, taskId), state, taskMd });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return rows;
|
|
143
|
+
}
|
|
84
144
|
if (!fs.existsSync(stateRoot))
|
|
85
145
|
return [];
|
|
86
146
|
return fs.readdirSync(stateRoot, { withFileTypes: true })
|
|
@@ -6,7 +6,6 @@ import { assertWritableInventory, buildArtifactLinkSection, inspectTaskArtifacts
|
|
|
6
6
|
import { parseArtifactName } from './artifact-name.js';
|
|
7
7
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
8
8
|
import { captureTaskWriteMetadata, writeTask } from './write.js';
|
|
9
|
-
import { allowsManualOverride } from './guard-override.js';
|
|
10
9
|
const HEAD_RE = /^[0-9a-f]{40}$/;
|
|
11
10
|
const REVIEW_ACTION_RE = /^Review PR \(Round ([1-9]\d*)\)( \[started\])?$/;
|
|
12
11
|
const VERDICTS = new Set(['approved', 'changes-requested', 'commented']);
|
|
@@ -294,7 +293,7 @@ export function applyPrReviewActivityIntent(intent, options = {}) {
|
|
|
294
293
|
const resolved = resolveTaskRef(intent.taskRef, { repoRoot: options.repoRoot });
|
|
295
294
|
if (!resolved.ok)
|
|
296
295
|
return failed(intent, resolved.code, resolved.message, resolved.taskId);
|
|
297
|
-
if (resolved.state !== 'active'
|
|
296
|
+
if (resolved.state !== 'active') {
|
|
298
297
|
return failed(intent, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
|
|
299
298
|
}
|
|
300
299
|
if (options.lockAlreadyHeld)
|
|
@@ -166,10 +166,44 @@ function inspectArtifactPatterns(content, schema) {
|
|
|
166
166
|
diagnostics
|
|
167
167
|
};
|
|
168
168
|
}
|
|
169
|
-
function
|
|
169
|
+
function inspectArtifactStateCheck(content, schema, scopeToStateCheckSection) {
|
|
170
|
+
const stateCheck = schema.sections.find((section) => section.id === 'state-check');
|
|
171
|
+
if (!stateCheck || schema.stateCheckPatterns.length === 0) {
|
|
172
|
+
return { ok: true, family: schema.family, semanticDigest: canonicalSemanticDigest(content), diagnostics: [] };
|
|
173
|
+
}
|
|
174
|
+
if (!scopeToStateCheckSection) {
|
|
175
|
+
const diagnostics = schema.stateCheckPatterns
|
|
176
|
+
.filter((pattern) => !new RegExp(pattern, 'm').test(content))
|
|
177
|
+
.map((pattern) => diagnostic('ARTIFACT_STATE_CHECK_PATTERN_MISSING', `artifact is missing required state-check command '${pattern}'`, stateCheck.id, null));
|
|
178
|
+
return {
|
|
179
|
+
ok: diagnostics.length === 0,
|
|
180
|
+
family: schema.family,
|
|
181
|
+
semanticDigest: canonicalSemanticDigest(content),
|
|
182
|
+
diagnostics
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
const scanned = scanVisibleMarkdown(content);
|
|
186
|
+
const headings = scanned.headings.filter((heading) => (heading.level === 2 && sectionHeadings(schema, stateCheck).includes(heading.text)));
|
|
187
|
+
if (headings.length !== 1) {
|
|
188
|
+
return { ok: true, family: schema.family, semanticDigest: canonicalSemanticDigest(content), diagnostics: [] };
|
|
189
|
+
}
|
|
190
|
+
const bodyBounds = sectionBodyBounds(content, headings[0], scanned);
|
|
191
|
+
const body = content.slice(bodyBounds.start, bodyBounds.end);
|
|
192
|
+
const diagnostics = schema.stateCheckPatterns
|
|
193
|
+
.filter((pattern) => !new RegExp(pattern, 'm').test(body))
|
|
194
|
+
.map((pattern) => diagnostic('ARTIFACT_STATE_CHECK_PATTERN_MISSING', `state-check section is missing required command '${pattern}'`, stateCheck.id, lineNumber(content, headings[0].start)));
|
|
195
|
+
return {
|
|
196
|
+
ok: diagnostics.length === 0,
|
|
197
|
+
family: schema.family,
|
|
198
|
+
semanticDigest: canonicalSemanticDigest(content),
|
|
199
|
+
diagnostics
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function inspectArtifactContract(content, schema, options = {}) {
|
|
170
203
|
const structure = inspectArtifactStructure(content, schema);
|
|
204
|
+
const stateCheck = inspectArtifactStateCheck(content, schema, options.scopeStateCheckSection === true);
|
|
171
205
|
const patterns = inspectArtifactPatterns(content, schema);
|
|
172
|
-
const diagnostics = [...structure.diagnostics, ...patterns.diagnostics];
|
|
206
|
+
const diagnostics = [...structure.diagnostics, ...stateCheck.diagnostics, ...patterns.diagnostics];
|
|
173
207
|
return {
|
|
174
208
|
ok: diagnostics.length === 0,
|
|
175
209
|
family: schema.family,
|
|
@@ -81,21 +81,21 @@ function buildSections(family) {
|
|
|
81
81
|
marker: `artifact-section:${family}:${id}`
|
|
82
82
|
}));
|
|
83
83
|
}
|
|
84
|
+
const TASK_SNAPSHOT_PATTERN = 'agent-infra-internal[ \\t]+task-snapshot\\b';
|
|
84
85
|
const ARTIFACT_SCHEMAS = [
|
|
85
|
-
{ family: 'analysis', title: { zh: '需求分析报告', en: 'Requirements Analysis' }, sections: buildSections('analysis'), requiredPatterns: [
|
|
86
|
-
{ family: 'review-analysis', title: { zh: '需求分析审查报告', en: 'Analysis Review Report' }, sections: buildSections('review-analysis'), requiredPatterns: ['^### (?:审查决定|Approval Decision)$'
|
|
87
|
-
{ family: 'plan', title: { zh: '技术方案', en: 'Technical Plan' }, sections: buildSections('plan'), requiredPatterns: [
|
|
88
|
-
{ family: 'review-plan', title: { zh: '技术方案审查报告', en: 'Plan Review Report' }, sections: buildSections('review-plan'), requiredPatterns: ['^### (?:审查决定|Approval Decision)$'
|
|
89
|
-
{ family: 'code', title: { zh: '实现报告', en: 'Implementation Report' }, sections: buildSections('code'), requiredPatterns: [
|
|
86
|
+
{ family: 'analysis', title: { zh: '需求分析报告', en: 'Requirements Analysis' }, sections: buildSections('analysis'), stateCheckPatterns: [TASK_SNAPSHOT_PATTERN], requiredPatterns: [] },
|
|
87
|
+
{ family: 'review-analysis', title: { zh: '需求分析审查报告', en: 'Analysis Review Report' }, sections: buildSections('review-analysis'), stateCheckPatterns: [TASK_SNAPSHOT_PATTERN], requiredPatterns: ['^### (?:审查决定|Approval Decision)$'] },
|
|
88
|
+
{ family: 'plan', title: { zh: '技术方案', en: 'Technical Plan' }, sections: buildSections('plan'), stateCheckPatterns: [TASK_SNAPSHOT_PATTERN], requiredPatterns: [] },
|
|
89
|
+
{ family: 'review-plan', title: { zh: '技术方案审查报告', en: 'Plan Review Report' }, sections: buildSections('review-plan'), stateCheckPatterns: [TASK_SNAPSHOT_PATTERN], requiredPatterns: ['^### (?:审查决定|Approval Decision)$'] },
|
|
90
|
+
{ family: 'code', title: { zh: '实现报告', en: 'Implementation Report' }, sections: buildSections('code'), stateCheckPatterns: [TASK_SNAPSHOT_PATTERN], requiredPatterns: [] },
|
|
90
91
|
{ family: 'review-code', title: { zh: '代码审查报告', en: 'Code Review Report' }, sections: buildSections('review-code'), requiredPatterns: [
|
|
91
92
|
'^### (?:审查决定|Approval Decision)$',
|
|
92
|
-
'^\\$ ',
|
|
93
93
|
'^- \\*\\*(?:总体结论|Overall Verdict)\\*\\*[::]\\s*(?:通过|需要修改|拒绝|Approved|Changes Requested|Rejected)\\s*$',
|
|
94
94
|
'^- \\*\\*(?:审查基线提交|Review Baseline Commit)\\*\\*[::]\\s*\\S',
|
|
95
95
|
'^- \\*\\*(?:审查差异基线|Reviewed Diff Base)\\*\\*[::]\\s*[0-9a-f]{40,64}\\s*$',
|
|
96
96
|
'^- \\*\\*(?:审查差异指纹|Reviewed Diff Fingerprint)\\*\\*[::]\\s*sha256:[0-9a-f]{64}\\s*$',
|
|
97
97
|
'^- \\*\\*(?:审查快照树|Reviewed Snapshot Tree)\\*\\*[::]\\s*[0-9a-f]{40,64}\\s*$'
|
|
98
|
-
] }
|
|
98
|
+
], stateCheckPatterns: [TASK_SNAPSHOT_PATTERN] }
|
|
99
99
|
];
|
|
100
100
|
function getArtifactSchema(family) {
|
|
101
101
|
return ARTIFACT_SCHEMAS.find((schema) => schema.family === family) ?? null;
|
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import crypto from 'node:crypto';
|
|
3
|
-
import fs from 'node:fs';
|
|
4
3
|
import { normalizeAgentToken } from '../agent-clients/tokens.js';
|
|
5
4
|
import { isAgentClientId } from '../agent-clients/types.js';
|
|
6
5
|
import { getAgentClientAdapter } from '../agent-clients/registry.js';
|
|
7
|
-
import { consumeHumanOverride, failureId, overrideDryRunConflict } from './human-override.js';
|
|
8
6
|
import { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, advanceOrchestration, awaitOrchestrationDelegationActivation, beginOrResumeOrchestration, dispatchOrchestrationDelegation, OrchestrationStateError, pauseOrchestration, prepareOrchestrationDelegation, recoverPreparedOrchestrationDelegation, routeOrchestration, sealMatchingOrchestrationDelegation, sealOrchestrationDelegation, statusOrchestration } from './orchestration.js';
|
|
9
7
|
import { applyTaskFinalization, commitPreparedTaskFinalization } from './finalization.js';
|
|
10
8
|
import { applyTaskLifecycle, taskLifecycleFailure } from './lifecycle.js';
|
|
11
|
-
import { locateActivityLog } from './activity-log.js';
|
|
12
9
|
import { resolveTaskRef, TASK_ID_RE } from './resolve-ref.js';
|
|
13
10
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
14
11
|
import { verifyTaskEvent } from './verification.js';
|
|
15
12
|
import { createCodexCapabilityStore } from '../agent-clients/adapters/codex-lifecycle/capability-store.js';
|
|
16
|
-
import {
|
|
17
|
-
import { computeLifecycleBuildIdentity, verifyLifecycleBuildIdentity } from '../agent-clients/adapters/codex-lifecycle/build-identity.js';
|
|
13
|
+
import { computeLifecycleBuildIdentity } from '../agent-clients/adapters/codex-lifecycle/build-identity.js';
|
|
18
14
|
const lifecycleAuthorityPhases = [
|
|
19
15
|
'orchestration.prepare', 'artifact.finalize-local', 'task-event.completed'
|
|
20
16
|
];
|
|
@@ -184,7 +180,6 @@ export function queryLifecycleRecoveryOperation(operationId, options = {}) {
|
|
|
184
180
|
const store = options.capabilityStore ?? createCodexCapabilityStore();
|
|
185
181
|
const registry = authorityOperationMap.get(store);
|
|
186
182
|
const known = [...(registry?.values() ?? [])].filter((value) => value.operationId === operationId);
|
|
187
|
-
const intents = options.repoRoot ? findArtifactRecoveryIntentsByOperation(options.repoRoot, operationId) : [];
|
|
188
183
|
const capabilities = store.findByRecoveryOperation(operationId);
|
|
189
184
|
const phaseMap = new Map();
|
|
190
185
|
for (const capability of capabilities) {
|
|
@@ -208,23 +203,10 @@ export function queryLifecycleRecoveryOperation(operationId, options = {}) {
|
|
|
208
203
|
? 'consumed' : 'issued'
|
|
209
204
|
});
|
|
210
205
|
}
|
|
211
|
-
for (const intent of intents) {
|
|
212
|
-
if (!intent.phase)
|
|
213
|
-
continue;
|
|
214
|
-
const key = `${intent.phase}\0${intent.requestId}`;
|
|
215
|
-
if (phaseMap.has(key))
|
|
216
|
-
continue;
|
|
217
|
-
phaseMap.set(key, {
|
|
218
|
-
phase: intent.phase,
|
|
219
|
-
requestId: intent.requestId,
|
|
220
|
-
attestationId: null,
|
|
221
|
-
state: intent.state === 'consumed' ? 'consumed' : 'observed'
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
206
|
const capabilityState = capabilities.some((record) => record.recoveryState === 'consumed')
|
|
225
207
|
? 'consumed'
|
|
226
208
|
: capabilities.some((record) => record.recoveryState === 'reserved') ? 'reserved' : 'unknown';
|
|
227
|
-
const committed = capabilityState === 'consumed'
|
|
209
|
+
const committed = capabilityState === 'consumed';
|
|
228
210
|
return {
|
|
229
211
|
version: 1,
|
|
230
212
|
operationId,
|
|
@@ -233,120 +215,6 @@ export function queryLifecycleRecoveryOperation(operationId, options = {}) {
|
|
|
233
215
|
phases: Object.freeze([...phaseMap.values()])
|
|
234
216
|
};
|
|
235
217
|
}
|
|
236
|
-
function lifecycleRecoveryCompensationError(code, message) {
|
|
237
|
-
const error = new Error(`${code}: ${message}`);
|
|
238
|
-
error.name = code;
|
|
239
|
-
return error;
|
|
240
|
-
}
|
|
241
|
-
function lifecycleRecoveryEventCommitted(selector, repoRoot) {
|
|
242
|
-
const resolved = resolveTaskRef(selector.taskId, { repoRoot });
|
|
243
|
-
if (!resolved.ok || resolved.taskId !== selector.taskId)
|
|
244
|
-
return false;
|
|
245
|
-
let content;
|
|
246
|
-
try {
|
|
247
|
-
content = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
248
|
-
}
|
|
249
|
-
catch {
|
|
250
|
-
return false;
|
|
251
|
-
}
|
|
252
|
-
const section = locateActivityLog(content);
|
|
253
|
-
if (!section)
|
|
254
|
-
return false;
|
|
255
|
-
const labels = { analysis: 'Analyze Task', plan: 'Plan Task', code: 'Code Task' };
|
|
256
|
-
const prefix = `${labels[selector.family]} (Round ${selector.round}`;
|
|
257
|
-
return section.entries.some((entry) => {
|
|
258
|
-
if (entry.step.endsWith(' [started]') || !entry.step.startsWith(prefix))
|
|
259
|
-
return false;
|
|
260
|
-
const delimiter = entry.step[prefix.length];
|
|
261
|
-
return (delimiter === ',' || delimiter === ')') && entry.note.endsWith(`→ ${selector.artifact}`);
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
function lifecycleRecoveryIntentMatches(intent, selector) {
|
|
265
|
-
return intent.taskId === selector.taskId
|
|
266
|
-
&& intent.family === selector.family
|
|
267
|
-
&& intent.artifact === selector.artifact
|
|
268
|
-
&& (intent.state === 'commit-started' || intent.state === 'consumption-started' || intent.state === 'consumed');
|
|
269
|
-
}
|
|
270
|
-
/**
|
|
271
|
-
* Completes only the durable tail of a committed task event after a process
|
|
272
|
-
* restart. The task event itself is the commit boundary and is never replayed.
|
|
273
|
-
*/
|
|
274
|
-
export function recoverLifecycleRecoveryOperation(input, options) {
|
|
275
|
-
const selector = validateLifecycleAuthorityRequest(input);
|
|
276
|
-
if (selector.phase !== 'task-event.completed') {
|
|
277
|
-
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PHASE_UNSUPPORTED', 'only task-event.completed supports durable compensation');
|
|
278
|
-
}
|
|
279
|
-
const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(options.repoRoot);
|
|
280
|
-
if (selector.expectedBuildIdentityDigest !== lifecycleAuthorityBuildDigest(buildIdentity)) {
|
|
281
|
-
throw lifecycleRecoveryCompensationError('LIFECYCLE_AUTHORITY_BUILD_MISMATCH', 'current lifecycle build does not match the recovery request');
|
|
282
|
-
}
|
|
283
|
-
if (selector.expectedControlGeneration !== options.controllerBinding.controlGeneration
|
|
284
|
-
|| selector.expectedControllerInstanceDigest !== options.controllerBinding.instanceDigest) {
|
|
285
|
-
throw lifecycleRecoveryCompensationError('LIFECYCLE_AUTHORITY_CONTROLLER_MISMATCH', 'current controller binding does not match the recovery request');
|
|
286
|
-
}
|
|
287
|
-
const store = options.capabilityStore ?? createCodexCapabilityStore();
|
|
288
|
-
const expected = {
|
|
289
|
-
taskId: selector.taskId,
|
|
290
|
-
hookDefinitionHash: selector.expectedHookDefinitionHash,
|
|
291
|
-
buildIdentity,
|
|
292
|
-
controller: options.controllerBinding
|
|
293
|
-
};
|
|
294
|
-
const capabilities = store.findByRecoveryOperation(selector.operationId);
|
|
295
|
-
if (capabilities.length !== 1 || capabilities[0].capabilityRefDigest !== lifecycleAuthorityDigest(selector.authorityRef)) {
|
|
296
|
-
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PROVENANCE_INVALID', 'durable capability provenance does not identify this operation');
|
|
297
|
-
}
|
|
298
|
-
const capability = capabilities[0];
|
|
299
|
-
const build = verifyLifecycleBuildIdentity(capability.buildIdentity, buildIdentity);
|
|
300
|
-
const expiredReserved = capability.status === 'expired' && capability.recoveryState === 'reserved';
|
|
301
|
-
if (!build.ok
|
|
302
|
-
|| capability.taskId !== selector.taskId
|
|
303
|
-
|| capability.hookDefinitionHash !== selector.expectedHookDefinitionHash
|
|
304
|
-
|| capability.controller?.controlGeneration !== options.controllerBinding.controlGeneration
|
|
305
|
-
|| capability.controller?.instanceDigest !== options.controllerBinding.instanceDigest
|
|
306
|
-
|| capability.recoveryState === 'unreserved'
|
|
307
|
-
|| capability.recoveryOperationId !== selector.operationId
|
|
308
|
-
|| (!['attested', 'consumed'].includes(capability.status) && !expiredReserved)) {
|
|
309
|
-
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PROVENANCE_INVALID', 'durable capability provenance does not match the recovery request');
|
|
310
|
-
}
|
|
311
|
-
const phase = capability.recoveryPhases.find((entry) => entry.phase === selector.phase);
|
|
312
|
-
if (!phase) {
|
|
313
|
-
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PHASE_MISSING', 'durable task completion phase is missing');
|
|
314
|
-
}
|
|
315
|
-
const intents = findArtifactRecoveryIntentsByOperation(options.repoRoot, selector.operationId)
|
|
316
|
-
.filter((intent) => intent.taskId === selector.taskId
|
|
317
|
-
&& intent.family === selector.family
|
|
318
|
-
&& intent.artifact === selector.artifact);
|
|
319
|
-
if (intents.length !== 1) {
|
|
320
|
-
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_INTENT_INVALID', 'durable artifact recovery intent does not identify this task event');
|
|
321
|
-
}
|
|
322
|
-
const intent = intents[0];
|
|
323
|
-
if (!lifecycleRecoveryIntentMatches(intent, selector)
|
|
324
|
-
|| intent.phase !== selector.phase
|
|
325
|
-
|| intent.requestId !== selector.lifecycleRequestId
|
|
326
|
-
|| intent.authorityDigest === null) {
|
|
327
|
-
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_INTENT_INVALID', 'durable artifact recovery intent does not match the recovery request');
|
|
328
|
-
}
|
|
329
|
-
if (!lifecycleRecoveryEventCommitted(selector, options.repoRoot)) {
|
|
330
|
-
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_COMMIT_UNCONFIRMED', 'task completion is not durably present; compensation is not allowed');
|
|
331
|
-
}
|
|
332
|
-
if (capability.recoveryState === 'reserved') {
|
|
333
|
-
if (phase.state === 'issued') {
|
|
334
|
-
store.consumeRecoveryPhase(selector.authorityRef, selector.operationId, selector.phase, phase.requestId, expected, { allowExpiredReserved: true });
|
|
335
|
-
}
|
|
336
|
-
store.consumeReference(selector.authorityRef, selector.operationId, expected, { allowExpiredReserved: true });
|
|
337
|
-
}
|
|
338
|
-
else if (phase.state !== 'consumed') {
|
|
339
|
-
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_STATE_INVALID', 'consumed capability has an unconsumed completion phase');
|
|
340
|
-
}
|
|
341
|
-
if (intent.state !== 'consumed') {
|
|
342
|
-
writeArtifactRecoveryIntent(options.repoRoot, {
|
|
343
|
-
...intent,
|
|
344
|
-
state: 'consumed',
|
|
345
|
-
updatedAt: (options.now ?? Date.now)()
|
|
346
|
-
}, { expected: intent });
|
|
347
|
-
}
|
|
348
|
-
return queryLifecycleRecoveryOperation(selector.operationId, { repoRoot: options.repoRoot, capabilityStore: store });
|
|
349
|
-
}
|
|
350
218
|
function authorityRejected(request, code, message) {
|
|
351
219
|
return { version: 1, requestId: request.requestId, operationId: request.operationId, phase: request.phase, status: 'rejected', attestation: null, error: { code, message } };
|
|
352
220
|
}
|
|
@@ -406,6 +274,7 @@ export function createSandboxExecutorExecutionContext(params) {
|
|
|
406
274
|
generation: params.generation,
|
|
407
275
|
manifestPath: absolute('manifestPath', params.manifestPath),
|
|
408
276
|
requestId: params.requestId,
|
|
277
|
+
handoffDirectory: absolute('handoffDirectory', params.handoffDirectory ?? path.join(params.worktreeRoot, '.agent-infra-handoff')),
|
|
409
278
|
...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) }),
|
|
410
279
|
...(params.lifecycleRecoveryAttestation === undefined ? {} : {
|
|
411
280
|
lifecycleRecoveryAttestation: params.lifecycleRecoveryAttestation === null
|
|
@@ -433,6 +302,7 @@ export function assertTaskControlExecutionContext(context) {
|
|
|
433
302
|
absolute('worktreeRoot', context.worktreeRoot);
|
|
434
303
|
absolute('runtimeDir', context.runtimeDir);
|
|
435
304
|
absolute('manifestPath', context.manifestPath);
|
|
305
|
+
absolute('handoffDirectory', context.handoffDirectory);
|
|
436
306
|
if (path.resolve(context.runtimeDir) !== path.join(path.dirname(path.resolve(context.manifestPath)), 'runtime')) {
|
|
437
307
|
invalidContext('runtimeDir is not bound to the manifest control root');
|
|
438
308
|
}
|
|
@@ -463,10 +333,6 @@ function lifecycleTaskId(request, repoRoot) {
|
|
|
463
333
|
}
|
|
464
334
|
}
|
|
465
335
|
async function applyLifecycleWithAuthority(context, request) {
|
|
466
|
-
const conflict = overrideDryRunConflict(request);
|
|
467
|
-
if (conflict) {
|
|
468
|
-
return taskLifecycleFailure(request, { code: 'LIFECYCLE_PAYLOAD_INVALID', message: conflict.message });
|
|
469
|
-
}
|
|
470
336
|
const execute = async () => {
|
|
471
337
|
if (request.intent === 'recover-started') {
|
|
472
338
|
const adapter = isAgentClientId(request.agent)
|
|
@@ -481,29 +347,7 @@ async function applyLifecycleWithAuthority(context, request) {
|
|
|
481
347
|
}
|
|
482
348
|
return adapter.recoverStarted(request, { repoRoot: context.repoRoot });
|
|
483
349
|
}
|
|
484
|
-
|
|
485
|
-
if (lifecycleResult.status !== 'failed' || !request.overrideTicket)
|
|
486
|
-
return lifecycleResult;
|
|
487
|
-
if (!request.overrideTarget || !request.overrideScope) {
|
|
488
|
-
return {
|
|
489
|
-
...lifecycleResult,
|
|
490
|
-
error: { code: 'OVERRIDE_PAYLOAD_INVALID', message: 'override ticket requires --override-target and --override-scope' }
|
|
491
|
-
};
|
|
492
|
-
}
|
|
493
|
-
const override = await consumeHumanOverride({
|
|
494
|
-
taskRef: request.taskRef,
|
|
495
|
-
ticketId: request.overrideTicket,
|
|
496
|
-
failureId: failureId('lifecycle.apply', lifecycleResult.error?.code ?? 'LIFECYCLE_FAILED'),
|
|
497
|
-
target: request.overrideTarget,
|
|
498
|
-
scope: request.overrideScope,
|
|
499
|
-
intent: request.intent,
|
|
500
|
-
...('alertNumber' in request && request.alertNumber ? { alertNumber: request.alertNumber } : {}),
|
|
501
|
-
...('issueNumber' in request && request.issueNumber ? { issueNumber: request.issueNumber } : {}),
|
|
502
|
-
...('stagingDir' in request && request.stagingDir ? { stagingDir: request.stagingDir } : {})
|
|
503
|
-
}, { repoRoot: context.repoRoot });
|
|
504
|
-
if (override.status === 'failed')
|
|
505
|
-
return { ...lifecycleResult, humanOverride: override };
|
|
506
|
-
return { ...lifecycleResult, ...override, humanOverride: override, error: null };
|
|
350
|
+
return applyTaskLifecycle(request, { repoRoot: context.repoRoot });
|
|
507
351
|
};
|
|
508
352
|
const taskId = lifecycleTaskId(request, context.repoRoot);
|
|
509
353
|
if (!taskId)
|
|
@@ -594,7 +438,9 @@ export function dispatchTaskControlOperation(context, operation) {
|
|
|
594
438
|
return finalize(operation.request, {
|
|
595
439
|
repoRoot: context.repoRoot,
|
|
596
440
|
...(context.source === 'sandbox-executor' ? {
|
|
597
|
-
controlBinding: { generation: context.generation, requestId: context.requestId }
|
|
441
|
+
controlBinding: { generation: context.generation, requestId: context.requestId },
|
|
442
|
+
handoffDirectory: context.handoffDirectory,
|
|
443
|
+
manifestPath: context.manifestPath
|
|
598
444
|
} : {}),
|
|
599
445
|
preflight: (request, options) => verifyTaskEvent({ ...request, event: 'complete-task.hard-preflight' }, options)
|
|
600
446
|
});
|
|
@@ -629,7 +475,7 @@ function operationInvalid(message) {
|
|
|
629
475
|
}
|
|
630
476
|
const LIFECYCLE_FLAGS = new Set([
|
|
631
477
|
'--agent', '--reason', '--unblock-condition', '--note', '--alert-number', '--staging-dir', '--issue-number',
|
|
632
|
-
'--auto', '--
|
|
478
|
+
'--auto', '--dry-run'
|
|
633
479
|
]);
|
|
634
480
|
const FINALIZATION_FLAGS = new Set(['--agent']);
|
|
635
481
|
const ORCHESTRATION_FLAGS = new Set([
|
|
@@ -700,9 +546,6 @@ export function parseTaskControlOperation(family, args) {
|
|
|
700
546
|
...(value(values, '--unblock-condition') ? { unblockCondition: value(values, '--unblock-condition') } : {}),
|
|
701
547
|
...(value(values, '--note') ? { note: value(values, '--note') } : {}),
|
|
702
548
|
...(value(values, '--staging-dir') ? { stagingDir: value(values, '--staging-dir') } : {}),
|
|
703
|
-
...(value(values, '--override-ticket') ? { overrideTicket: value(values, '--override-ticket') } : {}),
|
|
704
|
-
...(value(values, '--override-target') ? { overrideTarget: value(values, '--override-target') } : {}),
|
|
705
|
-
...(value(values, '--override-scope') ? { overrideScope: value(values, '--override-scope') } : {}),
|
|
706
549
|
...(value(values, '--alert-number') ? { alertNumber: Number(value(values, '--alert-number')) } : {}),
|
|
707
550
|
...(value(values, '--issue-number') ? { issueNumber: Number(value(values, '--issue-number')) } : {}),
|
|
708
551
|
...(values['--auto'] === true ? { auto: true } : {}),
|
|
@@ -6,7 +6,6 @@ import { LEDGER_COLUMNS, LEDGER_HEADINGS, LEDGER_SECTION_MISSING_CODE, LEDGER_SE
|
|
|
6
6
|
import { resolveTaskContext } from './resolve-ref.js';
|
|
7
7
|
import { extractSection, extractSubSection, findSectionHeading } from './sections.js';
|
|
8
8
|
import { captureTaskWriteMetadata, writeTask } from './write.js';
|
|
9
|
-
import { allowsManualOverride } from './guard-override.js';
|
|
10
9
|
import { consumeHumanDecisionReworkIntents, parseReworkIntentDocument, reworkIntentMutation } from './rework-intent.js';
|
|
11
10
|
const DECISION_ALIASES = ['人工裁决', 'Human Rulings', 'Human Decisions', 'Human Decision'];
|
|
12
11
|
const ACTIVITY_ALIASES = ['活动日志', 'Activity Log'];
|
|
@@ -58,8 +57,7 @@ function applyHumanDecision(request, options = {}) {
|
|
|
58
57
|
const resolved = resolveTaskContext(request.taskRef, { repoRoot: options.repoRoot });
|
|
59
58
|
if (!resolved.ok)
|
|
60
59
|
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
61
|
-
|
|
62
|
-
if (resolved.state !== 'active' && !stateOverride)
|
|
60
|
+
if (resolved.state !== 'active')
|
|
63
61
|
return failed('TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
|
|
64
62
|
let content;
|
|
65
63
|
let rows;
|
|
@@ -172,7 +170,7 @@ function applyHumanDecision(request, options = {}) {
|
|
|
172
170
|
catch (error) {
|
|
173
171
|
return failed('DECISION_DOCUMENT_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId, row.id);
|
|
174
172
|
}
|
|
175
|
-
const writeResult = writeTask({ taskRef: resolved.taskId, expectedState:
|
|
173
|
+
const writeResult = writeTask({ taskRef: resolved.taskId, expectedState: 'active', mutations, dryRun: request.dryRun }, {
|
|
176
174
|
...options,
|
|
177
175
|
taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state },
|
|
178
176
|
metadataProvider: () => metadata
|
package/dist/lib/task/events.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { execFileSync } from 'node:child_process';
|
|
4
|
+
import { resolveBranchWorktree } from '../git/branch-worktree.js';
|
|
4
5
|
import { appendActivityEntry, locateActivityLog, pairEntries, startedBackedRows } from './activity-log.js';
|
|
5
6
|
import { buildArtifactLinkSection, inspectArtifactDirectory, resolveArtifactContext, validateCompletedArtifact } from './artifact-lifecycle.js';
|
|
6
7
|
import { artifactName, parseArtifactName } from './artifact-name.js';
|
|
@@ -16,7 +17,6 @@ import { validateLifecycleExecution } from './lifecycle-execution.js';
|
|
|
16
17
|
import { commitOrchestrationStageCompletion } from './orchestration.js';
|
|
17
18
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
18
19
|
import { captureTaskWriteMetadata, writeTask } from './write.js';
|
|
19
|
-
import { allowsManualOverride } from './guard-override.js';
|
|
20
20
|
import { validateLocalArtifact } from './local-artifact-finalization.js';
|
|
21
21
|
import { buildLifecycleFacts, canStart, effectiveReworkTarget } from './capabilities.js';
|
|
22
22
|
import { createInvalidationOperation, invalidationMutation, parseInvalidationDocument, targetIdFor, upsertInvalidation } from './invalidation.js';
|
|
@@ -41,24 +41,7 @@ const eventCatalog = [
|
|
|
41
41
|
function branchWorktree(repoRoot, branch) {
|
|
42
42
|
if (!branch)
|
|
43
43
|
return repoRoot;
|
|
44
|
-
|
|
45
|
-
const records = execFileSync('git', ['worktree', 'list', '--porcelain'], {
|
|
46
|
-
cwd: repoRoot,
|
|
47
|
-
encoding: 'utf8',
|
|
48
|
-
stdio: ['ignore', 'pipe', 'pipe']
|
|
49
|
-
}).trim().split(/\r?\n\r?\n/);
|
|
50
|
-
const expectedRef = `refs/heads/${branch}`;
|
|
51
|
-
for (const record of records) {
|
|
52
|
-
const worktree = /^worktree (.+)$/m.exec(record)?.[1];
|
|
53
|
-
const branchRef = /^branch (.+)$/m.exec(record)?.[1];
|
|
54
|
-
if (worktree && branchRef === expectedRef)
|
|
55
|
-
return worktree;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
catch {
|
|
59
|
-
// A missing worktree must not anchor a review to an unrelated repository.
|
|
60
|
-
}
|
|
61
|
-
return null;
|
|
44
|
+
return resolveBranchWorktree(repoRoot, branch);
|
|
62
45
|
}
|
|
63
46
|
function approvedCleanReviewedCommit(reviewContent, verdict, repoRoot, branch) {
|
|
64
47
|
if (verdict !== 'approved')
|
|
@@ -84,7 +67,7 @@ function approvedCleanReviewedCommit(reviewContent, verdict, repoRoot, branch) {
|
|
|
84
67
|
return null;
|
|
85
68
|
}
|
|
86
69
|
}
|
|
87
|
-
const BASE_FIELDS = new Set(['taskRef', 'event', 'agent', 'dryRun', '
|
|
70
|
+
const BASE_FIELDS = new Set(['taskRef', 'event', 'agent', 'dryRun', 'initiator', 'requestId', 'reasonCode', 'sourceFinding', 'sourceArtifact', 'sourceSha256']);
|
|
88
71
|
const SCHEMAS = {
|
|
89
72
|
'analyze.started': { optional: ['round'] },
|
|
90
73
|
'analyze.awaiting-input': { required: ['question'] },
|
|
@@ -722,8 +705,7 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
722
705
|
const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
|
|
723
706
|
if (!resolved.ok)
|
|
724
707
|
return failed(request, { code: resolved.code, message: resolved.message }, { taskId: resolved.taskId });
|
|
725
|
-
|
|
726
|
-
if (resolved.state !== 'active' && !stateOverride)
|
|
708
|
+
if (resolved.state !== 'active')
|
|
727
709
|
return failed(request, { code: 'TASK_STATE_MISMATCH', message: `task ${resolved.taskId} is ${resolved.state}, expected active` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
728
710
|
let content;
|
|
729
711
|
try {
|
|
@@ -842,7 +824,7 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
842
824
|
}
|
|
843
825
|
return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, row.started, frontmatter, artifactContext);
|
|
844
826
|
}
|
|
845
|
-
if (eventIdentity.phase === 'completed' && !row?.started && completedRows.length === 0
|
|
827
|
+
if (eventIdentity.phase === 'completed' && !row?.started && completedRows.length === 0)
|
|
846
828
|
return failed(normalized, { code: 'EVENT_START_MISSING', message: 'completion requires a started event or an earlier completed attempt' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
847
829
|
if (eventIdentity.phase === 'completed') {
|
|
848
830
|
const validated = validateCompletedArtifact(resolved.taskDir, FAMILY[eventIdentity.family].artifact, normalized.artifact, normalized.round);
|
|
@@ -912,12 +894,9 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
912
894
|
if (!facts.ok)
|
|
913
895
|
return failed(normalized, { code: 'EVENT_TRANSITION_INVALID', message: `${facts.code}: ${facts.message}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
914
896
|
const capability = canStart(lifecycleAction(eventIdentity.family), facts.facts, trigger);
|
|
915
|
-
const capabilityOverride = capability.reasonCode === 'TASK_NOT_ACTIVE'
|
|
916
|
-
? stateOverride
|
|
917
|
-
: allowsManualOverride(options.manualOverride, 'task-event', 'EVENT_TRANSITION_INVALID');
|
|
918
897
|
const safetyFailure = capability.reasonCode === 'TASK_NOT_ACTIVE'
|
|
919
898
|
|| capability.reasonCode === 'LIFECYCLE_EXECUTION_OPEN';
|
|
920
|
-
if (!capability.allowed && (normalized.initiator === 'orchestrator' || safetyFailure)
|
|
899
|
+
if (!capability.allowed && (normalized.initiator === 'orchestrator' || safetyFailure)) {
|
|
921
900
|
const code = capability.reasonCode === 'INVALIDATION_INCOMPLETE' ? 'TASK_INVALIDATION_BLOCKED' : 'EVENT_TRANSITION_INVALID';
|
|
922
901
|
return failed(normalized, { code, message: `${capability.reasonCode}: ${capability.evidence.join(', ')}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
923
902
|
}
|
|
@@ -1073,7 +1052,7 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
1073
1052
|
mutations.push({ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: section.heading, body });
|
|
1074
1053
|
const sourceCompletion = eventIdentity.phase === 'completed'
|
|
1075
1054
|
&& ['analyze', 'plan', 'code'].includes(eventIdentity.family);
|
|
1076
|
-
const result = writeTask({ taskRef: normalized.taskRef, expectedState:
|
|
1055
|
+
const result = writeTask({ taskRef: normalized.taskRef, expectedState: 'active', dryRun: normalized.dryRun, mutations }, { ...options, invalidationContext: sourceCompletion ? 'source-completion' : 'standard', metadataProvider: () => metadata });
|
|
1077
1056
|
if (result.status === 'failed')
|
|
1078
1057
|
return failed(normalized, result.error, { taskId: result.taskId, taskMdPath: result.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, timestamp: result.timestamp, agentInfraVersion: result.agentInfraVersion, operations: result.operations, artifactContext });
|
|
1079
1058
|
if (!normalized.dryRun && orchestrationCompletion) {
|