@fitlab-ai/agent-infra 0.11.3 → 0.11.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -8
- package/README.zh-CN.md +17 -8
- package/dist/bin/internal-cli.js +1 -6
- package/dist/lib/decide.js +4 -41
- package/dist/lib/git/branch-worktree.js +45 -0
- package/dist/lib/internal/cli-route-inventory.js +1 -4
- package/dist/lib/internal/task-activity.js +7 -40
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-event.js +4 -37
- package/dist/lib/internal/task-finalization.js +48 -1
- package/dist/lib/internal/task-ledger.js +14 -44
- package/dist/lib/internal/task-lifecycle.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +2 -4
- package/dist/lib/internal/task-review.js +5 -37
- package/dist/lib/internal/task-verify.js +3 -47
- package/dist/lib/internal/task-warning.js +4 -40
- package/dist/lib/platform/completion-backfill.js +63 -17
- package/dist/lib/platform/pr-summary.js +4 -23
- package/dist/lib/platform/verification-sync.js +12 -49
- package/dist/lib/sandbox/control/client.js +4 -1
- package/dist/lib/sandbox/control/executor.js +12 -4
- package/dist/lib/sandbox/control/protocol.js +2 -1
- package/dist/lib/sandbox/control/server.js +26 -143
- package/dist/lib/sandbox/removal.js +15 -8
- package/dist/lib/sandbox/task-cleanup.js +13 -20
- package/dist/lib/task/activity-intent.js +1 -2
- package/dist/lib/task/artifact-command.js +11 -4
- package/dist/lib/task/artifact-lifecycle.js +217 -85
- package/dist/lib/task/artifact-operations.js +37 -28
- package/dist/lib/task/artifact-receipts.js +4 -1
- package/dist/lib/task/artifact-schema.js +8 -7
- package/dist/lib/task/artifact-selection.js +25 -0
- package/dist/lib/task/capabilities.js +158 -39
- package/dist/lib/task/commands/status.js +9 -2
- package/dist/lib/task/control-authority.js +12 -168
- package/dist/lib/task/decision-intents.js +14 -4
- package/dist/lib/task/delivery.js +1 -43
- package/dist/lib/task/events.js +68 -73
- package/dist/lib/task/finalization-handoff.js +92 -0
- package/dist/lib/task/finalization.js +423 -154
- package/dist/lib/task/intermediate-cleanup.js +9 -148
- package/dist/lib/task/ledger-intents.js +159 -20
- package/dist/lib/task/lifecycle-path.js +75 -0
- package/dist/lib/task/lifecycle.js +60 -11
- package/dist/lib/task/local-artifact-finalization.js +13 -47
- package/dist/lib/task/orchestration.js +17 -7
- package/dist/lib/task/review-command.js +1 -2
- package/dist/lib/task/review-finalization.js +4 -18
- package/dist/lib/task/rework-intent.js +70 -4
- package/dist/lib/task/snapshot.js +6 -6
- package/dist/lib/task/verification-engine.js +25 -30
- package/dist/lib/task/verification.js +2 -13
- package/dist/lib/task/workflow-warning-intents.js +23 -10
- package/dist/lib/task/workspace-snapshot.js +7 -2
- package/dist/lib/task/write.js +1 -2
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/decide.ts +5 -35
- package/lib/git/branch-worktree.ts +41 -0
- package/lib/internal/cli-route-inventory.ts +1 -3
- package/lib/internal/task-activity.ts +6 -34
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-event.ts +4 -37
- package/lib/internal/task-finalization.ts +47 -1
- package/lib/internal/task-ledger.ts +15 -41
- package/lib/internal/task-lifecycle.ts +2 -2
- package/lib/internal/task-operation-registry.ts +2 -4
- package/lib/internal/task-review.ts +5 -31
- package/lib/internal/task-verify.ts +3 -39
- package/lib/internal/task-warning.ts +3 -32
- package/lib/platform/completion-backfill.ts +78 -16
- package/lib/platform/pr-summary.ts +4 -25
- package/lib/platform/verification-sync.ts +13 -53
- package/lib/sandbox/control/client.ts +5 -1
- package/lib/sandbox/control/executor.ts +13 -4
- package/lib/sandbox/control/protocol.ts +3 -2
- package/lib/sandbox/control/server.ts +21 -146
- package/lib/sandbox/removal.ts +23 -7
- package/lib/sandbox/task-cleanup.ts +12 -20
- package/lib/task/activity-intent.ts +1 -2
- package/lib/task/artifact-command.ts +14 -4
- package/lib/task/artifact-lifecycle.ts +236 -85
- package/lib/task/artifact-operations.ts +59 -28
- package/lib/task/artifact-receipts.ts +4 -1
- package/lib/task/artifact-schema.ts +10 -7
- package/lib/task/artifact-selection.ts +53 -0
- package/lib/task/capabilities.ts +154 -40
- package/lib/task/commands/status.ts +11 -2
- package/lib/task/control-authority.ts +17 -189
- package/lib/task/decision-intents.ts +11 -4
- package/lib/task/delivery.ts +1 -40
- package/lib/task/events.ts +73 -74
- package/lib/task/finalization-handoff.ts +104 -0
- package/lib/task/finalization.ts +388 -122
- package/lib/task/intermediate-cleanup.ts +9 -125
- package/lib/task/ledger-intents.ts +157 -20
- package/lib/task/lifecycle-path.ts +98 -0
- package/lib/task/lifecycle.ts +58 -13
- package/lib/task/local-artifact-finalization.ts +14 -56
- package/lib/task/orchestration.ts +26 -8
- package/lib/task/review-command.ts +2 -5
- package/lib/task/review-finalization.ts +4 -20
- package/lib/task/rework-intent.ts +75 -5
- package/lib/task/snapshot.ts +6 -6
- package/lib/task/verification-engine.ts +29 -30
- package/lib/task/verification.ts +3 -16
- package/lib/task/workflow-warning-intents.ts +20 -10
- package/lib/task/workspace-snapshot.ts +3 -2
- package/lib/task/write.ts +1 -4
- package/package.json +1 -1
- package/templates/.agents/rules/review-handshake.en.md +4 -4
- package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
- package/templates/.agents/skills/analyze-task/SKILL.en.md +21 -5
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +21 -5
- package/templates/.agents/skills/code-task/SKILL.en.md +14 -8
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +18 -13
- package/templates/.agents/skills/code-task/reference/code-rules.en.md +2 -2
- package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +4 -4
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +4 -4
- package/templates/.agents/skills/code-task/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +7 -7
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +2 -2
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +1 -3
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +1 -3
- package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/plan-task/SKILL.en.md +13 -10
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +13 -10
- package/templates/.agents/skills/review-analysis/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/review-code/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/review-code/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/review-plan/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/review-pr/config/verify.en.json +3 -1
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +3 -1
- package/templates/.agents/skills/review-pr/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +2 -2
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -2
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -2
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +18 -2
- package/templates/.agents/templates/task.en.md +2 -2
- package/templates/.agents/templates/task.zh-CN.md +2 -2
- package/templates/.agents/workflows/bug-fix.en.yaml +11 -6
- package/templates/.agents/workflows/bug-fix.zh-CN.yaml +11 -6
- package/templates/.agents/workflows/feature-development.en.yaml +11 -6
- package/templates/.agents/workflows/feature-development.zh-CN.yaml +11 -6
- package/templates/.agents/workflows/refactoring.en.yaml +11 -6
- package/templates/.agents/workflows/refactoring.zh-CN.yaml +11 -6
- package/dist/lib/internal/task-override.js +0 -138
- package/dist/lib/sandbox/control/finalization-response.js +0 -33
- package/dist/lib/task/artifact-recovery.js +0 -476
- package/dist/lib/task/artifact-repair-intent.js +0 -181
- package/dist/lib/task/guard-override.js +0 -31
- package/dist/lib/task/human-override.js +0 -1259
- package/lib/internal/task-override.ts +0 -144
- package/lib/sandbox/control/finalization-response.ts +0 -35
- package/lib/task/artifact-recovery.ts +0 -572
- package/lib/task/artifact-repair-intent.ts +0 -227
- package/lib/task/guard-override.ts +0 -50
- package/lib/task/human-override.ts +0 -1522
|
@@ -2,7 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { parseArtifactName } from './artifact-name.js';
|
|
4
4
|
import { invalidationBlocks, isArtifactInvalidated, parseInvalidationDocument } from './invalidation.js';
|
|
5
|
-
import { receiptForOutput } from './artifact-receipts.js';
|
|
5
|
+
import { parseArtifactReceipts, receiptForOutput } from './artifact-receipts.js';
|
|
6
6
|
import { parseTypedTaskFrontmatter } from './frontmatter.js';
|
|
7
7
|
import { parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
|
|
8
8
|
import { parseReviewSummary, resolveCanonicalVerdict } from './review-artifacts.js';
|
|
@@ -10,6 +10,8 @@ import { parseReworkIntentDocument } from './rework-intent.js';
|
|
|
10
10
|
import { sha256File } from './artifact-receipts.js';
|
|
11
11
|
import { hasOpenLifecycleExecution } from './activity-log.js';
|
|
12
12
|
import { parseQualificationAudit, parseTaskQualification } from './qualification-audit.js';
|
|
13
|
+
import { parseLifecyclePathDecision, pathIncludes } from './lifecycle-path.js';
|
|
14
|
+
import { parseImplementationInputs } from './implementation-inputs.js';
|
|
13
15
|
const ARTIFACT_AUDIT_FAMILIES = new Set(['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code']);
|
|
14
16
|
const REASONS = new Set([
|
|
15
17
|
'user-request', 'new-requirement', 'upstream-fact-doubt', 'review-finding', 'retry', 'validation-rerun'
|
|
@@ -23,6 +25,11 @@ function allow(...evidence) {
|
|
|
23
25
|
function hasArtifact(facts, action) {
|
|
24
26
|
return (facts.artifacts[action]?.length ?? 0) > 0;
|
|
25
27
|
}
|
|
28
|
+
function effectiveReworkTarget(target, pathState) {
|
|
29
|
+
if (target === 'plan' && pathState?.status === 'valid' && !pathIncludes(pathState, 'plan'))
|
|
30
|
+
return 'analysis';
|
|
31
|
+
return target;
|
|
32
|
+
}
|
|
26
33
|
function canStart(action, facts, trigger) {
|
|
27
34
|
if (!trigger || trigger.requestedAction !== action)
|
|
28
35
|
return deny('TRIGGER_ACTION_MISMATCH', `requested=${trigger?.requestedAction ?? 'missing'}`);
|
|
@@ -45,11 +52,30 @@ function canStart(action, facts, trigger) {
|
|
|
45
52
|
return deny('TASK_NOT_ACTIVE', `state=${facts.taskState}`);
|
|
46
53
|
if (invalidationBlocks(facts.invalidation))
|
|
47
54
|
return deny('INVALIDATION_INCOMPLETE');
|
|
48
|
-
if (facts.qualificationStale && facts.recommendedAction !== action) {
|
|
49
|
-
return deny('QUALIFICATION_STALE', ...(facts.qualificationStaleArtifacts ?? ['qualification audit is stale']));
|
|
50
|
-
}
|
|
51
55
|
if (facts.executionBusy)
|
|
52
56
|
return deny('EXECUTION_BUSY');
|
|
57
|
+
const pendingIntent = (facts.reworkIntents ?? []).find((intent) => intent.status === 'pending');
|
|
58
|
+
if (pendingIntent) {
|
|
59
|
+
const target = effectiveReworkTarget(pendingIntent.target, facts.pathState);
|
|
60
|
+
const requirementRestart = action === 'analysis' && trigger.reasonCode === 'new-requirement';
|
|
61
|
+
if (!requirementRestart && (target === 'pause' || target !== action)) {
|
|
62
|
+
return deny(target === 'pause' ? 'REWORK_PAUSED' : 'REWORK_INTENT_TARGET_MISMATCH', pendingIntent.intentId);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (facts.pathState && facts.pathState.status !== 'valid') {
|
|
66
|
+
return action === 'analysis'
|
|
67
|
+
? allow(`lifecycle-path-${facts.pathState.status}`)
|
|
68
|
+
: deny('LIFECYCLE_PATH_INVALID', facts.pathState.message);
|
|
69
|
+
}
|
|
70
|
+
if (facts.pathState?.status === 'valid'
|
|
71
|
+
&& ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code'].includes(action)
|
|
72
|
+
&& !pathIncludes(facts.pathState, action)) {
|
|
73
|
+
return deny('ARTIFACT_STAGE_NOT_SELECTED', `path=${facts.pathState.decision.path}`, `stage=${action}`);
|
|
74
|
+
}
|
|
75
|
+
if (facts.reworkClassificationRequired?.includes(action)
|
|
76
|
+
&& !(facts.reworkIntents ?? []).some((intent) => intent.status === 'pending')) {
|
|
77
|
+
return deny('REWORK_CLASSIFICATION_REQUIRED', `stage=${action}`);
|
|
78
|
+
}
|
|
53
79
|
const implicit = trigger.explicitRequest === false;
|
|
54
80
|
if (action === 'analysis') {
|
|
55
81
|
if (!implicit || hasArtifact(facts, 'analysis') || facts.recommendedAction === 'analysis')
|
|
@@ -60,6 +86,9 @@ function canStart(action, facts, trigger) {
|
|
|
60
86
|
return hasArtifact(facts, 'analysis') ? allow('analysis-artifact') : deny('ANALYSIS_ARTIFACT_REQUIRED');
|
|
61
87
|
}
|
|
62
88
|
if (action === 'plan') {
|
|
89
|
+
if (facts.pathState?.status === 'valid' && facts.pathState.decision.path === 'standard') {
|
|
90
|
+
return hasArtifact(facts, 'analysis') ? allow('analysis-artifact') : deny('ANALYSIS_ARTIFACT_REQUIRED');
|
|
91
|
+
}
|
|
63
92
|
if (!hasArtifact(facts, 'review-analysis'))
|
|
64
93
|
return deny('ANALYSIS_REVIEW_REQUIRED');
|
|
65
94
|
if (!reviewMatchesLatest(facts, 'analysis', 'review-analysis'))
|
|
@@ -75,6 +104,8 @@ function canStart(action, facts, trigger) {
|
|
|
75
104
|
}
|
|
76
105
|
if (action === 'code') {
|
|
77
106
|
if (trigger.implementationInput) {
|
|
107
|
+
if (facts.resolvedHumanDecisions?.code === 'implementation')
|
|
108
|
+
return allow('human-decision-implementation-input');
|
|
78
109
|
if (!hasArtifact(facts, 'review-code'))
|
|
79
110
|
return deny('CODE_REVIEW_REQUIRED');
|
|
80
111
|
if (!reviewMatchesLatest(facts, 'code', 'review-code'))
|
|
@@ -83,6 +114,20 @@ function canStart(action, facts, trigger) {
|
|
|
83
114
|
return deny('CODE_REVIEW_NOT_APPROVED');
|
|
84
115
|
return allow('implementation-input');
|
|
85
116
|
}
|
|
117
|
+
if (facts.pathState?.status === 'valid' && facts.pathState.decision.path === 'streamlined') {
|
|
118
|
+
if (!hasArtifact(facts, 'analysis'))
|
|
119
|
+
return deny('ANALYSIS_ARTIFACT_REQUIRED');
|
|
120
|
+
if (facts.unresolvedLedger.analysis > 0)
|
|
121
|
+
return deny('ANALYSIS_LEDGER_BLOCKED', `unresolved=${facts.unresolvedLedger.analysis}`);
|
|
122
|
+
return allow('analysis-path-approved');
|
|
123
|
+
}
|
|
124
|
+
if (facts.pathState?.status === 'valid' && facts.pathState.decision.path === 'standard') {
|
|
125
|
+
if (!hasArtifact(facts, 'plan'))
|
|
126
|
+
return deny('PLAN_ARTIFACT_REQUIRED');
|
|
127
|
+
if (facts.unresolvedLedger.plan > 0)
|
|
128
|
+
return deny('PLAN_LEDGER_BLOCKED', `unresolved=${facts.unresolvedLedger.plan}`);
|
|
129
|
+
return allow('plan-path-approved');
|
|
130
|
+
}
|
|
86
131
|
if (!hasArtifact(facts, 'review-plan'))
|
|
87
132
|
return deny('PLAN_REVIEW_REQUIRED');
|
|
88
133
|
if (!reviewMatchesLatest(facts, 'plan', 'review-plan'))
|
|
@@ -113,14 +158,6 @@ function latestArtifact(names) {
|
|
|
113
158
|
return names.map(parseArtifactName).filter((identity) => identity !== null)
|
|
114
159
|
.sort((left, right) => right.round - left.round || left.name.localeCompare(right.name))[0]?.name ?? null;
|
|
115
160
|
}
|
|
116
|
-
function reviewedInputName(content, expectedFamily) {
|
|
117
|
-
const header = content.split(/\r?\n/).findIndex((line) => /\*\*(?:审查输入|Review Input)\*\*[::]/.test(line));
|
|
118
|
-
if (header < 0)
|
|
119
|
-
return null;
|
|
120
|
-
const referenceBlock = content.split(/\r?\n/).slice(header, header + 12).join('\n');
|
|
121
|
-
const expected = new RegExp('`' + expectedFamily + '(?:-r[2-9]|-r[1-9]\\d+)?\\.md`');
|
|
122
|
-
return expected.exec(referenceBlock)?.[0].slice(1, -1) ?? null;
|
|
123
|
-
}
|
|
124
161
|
function reviewMatchesLatest(facts, source, review) {
|
|
125
162
|
const latestSource = latestArtifact(facts.artifacts[source] ?? []);
|
|
126
163
|
return Boolean(latestSource && facts.reviewedInputs?.[review] === latestSource);
|
|
@@ -133,30 +170,56 @@ function qualificationCandidateSnapshotMatches(audit, qualification) {
|
|
|
133
170
|
&& candidate.evidence === row.evidence && candidate.constraintIds.join(',') === row.constraintIds.join(','));
|
|
134
171
|
});
|
|
135
172
|
}
|
|
136
|
-
const QUALIFICATION_RECOVERY_ORDER = [
|
|
137
|
-
{ action: 'analysis', pattern: /^analysis(?:-r\d+)?\.md$/ },
|
|
138
|
-
{ action: 'review-analysis', pattern: /^review-analysis(?:-r\d+)?\.md$/ },
|
|
139
|
-
{ action: 'plan', pattern: /^plan(?:-r\d+)?\.md$/ },
|
|
140
|
-
{ action: 'review-plan', pattern: /^review-plan(?:-r\d+)?\.md$/ },
|
|
141
|
-
{ action: 'code', pattern: /^code(?:-r\d+)?\.md$/ },
|
|
142
|
-
{ action: 'review-code', pattern: /^review-code(?:-r\d+)?\.md$/ }
|
|
143
|
-
];
|
|
144
|
-
function qualificationRecoveryAction(facts) {
|
|
145
|
-
const stale = facts.qualificationStaleArtifacts ?? [];
|
|
146
|
-
return QUALIFICATION_RECOVERY_ORDER.find(({ pattern }) => stale.some((name) => pattern.test(name)))?.action ?? null;
|
|
147
|
-
}
|
|
148
173
|
function recommendNext(facts) {
|
|
149
|
-
if (facts.
|
|
150
|
-
|
|
151
|
-
return {
|
|
152
|
-
action,
|
|
153
|
-
reasonCode: 'QUALIFICATION_RECOVERY_REQUIRED',
|
|
154
|
-
evidence: facts.qualificationStaleArtifacts ?? ['qualification audit is stale']
|
|
155
|
-
};
|
|
174
|
+
if ((facts.reworkClassificationRequired?.length ?? 0) > 0
|
|
175
|
+
&& !(facts.reworkIntents ?? []).some((intent) => intent.status === 'pending')) {
|
|
176
|
+
return { action: null, reasonCode: 'REWORK_CLASSIFICATION_REQUIRED', evidence: facts.reworkClassificationRequired ?? [] };
|
|
156
177
|
}
|
|
157
178
|
const pendingIntent = (facts.reworkIntents ?? []).find((intent) => intent.status === 'pending');
|
|
158
|
-
if (pendingIntent)
|
|
159
|
-
|
|
179
|
+
if (pendingIntent) {
|
|
180
|
+
const target = effectiveReworkTarget(pendingIntent.target, facts.pathState);
|
|
181
|
+
return { action: target === 'pause' ? null : target, reasonCode: target === 'pause' ? 'REWORK_PAUSED' : 'REWORK_INTENT_PENDING', evidence: [pendingIntent.intentId, pendingIntent.findingId] };
|
|
182
|
+
}
|
|
183
|
+
if (facts.pathState && facts.pathState.status !== 'valid') {
|
|
184
|
+
return { action: 'analysis', reasonCode: facts.pathState.status === 'missing' ? 'LIFECYCLE_PATH_MISSING' : 'LIFECYCLE_PATH_INVALID', evidence: [facts.pathState.message] };
|
|
185
|
+
}
|
|
186
|
+
if (facts.pathState?.status === 'valid') {
|
|
187
|
+
for (const stage of facts.pathState.decision.stages) {
|
|
188
|
+
if (stage === 'analysis' && !hasArtifact(facts, stage))
|
|
189
|
+
return { action: stage, reasonCode: 'ANALYSIS_ARTIFACT_MISSING', evidence: ['analysis artifact is absent'] };
|
|
190
|
+
if (stage === 'review-analysis' && (!reviewMatchesLatest(facts, 'analysis', stage) || facts.reviews[stage] !== 'approved')) {
|
|
191
|
+
if (facts.reviews[stage] === 'changes-requested' && facts.resolvedHumanDecisions?.analysis === 'review') {
|
|
192
|
+
return { action: stage, reasonCode: 'HUMAN_DECISION_REVIEW_REQUIRED', evidence: ['analysis decision was resolved'] };
|
|
193
|
+
}
|
|
194
|
+
return facts.reviews[stage] === 'changes-requested'
|
|
195
|
+
? { action: 'analysis', reasonCode: 'ANALYSIS_REWORK_REQUIRED', evidence: ['analysis review is not approved'] }
|
|
196
|
+
: { action: stage, reasonCode: 'ANALYSIS_REVIEW_MISSING', evidence: ['analysis review does not bind the latest analysis artifact'] };
|
|
197
|
+
}
|
|
198
|
+
if (stage === 'plan' && !hasArtifact(facts, stage))
|
|
199
|
+
return { action: stage, reasonCode: 'PLAN_ARTIFACT_MISSING', evidence: ['plan artifact is absent'] };
|
|
200
|
+
if (stage === 'review-plan' && (!reviewMatchesLatest(facts, 'plan', stage) || facts.reviews[stage] !== 'approved')) {
|
|
201
|
+
if (facts.reviews[stage] === 'changes-requested' && facts.resolvedHumanDecisions?.plan === 'review') {
|
|
202
|
+
return { action: stage, reasonCode: 'HUMAN_DECISION_REVIEW_REQUIRED', evidence: ['plan decision was resolved'] };
|
|
203
|
+
}
|
|
204
|
+
return facts.reviews[stage] === 'changes-requested'
|
|
205
|
+
? { action: 'plan', reasonCode: 'PLAN_REWORK_REQUIRED', evidence: ['plan review is not approved'] }
|
|
206
|
+
: { action: stage, reasonCode: 'PLAN_REVIEW_MISSING', evidence: ['plan review does not bind the latest plan artifact'] };
|
|
207
|
+
}
|
|
208
|
+
if (stage === 'code' && !hasArtifact(facts, stage))
|
|
209
|
+
return { action: stage, reasonCode: 'CODE_ARTIFACT_MISSING', evidence: ['code artifact is absent'] };
|
|
210
|
+
if (stage === 'review-code' && (!reviewMatchesLatest(facts, 'code', stage) || facts.reviews[stage] !== 'approved')) {
|
|
211
|
+
if (facts.reviews[stage] === 'changes-requested' && facts.resolvedHumanDecisions?.code) {
|
|
212
|
+
return facts.resolvedHumanDecisions.code === 'implementation'
|
|
213
|
+
? { action: 'code', reasonCode: 'HUMAN_DECISION_IMPLEMENTATION_REQUIRED', evidence: ['code decision requires implementation'] }
|
|
214
|
+
: { action: stage, reasonCode: 'HUMAN_DECISION_REVIEW_REQUIRED', evidence: ['code decision requires no implementation'] };
|
|
215
|
+
}
|
|
216
|
+
return facts.reviews[stage] === 'changes-requested'
|
|
217
|
+
? { action: 'code', reasonCode: 'CODE_REWORK_REQUIRED', evidence: ['code review is not approved'] }
|
|
218
|
+
: { action: stage, reasonCode: 'CODE_REVIEW_MISSING', evidence: ['code review does not bind the latest code artifact'] };
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return { action: null, reasonCode: 'LIFECYCLE_REVIEWED', evidence: ['selected lifecycle path is complete'] };
|
|
222
|
+
}
|
|
160
223
|
if (!hasArtifact(facts, 'analysis'))
|
|
161
224
|
return { action: 'analysis', reasonCode: 'ANALYSIS_ARTIFACT_MISSING', evidence: ['analysis artifact is absent'] };
|
|
162
225
|
if (!hasArtifact(facts, 'review-analysis') || !reviewMatchesLatest(facts, 'analysis', 'review-analysis')) {
|
|
@@ -213,9 +276,12 @@ function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBu
|
|
|
213
276
|
const family = familyFor(name);
|
|
214
277
|
return !family || !isArtifactInvalidated(invalidation.document, family, name);
|
|
215
278
|
});
|
|
279
|
+
const allArtifactHashes = {};
|
|
280
|
+
for (const name of files)
|
|
281
|
+
allArtifactHashes[name] = sha256File(path.join(taskDir, name));
|
|
216
282
|
const artifactHashes = {};
|
|
217
283
|
for (const name of activeFiles)
|
|
218
|
-
artifactHashes[name] =
|
|
284
|
+
artifactHashes[name] = allArtifactHashes[name];
|
|
219
285
|
const artifacts = Object.fromEntries(artifactFamilies.map((family) => [
|
|
220
286
|
family, activeFiles.filter((name) => familyFor(name) === family)
|
|
221
287
|
]));
|
|
@@ -264,9 +330,12 @@ function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBu
|
|
|
264
330
|
continue;
|
|
265
331
|
const reviewContent = fs.readFileSync(path.join(taskDir, latest), 'utf8');
|
|
266
332
|
const expectedFamily = family === 'review-analysis' ? 'analysis' : family === 'review-plan' ? 'plan' : 'code';
|
|
267
|
-
const
|
|
268
|
-
const
|
|
269
|
-
if (input
|
|
333
|
+
const receipt = receiptForOutput(content, latest);
|
|
334
|
+
const input = receipt?.input;
|
|
335
|
+
if (input
|
|
336
|
+
&& parseArtifactName(input)?.family === expectedFamily
|
|
337
|
+
&& (artifacts[expectedFamily] ?? []).includes(input)
|
|
338
|
+
&& artifactHashes[input] === receipt.inputSha256)
|
|
270
339
|
reviewedInputs[family] = input;
|
|
271
340
|
const parsed = parseReviewSummary(reviewContent);
|
|
272
341
|
if (!parsed.ok)
|
|
@@ -275,11 +344,58 @@ function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBu
|
|
|
275
344
|
if (verdict.ok)
|
|
276
345
|
reviews[family] = verdict.verdict === 'Approved' ? 'approved' : verdict.verdict === 'Changes Requested' ? 'changes-requested' : 'rejected';
|
|
277
346
|
}
|
|
347
|
+
const reworkClassificationRequired = [];
|
|
348
|
+
const receipts = parseArtifactReceipts(content).rows;
|
|
349
|
+
for (const [reviewFamily, stage] of [['review-analysis', 'analysis'], ['review-plan', 'plan'], ['review-code', 'code']]) {
|
|
350
|
+
const cycles = receipts.filter((receipt) => receipt.event === `${reviewFamily}.completed` && files.includes(receipt.output)
|
|
351
|
+
&& allArtifactHashes[receipt.input] === receipt.inputSha256).map((receipt) => {
|
|
352
|
+
const parsed = parseReviewSummary(fs.readFileSync(path.join(taskDir, receipt.output), 'utf8'));
|
|
353
|
+
const verdict = parsed.ok ? resolveCanonicalVerdict(parsed.summary) : null;
|
|
354
|
+
return {
|
|
355
|
+
input: receipt.input,
|
|
356
|
+
output: receipt.output,
|
|
357
|
+
outputRound: parseArtifactName(receipt.output)?.round ?? 0,
|
|
358
|
+
inputRound: parseArtifactName(receipt.input)?.round ?? 0,
|
|
359
|
+
verdict: verdict?.ok ? verdict.verdict : null
|
|
360
|
+
};
|
|
361
|
+
}).sort((left, right) => left.inputRound - right.inputRound || left.outputRound - right.outputRound || left.output.localeCompare(right.output));
|
|
362
|
+
const latest = cycles.at(-1);
|
|
363
|
+
const trailingInputRounds = new Set();
|
|
364
|
+
for (let index = cycles.length - 1; index >= 0 && cycles[index].verdict === 'Changes Requested'; index -= 1) {
|
|
365
|
+
trailingInputRounds.add(cycles[index].inputRound);
|
|
366
|
+
}
|
|
367
|
+
const handled = latest && (rework.intents ?? []).some((intent) => intent.sourceArtifact === latest.output
|
|
368
|
+
&& intent.sourceSha256 === allArtifactHashes[latest.output]);
|
|
369
|
+
const latestActiveReview = latestArtifact(artifacts[reviewFamily] ?? []);
|
|
370
|
+
const latestActiveInput = latestArtifact(artifacts[stage] ?? []);
|
|
371
|
+
const latestCycleIsActive = latest?.output === latestActiveReview && latest.input === latestActiveInput;
|
|
372
|
+
if (latestCycleIsActive && latest.verdict === 'Changes Requested' && trailingInputRounds.size >= 2 && !handled)
|
|
373
|
+
reworkClassificationRequired.push(stage);
|
|
374
|
+
}
|
|
278
375
|
const unresolvedLedger = { analysis: 0, plan: 0, code: 0 };
|
|
279
376
|
if (ledger.present) {
|
|
280
377
|
for (const stage of ['analysis', 'plan', 'code'])
|
|
281
378
|
unresolvedLedger[stage] = summarizeLedgerStage(ledger.rows, stage).unresolved.length;
|
|
282
379
|
}
|
|
380
|
+
const latestAnalysis = latestArtifact(artifacts.analysis ?? []);
|
|
381
|
+
const pathState = latestAnalysis
|
|
382
|
+
? parseLifecyclePathDecision(fs.readFileSync(path.join(taskDir, latestAnalysis), 'utf8'), latestAnalysis, artifactHashes[latestAnalysis] ?? '')
|
|
383
|
+
: { status: 'missing', decision: null, message: 'analysis artifact is missing' };
|
|
384
|
+
const resolvedHumanDecisions = {};
|
|
385
|
+
const implementationInputs = parseImplementationInputs(content).rows;
|
|
386
|
+
for (const stage of ['analysis', 'plan', 'code']) {
|
|
387
|
+
const reviewFamily = `review-${stage}`;
|
|
388
|
+
const latestReview = latestArtifact(artifacts[reviewFamily] ?? []);
|
|
389
|
+
if (!latestReview || reviews[reviewFamily] !== 'changes-requested')
|
|
390
|
+
continue;
|
|
391
|
+
const latestHash = artifactHashes[latestReview];
|
|
392
|
+
const decision = rework.intents.find((intent) => intent.status === 'consumed'
|
|
393
|
+
&& intent.classification === 'human-decision' && intent.sourceArtifact === latestReview && intent.sourceSha256 === latestHash);
|
|
394
|
+
if (!decision)
|
|
395
|
+
continue;
|
|
396
|
+
const implementationInput = implementationInputs.find((input) => input.ledgerId === decision.findingId);
|
|
397
|
+
resolvedHumanDecisions[stage] = stage === 'code' && implementationInput?.status === 'pending' ? 'implementation' : 'review';
|
|
398
|
+
}
|
|
283
399
|
const facts = {
|
|
284
400
|
taskState, currentStep: String(metadata.current_step ?? ''), artifacts, reviews,
|
|
285
401
|
staleArtifacts, reviewedInputs, artifactHashes,
|
|
@@ -288,7 +404,10 @@ function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBu
|
|
|
288
404
|
executionBusy: executionBusy || hasOpenLifecycleExecution(content),
|
|
289
405
|
recommendedAction: null,
|
|
290
406
|
qualificationStale: qualificationStaleArtifacts.length > 0,
|
|
291
|
-
qualificationStaleArtifacts
|
|
407
|
+
qualificationStaleArtifacts,
|
|
408
|
+
pathState,
|
|
409
|
+
reworkClassificationRequired,
|
|
410
|
+
resolvedHumanDecisions
|
|
292
411
|
};
|
|
293
412
|
facts.recommendedAction = recommendNext(facts).action;
|
|
294
413
|
return { ok: true, facts };
|
|
@@ -297,5 +416,5 @@ function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBu
|
|
|
297
416
|
return { ok: false, code: 'TASK_CAPABILITY_FACTS_INVALID', message: error instanceof Error ? error.message : String(error) };
|
|
298
417
|
}
|
|
299
418
|
}
|
|
300
|
-
export { buildLifecycleFacts, canStart, recommendNext };
|
|
419
|
+
export { buildLifecycleFacts, canStart, effectiveReworkTarget, recommendNext };
|
|
301
420
|
//# sourceMappingURL=capabilities.js.map
|
|
@@ -325,7 +325,11 @@ function renderStatus(model) {
|
|
|
325
325
|
['processed', `${model.invalidation?.processed ?? 0}/${model.invalidation?.total ?? 0}`],
|
|
326
326
|
['pending_intents', String(model.invalidation?.pendingIntents ?? 0)]
|
|
327
327
|
]));
|
|
328
|
-
lines.push('', 'Recommendation', ...renderPairs([
|
|
328
|
+
lines.push('', 'Recommendation', ...renderPairs([
|
|
329
|
+
['path', model.lifecyclePath ?? DASH],
|
|
330
|
+
['action', model.recommendation ?? DASH],
|
|
331
|
+
['reason', model.recommendationReason ?? DASH]
|
|
332
|
+
]));
|
|
329
333
|
lines.push('', 'Orchestration', ...renderPairs([
|
|
330
334
|
['status', model.orchestration.status],
|
|
331
335
|
['run_id', model.orchestration.runId],
|
|
@@ -411,7 +415,8 @@ function buildFromResolved(input) {
|
|
|
411
415
|
pendingIntents
|
|
412
416
|
};
|
|
413
417
|
const facts = buildLifecycleFacts(input.taskDir, content, fm.status ?? 'active');
|
|
414
|
-
const
|
|
418
|
+
const next = facts.ok ? recommendNext(facts.facts) : null;
|
|
419
|
+
const recommendation = next?.action ?? DASH;
|
|
415
420
|
return {
|
|
416
421
|
taskId: input.taskId,
|
|
417
422
|
shortId: input.shortId ?? loadShortIdByTaskId(input.repoRoot).get(input.taskId) ?? DASH,
|
|
@@ -424,6 +429,8 @@ function buildFromResolved(input) {
|
|
|
424
429
|
orchestration: collectOrchestration(input.taskDir),
|
|
425
430
|
invalidation: invalidationInfo,
|
|
426
431
|
recommendation,
|
|
432
|
+
recommendationReason: next?.reasonCode ?? 'TASK_CAPABILITY_FACTS_INVALID',
|
|
433
|
+
lifecyclePath: facts.ok && facts.facts.pathState?.status === 'valid' ? facts.facts.pathState.decision.path : facts.ok ? facts.facts.pathState?.status ?? DASH : DASH,
|
|
427
434
|
git: collectGit(fm.branch ?? '', run)
|
|
428
435
|
};
|
|
429
436
|
}
|
|
@@ -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
|
-
import { applyTaskFinalization } from './finalization.js';
|
|
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)
|
|
@@ -590,10 +434,13 @@ export function dispatchTaskControlOperation(context, operation) {
|
|
|
590
434
|
}
|
|
591
435
|
if (operation.family === 'task-finalization') {
|
|
592
436
|
operationTaskId(context, operation.request.taskRef);
|
|
593
|
-
|
|
437
|
+
const finalize = context.source === 'sandbox-executor' ? commitPreparedTaskFinalization : applyTaskFinalization;
|
|
438
|
+
return finalize(operation.request, {
|
|
594
439
|
repoRoot: context.repoRoot,
|
|
595
440
|
...(context.source === 'sandbox-executor' ? {
|
|
596
|
-
controlBinding: { generation: context.generation, requestId: context.requestId }
|
|
441
|
+
controlBinding: { generation: context.generation, requestId: context.requestId },
|
|
442
|
+
handoffDirectory: context.handoffDirectory,
|
|
443
|
+
manifestPath: context.manifestPath
|
|
597
444
|
} : {}),
|
|
598
445
|
preflight: (request, options) => verifyTaskEvent({ ...request, event: 'complete-task.hard-preflight' }, options)
|
|
599
446
|
});
|
|
@@ -628,7 +475,7 @@ function operationInvalid(message) {
|
|
|
628
475
|
}
|
|
629
476
|
const LIFECYCLE_FLAGS = new Set([
|
|
630
477
|
'--agent', '--reason', '--unblock-condition', '--note', '--alert-number', '--staging-dir', '--issue-number',
|
|
631
|
-
'--auto', '--
|
|
478
|
+
'--auto', '--dry-run'
|
|
632
479
|
]);
|
|
633
480
|
const FINALIZATION_FLAGS = new Set(['--agent']);
|
|
634
481
|
const ORCHESTRATION_FLAGS = new Set([
|
|
@@ -699,9 +546,6 @@ export function parseTaskControlOperation(family, args) {
|
|
|
699
546
|
...(value(values, '--unblock-condition') ? { unblockCondition: value(values, '--unblock-condition') } : {}),
|
|
700
547
|
...(value(values, '--note') ? { note: value(values, '--note') } : {}),
|
|
701
548
|
...(value(values, '--staging-dir') ? { stagingDir: value(values, '--staging-dir') } : {}),
|
|
702
|
-
...(value(values, '--override-ticket') ? { overrideTicket: value(values, '--override-ticket') } : {}),
|
|
703
|
-
...(value(values, '--override-target') ? { overrideTarget: value(values, '--override-target') } : {}),
|
|
704
|
-
...(value(values, '--override-scope') ? { overrideScope: value(values, '--override-scope') } : {}),
|
|
705
549
|
...(value(values, '--alert-number') ? { alertNumber: Number(value(values, '--alert-number')) } : {}),
|
|
706
550
|
...(value(values, '--issue-number') ? { issueNumber: Number(value(values, '--issue-number')) } : {}),
|
|
707
551
|
...(values['--auto'] === true ? { auto: true } : {}),
|
|
@@ -6,7 +6,7 @@ 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 {
|
|
9
|
+
import { consumeHumanDecisionReworkIntents, parseReworkIntentDocument, reworkIntentMutation } from './rework-intent.js';
|
|
10
10
|
const DECISION_ALIASES = ['人工裁决', 'Human Rulings', 'Human Decisions', 'Human Decision'];
|
|
11
11
|
const ACTIVITY_ALIASES = ['活动日志', 'Activity Log'];
|
|
12
12
|
function failed(code, message, taskId = null, ledgerId = null) {
|
|
@@ -57,8 +57,7 @@ function applyHumanDecision(request, options = {}) {
|
|
|
57
57
|
const resolved = resolveTaskContext(request.taskRef, { repoRoot: options.repoRoot });
|
|
58
58
|
if (!resolved.ok)
|
|
59
59
|
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
60
|
-
|
|
61
|
-
if (resolved.state !== 'active' && !stateOverride)
|
|
60
|
+
if (resolved.state !== 'active')
|
|
62
61
|
return failed('TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
|
|
63
62
|
let content;
|
|
64
63
|
let rows;
|
|
@@ -160,7 +159,18 @@ function applyHumanDecision(request, options = {}) {
|
|
|
160
159
|
kind: 'section', aliases: ACTIVITY_ALIASES, heading: activity.heading,
|
|
161
160
|
body: appendActivityEntry(activity, { time: metadata.timestamp, step: 'Human Decision', agent: 'human', note: `${row.id} decided → ${recordId}` })
|
|
162
161
|
});
|
|
163
|
-
|
|
162
|
+
try {
|
|
163
|
+
const parsedRework = parseReworkIntentDocument(content);
|
|
164
|
+
if (!parsedRework.ok)
|
|
165
|
+
return failed('DECISION_DOCUMENT_INVALID', parsedRework.message, resolved.taskId, row.id);
|
|
166
|
+
const consumed = consumeHumanDecisionReworkIntents(parsedRework.intents, row.id, metadata.timestamp);
|
|
167
|
+
if (consumed.changed)
|
|
168
|
+
mutations.push(reworkIntentMutation(content, consumed.intents));
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
return failed('DECISION_DOCUMENT_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId, row.id);
|
|
172
|
+
}
|
|
173
|
+
const writeResult = writeTask({ taskRef: resolved.taskId, expectedState: 'active', mutations, dryRun: request.dryRun }, {
|
|
164
174
|
...options,
|
|
165
175
|
taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state },
|
|
166
176
|
metadataProvider: () => metadata
|