@fitlab-ai/agent-infra 0.11.4 → 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/dist/bin/internal-cli.js +0 -5
- package/dist/lib/decide.js +4 -41
- package/dist/lib/git/branch-worktree.js +45 -0
- package/dist/lib/internal/cli-route-inventory.js +0 -3
- package/dist/lib/internal/task-activity.js +7 -40
- package/dist/lib/internal/task-event.js +4 -37
- package/dist/lib/internal/task-finalization.js +9 -2
- package/dist/lib/internal/task-ledger.js +5 -43
- package/dist/lib/internal/task-lifecycle.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +1 -4
- package/dist/lib/internal/task-review.js +5 -37
- package/dist/lib/internal/task-verify.js +3 -47
- package/dist/lib/internal/task-warning.js +4 -40
- package/dist/lib/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/removal.js +15 -8
- package/dist/lib/task/activity-intent.js +1 -2
- package/dist/lib/task/artifact-operations.js +36 -2
- package/dist/lib/task/artifact-schema.js +7 -7
- package/dist/lib/task/control-authority.js +9 -166
- package/dist/lib/task/decision-intents.js +2 -4
- package/dist/lib/task/events.js +7 -28
- package/dist/lib/task/finalization-handoff.js +92 -0
- package/dist/lib/task/finalization.js +123 -12
- package/dist/lib/task/intermediate-cleanup.js +9 -148
- package/dist/lib/task/ledger-intents.js +3 -6
- package/dist/lib/task/lifecycle.js +50 -11
- package/dist/lib/task/local-artifact-finalization.js +13 -28
- package/dist/lib/task/review-command.js +1 -2
- package/dist/lib/task/review-finalization.js +1 -2
- package/dist/lib/task/snapshot.js +6 -6
- package/dist/lib/task/verification-engine.js +16 -11
- package/dist/lib/task/verification.js +1 -13
- package/dist/lib/task/workflow-warning-intents.js +2 -4
- package/dist/lib/task/write.js +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 +0 -2
- package/lib/internal/task-activity.ts +6 -34
- package/lib/internal/task-event.ts +4 -37
- package/lib/internal/task-finalization.ts +9 -2
- package/lib/internal/task-ledger.ts +5 -38
- package/lib/internal/task-lifecycle.ts +2 -2
- package/lib/internal/task-operation-registry.ts +1 -4
- package/lib/internal/task-review.ts +5 -31
- package/lib/internal/task-verify.ts +3 -39
- package/lib/internal/task-warning.ts +3 -32
- package/lib/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/removal.ts +23 -7
- package/lib/task/activity-intent.ts +1 -2
- package/lib/task/artifact-operations.ts +58 -2
- package/lib/task/artifact-schema.ts +9 -7
- package/lib/task/control-authority.ts +14 -188
- package/lib/task/decision-intents.ts +2 -4
- package/lib/task/events.ts +8 -27
- package/lib/task/finalization-handoff.ts +104 -0
- package/lib/task/finalization.ts +120 -12
- package/lib/task/intermediate-cleanup.ts +9 -125
- package/lib/task/ledger-intents.ts +3 -6
- package/lib/task/lifecycle.ts +45 -13
- package/lib/task/local-artifact-finalization.ts +16 -35
- package/lib/task/review-command.ts +2 -5
- package/lib/task/review-finalization.ts +1 -4
- package/lib/task/snapshot.ts +6 -6
- package/lib/task/verification-engine.ts +20 -10
- package/lib/task/verification.ts +1 -15
- package/lib/task/workflow-warning-intents.ts +2 -4
- package/lib/task/write.ts +1 -4
- package/package.json +1 -1
- package/templates/.agents/skills/analyze-task/SKILL.en.md +11 -2
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +11 -2
- package/templates/.agents/skills/code-task/reference/report-template.en.md +6 -6
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +6 -6
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +2 -2
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +1 -3
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +1 -3
- package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/review-code/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/review-pr/config/verify.en.json +3 -1
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +3 -1
- package/templates/.agents/skills/review-pr/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +2 -2
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -2
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -2
- package/dist/lib/internal/task-override.js +0 -138
- package/dist/lib/task/artifact-recovery.js +0 -476
- package/dist/lib/task/artifact-repair-intent.js +0 -181
- package/dist/lib/task/guard-override.js +0 -31
- package/dist/lib/task/human-override.js +0 -1259
- package/lib/internal/task-override.ts +0 -144
- package/lib/task/artifact-recovery.ts +0 -572
- package/lib/task/artifact-repair-intent.ts +0 -227
- package/lib/task/guard-override.ts +0 -50
- package/lib/task/human-override.ts +0 -1522
|
@@ -56,25 +56,25 @@ function collectTaskSnapshot(taskRef, options = {}) {
|
|
|
56
56
|
const observations = [];
|
|
57
57
|
try {
|
|
58
58
|
const output = stripOneTrailingNewline((options.gitStatus ?? defaultGitStatus)(resolved.repoRoot));
|
|
59
|
-
observations.push({ name: 'git', status: 'ready', command: '
|
|
59
|
+
observations.push({ name: 'git', status: 'ready', command: 'git status --short --untracked-files=all', output, error: null });
|
|
60
60
|
}
|
|
61
61
|
catch (error) {
|
|
62
|
-
return failedSnapshot(resolved, taskRef, observations, 'git', '
|
|
62
|
+
return failedSnapshot(resolved, taskRef, observations, 'git', 'git status --short --untracked-files=all', 'SNAPSHOT_GIT_FAILED', error);
|
|
63
63
|
}
|
|
64
64
|
try {
|
|
65
65
|
const entries = (options.readDirectory ?? ((dir) => fs.readdirSync(dir, { withFileTypes: true })))(resolved.taskDir);
|
|
66
66
|
const output = renderDirectory(entries, resolved.taskDir, options.lstat ?? fs.lstatSync);
|
|
67
|
-
observations.push({ name: 'task-directory', status: 'ready', command:
|
|
67
|
+
observations.push({ name: 'task-directory', status: 'ready', command: `fs.readdirSync ${taskDirPath}/`, output, error: null });
|
|
68
68
|
}
|
|
69
69
|
catch (error) {
|
|
70
|
-
return failedSnapshot(resolved, taskRef, observations, 'task-directory',
|
|
70
|
+
return failedSnapshot(resolved, taskRef, observations, 'task-directory', `fs.readdirSync ${taskDirPath}/`, 'SNAPSHOT_DIRECTORY_READ_FAILED', error);
|
|
71
71
|
}
|
|
72
72
|
try {
|
|
73
73
|
const content = (options.readTask ?? ((file) => fs.readFileSync(file, 'utf8')))(resolved.taskMdPath);
|
|
74
|
-
observations.push({ name: 'task-tail', status: 'ready', command:
|
|
74
|
+
observations.push({ name: 'task-tail', status: 'ready', command: `fs.readFileSync ${taskPath} (last 10 lines)`, output: renderTail(content), error: null });
|
|
75
75
|
}
|
|
76
76
|
catch (error) {
|
|
77
|
-
return failedSnapshot(resolved, taskRef, observations, 'task-tail',
|
|
77
|
+
return failedSnapshot(resolved, taskRef, observations, 'task-tail', `fs.readFileSync ${taskPath} (last 10 lines)`, 'SNAPSHOT_TASK_READ_FAILED', error);
|
|
78
78
|
}
|
|
79
79
|
return {
|
|
80
80
|
status: 'ready', changed: false, requestRef: taskRef, taskId: resolved.taskId,
|
|
@@ -11,6 +11,7 @@ import { inspectPlatformPullRequest } from "../platform/pull-requests.js";
|
|
|
11
11
|
import { resolveMaterializedReviewedHeadRelation } from "../platform/change-request-git-evidence.js";
|
|
12
12
|
import { resolveReviewedHeadRelation } from "../platform/merged-pr-equivalence.js";
|
|
13
13
|
import { resolveLocalReviewedCommitRelation } from "../git/reviewed-commit-equivalence.js";
|
|
14
|
+
import { resolveBranchWorktree } from "../git/branch-worktree.js";
|
|
14
15
|
import { parseTypedTaskFrontmatter } from "./frontmatter.js";
|
|
15
16
|
import { LEDGER_TERMINAL, LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from "./ledger.js";
|
|
16
17
|
import { isValidAgentInfraVersion } from "../version.js";
|
|
@@ -483,6 +484,18 @@ function checkArtifact({ taskDir, config, artifactFile, skillName }) {
|
|
|
483
484
|
return failResult("artifact", `${path.basename(artifactPath)} is missing required pattern: ${pattern}`);
|
|
484
485
|
}
|
|
485
486
|
}
|
|
487
|
+
const stateCheckPatterns = config.state_check_patterns || [];
|
|
488
|
+
if (stateCheckPatterns.length > 0) {
|
|
489
|
+
const scanned = scanVisibleMarkdown(content);
|
|
490
|
+
const stateCheckHeading = scanned.headings.find((heading) => (heading.level === 2 && (heading.text === "状态核对" || heading.text === "State Check")));
|
|
491
|
+
const nextHeading = stateCheckHeading && scanned.headings.find((heading) => (heading.start > stateCheckHeading.start && heading.level <= 2));
|
|
492
|
+
const stateCheckBody = stateCheckHeading ? content.slice(stateCheckHeading.end, nextHeading?.start ?? content.length) : "";
|
|
493
|
+
for (const pattern of stateCheckPatterns) {
|
|
494
|
+
if (!new RegExp(pattern, "m").test(stateCheckBody)) {
|
|
495
|
+
return failResult("artifact", `${path.basename(artifactPath)} state-check section is missing required command: ${pattern}`);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
486
499
|
return passResult("artifact", `${path.basename(artifactPath)} passed (${requiredSections.length} sections)`);
|
|
487
500
|
}
|
|
488
501
|
function checkDecisionDetails({ taskDir, artifactFile }) {
|
|
@@ -1078,17 +1091,9 @@ function checkReviewFact({ taskDir, artifactFile, repositoryRoot }) {
|
|
|
1078
1091
|
function reviewWorktreeForTask(repositoryRoot, branch) {
|
|
1079
1092
|
if (!branch)
|
|
1080
1093
|
return null;
|
|
1081
|
-
const
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
1085
|
-
}).trim().split(/\r?\n\r?\n/);
|
|
1086
|
-
for (const record of records) {
|
|
1087
|
-
const worktree = /^worktree (.+)$/m.exec(record)?.[1];
|
|
1088
|
-
const branchRef = /^branch (.+)$/m.exec(record)?.[1];
|
|
1089
|
-
if (worktree && branchRef === expectedRef)
|
|
1090
|
-
return worktree;
|
|
1091
|
-
}
|
|
1094
|
+
const worktree = resolveBranchWorktree(repositoryRoot, branch);
|
|
1095
|
+
if (worktree)
|
|
1096
|
+
return worktree;
|
|
1092
1097
|
throw new Error(`Task branch '${branch}' is not checked out in a registered worktree`);
|
|
1093
1098
|
}
|
|
1094
1099
|
function resolvePostReviewBaseline({ gitRoot, lastReviewedCommit, reviewArtifact }) {
|
|
@@ -4,7 +4,6 @@ import path from 'node:path';
|
|
|
4
4
|
import { verifyInProcess } from './verification-engine.js';
|
|
5
5
|
import { parseArtifactName } from './artifact-name.js';
|
|
6
6
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
7
|
-
import { allowsManualOverride } from './guard-override.js';
|
|
8
7
|
const gate = (skill, expectedState, artifactFamily) => ({
|
|
9
8
|
skill, expectedState, mode: 'gate', ...(artifactFamily ? { artifactFamily } : {})
|
|
10
9
|
});
|
|
@@ -50,7 +49,7 @@ async function verifyTaskEvent(request, options = {}) {
|
|
|
50
49
|
if (!resolved.ok)
|
|
51
50
|
return failure(request, resolved.code, resolved.message, { taskId: resolved.taskId, skill: spec.skill, mode: spec.mode });
|
|
52
51
|
const identity = { taskId: resolved.taskId, taskDir: resolved.taskDir, taskState: resolved.state, skill: spec.skill, mode: spec.mode };
|
|
53
|
-
if (resolved.state !== spec.expectedState
|
|
52
|
+
if (resolved.state !== spec.expectedState) {
|
|
54
53
|
return failure(request, 'VERIFY_TASK_STATE_MISMATCH', `event '${request.event}' requires workspace '${spec.expectedState}', received '${resolved.state}'`, identity);
|
|
55
54
|
}
|
|
56
55
|
if (spec.artifactFamily) {
|
|
@@ -69,23 +68,12 @@ async function verifyTaskEvent(request, options = {}) {
|
|
|
69
68
|
if (spec.mode === 'gate') {
|
|
70
69
|
const payload = await engine({ mode: 'gate', skillName: spec.skill, taskDir: resolved.taskDir, artifactFile: request.artifact, checks: [], repositoryRoot: resolved.repoRoot });
|
|
71
70
|
const status = payload.gate;
|
|
72
|
-
if (status !== 'pass' && allowsManualOverride(options.manualOverride, 'verification-engine', status === 'blocked' ? 'CHECK_BLOCKED' : 'CHECK_FAILED')) {
|
|
73
|
-
invocations.push({ status: 'pass', exitCode: 0, payload: { ...payload, gate: 'pass', humanOverride: 'human-approved', originalGate: status } });
|
|
74
|
-
return {
|
|
75
|
-
status: 'pass', changed: false, event: request.event, requestRef: request.taskRef,
|
|
76
|
-
...identity, artifact: request.artifact ?? null, invocations, error: null
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
71
|
invocations.push({ status, exitCode: { pass: 0, fail: 1, blocked: 2 }[status], payload });
|
|
80
72
|
}
|
|
81
73
|
else {
|
|
82
74
|
for (const check of spec.checks ?? []) {
|
|
83
75
|
const payload = await engine({ mode: 'checks', skillName: spec.skill, taskDir: resolved.taskDir, artifactFile: request.artifact, checks: [check], repositoryRoot: resolved.repoRoot });
|
|
84
76
|
const status = (payload.effectiveStatus ?? payload.status);
|
|
85
|
-
if (status !== 'pass' && allowsManualOverride(options.manualOverride, 'verification-engine', status === 'blocked' ? 'CHECK_BLOCKED' : 'CHECK_FAILED')) {
|
|
86
|
-
invocations.push({ status: 'pass', exitCode: 0, payload: { ...payload, status: 'pass', humanOverride: 'human-approved', originalStatus: status } });
|
|
87
|
-
break;
|
|
88
|
-
}
|
|
89
77
|
invocations.push({ status, exitCode: { pass: 0, fail: 1, blocked: 2 }[status], payload });
|
|
90
78
|
if (status !== 'pass')
|
|
91
79
|
break;
|
|
@@ -3,7 +3,6 @@ import { resolveTaskRef } from './resolve-ref.js';
|
|
|
3
3
|
import { parseTaskFrontmatter } from './frontmatter.js';
|
|
4
4
|
import { WORKFLOW_WARNING_COLUMNS, WORKFLOW_WARNING_HEADINGS, WORKFLOW_WARNING_SEVERITIES, WORKFLOW_WARNING_STATUSES, parseWorkflowWarnings } from './workflow-warnings.js';
|
|
5
5
|
import { captureTaskWriteMetadata, writeTask } from './write.js';
|
|
6
|
-
import { allowsManualOverride } from './guard-override.js';
|
|
7
6
|
import { retryHintForWarning } from './operation-outcome.js';
|
|
8
7
|
function failed(intent, code, message, taskId = null, entityId = null) {
|
|
9
8
|
return { status: 'failed', changed: false, intent: intent.kind, taskId, entityId, before: null, after: null, warnings: [], operations: [], error: { code, message } };
|
|
@@ -44,8 +43,7 @@ function applyWorkflowWarningIntent(intent, options = {}) {
|
|
|
44
43
|
const resolved = resolveTaskRef(intent.taskRef, { repoRoot: options.repoRoot });
|
|
45
44
|
if (!resolved.ok)
|
|
46
45
|
return failed(intent, resolved.code, resolved.message, resolved.taskId);
|
|
47
|
-
|
|
48
|
-
if (resolved.state !== 'active' && !stateOverride)
|
|
46
|
+
if (resolved.state !== 'active')
|
|
49
47
|
return failed(intent, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
|
|
50
48
|
let content;
|
|
51
49
|
let rows;
|
|
@@ -102,7 +100,7 @@ function applyWorkflowWarningIntent(intent, options = {}) {
|
|
|
102
100
|
after = { ...before, status: intent.status, resolvedAt: metadata.timestamp, resolution };
|
|
103
101
|
}
|
|
104
102
|
const writeResult = writeTask({
|
|
105
|
-
taskRef: intent.taskRef, expectedState:
|
|
103
|
+
taskRef: intent.taskRef, expectedState: 'active', mutations: [...sectionMutation(content), rowMutation(after)], dryRun: intent.dryRun
|
|
106
104
|
}, {
|
|
107
105
|
...options,
|
|
108
106
|
taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state },
|
package/dist/lib/task/write.js
CHANGED
|
@@ -3,7 +3,6 @@ import path from 'node:path';
|
|
|
3
3
|
import { isValidAgentInfraVersion, VERSION } from '../version.js';
|
|
4
4
|
import { parseTypedTaskFrontmatter, updateTaskFrontmatter } from './frontmatter.js';
|
|
5
5
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
6
|
-
import { allowsManualOverride } from './guard-override.js';
|
|
7
6
|
import { mutateTableRow, upsertSection } from './sections.js';
|
|
8
7
|
import { validateCurrentTaskContract } from './current-contract.js';
|
|
9
8
|
import { invalidationBlocks, parseInvalidationDocument } from './invalidation.js';
|
|
@@ -73,7 +72,7 @@ function writeTask(request, options = {}) {
|
|
|
73
72
|
taskMdPath: resolved.taskMdPath,
|
|
74
73
|
actualState: resolved.state
|
|
75
74
|
};
|
|
76
|
-
if (resolved.state !== request.expectedState
|
|
75
|
+
if (resolved.state !== request.expectedState) {
|
|
77
76
|
return failure(request, identity, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected ${request.expectedState}`);
|
|
78
77
|
}
|
|
79
78
|
const io = { ...DEFAULT_FILE_SYSTEM, ...options.fileSystem };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"protocolVersion": 3,
|
|
3
|
-
"packageVersion": "0.11.
|
|
4
|
-
"sourceInputHash": "
|
|
5
|
-
"internalExecutableBuildHash": "
|
|
3
|
+
"packageVersion": "0.11.5",
|
|
4
|
+
"sourceInputHash": "2f29b7f1f134733c3bc1bb7c92ea6c11e57462ed51a5a96b0198b8824a3f455d",
|
|
5
|
+
"internalExecutableBuildHash": "cb9d00aa3fa974cf936a8aad310e1fa5f28842351f52a53fed43345fe9c46439"
|
|
6
6
|
}
|
package/lib/decide.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { VERSION } from './version.ts';
|
|
|
2
2
|
import { parseTaskScope } from './task/command-options.ts';
|
|
3
3
|
import { applyHumanDecision } from './task/decision-intents.ts';
|
|
4
4
|
import { canonicalTimestamp } from './task/write.ts';
|
|
5
|
-
import { consumeHumanOverride, failureId } from './task/human-override.ts';
|
|
6
5
|
import { resolveTaskContext } from './task/resolve-ref.ts';
|
|
7
6
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task/task-execution-lock.ts';
|
|
8
7
|
|
|
@@ -12,12 +11,9 @@ type DecideOptions = {
|
|
|
12
11
|
version?: string;
|
|
13
12
|
};
|
|
14
13
|
|
|
15
|
-
function parseDecisionParts(parts: string[]): { decision: string; needsImplementation: boolean | undefined
|
|
14
|
+
function parseDecisionParts(parts: string[]): { decision: string; needsImplementation: boolean | undefined } {
|
|
16
15
|
const decision: string[] = [];
|
|
17
16
|
let needsImplementation: boolean | undefined;
|
|
18
|
-
let overrideTicket: string | undefined;
|
|
19
|
-
let overrideTarget: string | undefined;
|
|
20
|
-
let overrideScope: string | undefined;
|
|
21
17
|
for (let index = 0; index < parts.length; index += 1) {
|
|
22
18
|
const part = parts[index];
|
|
23
19
|
if (part === '--needs-implementation') {
|
|
@@ -27,18 +23,14 @@ function parseDecisionParts(parts: string[]): { decision: string; needsImplement
|
|
|
27
23
|
throw new Error("--needs-implementation must be 'true' or 'false'");
|
|
28
24
|
}
|
|
29
25
|
needsImplementation = value === 'true';
|
|
30
|
-
} else if (part
|
|
31
|
-
|
|
32
|
-
if (!value) throw new Error(`${part} requires a value`);
|
|
33
|
-
if (part === '--override-ticket') overrideTicket = value;
|
|
34
|
-
else if (part === '--override-target') overrideTarget = value;
|
|
35
|
-
else overrideScope = value;
|
|
26
|
+
} else if (part?.startsWith('--')) {
|
|
27
|
+
throw new Error(`unknown option '${part}'`);
|
|
36
28
|
} else {
|
|
37
29
|
decision.push(parts[index]!);
|
|
38
30
|
}
|
|
39
31
|
}
|
|
40
32
|
if (decision.length === 0) throw new Error('decision content is required');
|
|
41
|
-
return { decision: decision.join(' '), needsImplementation
|
|
33
|
+
return { decision: decision.join(' '), needsImplementation };
|
|
42
34
|
}
|
|
43
35
|
|
|
44
36
|
export async function decide(args: string[], options: DecideOptions = {}): Promise<number> {
|
|
@@ -79,29 +71,7 @@ export async function decide(args: string[], options: DecideOptions = {}): Promi
|
|
|
79
71
|
metadataProvider: () => ({ timestamp: now, agentInfraVersion: options.version ?? VERSION })
|
|
80
72
|
};
|
|
81
73
|
let result;
|
|
82
|
-
const execute = async () =>
|
|
83
|
-
let current = applyHumanDecision(request, writeOptions);
|
|
84
|
-
if (current.status !== 'failed' || !parsedDecision.overrideTicket) return current;
|
|
85
|
-
if (!parsedDecision.overrideTarget || !parsedDecision.overrideScope) throw new Error('override ticket requires --override-target and --override-scope');
|
|
86
|
-
const consumed = await consumeHumanOverride({
|
|
87
|
-
taskRef: resolved.taskId,
|
|
88
|
-
ticketId: parsedDecision.overrideTicket,
|
|
89
|
-
failureId: failureId('decision-intent', current.error?.code ?? 'TASK_STATE_MISMATCH'),
|
|
90
|
-
target: parsedDecision.overrideTarget,
|
|
91
|
-
scope: parsedDecision.overrideScope
|
|
92
|
-
}, {
|
|
93
|
-
...writeOptions,
|
|
94
|
-
effectExecutor: (capability) => {
|
|
95
|
-
const retried = applyHumanDecision(request, { ...writeOptions, manualOverride: capability });
|
|
96
|
-
current = retried;
|
|
97
|
-
return retried.status === 'failed' || retried.status === 'planned'
|
|
98
|
-
? { code: 'OVERRIDE_EFFECT_FAILED', message: retried.status === 'planned' ? 'producer returned planned; no decision effect was committed' : `${retried.error?.code ?? 'DECISION_FAILED'}: ${retried.error?.message ?? 'manual decision effect failed'}` }
|
|
99
|
-
: null;
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
if (consumed.status === 'failed') throw new Error(consumed.error.message);
|
|
103
|
-
return current;
|
|
104
|
-
};
|
|
74
|
+
const execute = async () => applyHumanDecision(request, writeOptions);
|
|
105
75
|
result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-decision', execute);
|
|
106
76
|
if (result.error) throw new Error(result.error.message);
|
|
107
77
|
return 0;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
function gitText(repositoryRoot: string, args: string[]): string | null {
|
|
4
|
+
try {
|
|
5
|
+
return execFileSync('git', ['-C', repositoryRoot, ...args], {
|
|
6
|
+
encoding: 'utf8',
|
|
7
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
8
|
+
}).trim();
|
|
9
|
+
} catch {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function verifiedWorktreeRoot(candidate: string, expectedBranch: string, expectedCommonDirectory: string): string | null {
|
|
15
|
+
const commonDirectory = gitText(candidate, ['rev-parse', '--path-format=absolute', '--git-common-dir']);
|
|
16
|
+
if (commonDirectory !== expectedCommonDirectory) return null;
|
|
17
|
+
const root = gitText(candidate, ['rev-parse', '--show-toplevel']);
|
|
18
|
+
if (!root) return null;
|
|
19
|
+
const branch = gitText(root, ['symbolic-ref', '--short', 'HEAD']);
|
|
20
|
+
return branch === expectedBranch ? root : null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function resolveBranchWorktree(repositoryRoot: string, expectedBranch: string): string | null {
|
|
24
|
+
const commonDirectory = gitText(repositoryRoot, ['rev-parse', '--path-format=absolute', '--git-common-dir']);
|
|
25
|
+
if (!commonDirectory) return null;
|
|
26
|
+
const currentRoot = verifiedWorktreeRoot(repositoryRoot, expectedBranch, commonDirectory);
|
|
27
|
+
if (currentRoot) return currentRoot;
|
|
28
|
+
|
|
29
|
+
const records = gitText(repositoryRoot, ['worktree', 'list', '--porcelain']);
|
|
30
|
+
if (!records) return null;
|
|
31
|
+
const expectedRef = `refs/heads/${expectedBranch}`;
|
|
32
|
+
for (const record of records.split(/\r?\n\r?\n/)) {
|
|
33
|
+
const candidate = /^worktree (.+)$/m.exec(record)?.[1];
|
|
34
|
+
const branch = /^branch (.+)$/m.exec(record)?.[1];
|
|
35
|
+
if (candidate && branch === expectedRef) {
|
|
36
|
+
const verifiedRoot = verifiedWorktreeRoot(candidate, expectedBranch, commonDirectory);
|
|
37
|
+
if (verifiedRoot) return verifiedRoot;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
@@ -61,7 +61,6 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
|
|
|
61
61
|
'task-review': ['preflight', 'finalize-summary'],
|
|
62
62
|
'task-event': ['event'],
|
|
63
63
|
'task-invalidation': ['reconcile'],
|
|
64
|
-
'task-override': ['diagnose', 'issue', 'consume'],
|
|
65
64
|
'task-lifecycle': ['intent'],
|
|
66
65
|
'task-finalization': ['complete'],
|
|
67
66
|
'task-short-id': ['list', 'list-verify', 'alloc', 'release', 'resolve'],
|
|
@@ -98,7 +97,6 @@ export function internalRouteSelector(command: string, args: readonly string[]):
|
|
|
98
97
|
if (command === 'task-orchestration') return args[1] === 'status' ? 'status' : args[1] ? 'progress' : '';
|
|
99
98
|
if (command === 'task-review') return args[1] ?? '';
|
|
100
99
|
if (command === 'task-invalidation') return args[1] ?? '';
|
|
101
|
-
if (command === 'task-override') return args[1] ?? '';
|
|
102
100
|
if (command === 'task-artifact') return args[1] ?? '';
|
|
103
101
|
if (command === 'platform-pr-review' && first(args) === 'publish') {
|
|
104
102
|
const scope = optionValue(args, '--scope') ?? '';
|
|
@@ -6,7 +6,6 @@ import type {
|
|
|
6
6
|
PrReviewActivityIntent,
|
|
7
7
|
PrReviewInspectIntent
|
|
8
8
|
} from '../task/activity-intent.ts';
|
|
9
|
-
import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.ts';
|
|
10
9
|
import { resolveTaskRef } from '../task/resolve-ref.ts';
|
|
11
10
|
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
|
|
12
11
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
|
|
@@ -28,14 +27,13 @@ const FLAGS: Record<string, string> = {
|
|
|
28
27
|
'--minor': 'minor',
|
|
29
28
|
'--outcome': 'outcome',
|
|
30
29
|
'--reason': 'reason'
|
|
31
|
-
, '--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
|
|
32
30
|
};
|
|
33
31
|
const COMMON = ['agent', 'artifact', 'head'] as const;
|
|
34
32
|
const ALLOWED: Record<string, ReadonlySet<string>> = {
|
|
35
33
|
'pr-review-inspect': new Set(['kind', 'taskRef']),
|
|
36
|
-
'pr-review-start': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'dryRun'
|
|
37
|
-
'pr-review-complete': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'verdict', 'blockers', 'major', 'minor', 'dryRun'
|
|
38
|
-
'pr-review-terminate': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'outcome', 'reason', 'dryRun'
|
|
34
|
+
'pr-review-start': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'dryRun']),
|
|
35
|
+
'pr-review-complete': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'verdict', 'blockers', 'major', 'minor', 'dryRun']),
|
|
36
|
+
'pr-review-terminate': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'outcome', 'reason', 'dryRun'])
|
|
39
37
|
};
|
|
40
38
|
|
|
41
39
|
function usageFailure(message: string): void {
|
|
@@ -95,45 +93,19 @@ async function taskActivity(args: string[] = []): Promise<void> {
|
|
|
95
93
|
for (const key of operationRequired) {
|
|
96
94
|
if (values[key] === undefined) { usageFailure(`${kind} requires '${key}'`); return; }
|
|
97
95
|
}
|
|
98
|
-
const dryRunConflict = overrideDryRunConflict(values);
|
|
99
|
-
if (dryRunConflict) { usageFailure(dryRunConflict.message); return; }
|
|
100
96
|
const resolved = resolveTaskRef(taskRef);
|
|
101
97
|
if (!resolved.ok) { usageFailure(resolved.message); return; }
|
|
102
98
|
let result;
|
|
103
|
-
let humanOverride: unknown = null;
|
|
104
99
|
try {
|
|
105
|
-
result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-activity.${kind}`, async () => {
|
|
106
|
-
let current = applyPrReviewActivityIntent(values as PrReviewActivityIntent, { lockAlreadyHeld: true });
|
|
107
|
-
if (current.status !== 'failed' || !values.overrideTicket) return current;
|
|
108
|
-
if (!values.overrideTarget || !values.overrideScope) { usageFailure('override ticket requires target and scope'); return current; }
|
|
109
|
-
const consumed = await consumeHumanOverride({
|
|
110
|
-
taskRef, ticketId: String(values.overrideTicket),
|
|
111
|
-
failureId: failureId('activity-intent', current.error?.code ?? 'TASK_STATE_MISMATCH'),
|
|
112
|
-
target: String(values.overrideTarget), scope: String(values.overrideScope)
|
|
113
|
-
}, {
|
|
114
|
-
effectExecutor: (capability) => {
|
|
115
|
-
const retried = applyPrReviewActivityIntent(values as PrReviewActivityIntent, { lockAlreadyHeld: true, manualOverride: capability });
|
|
116
|
-
current = retried;
|
|
117
|
-
return retried.status === 'failed' || retried.status === 'planned'
|
|
118
|
-
? { code: 'OVERRIDE_EFFECT_FAILED', message: retried.status === 'planned' ? 'producer returned planned; no activity effect was committed' : `${retried.error?.code ?? 'ACTIVITY_FAILED'}: ${retried.error?.message ?? 'manual activity effect failed'}` }
|
|
119
|
-
: null;
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
humanOverride = consumed;
|
|
123
|
-
return current;
|
|
124
|
-
});
|
|
100
|
+
result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-activity.${kind}`, async () => applyPrReviewActivityIntent(values as PrReviewActivityIntent, { lockAlreadyHeld: true }));
|
|
125
101
|
} catch (error) {
|
|
126
102
|
if (!(error instanceof TaskExecutionLockError)) throw error;
|
|
127
103
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: error.code, message: error.message } })}\n`);
|
|
128
104
|
process.exitCode = 1;
|
|
129
105
|
return;
|
|
130
106
|
}
|
|
131
|
-
process.stdout.write(`${JSON.stringify(
|
|
132
|
-
|
|
133
|
-
humanOverride && typeof humanOverride === 'object' &&
|
|
134
|
-
(humanOverride as { status?: unknown }).status === 'failed'
|
|
135
|
-
);
|
|
136
|
-
if (result.status === 'failed' || overrideFailed) process.exitCode = 1;
|
|
107
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
108
|
+
if (result.status === 'failed') process.exitCode = 1;
|
|
137
109
|
}
|
|
138
110
|
|
|
139
111
|
export { taskActivity };
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { normalizeAgentToken, AGENT_USAGE_HINT } from '../agent-clients/tokens.ts';
|
|
2
2
|
import { applyTaskEvent, eventCatalog } from '../task/events.ts';
|
|
3
3
|
import type { TaskEventRequest, Verdict } from '../task/events.ts';
|
|
4
|
-
import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.ts';
|
|
5
4
|
import { resolveTaskRef } from '../task/resolve-ref.ts';
|
|
6
5
|
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
|
|
7
6
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
|
|
@@ -21,8 +20,7 @@ const FLAGS: Record<string, keyof TaskEventRequest> = {
|
|
|
21
20
|
'--verdict': 'verdict', '--blockers': 'blockers', '--major': 'major',
|
|
22
21
|
'--minor': 'minor', '--manual-validation': 'manualValidation', '--files-modified': 'filesModified',
|
|
23
22
|
'--tests-passed': 'testsPassed', '--summary-result': 'summaryResult',
|
|
24
|
-
'--transaction-id': 'transactionId', '--receipt-digest': 'receiptDigest', '--pr-head-sha': 'prHeadSha'
|
|
25
|
-
'--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
|
|
23
|
+
'--transaction-id': 'transactionId', '--receipt-digest': 'receiptDigest', '--pr-head-sha': 'prHeadSha'
|
|
26
24
|
};
|
|
27
25
|
const NUMERIC = new Set(['round', 'question', 'blockers', 'major', 'minor', 'manualValidation', 'filesModified', 'testsPassed']);
|
|
28
26
|
|
|
@@ -59,8 +57,6 @@ export function parseTaskEventRequest(args: readonly string[]): TaskEventRequest
|
|
|
59
57
|
const agent = normalizeAgentToken(String(request.agent ?? ''));
|
|
60
58
|
if (!agent) throw new Error(`invalid --agent '${request.agent}': ${AGENT_USAGE_HINT}`);
|
|
61
59
|
request.agent = agent;
|
|
62
|
-
const dryRunConflict = overrideDryRunConflict(request as unknown as Record<string, unknown>);
|
|
63
|
-
if (dryRunConflict) throw new Error(dryRunConflict.message);
|
|
64
60
|
return request;
|
|
65
61
|
}
|
|
66
62
|
|
|
@@ -78,45 +74,16 @@ async function taskEvent(args: string[] = []): Promise<void> {
|
|
|
78
74
|
return;
|
|
79
75
|
}
|
|
80
76
|
let result;
|
|
81
|
-
let humanOverride: unknown = null;
|
|
82
77
|
try {
|
|
83
|
-
result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-event.${request.event}`, async () => {
|
|
84
|
-
let current = applyTaskEvent(request, { lockAlreadyHeld: true });
|
|
85
|
-
const values = request as Record<string, unknown>;
|
|
86
|
-
if (current.status !== 'failed' || !values.overrideTicket) return current;
|
|
87
|
-
if (!values.overrideTarget || !values.overrideScope) {
|
|
88
|
-
return { ...current, humanOverride: { status: 'failed', error: { code: 'OVERRIDE_PAYLOAD_INVALID', message: 'override ticket requires target and scope' } } } as typeof current & { humanOverride: unknown };
|
|
89
|
-
}
|
|
90
|
-
const consumed = await consumeHumanOverride({
|
|
91
|
-
taskRef: request.taskRef,
|
|
92
|
-
ticketId: String(values.overrideTicket),
|
|
93
|
-
failureId: failureId('task-event', current.error?.code ?? 'EVENT_TRANSITION_INVALID'),
|
|
94
|
-
target: String(values.overrideTarget),
|
|
95
|
-
scope: String(values.overrideScope)
|
|
96
|
-
}, {
|
|
97
|
-
effectExecutor: (capability) => {
|
|
98
|
-
const retried = applyTaskEvent(request, { lockAlreadyHeld: true, manualOverride: capability });
|
|
99
|
-
current = retried;
|
|
100
|
-
return retried.status === 'failed' || retried.status === 'planned'
|
|
101
|
-
? { code: 'OVERRIDE_EFFECT_FAILED', message: retried.status === 'planned' ? 'producer returned planned; no task event was committed' : `${retried.error?.code ?? 'EVENT_FAILED'}: ${retried.error?.message ?? 'manual task event effect failed'}` }
|
|
102
|
-
: null;
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
humanOverride = consumed;
|
|
106
|
-
return current;
|
|
107
|
-
});
|
|
78
|
+
result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-event.${request.event}`, async () => applyTaskEvent(request, { lockAlreadyHeld: true }));
|
|
108
79
|
} catch (error) {
|
|
109
80
|
if (!(error instanceof TaskExecutionLockError)) throw error;
|
|
110
81
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: error.code, message: error.message } })}\n`);
|
|
111
82
|
process.exitCode = 1;
|
|
112
83
|
return;
|
|
113
84
|
}
|
|
114
|
-
process.stdout.write(`${JSON.stringify(
|
|
115
|
-
|
|
116
|
-
humanOverride && typeof humanOverride === 'object' &&
|
|
117
|
-
(humanOverride as { status?: unknown }).status === 'failed'
|
|
118
|
-
);
|
|
119
|
-
if (result.status === 'failed' || overrideFailed) process.exitCode = 1;
|
|
85
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
86
|
+
if (result.status === 'failed') process.exitCode = 1;
|
|
120
87
|
}
|
|
121
88
|
|
|
122
89
|
export { taskEvent };
|
|
@@ -3,7 +3,8 @@ import {
|
|
|
3
3
|
dispatchTaskControlOperation,
|
|
4
4
|
parseTaskControlOperation
|
|
5
5
|
} from '../task/control-authority.ts';
|
|
6
|
-
import { applyTaskFinalization, prepareTaskFinalization } from '../task/finalization.ts';
|
|
6
|
+
import { applyTaskFinalization, bindTaskFinalizationReceipt, prepareTaskFinalization } from '../task/finalization.ts';
|
|
7
|
+
import { publishTaskFinalizationHandoff } from '../task/finalization-handoff.ts';
|
|
7
8
|
import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.ts';
|
|
8
9
|
import { verifyTaskEvent } from '../task/verification.ts';
|
|
9
10
|
import { resolveSandboxControlTransport } from './task-operation-registry.ts';
|
|
@@ -88,7 +89,13 @@ async function taskFinalization(args: string[] = []): Promise<void> {
|
|
|
88
89
|
return;
|
|
89
90
|
}
|
|
90
91
|
try {
|
|
91
|
-
const response = requestSandboxTaskFinalization({
|
|
92
|
+
const response = requestSandboxTaskFinalization({
|
|
93
|
+
agent: boundOperation.request.agent,
|
|
94
|
+
prepareHandoff: (binding) => {
|
|
95
|
+
const receipt = bindTaskFinalizationReceipt(repoRoot, boundOperation.request.taskRef, binding);
|
|
96
|
+
return publishTaskFinalizationHandoff('/share/branch', receipt, binding);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
92
99
|
process.stdout.write(response.stdout);
|
|
93
100
|
process.stderr.write(response.stderr);
|
|
94
101
|
process.exitCode = response.exitCode ?? 1;
|
|
@@ -2,7 +2,6 @@ import fs from 'node:fs';
|
|
|
2
2
|
|
|
3
3
|
import { applyLedgerIntent } from '../task/ledger-intents.ts';
|
|
4
4
|
import type { LedgerIntent } from '../task/ledger-intents.ts';
|
|
5
|
-
import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.ts';
|
|
6
5
|
import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, isReviewStage, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from '../task/ledger.ts';
|
|
7
6
|
import { resolveTaskRef } from '../task/resolve-ref.ts';
|
|
8
7
|
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
|
|
@@ -14,8 +13,7 @@ const FLAGS: Record<string, string> = {
|
|
|
14
13
|
'--severity': 'severity', '--evidence': 'evidence', '--id': 'id', '--round': 'round',
|
|
15
14
|
'--status': 'status', '--artifact': 'artifact', '--needs-implementation': 'needsImplementation',
|
|
16
15
|
'--intent-id': 'intentId', '--finding-id': 'findingId', '--source-artifact': 'sourceArtifact',
|
|
17
|
-
'--source-sha256': 'sourceSha256', '--classification': 'classification'
|
|
18
|
-
'--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
|
|
16
|
+
'--source-sha256': 'sourceSha256', '--classification': 'classification'
|
|
19
17
|
};
|
|
20
18
|
const NUMERIC = new Set(['ordinal', 'round']);
|
|
21
19
|
const BOOLEAN = new Set(['needsImplementation']);
|
|
@@ -78,16 +76,11 @@ async function taskLedger(args: string[] = []): Promise<void> {
|
|
|
78
76
|
'decision-upsert': ['needsImplementation'],
|
|
79
77
|
'rework-intent-rebuild': ['findingId', 'sourceArtifact', 'sourceSha256', 'classification']
|
|
80
78
|
};
|
|
81
|
-
const
|
|
82
|
-
? []
|
|
83
|
-
: ['overrideTicket', 'overrideTarget', 'overrideScope'];
|
|
84
|
-
const allowed = new Set(['kind', 'taskRef', 'dryRun', ...required[kind]!, ...(optional[kind] ?? []), ...overrideFields]);
|
|
79
|
+
const allowed = new Set(['kind', 'taskRef', 'dryRun', ...required[kind]!, ...(optional[kind] ?? [])]);
|
|
85
80
|
const unexpected = Object.keys(values).find((key) => !allowed.has(key));
|
|
86
81
|
const missing = required[kind]!.find((key) => values[key] === undefined);
|
|
87
82
|
if (unexpected) { usageFailure(`${kind} does not accept '${unexpected}'`); return; }
|
|
88
83
|
if (missing) { usageFailure(`${kind} requires '${missing}'`); return; }
|
|
89
|
-
const dryRunConflict = overrideDryRunConflict(values);
|
|
90
|
-
if (dryRunConflict) { usageFailure(dryRunConflict.message); return; }
|
|
91
84
|
if (values.classification !== undefined && !REWORK_CLASSIFICATIONS.has(String(values.classification))) {
|
|
92
85
|
usageFailure(`invalid rework classification '${values.classification}'`); return;
|
|
93
86
|
}
|
|
@@ -124,42 +117,16 @@ async function taskLedger(args: string[] = []): Promise<void> {
|
|
|
124
117
|
const lockResolved = resolveTaskRef(taskRef);
|
|
125
118
|
if (!lockResolved.ok) { usageFailure(lockResolved.message); return; }
|
|
126
119
|
let result;
|
|
127
|
-
let humanOverride: unknown = null;
|
|
128
120
|
try {
|
|
129
|
-
result = await withTaskExecutionLock(lockResolved.repoRoot, lockResolved.taskId, `task-ledger.${kind}`, async () =>
|
|
130
|
-
let current = applyLedgerIntent(values as LedgerIntent);
|
|
131
|
-
if (current.status !== 'failed' || !values.overrideTicket) return current;
|
|
132
|
-
if (!values.overrideTarget || !values.overrideScope) { usageFailure('override ticket requires target and scope'); return current; }
|
|
133
|
-
const consumed = await consumeHumanOverride({
|
|
134
|
-
taskRef,
|
|
135
|
-
ticketId: String(values.overrideTicket),
|
|
136
|
-
failureId: failureId('ledger-intent', current.error?.code ?? 'TASK_STATE_MISMATCH'),
|
|
137
|
-
target: String(values.overrideTarget),
|
|
138
|
-
scope: String(values.overrideScope)
|
|
139
|
-
}, {
|
|
140
|
-
effectExecutor: (capability) => {
|
|
141
|
-
const retried = applyLedgerIntent(values as LedgerIntent, { manualOverride: capability });
|
|
142
|
-
current = retried;
|
|
143
|
-
return retried.status === 'failed'
|
|
144
|
-
? { code: 'OVERRIDE_EFFECT_FAILED', message: `${retried.error?.code ?? 'LEDGER_FAILED'}: ${retried.error?.message ?? 'manual ledger effect failed'}` }
|
|
145
|
-
: null;
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
humanOverride = consumed;
|
|
149
|
-
return current;
|
|
150
|
-
});
|
|
121
|
+
result = await withTaskExecutionLock(lockResolved.repoRoot, lockResolved.taskId, `task-ledger.${kind}`, async () => applyLedgerIntent(values as LedgerIntent));
|
|
151
122
|
} catch (error) {
|
|
152
123
|
if (!(error instanceof TaskExecutionLockError)) throw error;
|
|
153
124
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: error.code, message: error.message } })}\n`);
|
|
154
125
|
process.exitCode = 1;
|
|
155
126
|
return;
|
|
156
127
|
}
|
|
157
|
-
process.stdout.write(`${JSON.stringify(
|
|
158
|
-
|
|
159
|
-
humanOverride && typeof humanOverride === 'object' &&
|
|
160
|
-
(humanOverride as { status?: unknown }).status === 'failed'
|
|
161
|
-
);
|
|
162
|
-
if (result.status === 'failed' || overrideFailed) process.exitCode = 1;
|
|
128
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
129
|
+
if (result.status === 'failed') process.exitCode = 1;
|
|
163
130
|
}
|
|
164
131
|
|
|
165
132
|
export { taskLedger };
|
|
@@ -10,7 +10,7 @@ import type { TaskLifecycleResult } from '../task/lifecycle.ts';
|
|
|
10
10
|
import type { AgentClientLifecycleRecoveryResult as LifecycleRecoveryResult } from '../agent-clients/adapter.ts';
|
|
11
11
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
|
|
12
12
|
|
|
13
|
-
const USAGE = `Usage: agent-infra-internal task-lifecycle <N | TASK-id> <intent> --agent <agent> [intent flags] [--dry-run]\n\nIntents: ${lifecycleIntentCatalog.join(', ')}\nrecover-started: --auto\
|
|
13
|
+
const USAGE = `Usage: agent-infra-internal task-lifecycle <N | TASK-id> <intent> --agent <agent> [intent flags] [--dry-run]\n\nIntents: ${lifecycleIntentCatalog.join(', ')}\nrecover-started: --auto\n`;
|
|
14
14
|
|
|
15
15
|
function usageFailure(message: string): void {
|
|
16
16
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'LIFECYCLE_PAYLOAD_INVALID', message } })}\n`);
|
|
@@ -62,7 +62,7 @@ async function taskLifecycle(args: string[] = []): Promise<void> {
|
|
|
62
62
|
const result = await dispatchTaskControlOperation(
|
|
63
63
|
createDirectHostExecutionContext({ repoRoot }),
|
|
64
64
|
operation
|
|
65
|
-
) as
|
|
65
|
+
) as TaskLifecycleResult | LifecycleRecoveryResult;
|
|
66
66
|
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
67
67
|
if (result.status === 'failed' || result.status === 'owner-unknown' || result.status === 'conflict') process.exitCode = 1;
|
|
68
68
|
}
|
|
@@ -143,9 +143,6 @@ function internalTaskRoutes(): TaskOperationDescriptor[] {
|
|
|
143
143
|
descriptor('internal', 'task-review', 'finalize-summary', 'task-bound', 'progress'),
|
|
144
144
|
descriptor('internal', 'task-event', 'event', 'task-bound', 'progress'),
|
|
145
145
|
descriptor('internal', 'task-invalidation', 'reconcile', 'task-bound', 'progress'),
|
|
146
|
-
descriptor('internal', 'task-override', 'diagnose', 'task-bound', 'diagnostic'),
|
|
147
|
-
descriptor('internal', 'task-override', 'issue', 'task-bound', 'progress'),
|
|
148
|
-
descriptor('internal', 'task-override', 'consume', 'task-bound', 'progress'),
|
|
149
146
|
descriptor('internal', 'task-lifecycle', 'intent', 'task-bound', 'progress'),
|
|
150
147
|
descriptor('internal', 'task-finalization', 'complete', 'task-bound', 'progress'),
|
|
151
148
|
descriptor('internal', 'task-short-id', 'list', 'conditional', 'diagnostic'),
|
|
@@ -224,7 +221,7 @@ export const INTERNAL_DISPATCHER_ROUTES = Object.freeze([
|
|
|
224
221
|
'platform-comment', 'platform-issue', 'platform-pr', 'platform-pr-review', 'pr-review-grade',
|
|
225
222
|
'platform-checks', 'task-context', 'task-ledger', 'task-warning', 'task-activity', 'task-artifact',
|
|
226
223
|
'task-orchestration', 'task-review', 'task-event', 'task-invalidation', 'task-lifecycle',
|
|
227
|
-
'task-finalization', 'task-
|
|
224
|
+
'task-finalization', 'task-short-id', 'task-snapshot', 'task-verify', 'task-validate', 'manual-validation'
|
|
228
225
|
]);
|
|
229
226
|
export const PUBLIC_DISPATCHER_ROUTES = Object.freeze([
|
|
230
227
|
'agent-client', 'cp', 'data', 'decide', 'help', 'init', 'merge', 'run', 'sandbox', 'server', 'task', 'sync', 'update', 'version'
|