@fitlab-ai/agent-infra 0.11.2 → 0.11.4
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 +7 -2
- package/dist/lib/agent-clients/adapter.js +3 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +234 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +8 -35
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +1 -34
- package/dist/lib/agent-clients/adapters/codex.js +10 -0
- package/dist/lib/internal/cli-route-inventory.js +1 -1
- package/dist/lib/internal/manual-validation.js +22 -27
- package/dist/lib/internal/sandbox-control.js +1 -43
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-finalization.js +41 -1
- package/dist/lib/internal/task-ledger.js +13 -5
- package/dist/lib/internal/task-lifecycle.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +7 -2
- package/dist/lib/platform/completion-backfill.js +63 -17
- package/dist/lib/platform/issue-comments.js +2 -31
- package/dist/lib/platform/pr-summary.js +7 -2
- package/dist/lib/sandbox/control/client.js +3 -44
- package/dist/lib/sandbox/control/executor.js +14 -79
- package/dist/lib/sandbox/control/protocol.js +5 -64
- package/dist/lib/sandbox/control/server.js +49 -343
- package/dist/lib/sandbox/control/state.js +1 -7
- package/dist/lib/sandbox/control/task-view.js +1 -2
- package/dist/lib/sandbox/task-cleanup.js +13 -20
- 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 +1 -26
- package/dist/lib/task/artifact-receipts.js +4 -1
- package/dist/lib/task/artifact-schema.js +1 -0
- 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 +37 -38
- package/dist/lib/task/control-recovery.js +3 -42
- package/dist/lib/task/decision-intents.js +12 -0
- package/dist/lib/task/delegation-receipts.js +2 -2
- package/dist/lib/task/delivery.js +1 -43
- package/dist/lib/task/events.js +80 -64
- package/dist/lib/task/finalization-artifacts.js +1 -3
- package/dist/lib/task/finalization-state.js +1 -1
- package/dist/lib/task/finalization.js +319 -234
- package/dist/lib/task/ledger-intents.js +157 -15
- package/dist/lib/task/lifecycle-path.js +75 -0
- package/dist/lib/task/lifecycle.js +11 -1
- package/dist/lib/task/local-artifact-finalization.js +1 -20
- package/dist/lib/task/manual-validation-receipt.js +8 -3
- package/dist/lib/task/orchestration.js +29 -13
- package/dist/lib/task/review-finalization.js +3 -16
- package/dist/lib/task/rework-intent.js +70 -4
- package/dist/lib/task/verification-engine.js +20 -30
- package/dist/lib/task/verification.js +1 -0
- package/dist/lib/task/workflow-warning-intents.js +21 -6
- package/dist/lib/task/workspace-snapshot.js +7 -2
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapter.ts +46 -0
- package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +297 -0
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +13 -33
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +1 -44
- package/lib/agent-clients/adapters/codex.ts +10 -0
- package/lib/internal/cli-route-inventory.ts +1 -1
- package/lib/internal/manual-validation.ts +22 -26
- package/lib/internal/sandbox-control.ts +0 -42
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-finalization.ts +40 -1
- package/lib/internal/task-ledger.ts +12 -5
- package/lib/internal/task-lifecycle.ts +2 -2
- package/lib/internal/task-operation-registry.ts +8 -4
- package/lib/platform/completion-backfill.ts +78 -16
- package/lib/platform/issue-comments.ts +1 -35
- package/lib/platform/pr-summary.ts +10 -2
- package/lib/sandbox/control/client.ts +5 -68
- package/lib/sandbox/control/executor.ts +14 -81
- package/lib/sandbox/control/protocol.ts +7 -83
- package/lib/sandbox/control/server.ts +47 -362
- package/lib/sandbox/control/state.ts +1 -9
- package/lib/sandbox/control/task-view.ts +1 -2
- package/lib/sandbox/task-cleanup.ts +12 -20
- package/lib/task/artifact-command.ts +14 -4
- package/lib/task/artifact-lifecycle.ts +236 -85
- package/lib/task/artifact-operations.ts +1 -26
- package/lib/task/artifact-receipts.ts +4 -1
- package/lib/task/artifact-schema.ts +1 -0
- 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 +41 -48
- package/lib/task/control-recovery.ts +3 -43
- package/lib/task/decision-intents.ts +9 -0
- package/lib/task/delegation-receipts.ts +2 -2
- package/lib/task/delivery.ts +1 -40
- package/lib/task/events.ts +82 -64
- package/lib/task/finalization-artifacts.ts +1 -3
- package/lib/task/finalization-state.ts +1 -1
- package/lib/task/finalization.ts +310 -216
- package/lib/task/ledger-intents.ts +155 -15
- package/lib/task/lifecycle-path.ts +98 -0
- package/lib/task/lifecycle.ts +15 -2
- package/lib/task/local-artifact-finalization.ts +2 -25
- package/lib/task/manual-validation-receipt.ts +8 -2
- package/lib/task/orchestration.ts +56 -27
- package/lib/task/review-finalization.ts +3 -16
- package/lib/task/rework-intent.ts +75 -5
- package/lib/task/verification-engine.ts +19 -30
- package/lib/task/verification.ts +2 -1
- package/lib/task/workflow-warning-intents.ts +18 -6
- package/lib/task/workspace-snapshot.ts +3 -2
- package/package.json +1 -1
- package/templates/.agents/rules/lifecycle-orchestration.en.md +19 -5
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +19 -5
- 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 +11 -4
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +11 -4
- 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 +1 -1
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-task/SKILL.en.md +7 -8
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +7 -8
- 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-code/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -1
- 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/run-manual-validation/SKILL.en.md +2 -2
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/run-task/SKILL.en.md +9 -8
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -8
- 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/sandbox/control/finalization-response.js +0 -35
- package/dist/lib/sandbox/control/task-workflow.js +0 -74
- package/dist/lib/sandbox/control/workflow-executor.js +0 -142
- package/dist/lib/task/lifecycle-recovery.js +0 -549
- package/dist/lib/task/recovery-warning.js +0 -19
- package/dist/lib/task/workflow-command.js +0 -21
- package/dist/lib/task/workflow-dispatch.js +0 -213
- package/lib/sandbox/control/finalization-response.ts +0 -37
- package/lib/sandbox/control/task-workflow.ts +0 -95
- package/lib/sandbox/control/workflow-executor.ts +0 -156
- package/lib/task/lifecycle-recovery.ts +0 -691
- package/lib/task/recovery-warning.ts +0 -21
- package/lib/task/workflow-command.ts +0 -22
- package/lib/task/workflow-dispatch.ts +0 -214
|
@@ -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
|
}
|
|
@@ -3,12 +3,11 @@ import crypto from 'node:crypto';
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import { normalizeAgentToken } from '../agent-clients/tokens.js';
|
|
5
5
|
import { isAgentClientId } from '../agent-clients/types.js';
|
|
6
|
+
import { getAgentClientAdapter } from '../agent-clients/registry.js';
|
|
6
7
|
import { consumeHumanOverride, failureId, overrideDryRunConflict } from './human-override.js';
|
|
7
8
|
import { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, advanceOrchestration, awaitOrchestrationDelegationActivation, beginOrResumeOrchestration, dispatchOrchestrationDelegation, OrchestrationStateError, pauseOrchestration, prepareOrchestrationDelegation, recoverPreparedOrchestrationDelegation, routeOrchestration, sealMatchingOrchestrationDelegation, sealOrchestrationDelegation, statusOrchestration } from './orchestration.js';
|
|
8
|
-
import {
|
|
9
|
-
import { applyTaskFinalization } from './finalization.js';
|
|
9
|
+
import { applyTaskFinalization, commitPreparedTaskFinalization } from './finalization.js';
|
|
10
10
|
import { applyTaskLifecycle, taskLifecycleFailure } from './lifecycle.js';
|
|
11
|
-
import { recoverStartedLifecycleUnderLock, recoveryFailure } from './lifecycle-recovery.js';
|
|
12
11
|
import { locateActivityLog } from './activity-log.js';
|
|
13
12
|
import { resolveTaskRef, TASK_ID_RE } from './resolve-ref.js';
|
|
14
13
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
@@ -407,7 +406,6 @@ export function createSandboxExecutorExecutionContext(params) {
|
|
|
407
406
|
generation: params.generation,
|
|
408
407
|
manifestPath: absolute('manifestPath', params.manifestPath),
|
|
409
408
|
requestId: params.requestId,
|
|
410
|
-
...(params.diagnosticLog === undefined ? {} : { diagnosticLog: params.diagnosticLog }),
|
|
411
409
|
...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) }),
|
|
412
410
|
...(params.lifecycleRecoveryAttestation === undefined ? {} : {
|
|
413
411
|
lifecycleRecoveryAttestation: params.lifecycleRecoveryAttestation === null
|
|
@@ -450,11 +448,7 @@ function operationTaskId(context, taskRef) {
|
|
|
450
448
|
function domainOptions(context, options) {
|
|
451
449
|
return {
|
|
452
450
|
...options,
|
|
453
|
-
repoRoot: context.repoRoot
|
|
454
|
-
...(context.source === 'sandbox-executor' && context.diagnosticLog !== undefined
|
|
455
|
-
? { diagnosticLog: context.diagnosticLog }
|
|
456
|
-
: {}),
|
|
457
|
-
...(context.source === 'sandbox-executor' ? { gitWorktreeRoot: context.worktreeRoot } : {})
|
|
451
|
+
repoRoot: context.repoRoot
|
|
458
452
|
};
|
|
459
453
|
}
|
|
460
454
|
function lifecycleTaskId(request, repoRoot) {
|
|
@@ -475,7 +469,17 @@ async function applyLifecycleWithAuthority(context, request) {
|
|
|
475
469
|
}
|
|
476
470
|
const execute = async () => {
|
|
477
471
|
if (request.intent === 'recover-started') {
|
|
478
|
-
|
|
472
|
+
const adapter = isAgentClientId(request.agent)
|
|
473
|
+
? getAgentClientAdapter(request.agent).orchestrationAdapter
|
|
474
|
+
: undefined;
|
|
475
|
+
if (!adapter?.recoverStarted) {
|
|
476
|
+
return {
|
|
477
|
+
status: 'no-op', changed: false, targetState: 'active',
|
|
478
|
+
requestRef: request.taskRef, intent: 'recover-started', taskId: null,
|
|
479
|
+
receiptId: null, childId: null, error: null
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
return adapter.recoverStarted(request, { repoRoot: context.repoRoot });
|
|
479
483
|
}
|
|
480
484
|
const lifecycleResult = applyTaskLifecycle(request, { repoRoot: context.repoRoot });
|
|
481
485
|
if (lifecycleResult.status !== 'failed' || !request.overrideTicket)
|
|
@@ -511,7 +515,12 @@ async function applyLifecycleWithAuthority(context, request) {
|
|
|
511
515
|
if (!(error instanceof TaskExecutionLockError))
|
|
512
516
|
throw error;
|
|
513
517
|
if (request.intent === 'recover-started') {
|
|
514
|
-
return
|
|
518
|
+
return {
|
|
519
|
+
status: 'owner-unknown', changed: false, targetState: 'active',
|
|
520
|
+
requestRef: request.taskRef, intent: 'recover-started', taskId,
|
|
521
|
+
receiptId: null, childId: null,
|
|
522
|
+
error: { code: error.code, message: error.message }
|
|
523
|
+
};
|
|
515
524
|
}
|
|
516
525
|
return taskLifecycleFailure(request, { code: error.code, message: error.message }, taskId);
|
|
517
526
|
}
|
|
@@ -536,8 +545,10 @@ function orchestration(context, operation) {
|
|
|
536
545
|
requestedModel: input.requestedModel,
|
|
537
546
|
requestedReasoningEffort: input.requestedReasoningEffort,
|
|
538
547
|
};
|
|
539
|
-
|
|
540
|
-
|
|
548
|
+
const prepareDelegation = getAgentClientAdapter(prepareInput.client)
|
|
549
|
+
.orchestrationAdapter?.prepareDelegation;
|
|
550
|
+
if (prepareDelegation) {
|
|
551
|
+
return prepareDelegation(operation.taskRef, prepareInput, {
|
|
541
552
|
repoRoot: context.repoRoot,
|
|
542
553
|
orchestrationOptions: options,
|
|
543
554
|
});
|
|
@@ -579,7 +590,8 @@ export function dispatchTaskControlOperation(context, operation) {
|
|
|
579
590
|
}
|
|
580
591
|
if (operation.family === 'task-finalization') {
|
|
581
592
|
operationTaskId(context, operation.request.taskRef);
|
|
582
|
-
|
|
593
|
+
const finalize = context.source === 'sandbox-executor' ? commitPreparedTaskFinalization : applyTaskFinalization;
|
|
594
|
+
return finalize(operation.request, {
|
|
583
595
|
repoRoot: context.repoRoot,
|
|
584
596
|
...(context.source === 'sandbox-executor' ? {
|
|
585
597
|
controlBinding: { generation: context.generation, requestId: context.requestId }
|
|
@@ -617,7 +629,7 @@ function operationInvalid(message) {
|
|
|
617
629
|
}
|
|
618
630
|
const LIFECYCLE_FLAGS = new Set([
|
|
619
631
|
'--agent', '--reason', '--unblock-condition', '--note', '--alert-number', '--staging-dir', '--issue-number',
|
|
620
|
-
'--
|
|
632
|
+
'--auto', '--override-ticket', '--override-target', '--override-scope', '--dry-run'
|
|
621
633
|
]);
|
|
622
634
|
const FINALIZATION_FLAGS = new Set(['--agent']);
|
|
623
635
|
const ORCHESTRATION_FLAGS = new Set([
|
|
@@ -626,13 +638,13 @@ const ORCHESTRATION_FLAGS = new Set([
|
|
|
626
638
|
'--parent-id', '--before-fingerprint', '--stage', '--round', '--artifact', '--role',
|
|
627
639
|
'--native-agent', '--child-id', '--spawn-mode', '--actual-model', '--actual-reasoning-effort',
|
|
628
640
|
'--model-fallback-reason', '--reasoning-effort-fallback-reason', '--exit-code', '--after-fingerprint',
|
|
629
|
-
'--changed-paths', '--code', '--message', '--recoverable'
|
|
641
|
+
'--changed-paths', '--code', '--message', '--recoverable'
|
|
630
642
|
]);
|
|
631
643
|
function parseValues(args, start, flags) {
|
|
632
644
|
const values = {};
|
|
633
645
|
for (let index = start; index < args.length; index += 1) {
|
|
634
646
|
const flag = args[index];
|
|
635
|
-
if (flag === '--dry-run') {
|
|
647
|
+
if (flag === '--dry-run' || flag === '--auto') {
|
|
636
648
|
if (!flags.has(flag))
|
|
637
649
|
operationInvalid(`unknown option '${flag}'`);
|
|
638
650
|
if (values[flag] !== undefined)
|
|
@@ -693,31 +705,21 @@ export function parseTaskControlOperation(family, args) {
|
|
|
693
705
|
...(value(values, '--override-scope') ? { overrideScope: value(values, '--override-scope') } : {}),
|
|
694
706
|
...(value(values, '--alert-number') ? { alertNumber: Number(value(values, '--alert-number')) } : {}),
|
|
695
707
|
...(value(values, '--issue-number') ? { issueNumber: Number(value(values, '--issue-number')) } : {}),
|
|
696
|
-
...(
|
|
697
|
-
...(value(values, '--round') ? { round: Number(value(values, '--round')) } : {}),
|
|
698
|
-
...(value(values, '--artifact') ? { artifact: value(values, '--artifact') } : {}),
|
|
708
|
+
...(values['--auto'] === true ? { auto: true } : {}),
|
|
699
709
|
...(values['--dry-run'] === true ? { dryRun: true } : {})
|
|
700
710
|
};
|
|
701
711
|
if (intent === 'recover-started') {
|
|
702
|
-
const recoveryFlags = new Set(['--agent', '--
|
|
712
|
+
const recoveryFlags = new Set(['--agent', '--auto']);
|
|
703
713
|
for (const flag of Object.keys(values)) {
|
|
704
714
|
if (!recoveryFlags.has(flag))
|
|
705
715
|
operationInvalid(`option '${flag}' is not supported for recover-started`);
|
|
706
716
|
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
operationInvalid('--stage must be a supported lifecycle stage');
|
|
711
|
-
}
|
|
712
|
-
const round = Number(value(values, '--round'));
|
|
713
|
-
if (!Number.isSafeInteger(round) || round < 1)
|
|
714
|
-
operationInvalid('--round must be a positive integer');
|
|
715
|
-
if (value(values, '--reason').includes('\n') || value(values, '--reason').includes('\r')) {
|
|
716
|
-
operationInvalid('--reason must be a single line');
|
|
717
|
-
}
|
|
717
|
+
if (values['--auto'] !== true)
|
|
718
|
+
operationInvalid('--auto is required for recover-started');
|
|
719
|
+
return { family, request: input };
|
|
718
720
|
}
|
|
719
|
-
else if (values['--
|
|
720
|
-
operationInvalid('--
|
|
721
|
+
else if (values['--auto'] !== undefined) {
|
|
722
|
+
operationInvalid('--auto is only supported for recover-started');
|
|
721
723
|
}
|
|
722
724
|
return { family, request: input };
|
|
723
725
|
}
|
|
@@ -820,9 +822,6 @@ export function parseTaskControlOperation(family, args) {
|
|
|
820
822
|
input.message = value(values, '--message');
|
|
821
823
|
input.recoverable = recoverable === 'true';
|
|
822
824
|
}
|
|
823
|
-
|
|
824
|
-
? undefined
|
|
825
|
-
: { gitWorktreeRoot: value(values, '--git-worktree-root') };
|
|
826
|
-
return { family, taskRef, intent: parsedIntent, input, ...(options ? { options } : {}) };
|
|
825
|
+
return { family, taskRef, intent: parsedIntent, input };
|
|
827
826
|
}
|
|
828
827
|
//# sourceMappingURL=control-authority.js.map
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { isCompletionEvidence } from './orchestration.js';
|
|
3
|
-
import { TASK_WORKFLOW_OPERATIONS } from './workflow-command.js';
|
|
4
|
-
import { sameRecoveryWarning } from './recovery-warning.js';
|
|
5
2
|
/** Normalize the command result once at the control transport boundary. */
|
|
6
3
|
export function parseControlOutput(output) {
|
|
7
4
|
if (output === null)
|
|
@@ -20,20 +17,10 @@ export const SANDBOX_CONTROL_REQUIRED_COMPLETION_PHASES = Object.freeze([
|
|
|
20
17
|
'completed', 'evidence-written', 'publish-authorized'
|
|
21
18
|
]);
|
|
22
19
|
const LIFECYCLE_INTENTS = ['block', 'activate', 'cancel', 'complete', 'close-codescan', 'close-dependabot', 'restore', 'recover-started'];
|
|
23
|
-
const ORCHESTRATION_INTENTS = [
|
|
24
|
-
'begin-or-resume', 'route.read', 'route.clean-completion', 'status', 'prepare', 'dispatch',
|
|
25
|
-
'await-activation', 'recover-prepared', 'hook-start', 'hook-stop', 'advance', 'pause'
|
|
26
|
-
];
|
|
27
20
|
export const SANDBOX_CONTROL_RECOVERY_OPERATIONS = Object.freeze([
|
|
28
21
|
...LIFECYCLE_INTENTS.map((intent) => ({ family: 'task-lifecycle', intent, class: 'lifecycle-mutation' })),
|
|
29
22
|
{ family: 'task-finalization', intent: 'complete', class: 'finalization' },
|
|
30
|
-
...ORCHESTRATION_INTENTS.map((intent) => ({
|
|
31
|
-
family: 'task-orchestration',
|
|
32
|
-
intent,
|
|
33
|
-
class: intent === 'route.clean-completion' ? 'route.clean-completion' : intent === 'route.read' || intent === 'status' ? 'read-only' : 'orchestration'
|
|
34
|
-
})),
|
|
35
23
|
{ family: 'task-create', intent: 'create', class: 'task-create' },
|
|
36
|
-
...TASK_WORKFLOW_OPERATIONS.map((intent) => ({ family: 'task-workflow', intent, class: intent === 'artifact-inspect' || intent === 'decision-next-id' ? 'read-only' : 'workflow' })),
|
|
37
24
|
...['open', 'close', 'verify'].map((intent) => ({ family: 'codex-controller', intent, class: 'codex-controller' }))
|
|
38
25
|
]);
|
|
39
26
|
export function digestControlRecoveryIntent(family, intent) {
|
|
@@ -53,30 +40,8 @@ function domainEvidenceMatches(operation, result, domain) {
|
|
|
53
40
|
return false;
|
|
54
41
|
if (domain.consistent !== true)
|
|
55
42
|
return false;
|
|
56
|
-
if (operation.
|
|
57
|
-
|
|
58
|
-
const snapshot = domain.snapshot;
|
|
59
|
-
const lastReviewedCommit = domain.lastReviewedCommit;
|
|
60
|
-
return result.status === 'completed'
|
|
61
|
-
&& domain.status === 'completed'
|
|
62
|
-
&& domain.pendingDelegation === null
|
|
63
|
-
&& isCompletionEvidence(completion)
|
|
64
|
-
&& snapshot?.head === completion.head
|
|
65
|
-
&& snapshot.headTree === completion.headTree
|
|
66
|
-
&& snapshot.worktreeTree === completion.worktreeTree
|
|
67
|
-
&& lastReviewedCommit === completion.lastReviewedCommit;
|
|
68
|
-
}
|
|
69
|
-
if (operation.family === 'task-lifecycle' && operation.intent === 'recover-started') {
|
|
70
|
-
const retryRequired = domain.recoveryState === 'retry-required'
|
|
71
|
-
&& sameRecoveryWarning(domain.warning, result.warning);
|
|
72
|
-
return result.targetState === 'active'
|
|
73
|
-
&& ['applied', 'no-op'].includes(String(result.status))
|
|
74
|
-
&& domain.recovery === true
|
|
75
|
-
&& domain.targetState === 'active'
|
|
76
|
-
&& (domain.recoveryState === 'released' || retryRequired);
|
|
77
|
-
}
|
|
78
|
-
if (operation.class === 'read-only')
|
|
79
|
-
return result.changed === false && domain.snapshotValid === true;
|
|
43
|
+
if (operation.family === 'task-lifecycle' && operation.intent === 'recover-started')
|
|
44
|
+
return false;
|
|
80
45
|
return true;
|
|
81
46
|
}
|
|
82
47
|
export function classifySandboxControlRecovery(input) {
|
|
@@ -127,11 +92,7 @@ export function classifySandboxControlRecovery(input) {
|
|
|
127
92
|
return {
|
|
128
93
|
outcome: 'success',
|
|
129
94
|
responseReconstructable: true,
|
|
130
|
-
reasonCode:
|
|
131
|
-
? 'RECOVERY_ROUTE_COMPLETION_NOOP'
|
|
132
|
-
: input.operation.intent === 'recover-started' && input.domain?.recoveryState === 'retry-required'
|
|
133
|
-
? 'RECOVERY_RELEASE_RETRY_REQUIRED'
|
|
134
|
-
: 'RECOVERY_TERMINAL_AND_DOMAIN_MATCH'
|
|
95
|
+
reasonCode: 'RECOVERY_TERMINAL_AND_DOMAIN_MATCH'
|
|
135
96
|
};
|
|
136
97
|
}
|
|
137
98
|
export function operationRecoveryBinding(requestId, generation, taskId, family, intent) {
|
|
@@ -7,6 +7,7 @@ import { resolveTaskContext } from './resolve-ref.js';
|
|
|
7
7
|
import { extractSection, extractSubSection, findSectionHeading } from './sections.js';
|
|
8
8
|
import { captureTaskWriteMetadata, writeTask } from './write.js';
|
|
9
9
|
import { allowsManualOverride } from './guard-override.js';
|
|
10
|
+
import { consumeHumanDecisionReworkIntents, parseReworkIntentDocument, reworkIntentMutation } from './rework-intent.js';
|
|
10
11
|
const DECISION_ALIASES = ['人工裁决', 'Human Rulings', 'Human Decisions', 'Human Decision'];
|
|
11
12
|
const ACTIVITY_ALIASES = ['活动日志', 'Activity Log'];
|
|
12
13
|
function failed(code, message, taskId = null, ledgerId = null) {
|
|
@@ -160,6 +161,17 @@ function applyHumanDecision(request, options = {}) {
|
|
|
160
161
|
kind: 'section', aliases: ACTIVITY_ALIASES, heading: activity.heading,
|
|
161
162
|
body: appendActivityEntry(activity, { time: metadata.timestamp, step: 'Human Decision', agent: 'human', note: `${row.id} decided → ${recordId}` })
|
|
162
163
|
});
|
|
164
|
+
try {
|
|
165
|
+
const parsedRework = parseReworkIntentDocument(content);
|
|
166
|
+
if (!parsedRework.ok)
|
|
167
|
+
return failed('DECISION_DOCUMENT_INVALID', parsedRework.message, resolved.taskId, row.id);
|
|
168
|
+
const consumed = consumeHumanDecisionReworkIntents(parsedRework.intents, row.id, metadata.timestamp);
|
|
169
|
+
if (consumed.changed)
|
|
170
|
+
mutations.push(reworkIntentMutation(content, consumed.intents));
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
return failed('DECISION_DOCUMENT_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId, row.id);
|
|
174
|
+
}
|
|
163
175
|
const writeResult = writeTask({ taskRef: resolved.taskId, expectedState: stateOverride ? resolved.state : 'active', mutations, dryRun: request.dryRun }, {
|
|
164
176
|
...options,
|
|
165
177
|
taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state },
|
|
@@ -149,7 +149,7 @@ function hasStatusBoundEvidence(receipt) {
|
|
|
149
149
|
&& host !== undefined
|
|
150
150
|
&& Number.isSafeInteger(host.stopRevision)
|
|
151
151
|
&& host.stopRevision > host.startRevision
|
|
152
|
-
&& host.consumer ===
|
|
152
|
+
&& host.consumer === receipt.id
|
|
153
153
|
&& exactText(host.consumedAt);
|
|
154
154
|
}
|
|
155
155
|
if (!exactText(receipt.agent)
|
|
@@ -405,7 +405,7 @@ function abortActivatedDelegation(receipt, event) {
|
|
|
405
405
|
if (event.childId !== receipt.childId
|
|
406
406
|
|| !Number.isSafeInteger(event.stopRevision)
|
|
407
407
|
|| event.stopRevision <= receipt.hostEvidence.startRevision
|
|
408
|
-
|| event.consumer !==
|
|
408
|
+
|| event.consumer !== receipt.id
|
|
409
409
|
|| !exactText(event.consumedAt)) {
|
|
410
410
|
return fail('DELEGATION_RECOVERY_EVIDENCE_INVALID', 'recovery stop evidence does not match the activated delegation');
|
|
411
411
|
}
|