@fitlab-ai/agent-infra 0.9.15 → 0.11.0
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 +2 -6
- package/README.zh-CN.md +2 -6
- package/bin/internal-cli.sh +0 -13
- package/dist/bin/internal-cli.js +11 -49
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +6 -2
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +43 -3
- package/dist/lib/fs/durable-write.js +2 -0
- package/dist/lib/internal/cli-route-inventory.js +3 -4
- package/dist/lib/internal/manual-validation.js +17 -8
- package/dist/lib/internal/platform-comment.js +60 -2
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-finalization.js +2 -2
- package/dist/lib/internal/task-lifecycle.js +4 -4
- package/dist/lib/internal/task-operation-registry.js +22 -13
- package/dist/lib/internal/task-orchestration.js +2 -2
- package/dist/lib/internal/task-review.js +12 -7
- package/dist/lib/platform/activity-recovery.js +159 -0
- package/dist/lib/platform/issue-comments.js +89 -31
- package/dist/lib/platform/issues.js +1 -1
- package/dist/lib/platform/pr-change-report.js +5 -4
- package/dist/lib/platform/pr-summary.js +64 -32
- package/dist/lib/platform/pull-requests.js +3 -3
- package/dist/lib/platform/task-comment-projection.js +70 -0
- package/dist/lib/platform/task-identities.js +2 -9
- package/dist/lib/platform/task-recovery.js +55 -0
- package/dist/lib/platform/verification-sync.js +45 -7
- package/dist/lib/pr-review/evidence-grading.js +4 -8
- package/dist/lib/sandbox/commands/rm.js +21 -2
- package/dist/lib/sandbox/control/executor.js +4 -2
- package/dist/lib/sandbox/control/server.js +29 -9
- package/dist/lib/sandbox/control/state.js +7 -1
- package/dist/lib/sandbox/control/task-workflow.js +1 -1
- package/dist/lib/sandbox/control/workflow-executor.js +87 -35
- package/dist/lib/sandbox/index.js +1 -2
- package/dist/lib/sandbox/removal.js +116 -1011
- package/dist/lib/sandbox/workspace-identity.js +17 -1
- package/dist/lib/server/process-state.js +17 -5
- package/dist/lib/task/artifact-command.js +11 -30
- package/dist/lib/task/artifact-operations.js +17 -231
- package/dist/lib/task/artifact-recovery.js +476 -0
- package/dist/lib/task/artifact-repair-intent.js +90 -39
- package/dist/lib/task/commit-operation.js +31 -4
- package/dist/lib/task/control-authority.js +39 -8
- package/dist/lib/task/control-recovery.js +14 -2
- package/dist/lib/task/create-service.js +1 -8
- package/dist/lib/task/delegation-receipts.js +64 -3
- package/dist/lib/task/events.js +165 -23
- package/dist/lib/task/human-override.js +5 -8
- package/dist/lib/task/intermediate-cleanup.js +91 -8
- package/dist/lib/task/lifecycle-recovery.js +549 -0
- package/dist/lib/task/lifecycle.js +12 -3
- package/dist/lib/task/local-artifact-finalization.js +212 -123
- package/dist/lib/task/manual-validation-receipt.js +5 -1
- package/dist/lib/task/orchestration.js +52 -2
- package/dist/lib/task/pr-delivery-fact.js +12 -56
- package/dist/lib/task/recovery-warning.js +19 -0
- package/dist/lib/task/review-command.js +3 -2
- package/dist/lib/task/review-finalization.js +294 -74
- package/dist/lib/{host-control → task}/secure-fs.js +8 -19
- package/dist/lib/task/short-id.js +15 -7
- package/dist/lib/task/task-execution-lock.js +1 -1
- package/dist/lib/task/verification-engine.js +3 -14
- package/dist/lib/task/workflow-command.js +2 -1
- package/dist/lib/task/workflow-dispatch.js +161 -0
- package/dist/lib/version.js +1 -11
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +7 -2
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +55 -3
- package/lib/fs/durable-write.ts +1 -0
- package/lib/internal/cli-route-inventory.ts +3 -4
- package/lib/internal/manual-validation.ts +17 -8
- package/lib/internal/platform-comment.ts +53 -2
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-finalization.ts +1 -1
- package/lib/internal/task-lifecycle.ts +5 -4
- package/lib/internal/task-operation-registry.ts +22 -13
- package/lib/internal/task-orchestration.ts +1 -1
- package/lib/internal/task-review.ts +11 -7
- package/lib/platform/activity-recovery.ts +179 -0
- package/lib/platform/issue-comments.ts +105 -30
- package/lib/platform/issues.ts +1 -1
- package/lib/platform/pr-change-report.ts +7 -5
- package/lib/platform/pr-summary.ts +64 -32
- package/lib/platform/pull-requests.ts +3 -3
- package/lib/platform/task-comment-projection.ts +79 -0
- package/lib/platform/task-identities.ts +3 -14
- package/lib/platform/task-recovery.ts +54 -0
- package/lib/platform/verification-sync.ts +48 -11
- package/lib/pr-review/evidence-grading.ts +4 -9
- package/lib/sandbox/commands/rm.ts +34 -2
- package/lib/sandbox/control/executor.ts +4 -2
- package/lib/sandbox/control/protocol.ts +6 -1
- package/lib/sandbox/control/server.ts +30 -10
- package/lib/sandbox/control/state.ts +9 -1
- package/lib/sandbox/control/task-workflow.ts +1 -1
- package/lib/sandbox/control/workflow-executor.ts +106 -35
- package/lib/sandbox/index.ts +1 -2
- package/lib/sandbox/removal.ts +126 -1128
- package/lib/sandbox/workspace-identity.ts +19 -3
- package/lib/server/process-state.ts +26 -5
- package/lib/task/artifact-command.ts +12 -29
- package/lib/task/artifact-operations.ts +22 -252
- package/lib/task/artifact-recovery.ts +572 -0
- package/lib/task/artifact-repair-intent.ts +126 -51
- package/lib/task/commit-operation.ts +35 -4
- package/lib/task/control-authority.ts +48 -15
- package/lib/task/control-recovery.ts +13 -1
- package/lib/task/create-service.ts +1 -10
- package/lib/task/delegation-receipts.ts +73 -2
- package/lib/task/events.ts +188 -24
- package/lib/task/human-override.ts +5 -8
- package/lib/task/intermediate-cleanup.ts +66 -12
- package/lib/task/lifecycle-recovery.ts +691 -0
- package/lib/task/lifecycle.ts +13 -4
- package/lib/task/local-artifact-finalization.ts +249 -131
- package/lib/task/manual-validation-receipt.ts +5 -1
- package/lib/task/orchestration.ts +65 -0
- package/lib/task/pr-delivery-fact.ts +15 -62
- package/lib/task/recovery-warning.ts +21 -0
- package/lib/task/review-command.ts +2 -1
- package/lib/task/review-finalization.ts +298 -84
- package/lib/{host-control → task}/secure-fs.ts +10 -32
- package/lib/task/short-id.ts +27 -5
- package/lib/task/task-execution-lock.ts +1 -1
- package/lib/task/verification-engine.ts +3 -17
- package/lib/task/verification-types.ts +0 -2
- package/lib/task/workflow-command.ts +2 -1
- package/lib/task/workflow-dispatch.ts +171 -0
- package/lib/version.ts +1 -15
- package/package.json +1 -1
- package/templates/.agents/rules/create-issue.en.md +1 -1
- package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +12 -2
- package/templates/.agents/rules/issue-sync.zh-CN.md +13 -5
- package/templates/.agents/rules/local-artifact-repair.en.md +47 -39
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +45 -36
- package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/block-task/config/verify.json +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +7 -7
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/code-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/create-pr/config/verify.json +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +2 -2
- package/templates/.agents/skills/create-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/create-task/config/verify.json +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +8 -8
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/import-issue/config/verify.json +2 -2
- package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/restore-task/SKILL.en.md +11 -6
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +11 -6
- package/templates/.agents/skills/restore-task/config/verify.json +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +6 -6
- package/templates/.agents/skills/review-code/SKILL.en.md +5 -4
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -4
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -12
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -12
- package/templates/.agents/skills/review-plan/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -7
- package/templates/.agents/skills/review-pr/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +3 -3
- package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/templates/task.en.md +1 -1
- package/templates/.agents/templates/task.zh-CN.md +1 -1
- package/templates/.claude/commands/import-issue.en.md +1 -1
- package/templates/.claude/commands/import-issue.zh-CN.md +1 -1
- package/templates/.claude/commands/restore-task.en.md +1 -1
- package/templates/.claude/commands/restore-task.zh-CN.md +1 -1
- package/dist/lib/host-control/audit.js +0 -19
- package/dist/lib/host-control/client.js +0 -142
- package/dist/lib/host-control/command.js +0 -49
- package/dist/lib/host-control/path.js +0 -160
- package/dist/lib/host-control/server.js +0 -159
- package/dist/lib/host-control/service.js +0 -112
- package/dist/lib/internal/host-control.js +0 -28
- package/lib/host-control/audit.ts +0 -22
- package/lib/host-control/client.ts +0 -172
- package/lib/host-control/command.ts +0 -60
- package/lib/host-control/path.ts +0 -167
- package/lib/host-control/server.ts +0 -180
- package/lib/host-control/service.ts +0 -133
- package/lib/internal/host-control.ts +0 -28
|
@@ -9,10 +9,12 @@ import {
|
|
|
9
9
|
} from '../platform/issue-comments.ts';
|
|
10
10
|
import type { CommentKind } from '../platform/issue-comments.ts';
|
|
11
11
|
import type { PlatformResult } from '../platform/types.ts';
|
|
12
|
+
import { recoverTaskFromComments } from '../platform/task-recovery.ts';
|
|
12
13
|
import { backfillCompletionComments } from '../platform/completion-backfill.ts';
|
|
13
14
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
|
|
14
15
|
|
|
15
16
|
const USAGE = `Usage: agent-infra-internal platform-comment list --issue <token> [--cwd <path>]
|
|
17
|
+
agent-infra-internal platform-comment recover --issue <token> --task-id <TASK-id> --output <staging-task.md> [--cwd <path>]
|
|
16
18
|
agent-infra-internal platform-comment owner <task-ref> [--cwd <path>]
|
|
17
19
|
agent-infra-internal platform-comment backfill <task-ref> --agent <agent> [--cwd <path>]
|
|
18
20
|
agent-infra-internal platform-comment sync <task-ref> --kind <kind> --agent <agent> [--artifact <file>] [--body-file <path|->] [--status-label <label>] [--backfill] [--cwd <path>]
|
|
@@ -35,7 +37,7 @@ function parseFlags(args: string[], start: number): { values: Record<string, str
|
|
|
35
37
|
const seen = new Set<string>();
|
|
36
38
|
for (let index = start; index < args.length; index += 1) {
|
|
37
39
|
const flag = args[index]!;
|
|
38
|
-
if (!['--issue', '--cwd', '--kind', '--agent', '--artifact', '--body-file', '--status-label', '--backfill'].includes(flag)) {
|
|
40
|
+
if (!['--issue', '--task-id', '--output', '--cwd', '--kind', '--agent', '--artifact', '--body-file', '--status-label', '--backfill'].includes(flag)) {
|
|
39
41
|
return { values, error: `unknown option '${flag}'` };
|
|
40
42
|
}
|
|
41
43
|
if (seen.has(flag)) return { values, error: `duplicate option '${flag}'` };
|
|
@@ -59,11 +61,12 @@ async function platformComment(args: string[] = []): Promise<void> {
|
|
|
59
61
|
const operation = args[0];
|
|
60
62
|
if (!operation || ![
|
|
61
63
|
internalHandlerRoute('platform-comment', 'list', operation),
|
|
64
|
+
internalHandlerRoute('platform-comment', 'recover', operation),
|
|
62
65
|
internalHandlerRoute('platform-comment', 'owner', operation),
|
|
63
66
|
internalHandlerRoute('platform-comment', 'backfill', operation),
|
|
64
67
|
internalHandlerRoute('platform-comment', 'sync', operation)
|
|
65
68
|
].some(Boolean)) { fail('a valid operation is required'); return; }
|
|
66
|
-
const hasTaskRef = operation !== 'list';
|
|
69
|
+
const hasTaskRef = operation !== 'list' && operation !== 'recover';
|
|
67
70
|
const taskRef = hasTaskRef ? args[1] : undefined;
|
|
68
71
|
if (hasTaskRef && (!taskRef || taskRef.startsWith('--'))) { fail(`${operation} requires a task ref`); return; }
|
|
69
72
|
const parsed = parseFlags(args, hasTaskRef ? 2 : 1);
|
|
@@ -77,6 +80,54 @@ async function platformComment(args: string[] = []): Promise<void> {
|
|
|
77
80
|
finish(await listPlatformComments(issue, cwd));
|
|
78
81
|
return;
|
|
79
82
|
}
|
|
83
|
+
if (internalHandlerRoute('platform-comment', 'recover', operation)) {
|
|
84
|
+
const issue = parsed.values.issue;
|
|
85
|
+
const taskId = parsed.values.taskId;
|
|
86
|
+
const output = parsed.values.output;
|
|
87
|
+
if (typeof issue !== 'string' || !issue || typeof taskId !== 'string' || !/^TASK-\d{8}-\d{6}$/.test(taskId)
|
|
88
|
+
|| typeof output !== 'string' || !output) {
|
|
89
|
+
fail('recover requires --issue <token> --task-id <TASK-id> --output <staging-task.md>');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const unexpected = Object.keys(parsed.values).find((key) => !['issue', 'taskId', 'output', 'cwd'].includes(key));
|
|
93
|
+
if (unexpected) { fail(`recover does not accept '--${unexpected}'`); return; }
|
|
94
|
+
const outputPath = path.resolve(cwd, output);
|
|
95
|
+
const workspaceDir = path.resolve(cwd, '.agents', 'workspace');
|
|
96
|
+
const stagingDir = path.dirname(outputPath);
|
|
97
|
+
const stagingParent = fs.existsSync(stagingDir) ? path.dirname(fs.realpathSync(stagingDir)) : '';
|
|
98
|
+
const realWorkspace = fs.existsSync(workspaceDir) ? fs.realpathSync(workspaceDir) : '';
|
|
99
|
+
if (path.basename(outputPath) !== 'task.md' || stagingParent !== realWorkspace
|
|
100
|
+
|| !path.basename(stagingDir).startsWith('.restore-staging-') || !fs.existsSync(stagingDir)
|
|
101
|
+
|| !fs.lstatSync(stagingDir).isDirectory() || fs.existsSync(outputPath)) {
|
|
102
|
+
fail('recover output must be a new task.md inside an existing .agents/workspace/.restore-staging-* directory');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const listed = await listPlatformComments(issue, cwd);
|
|
106
|
+
if (listed.status === 'failed' || listed.status === 'blocked' || !listed.comments) { finish(listed); return; }
|
|
107
|
+
try {
|
|
108
|
+
const taskContent = recoverTaskFromComments({ taskId, comments: listed.comments });
|
|
109
|
+
fs.writeFileSync(outputPath, taskContent, { encoding: 'utf8', flag: 'wx', mode: 0o600 });
|
|
110
|
+
finish({
|
|
111
|
+
...listed,
|
|
112
|
+
status: 'applied',
|
|
113
|
+
changed: true,
|
|
114
|
+
operations: [{ name: `recovery:task:${taskId}`, status: 'applied', reasonCode: null }],
|
|
115
|
+
error: null
|
|
116
|
+
});
|
|
117
|
+
} catch (error) {
|
|
118
|
+
finish({
|
|
119
|
+
...listed,
|
|
120
|
+
status: 'failed',
|
|
121
|
+
changed: false,
|
|
122
|
+
error: {
|
|
123
|
+
code: 'RECOVERY_EVIDENCE_MISSING',
|
|
124
|
+
message: error instanceof Error ? error.message : String(error),
|
|
125
|
+
retryable: false
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
80
131
|
if (internalHandlerRoute('platform-comment', 'owner', operation)) {
|
|
81
132
|
const unexpected = Object.keys(parsed.values).find((key) => key !== 'cwd');
|
|
82
133
|
if (unexpected) { fail(`owner does not accept '--${unexpected}'`); return; }
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parseArtifactCommand, executeArtifactCommand } from '../task/artifact-command.ts';
|
|
2
2
|
import { ensureInternalHandlerRoute } from './cli-route-inventory.ts';
|
|
3
3
|
|
|
4
|
-
const USAGE = `Usage: agent-infra-internal task-artifact <N | TASK-id> inspect --family <family>\n agent-infra-internal task-artifact <N | TASK-id> init --family <family> --artifact <artifact> [--locale <zh-CN|en>]\n agent-infra-internal task-artifact <N | TASK-id>
|
|
4
|
+
const USAGE = `Usage: agent-infra-internal task-artifact <N | TASK-id> inspect --family <family>\n agent-infra-internal task-artifact <N | TASK-id> init --family <family> --artifact <artifact> [--locale <zh-CN|en>]\n agent-infra-internal task-artifact <N | TASK-id> preflight|finalize-local --family <analysis|plan|code> --artifact <artifact> [--recovery-id <id>]\n\nInspect, initialize, preflight, or finalize a workflow artifact without changing task state.\n`;
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
function taskArtifact(args: string[] = []): void {
|
|
@@ -38,8 +38,8 @@ function parseFailure(error: unknown): string {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
async function taskFinalization(args: string[] = []): Promise<void> {
|
|
41
|
-
if (!ensureInternalHandlerRoute('task-finalization', args)) return;
|
|
42
41
|
if (args[0] === '--help' || args[0] === '-h') { process.stdout.write(USAGE); return; }
|
|
42
|
+
if (!ensureInternalHandlerRoute('task-finalization', args)) return;
|
|
43
43
|
|
|
44
44
|
let operation;
|
|
45
45
|
try {
|
|
@@ -7,9 +7,10 @@ import {
|
|
|
7
7
|
import { lifecycleIntentCatalog } from '../task/lifecycle.ts';
|
|
8
8
|
import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.ts';
|
|
9
9
|
import type { TaskLifecycleResult } from '../task/lifecycle.ts';
|
|
10
|
+
import type { LifecycleRecoveryResult } from '../task/lifecycle-recovery.ts';
|
|
10
11
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
|
|
11
12
|
|
|
12
|
-
const USAGE = `Usage: agent-infra-internal task-lifecycle <N | TASK-id> <intent> --agent <agent> [intent flags] [--dry-run]\n\nIntents: ${lifecycleIntentCatalog.join(', ')}\nOverride: --override-ticket <ticket> --override-target <target> --override-scope <scope>\n`;
|
|
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: --stage <stage> --round <round> --artifact <artifact> --reason <reason>\nOverride: --override-ticket <ticket> --override-target <target> --override-scope <scope>\n`;
|
|
13
14
|
|
|
14
15
|
function usageFailure(message: string): void {
|
|
15
16
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'LIFECYCLE_PAYLOAD_INVALID', message } })}\n`);
|
|
@@ -23,8 +24,8 @@ function parseFailure(error: unknown): string {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
async function taskLifecycle(args: string[] = []): Promise<void> {
|
|
26
|
-
if (!ensureInternalHandlerRoute('task-lifecycle', args)) return;
|
|
27
27
|
if (args[0] === '--help' || args[0] === '-h') { process.stdout.write(USAGE); return; }
|
|
28
|
+
if (!ensureInternalHandlerRoute('task-lifecycle', args)) return;
|
|
28
29
|
|
|
29
30
|
let operation;
|
|
30
31
|
try {
|
|
@@ -61,9 +62,9 @@ async function taskLifecycle(args: string[] = []): Promise<void> {
|
|
|
61
62
|
const result = await dispatchTaskControlOperation(
|
|
62
63
|
createDirectHostExecutionContext({ repoRoot }),
|
|
63
64
|
operation
|
|
64
|
-
) as TaskLifecycleResult & { humanOverride?: unknown };
|
|
65
|
+
) as (TaskLifecycleResult | LifecycleRecoveryResult) & { humanOverride?: unknown };
|
|
65
66
|
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
66
|
-
if (result.status === 'failed') process.exitCode = 1;
|
|
67
|
+
if (result.status === 'failed' || result.status === 'owner-unknown' || result.status === 'conflict') process.exitCode = 1;
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
export { taskLifecycle };
|
|
@@ -56,10 +56,6 @@ function internalTaskRoutes(): TaskOperationDescriptor[] {
|
|
|
56
56
|
descriptor('internal', 'sandbox-control', 'execute', 'non-task', 'progress', 'none'),
|
|
57
57
|
descriptor('internal', 'sandbox-control', 'recover', 'conditional', 'recovery', 'delegated'),
|
|
58
58
|
descriptor('internal', 'sandbox-control', 'client', 'conditional', 'progress', 'delegated'),
|
|
59
|
-
descriptor('internal', 'host-control', 'serve', 'non-task', 'progress', 'none'),
|
|
60
|
-
descriptor('internal', 'host-control', 'status', 'non-task', 'diagnostic', 'none'),
|
|
61
|
-
descriptor('internal', 'host-control', 'install', 'non-task', 'progress', 'none'),
|
|
62
|
-
descriptor('internal', 'host-control', 'uninstall', 'non-task', 'progress', 'none'),
|
|
63
59
|
descriptor('internal', 'agent-client', 'next-steps', 'non-task', 'diagnostic', 'none'),
|
|
64
60
|
descriptor('internal', 'agent-client', 'model-selection', 'non-task', 'diagnostic', 'none'),
|
|
65
61
|
descriptor('internal', 'codex-lifecycle', 'preflight', 'non-task', 'diagnostic', 'none'),
|
|
@@ -91,6 +87,7 @@ function internalTaskRoutes(): TaskOperationDescriptor[] {
|
|
|
91
87
|
descriptor('internal', 'platform-metadata', 'init-milestones', 'non-task', 'remote-write', 'none'),
|
|
92
88
|
descriptor('internal', 'platform-context', 'resolve', 'non-task', 'diagnostic', 'none'),
|
|
93
89
|
descriptor('internal', 'platform-comment', 'list', 'non-task', 'diagnostic', 'none'),
|
|
90
|
+
descriptor('internal', 'platform-comment', 'recover', 'non-task', 'recovery', 'none'),
|
|
94
91
|
descriptor('internal', 'platform-comment', 'owner', 'task-bound', 'diagnostic'),
|
|
95
92
|
descriptor('internal', 'platform-comment', 'backfill', 'task-bound', 'remote-write'),
|
|
96
93
|
descriptor('internal', 'platform-comment', 'sync', 'task-bound', 'remote-write'),
|
|
@@ -136,10 +133,11 @@ function internalTaskRoutes(): TaskOperationDescriptor[] {
|
|
|
136
133
|
descriptor('internal', 'task-activity', 'pr-review-terminate', 'task-bound', 'progress'),
|
|
137
134
|
descriptor('internal', 'task-artifact', 'inspect', 'task-bound', 'diagnostic'),
|
|
138
135
|
descriptor('internal', 'task-artifact', 'init', 'task-bound', 'artifact-write'),
|
|
139
|
-
descriptor('internal', 'task-artifact', '
|
|
136
|
+
descriptor('internal', 'task-artifact', 'preflight', 'task-bound', 'artifact-write'),
|
|
140
137
|
descriptor('internal', 'task-artifact', 'finalize-local', 'task-bound', 'artifact-write'),
|
|
141
138
|
descriptor('internal', 'task-orchestration', 'status', 'task-bound', 'diagnostic'),
|
|
142
139
|
descriptor('internal', 'task-orchestration', 'progress', 'task-bound', 'progress'),
|
|
140
|
+
descriptor('internal', 'task-review', 'preflight', 'task-bound', 'artifact-write'),
|
|
143
141
|
descriptor('internal', 'task-review', 'finalize-summary', 'task-bound', 'progress'),
|
|
144
142
|
descriptor('internal', 'task-event', 'event', 'task-bound', 'progress'),
|
|
145
143
|
descriptor('internal', 'task-invalidation', 'reconcile', 'task-bound', 'progress'),
|
|
@@ -219,7 +217,7 @@ export const TASK_OPERATION_DESCRIPTORS = Object.freeze([
|
|
|
219
217
|
]);
|
|
220
218
|
|
|
221
219
|
export const INTERNAL_DISPATCHER_ROUTES = Object.freeze([
|
|
222
|
-
'task-create', 'task-qualification', 'sandbox-control', '
|
|
220
|
+
'task-create', 'task-qualification', 'sandbox-control', 'agent-client', 'codex-lifecycle', 'codex-sandbox-controller',
|
|
223
221
|
'git-workflow', 'task-delivery', 'release-workflow', 'platform-release-notes', 'platform-security', 'platform-metadata', 'platform-context',
|
|
224
222
|
'platform-comment', 'platform-issue', 'platform-pr', 'platform-pr-review', 'pr-review-grade',
|
|
225
223
|
'platform-checks', 'task-context', 'task-ledger', 'task-warning', 'task-activity', 'task-artifact',
|
|
@@ -343,28 +341,39 @@ export function resolveSandboxControlTransport(
|
|
|
343
341
|
env: NodeJS.ProcessEnv = process.env,
|
|
344
342
|
options: Readonly<{ statusMountPath?: string }> = {}
|
|
345
343
|
): SandboxControlTransportDecision {
|
|
346
|
-
const configuredStatusDir = env.AGENT_INFRA_CONTROL_STATUS_DIR;
|
|
347
|
-
const fixedStatusDir = options.statusMountPath
|
|
348
|
-
?? SANDBOX_CONTROL_STATUS_MOUNT;
|
|
349
344
|
const hasAnyMarker = TASK_MARKER_KEYS.some((key) => Boolean(env[key]))
|
|
350
345
|
|| Boolean(env.AGENT_INFRA_CONTROL_CONTROLLER_BINDING)
|
|
351
346
|
|| Boolean(env.AGENT_INFRA_EXECUTOR_MANIFEST);
|
|
352
|
-
const
|
|
347
|
+
const fixedStatusDir = options.statusMountPath
|
|
348
|
+
?? SANDBOX_CONTROL_STATUS_MOUNT;
|
|
349
|
+
const fixedStatusProbe = path.isAbsolute(fixedStatusDir)
|
|
353
350
|
? nativeDirectoryProbe(fixedStatusDir) : 'absent';
|
|
354
351
|
if (fixedStatusProbe === 'unknown') {
|
|
355
352
|
return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_UNAVAILABLE' };
|
|
356
353
|
}
|
|
357
354
|
const fixedStatusMounted = fixedStatusProbe === 'present';
|
|
355
|
+
// A fixed status mount is installed only in a task-bound sandbox. Its
|
|
356
|
+
// presence is not mutable by the sandbox process, so marker removal must
|
|
357
|
+
// never make that process look like a direct host invocation.
|
|
358
|
+
if (!hasAnyMarker) {
|
|
359
|
+
return fixedStatusMounted
|
|
360
|
+
? { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_CONFIGURATION_INCOMPLETE' }
|
|
361
|
+
: { kind: 'direct-host', reasonCode: null };
|
|
362
|
+
}
|
|
363
|
+
const hasCompleteConfig = TASK_CONTROL_CONFIG_KEYS.every((key) => Boolean(env[key]));
|
|
364
|
+
if (!hasCompleteConfig) {
|
|
365
|
+
return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_CONFIGURATION_INCOMPLETE' };
|
|
366
|
+
}
|
|
367
|
+
const configuredStatusDir = env.AGENT_INFRA_CONTROL_STATUS_DIR;
|
|
358
368
|
const configuredStatusProbe = configuredStatusDir && path.isAbsolute(configuredStatusDir)
|
|
359
369
|
? nativeDirectoryProbe(configuredStatusDir) : 'absent';
|
|
360
370
|
if (configuredStatusProbe === 'unknown') {
|
|
361
371
|
return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_UNAVAILABLE' };
|
|
362
372
|
}
|
|
363
|
-
const statusDir = configuredStatusDir && path.isAbsolute(configuredStatusDir)
|
|
364
|
-
? configuredStatusDir
|
|
373
|
+
const statusDir = configuredStatusDir && path.isAbsolute(configuredStatusDir)
|
|
374
|
+
? configuredStatusProbe === 'present' ? configuredStatusDir : null
|
|
365
375
|
: fixedStatusMounted ? fixedStatusDir : null;
|
|
366
376
|
const statusMounted = statusDir !== null;
|
|
367
|
-
const hasCompleteConfig = TASK_CONTROL_CONFIG_KEYS.every((key) => Boolean(env[key]));
|
|
368
377
|
if (env.AGENT_INFRA_EXECUTOR_MANIFEST || env.AGENT_INFRA_CONTROL_CONTROLLER_BINDING) {
|
|
369
378
|
return { kind: 'fail-closed', reasonCode: 'TASK_CONTROL_TRANSPORT_INVALID' };
|
|
370
379
|
}
|
|
@@ -20,11 +20,11 @@ function parseFailure(error: unknown): string {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
async function taskOrchestration(args: string[] = []): Promise<void> {
|
|
23
|
-
if (!ensureInternalHandlerRoute('task-orchestration', args)) return;
|
|
24
23
|
if (args[0] === '--help' || args[0] === '-h') {
|
|
25
24
|
process.stdout.write(USAGE);
|
|
26
25
|
return;
|
|
27
26
|
}
|
|
27
|
+
if (!ensureInternalHandlerRoute('task-orchestration', args)) return;
|
|
28
28
|
|
|
29
29
|
const route = internalHandlerRoute('task-orchestration', 'status', args[1] ?? '')
|
|
30
30
|
? 'status'
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { parseReviewCommand } from '../task/review-command.ts';
|
|
2
|
-
import { finalizeReviewSummary } from '../task/review-finalization.ts';
|
|
2
|
+
import { finalizeReviewSummary, preflightReviewSummary } from '../task/review-finalization.ts';
|
|
3
3
|
import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.ts';
|
|
4
4
|
import { resolveTaskRef } from '../task/resolve-ref.ts';
|
|
5
5
|
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
|
|
6
6
|
import { ensureInternalHandlerRoute } from './cli-route-inventory.ts';
|
|
7
7
|
|
|
8
|
-
const USAGE = 'Usage: agent-infra-internal task-review <task-ref> finalize-summary --stage <analysis|plan|code> --artifact <review-*.md> [--orchestrated] [--dry-run] [--override-ticket <ticket> --override-target <target> --override-scope <scope>]\n';
|
|
8
|
+
const USAGE = 'Usage: agent-infra-internal task-review <task-ref> <preflight|finalize-summary> --stage <analysis|plan|code> --artifact <review-*.md> [--recovery-id <id>] [--orchestrated] [--dry-run] [--override-ticket <ticket> --override-target <target> --override-scope <scope>]\n';
|
|
9
9
|
|
|
10
10
|
function failUsage(message: string): void {
|
|
11
11
|
process.stdout.write(`${JSON.stringify({
|
|
12
12
|
status: 'failed',
|
|
13
13
|
changed: false,
|
|
14
|
-
intent: '
|
|
14
|
+
intent: 'review-operation',
|
|
15
15
|
error: { code: 'REVIEW_PAYLOAD_INVALID', message }
|
|
16
16
|
})}\n`);
|
|
17
17
|
process.stderr.write(USAGE);
|
|
@@ -27,7 +27,8 @@ async function taskReview(args: string[] = []): Promise<void> {
|
|
|
27
27
|
let request;
|
|
28
28
|
try { request = parseReviewCommand(args); }
|
|
29
29
|
catch (error) { failUsage(error instanceof Error ? error.message : String(error)); return; }
|
|
30
|
-
const { stage, artifact, dryRun, orchestrated, overrideTicket, overrideTarget, overrideScope } = request;
|
|
30
|
+
const { stage, artifact, recoveryId, dryRun, orchestrated, overrideTicket, overrideTarget, overrideScope } = request;
|
|
31
|
+
const operation = args[1]!;
|
|
31
32
|
const dryRunConflict = overrideDryRunConflict({ dryRun, overrideTicket, overrideTarget, overrideScope });
|
|
32
33
|
if (dryRunConflict) { failUsage(dryRunConflict.message); return; }
|
|
33
34
|
const resolved = resolveTaskRef(args[0]!);
|
|
@@ -35,8 +36,11 @@ async function taskReview(args: string[] = []): Promise<void> {
|
|
|
35
36
|
let result;
|
|
36
37
|
let humanOverride: unknown = null;
|
|
37
38
|
try {
|
|
38
|
-
result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId,
|
|
39
|
-
let current =
|
|
39
|
+
result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-review.${operation}`, async () => {
|
|
40
|
+
let current = operation === 'preflight'
|
|
41
|
+
? preflightReviewSummary({ taskRef: args[0]!, stage, artifact, recoveryId, orchestrated, dryRun }, { lockAlreadyHeld: true })
|
|
42
|
+
: finalizeReviewSummary({ taskRef: args[0]!, stage, artifact, recoveryId, orchestrated, dryRun }, { lockAlreadyHeld: true });
|
|
43
|
+
if (operation === 'preflight') return current;
|
|
40
44
|
if (current.status !== 'failed' || !overrideTicket) return current;
|
|
41
45
|
if (!overrideTarget || !overrideScope) { failUsage('override ticket requires target and scope'); return current; }
|
|
42
46
|
const consumed = await consumeHumanOverride({
|
|
@@ -45,7 +49,7 @@ async function taskReview(args: string[] = []): Promise<void> {
|
|
|
45
49
|
target: overrideTarget, scope: overrideScope
|
|
46
50
|
}, {
|
|
47
51
|
effectExecutor: (capability) => {
|
|
48
|
-
const retried = finalizeReviewSummary({ taskRef: args[0]!, stage, artifact, orchestrated, dryRun }, { lockAlreadyHeld: true, manualOverride: capability });
|
|
52
|
+
const retried = finalizeReviewSummary({ taskRef: args[0]!, stage, artifact, recoveryId, orchestrated, dryRun }, { lockAlreadyHeld: true, manualOverride: capability });
|
|
49
53
|
current = retried;
|
|
50
54
|
return retried.status === 'failed' || retried.status === 'planned'
|
|
51
55
|
? { code: 'OVERRIDE_EFFECT_FAILED', message: retried.status === 'planned' ? 'producer returned planned; no review effect was committed' : `${retried.error?.code ?? 'REVIEW_FAILED'}: ${retried.error?.message ?? 'manual review effect failed'}` }
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import { locateActivityLog } from '../task/activity-log.ts';
|
|
4
|
+
import type { LogEntry } from '../task/activity-log.ts';
|
|
5
|
+
|
|
6
|
+
const METADATA_PREFIX = '<!-- activity-recovery:v1:';
|
|
7
|
+
const METADATA_SUFFIX = ' -->';
|
|
8
|
+
|
|
9
|
+
type ActivityRecoveryRecord = {
|
|
10
|
+
version: 1;
|
|
11
|
+
taskId: string;
|
|
12
|
+
artifact?: string;
|
|
13
|
+
entries: LogEntry[];
|
|
14
|
+
sha256: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function canonicalJson(value: unknown): string {
|
|
18
|
+
if (value === null || typeof value !== 'object') return JSON.stringify(value);
|
|
19
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`;
|
|
20
|
+
const record = value as Record<string, unknown>;
|
|
21
|
+
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(',')}}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function sha256(value: unknown): string {
|
|
25
|
+
return createHash('sha256').update(canonicalJson(value), 'utf8').digest('hex');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function baseStep(step: string): string {
|
|
29
|
+
return step.replace(/\s*\[(?:started|aborted)\]\s*$/, '');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function entriesForArtifact(taskContent: string, artifact: string): LogEntry[] {
|
|
33
|
+
const log = locateActivityLog(taskContent);
|
|
34
|
+
if (!log) return [];
|
|
35
|
+
const selected = new Set<number>();
|
|
36
|
+
for (let index = 0; index < log.entries.length; index += 1) {
|
|
37
|
+
const entry = log.entries[index]!;
|
|
38
|
+
if (!entry.note.includes(`→ ${artifact}`)) continue;
|
|
39
|
+
selected.add(index);
|
|
40
|
+
const step = baseStep(entry.step);
|
|
41
|
+
let startedAt = index;
|
|
42
|
+
for (let prior = index - 1; prior >= 0; prior -= 1) {
|
|
43
|
+
const candidate = log.entries[prior]!;
|
|
44
|
+
if (baseStep(candidate.step) !== step) continue;
|
|
45
|
+
if (/\[started\]\s*$/.test(candidate.step)) {
|
|
46
|
+
selected.add(prior);
|
|
47
|
+
startedAt = prior;
|
|
48
|
+
}
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
if (/^code(?:-r\d+)?\.md$/.test(artifact)) {
|
|
52
|
+
for (let between = startedAt + 1; between < index; between += 1) {
|
|
53
|
+
if (baseStep(log.entries[between]!.step) === 'Commit') selected.add(between);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return log.entries.filter((_entry, index) => selected.has(index));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function recordForEntries(taskId: string, entries: readonly LogEntry[], artifact?: string): ActivityRecoveryRecord | null {
|
|
61
|
+
if (entries.length === 0) return null;
|
|
62
|
+
const base = { version: 1 as const, taskId, ...(artifact ? { artifact } : {}), entries: [...entries] };
|
|
63
|
+
return { ...base, sha256: sha256(base) };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function recordForArtifact(taskId: string, taskContent: string, artifact: string): ActivityRecoveryRecord | null {
|
|
67
|
+
return recordForEntries(taskId, entriesForArtifact(taskContent, artifact), artifact);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function entriesForTask(taskContent: string): LogEntry[] {
|
|
71
|
+
const log = locateActivityLog(taskContent);
|
|
72
|
+
if (!log) return [];
|
|
73
|
+
const linked = new Set(entriesForArtifactNames(log.entries));
|
|
74
|
+
return log.entries.filter((entry, index) => !linked.has(index)
|
|
75
|
+
&& !['Create PR', 'Complete Task', 'Cancel Task'].includes(baseStep(entry.step)));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function entriesForSummary(taskContent: string): LogEntry[] {
|
|
79
|
+
const log = locateActivityLog(taskContent);
|
|
80
|
+
return log?.entries.filter((entry) => ['Create PR', 'Complete Task'].includes(baseStep(entry.step))) ?? [];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function entriesForCancel(taskContent: string): LogEntry[] {
|
|
84
|
+
const log = locateActivityLog(taskContent);
|
|
85
|
+
return log?.entries.filter((entry) => baseStep(entry.step) === 'Cancel Task') ?? [];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function recordForTask(taskId: string, taskContent: string): ActivityRecoveryRecord | null {
|
|
89
|
+
return recordForEntries(taskId, entriesForTask(taskContent));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function recordForSummary(taskId: string, taskContent: string): ActivityRecoveryRecord | null {
|
|
93
|
+
return recordForEntries(taskId, entriesForSummary(taskContent));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function recordForCancel(taskId: string, taskContent: string): ActivityRecoveryRecord | null {
|
|
97
|
+
return recordForEntries(taskId, entriesForCancel(taskContent));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function entriesForArtifactNames(entries: readonly LogEntry[]): number[] {
|
|
101
|
+
const selected = new Set<number>();
|
|
102
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
103
|
+
const entry = entries[index]!;
|
|
104
|
+
const artifact = /→ ([A-Za-z0-9][A-Za-z0-9._-]*\.md)(?:\s|$)/.exec(entry.note)?.[1];
|
|
105
|
+
if (!artifact) continue;
|
|
106
|
+
selected.add(index);
|
|
107
|
+
const step = baseStep(entry.step);
|
|
108
|
+
let startedAt = index;
|
|
109
|
+
for (let prior = index - 1; prior >= 0; prior -= 1) {
|
|
110
|
+
const candidate = entries[prior]!;
|
|
111
|
+
if (baseStep(candidate.step) !== step) continue;
|
|
112
|
+
if (/\[started\]\s*$/.test(candidate.step)) {
|
|
113
|
+
selected.add(prior);
|
|
114
|
+
startedAt = prior;
|
|
115
|
+
}
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
if (/^code(?:-r\d+)?\.md$/.test(artifact)) {
|
|
119
|
+
for (let between = startedAt + 1; between < index; between += 1) {
|
|
120
|
+
if (baseStep(entries[between]!.step) === 'Commit') selected.add(between);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return [...selected];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function renderActivityRecoveryMetadata(record: ActivityRecoveryRecord | null): string {
|
|
128
|
+
if (!record) return '';
|
|
129
|
+
const encoded = Buffer.from(canonicalJson(record), 'utf8').toString('base64url');
|
|
130
|
+
const first = record.entries[0]!;
|
|
131
|
+
const last = record.entries.at(-1)!;
|
|
132
|
+
return [
|
|
133
|
+
'<details><summary>恢复元数据</summary>',
|
|
134
|
+
'',
|
|
135
|
+
`- 动作:${baseStep(last.step)}`,
|
|
136
|
+
`- 记录:${record.entries.length} 条(${first.time} 至 ${last.time})`,
|
|
137
|
+
...(record.artifact ? [`- 产物:${record.artifact}`] : []),
|
|
138
|
+
`- 校验:\`${record.sha256}\``,
|
|
139
|
+
'',
|
|
140
|
+
`${METADATA_PREFIX}${encoded}${METADATA_SUFFIX}`,
|
|
141
|
+
'</details>',
|
|
142
|
+
''
|
|
143
|
+
].join('\n');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function parseActivityRecoveryMetadata(body: string): ActivityRecoveryRecord | null {
|
|
147
|
+
const match = body.match(/<!-- activity-recovery:v1:([A-Za-z0-9_-]+) -->/);
|
|
148
|
+
if (!match) return null;
|
|
149
|
+
let value: unknown;
|
|
150
|
+
try { value = JSON.parse(Buffer.from(match[1]!, 'base64url').toString('utf8')); }
|
|
151
|
+
catch { throw new Error('RECOVERY_EVIDENCE_MISSING: activity metadata is not valid'); }
|
|
152
|
+
if (!value || typeof value !== 'object') throw new Error('RECOVERY_EVIDENCE_MISSING: activity metadata is invalid');
|
|
153
|
+
const record = value as Partial<ActivityRecoveryRecord>;
|
|
154
|
+
if (record.version !== 1 || typeof record.taskId !== 'string' || (record.artifact !== undefined && typeof record.artifact !== 'string')
|
|
155
|
+
|| !Array.isArray(record.entries) || typeof record.sha256 !== 'string') {
|
|
156
|
+
throw new Error('RECOVERY_EVIDENCE_MISSING: activity metadata shape is invalid');
|
|
157
|
+
}
|
|
158
|
+
if (record.entries.some((entry) => !entry || typeof entry.time !== 'string' || typeof entry.step !== 'string' || typeof entry.agent !== 'string' || typeof entry.note !== 'string')) {
|
|
159
|
+
throw new Error('RECOVERY_EVIDENCE_MISSING: activity metadata entries are invalid');
|
|
160
|
+
}
|
|
161
|
+
const base = { version: 1 as const, taskId: record.taskId, ...(record.artifact ? { artifact: record.artifact } : {}), entries: record.entries as LogEntry[] };
|
|
162
|
+
if (sha256(base) !== record.sha256) throw new Error('RECOVERY_EVIDENCE_MISSING: activity metadata checksum does not match');
|
|
163
|
+
return { ...base, sha256: record.sha256 };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export {
|
|
167
|
+
entriesForArtifact,
|
|
168
|
+
entriesForCancel,
|
|
169
|
+
entriesForSummary,
|
|
170
|
+
entriesForTask,
|
|
171
|
+
parseActivityRecoveryMetadata,
|
|
172
|
+
recordForArtifact,
|
|
173
|
+
recordForCancel,
|
|
174
|
+
recordForEntries,
|
|
175
|
+
recordForSummary,
|
|
176
|
+
recordForTask,
|
|
177
|
+
renderActivityRecoveryMetadata
|
|
178
|
+
};
|
|
179
|
+
export type { ActivityRecoveryRecord };
|