@fitlab-ai/agent-infra 0.9.7 → 0.9.9
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/bin/internal-cli.js +11 -1
- package/dist/lib/agent-clients/adapters/claude-code.js +3 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +60 -22
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +8 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +175 -11
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-context.js +147 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +14 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +86 -69
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +2 -1
- package/dist/lib/decide.js +68 -14
- package/dist/lib/git/workflow.js +10 -1
- package/dist/lib/init.js +1 -1
- package/dist/lib/internal/codex-lifecycle.js +19 -5
- package/dist/lib/internal/codex-sandbox-controller.js +6 -13
- package/dist/lib/internal/git-workflow.js +5 -7
- package/dist/lib/internal/platform-pr.js +20 -7
- package/dist/lib/internal/release-workflow.js +3 -4
- package/dist/lib/internal/sandbox-control.js +79 -2
- package/dist/lib/internal/task-activity.js +57 -7
- package/dist/lib/internal/task-event.js +57 -4
- package/dist/lib/internal/task-finalization.js +54 -0
- package/dist/lib/internal/task-ledger.js +59 -5
- package/dist/lib/internal/task-lifecycle.js +59 -8
- package/dist/lib/internal/task-orchestration.js +161 -288
- package/dist/lib/internal/task-override.js +131 -0
- package/dist/lib/internal/task-review.js +64 -6
- package/dist/lib/internal/task-verify.js +65 -3
- package/dist/lib/internal/task-warning.js +57 -4
- package/dist/lib/platform/pr-summary.js +53 -13
- package/dist/lib/platform/pull-requests.js +205 -41
- package/dist/lib/runtime/agent-runtime.js +56 -0
- package/dist/lib/sandbox/commands/create.js +389 -311
- package/dist/lib/sandbox/commands/rm.js +316 -55
- package/dist/lib/sandbox/control/client.js +190 -6
- package/dist/lib/sandbox/control/container-identity.js +1 -1
- package/dist/lib/sandbox/control/controller-registration.js +273 -0
- package/dist/lib/sandbox/control/executor.js +145 -5
- package/dist/lib/sandbox/control/lifecycle.js +395 -73
- package/dist/lib/sandbox/control/protocol.js +67 -6
- package/dist/lib/sandbox/control/state.js +27 -11
- package/dist/lib/sandbox/engine.js +14 -7
- package/dist/lib/sandbox/index.js +5 -3
- package/dist/lib/sandbox/managed-fs.js +5 -0
- package/dist/lib/sandbox/mounts.js +20 -5
- package/dist/lib/sandbox/recovery.js +78 -10
- package/dist/lib/sandbox/task-resolver.js +50 -6
- package/dist/lib/sandbox/workspace-identity.js +27 -0
- package/dist/lib/sandbox/workspace-view.js +62 -40
- package/dist/lib/sandbox/worktree-safety.js +215 -2
- package/dist/lib/server/process-state.js +51 -1
- package/dist/lib/task/activity-intent.js +4 -1
- package/dist/lib/task/codex-orchestration.js +158 -33
- package/dist/lib/task/commands/status.js +59 -1
- package/dist/lib/task/commit-operation.js +335 -0
- package/dist/lib/task/commit-policy.js +18 -0
- package/dist/lib/task/decision-intents.js +4 -2
- package/dist/lib/task/delegation-receipts.js +279 -17
- package/dist/lib/task/events.js +9 -5
- package/dist/lib/task/finalization.js +567 -0
- package/dist/lib/task/guard-override.js +31 -0
- package/dist/lib/task/human-override.js +1240 -0
- package/dist/lib/task/ledger-intents.js +6 -3
- package/dist/lib/task/lifecycle.js +69 -14
- package/dist/lib/task/operation-outcome.js +33 -0
- package/dist/lib/task/orchestration.js +199 -719
- package/dist/lib/task/review-finalization.js +3 -2
- package/dist/lib/task/short-id.js +59 -1
- package/dist/lib/task/task-execution-lock.js +6 -1
- package/dist/lib/task/verification-engine.js +138 -59
- package/dist/lib/task/verification.js +15 -2
- package/dist/lib/task/workflow-warning-intents.js +54 -3
- package/dist/lib/task/write.js +2 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/claude-code.ts +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +63 -20
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +12 -0
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +278 -13
- package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +180 -0
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +19 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +9 -1
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +112 -93
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +2 -1
- package/lib/decide.ts +59 -14
- package/lib/git/workflow.ts +11 -2
- package/lib/init.ts +1 -1
- package/lib/internal/codex-lifecycle.ts +18 -5
- package/lib/internal/codex-sandbox-controller.ts +6 -12
- package/lib/internal/git-workflow.ts +5 -6
- package/lib/internal/platform-pr.ts +14 -7
- package/lib/internal/release-workflow.ts +3 -4
- package/lib/internal/sandbox-control.ts +92 -2
- package/lib/internal/task-activity.ts +46 -6
- package/lib/internal/task-event.ts +53 -4
- package/lib/internal/task-finalization.ts +68 -0
- package/lib/internal/task-ledger.ts +48 -4
- package/lib/internal/task-lifecycle.ts +56 -12
- package/lib/internal/task-orchestration.ts +32 -107
- package/lib/internal/task-override.ts +138 -0
- package/lib/internal/task-review.ts +51 -6
- package/lib/internal/task-verify.ts +52 -3
- package/lib/internal/task-warning.ts +44 -3
- package/lib/platform/pr-summary.ts +65 -14
- package/lib/platform/pull-requests.ts +211 -41
- package/lib/runtime/agent-runtime.ts +76 -0
- package/lib/sandbox/commands/create.ts +88 -4
- package/lib/sandbox/commands/rm.ts +413 -61
- package/lib/sandbox/config.ts +2 -1
- package/lib/sandbox/control/client.ts +255 -6
- package/lib/sandbox/control/container-identity.ts +3 -3
- package/lib/sandbox/control/controller-registration.ts +347 -0
- package/lib/sandbox/control/executor.ts +172 -6
- package/lib/sandbox/control/lifecycle.ts +465 -77
- package/lib/sandbox/control/protocol.ts +85 -9
- package/lib/sandbox/control/state.ts +35 -18
- package/lib/sandbox/engine.ts +20 -9
- package/lib/sandbox/index.ts +5 -3
- package/lib/sandbox/managed-fs.ts +5 -0
- package/lib/sandbox/mounts.ts +23 -5
- package/lib/sandbox/recovery.ts +90 -9
- package/lib/sandbox/task-resolver.ts +58 -7
- package/lib/sandbox/workspace-identity.ts +50 -2
- package/lib/sandbox/workspace-view.ts +73 -43
- package/lib/sandbox/worktree-safety.ts +236 -2
- package/lib/server/process-state.ts +48 -1
- package/lib/task/activity-intent.ts +4 -2
- package/lib/task/codex-orchestration.ts +167 -33
- package/lib/task/commands/status.ts +78 -1
- package/lib/task/commit-operation.ts +430 -0
- package/lib/task/commit-policy.ts +30 -0
- package/lib/task/decision-intents.ts +4 -2
- package/lib/task/delegation-receipts.ts +304 -19
- package/lib/task/events.ts +10 -5
- package/lib/task/finalization.ts +719 -0
- package/lib/task/guard-override.ts +50 -0
- package/lib/task/human-override.ts +1503 -0
- package/lib/task/ledger-intents.ts +6 -3
- package/lib/task/lifecycle.ts +71 -14
- package/lib/task/operation-outcome.ts +48 -0
- package/lib/task/orchestration.ts +239 -922
- package/lib/task/review-finalization.ts +6 -2
- package/lib/task/short-id.ts +65 -1
- package/lib/task/task-execution-lock.ts +10 -1
- package/lib/task/verification-engine.ts +145 -64
- package/lib/task/verification.ts +18 -3
- package/lib/task/workflow-warning-intents.ts +61 -4
- package/lib/task/write.ts +4 -1
- package/package.json +2 -2
- package/templates/.agents/QUICKSTART.en.md +2 -2
- package/templates/.agents/QUICKSTART.zh-CN.md +2 -2
- package/templates/.agents/hooks/lifecycle-delegation.js +9 -24
- package/templates/.agents/rules/lifecycle-orchestration.en.md +4 -3
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +4 -3
- package/templates/.agents/rules/task-management.en.md +2 -2
- package/templates/.agents/rules/task-management.zh-CN.md +2 -2
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -4
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -4
- 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/close-codescan/SKILL.en.md +6 -2
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +6 -2
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/commit/SKILL.en.md +41 -125
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +52 -123
- package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -1
- package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +14 -43
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +14 -49
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +2 -35
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +2 -35
- package/templates/.agents/skills/complete-task/SKILL.en.md +31 -38
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +31 -38
- package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
- 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 +6 -4
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +6 -4
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +2 -2
- package/templates/.agents/skills/run-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +3 -1
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +3 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +1 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +1 -1
- package/templates/.codex/hooks.json +4 -4
- package/dist/lib/task/commit-finalization.js +0 -305
- package/dist/lib/task/commit-intent.js +0 -165
- package/lib/task/commit-finalization.ts +0 -419
- package/lib/task/commit-intent.ts +0 -230
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { finalizeReviewSummary } from '../task/review-finalization.ts';
|
|
2
|
+
import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.ts';
|
|
3
|
+
import { resolveTaskRef } from '../task/resolve-ref.ts';
|
|
4
|
+
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
|
|
2
5
|
|
|
3
|
-
const USAGE = 'Usage: agent-infra-internal task-review <task-ref> finalize-summary --stage <analysis|plan|code> --artifact <review-*.md> [--orchestrated] [--dry-run]\n';
|
|
6
|
+
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';
|
|
4
7
|
|
|
5
8
|
function failUsage(message: string): void {
|
|
6
9
|
process.stdout.write(`${JSON.stringify({
|
|
@@ -30,10 +33,13 @@ function taskReview(args: string[] = []): void {
|
|
|
30
33
|
let artifact = '';
|
|
31
34
|
let dryRun = false;
|
|
32
35
|
let orchestrated = false;
|
|
36
|
+
let overrideTicket = '';
|
|
37
|
+
let overrideTarget = '';
|
|
38
|
+
let overrideScope = '';
|
|
33
39
|
const seen = new Set<string>();
|
|
34
40
|
for (let index = 2; index < args.length; index += 1) {
|
|
35
41
|
const flag = args[index]!;
|
|
36
|
-
if (!['--stage', '--artifact', '--orchestrated', '--dry-run'].includes(flag)) {
|
|
42
|
+
if (!['--stage', '--artifact', '--orchestrated', '--dry-run', '--override-ticket', '--override-target', '--override-scope'].includes(flag)) {
|
|
37
43
|
failUsage(`unknown option '${flag}'`);
|
|
38
44
|
return;
|
|
39
45
|
}
|
|
@@ -56,15 +62,54 @@ function taskReview(args: string[] = []): void {
|
|
|
56
62
|
return;
|
|
57
63
|
}
|
|
58
64
|
if (flag === '--stage') stage = value;
|
|
59
|
-
else artifact = value;
|
|
65
|
+
else if (flag === '--artifact') artifact = value;
|
|
66
|
+
else if (flag === '--override-ticket') overrideTicket = value;
|
|
67
|
+
else if (flag === '--override-target') overrideTarget = value;
|
|
68
|
+
else if (flag === '--override-scope') overrideScope = value;
|
|
60
69
|
}
|
|
61
70
|
if (!stage || !artifact) {
|
|
62
71
|
failUsage("options '--stage' and '--artifact' are required");
|
|
63
72
|
return;
|
|
64
73
|
}
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
74
|
+
const dryRunConflict = overrideDryRunConflict({ dryRun, overrideTicket, overrideTarget, overrideScope });
|
|
75
|
+
if (dryRunConflict) { failUsage(dryRunConflict.message); return; }
|
|
76
|
+
const resolved = resolveTaskRef(args[0]!);
|
|
77
|
+
if (!resolved.ok) { failUsage(resolved.message); return; }
|
|
78
|
+
let result;
|
|
79
|
+
let humanOverride: unknown = null;
|
|
80
|
+
try {
|
|
81
|
+
result = withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-review.finalize-summary', () => {
|
|
82
|
+
let current = finalizeReviewSummary({ taskRef: args[0]!, stage, artifact, orchestrated, dryRun }, { lockAlreadyHeld: true });
|
|
83
|
+
if (current.status !== 'failed' || !overrideTicket) return current;
|
|
84
|
+
if (!overrideTarget || !overrideScope) { failUsage('override ticket requires target and scope'); return current; }
|
|
85
|
+
const consumed = consumeHumanOverride({
|
|
86
|
+
taskRef: args[0]!, ticketId: overrideTicket,
|
|
87
|
+
failureId: failureId('review-finalization', current.error?.code ?? 'TASK_STATE_MISMATCH'),
|
|
88
|
+
target: overrideTarget, scope: overrideScope
|
|
89
|
+
}, {
|
|
90
|
+
effectExecutor: (capability) => {
|
|
91
|
+
const retried = finalizeReviewSummary({ taskRef: args[0]!, stage, artifact, orchestrated, dryRun }, { lockAlreadyHeld: true, manualOverride: capability });
|
|
92
|
+
current = retried;
|
|
93
|
+
return retried.status === 'failed' || retried.status === 'planned'
|
|
94
|
+
? { 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'}` }
|
|
95
|
+
: null;
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
humanOverride = consumed;
|
|
99
|
+
return current;
|
|
100
|
+
});
|
|
101
|
+
} catch (error) {
|
|
102
|
+
if (!(error instanceof TaskExecutionLockError)) throw error;
|
|
103
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: error.code, message: error.message } })}\n`);
|
|
104
|
+
process.exitCode = 1;
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
process.stdout.write(`${JSON.stringify(humanOverride ? { ...result, humanOverride } : result)}\n`);
|
|
108
|
+
const overrideFailed = Boolean(
|
|
109
|
+
humanOverride && typeof humanOverride === 'object' &&
|
|
110
|
+
(humanOverride as { status?: unknown }).status === 'failed'
|
|
111
|
+
);
|
|
112
|
+
if (result.status === 'failed' || overrideFailed) process.exitCode = 1;
|
|
68
113
|
}
|
|
69
114
|
|
|
70
115
|
export { taskReview };
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { renderTaskVerification, verifyTaskEvent } from '../task/verification.ts';
|
|
2
|
+
import { consumeHumanOverride, failureId } from '../task/human-override.ts';
|
|
3
|
+
import { resolveTaskRef } from '../task/resolve-ref.ts';
|
|
4
|
+
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
|
|
2
5
|
|
|
3
|
-
const USAGE = 'Usage: agent-infra-internal task-verify <N | TASK-id> <verification-event> [--artifact <canonical.md>] [--format json|text]\n';
|
|
6
|
+
const USAGE = 'Usage: agent-infra-internal task-verify <N | TASK-id> <verification-event> [--artifact <canonical.md>] [--format json|text] [--override-ticket <ticket> --override-target <target> --override-scope <scope>]\n';
|
|
4
7
|
|
|
5
8
|
function fail(message: string): void {
|
|
6
9
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'VERIFY_PAYLOAD_INVALID', message } })}\n`);
|
|
@@ -11,21 +14,67 @@ function fail(message: string): void {
|
|
|
11
14
|
function taskVerify(args: string[] = []): void {
|
|
12
15
|
if (args[0] === '--help' || args[0] === '-h') { process.stdout.write(USAGE); return; }
|
|
13
16
|
if (!args[0] || !args[1] || args[0].startsWith('--') || args[1].startsWith('--')) { fail('task ref and verification event are required'); return; }
|
|
17
|
+
const taskRef = args[0];
|
|
18
|
+
const event = args[1];
|
|
14
19
|
let artifact: string | undefined;
|
|
15
20
|
let format: 'json' | 'text' = 'json';
|
|
21
|
+
let overrideTicket: string | undefined;
|
|
22
|
+
let overrideTarget: string | undefined;
|
|
23
|
+
let overrideScope: string | undefined;
|
|
16
24
|
const seen = new Set<string>();
|
|
17
25
|
for (let index = 2; index < args.length; index += 1) {
|
|
18
26
|
const flag = args[index]!;
|
|
19
|
-
if (
|
|
27
|
+
if (!['--artifact', '--format', '--override-ticket', '--override-target', '--override-scope'].includes(flag)) { fail(`unknown option '${flag}'`); return; }
|
|
20
28
|
if (seen.has(flag)) { fail(`duplicate option '${flag}'`); return; }
|
|
21
29
|
const value = args[++index];
|
|
22
30
|
if (!value || value.startsWith('--')) { fail(`option '${flag}' requires a value`); return; }
|
|
23
31
|
seen.add(flag);
|
|
24
32
|
if (flag === '--artifact') artifact = value;
|
|
33
|
+
else if (flag === '--override-ticket') overrideTicket = value;
|
|
34
|
+
else if (flag === '--override-target') overrideTarget = value;
|
|
35
|
+
else if (flag === '--override-scope') overrideScope = value;
|
|
25
36
|
else if (value === 'json' || value === 'text') format = value;
|
|
26
37
|
else { fail("option '--format' requires 'json' or 'text'"); return; }
|
|
27
38
|
}
|
|
28
|
-
const
|
|
39
|
+
const resolved = resolveTaskRef(taskRef);
|
|
40
|
+
if (!resolved.ok) { fail(resolved.message); return; }
|
|
41
|
+
let result;
|
|
42
|
+
let humanOverride: unknown = null;
|
|
43
|
+
try {
|
|
44
|
+
result = withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-verify.${event}`, () => {
|
|
45
|
+
let current = verifyTaskEvent({ taskRef, event, ...(artifact ? { artifact } : {}) });
|
|
46
|
+
if (current.status === 'pass' || !overrideTicket) return current;
|
|
47
|
+
if (!overrideTarget || !overrideScope) { fail('override ticket requires target and scope'); return current; }
|
|
48
|
+
const failureProducer = current.status === 'blocked' || current.status === 'fail'
|
|
49
|
+
? 'verification-engine'
|
|
50
|
+
: 'task-verify';
|
|
51
|
+
const failureCode = current.status === 'blocked'
|
|
52
|
+
? 'CHECK_BLOCKED'
|
|
53
|
+
: current.status === 'fail'
|
|
54
|
+
? 'CHECK_FAILED'
|
|
55
|
+
: current.error?.code ?? 'VERIFY_TASK_STATE_MISMATCH';
|
|
56
|
+
const consumed = consumeHumanOverride({
|
|
57
|
+
taskRef, ticketId: overrideTicket,
|
|
58
|
+
failureId: failureId(failureProducer, failureCode),
|
|
59
|
+
target: overrideTarget, scope: overrideScope
|
|
60
|
+
}, {
|
|
61
|
+
effectExecutor: (capability) => {
|
|
62
|
+
const retried = verifyTaskEvent({ taskRef, event, ...(artifact ? { artifact } : {}) }, { manualOverride: capability });
|
|
63
|
+
current = retried;
|
|
64
|
+
return retried.status !== 'pass'
|
|
65
|
+
? { code: 'OVERRIDE_EFFECT_FAILED', message: `${retried.error?.code ?? 'VERIFY_FAILED'}: ${retried.error?.message ?? 'manual verification effect failed'}` }
|
|
66
|
+
: null;
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
humanOverride = consumed;
|
|
70
|
+
return current;
|
|
71
|
+
});
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (!(error instanceof TaskExecutionLockError)) throw error;
|
|
74
|
+
fail(`${error.code}: ${error.message}`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (humanOverride) result = { ...result, humanOverride } as typeof result;
|
|
29
78
|
process.stdout.write(format === 'text' ? renderTaskVerification(result) : `${JSON.stringify(result)}\n`);
|
|
30
79
|
process.exitCode = result.status === 'pass' ? 0 : result.status === 'blocked' ? 2 : 1;
|
|
31
80
|
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { applyWorkflowWarningIntent } from '../task/workflow-warning-intents.ts';
|
|
2
2
|
import type { WorkflowWarningIntent } from '../task/workflow-warning-intents.ts';
|
|
3
|
+
import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.ts';
|
|
4
|
+
import { resolveTaskRef } from '../task/resolve-ref.ts';
|
|
5
|
+
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
|
|
3
6
|
|
|
4
7
|
const USAGE = `Usage: agent-infra-internal task-warning <task-ref> <add|set-status|list> [intent flags] [--dry-run]\n`;
|
|
5
8
|
const FLAGS: Record<string, string> = {
|
|
6
9
|
'--step': 'step', '--severity': 'severity', '--code': 'code', '--target': 'target',
|
|
7
10
|
'--message': 'message', '--action': 'action', '--id': 'id', '--status': 'status', '--resolution': 'resolution'
|
|
11
|
+
, '--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
|
|
8
12
|
};
|
|
9
13
|
|
|
10
14
|
function usageFailure(message: string): void {
|
|
@@ -38,13 +42,50 @@ function taskWarning(args: string[] = []): void {
|
|
|
38
42
|
};
|
|
39
43
|
const optional = kind === 'list' ? ['status'] : [];
|
|
40
44
|
const allowed = new Set(['kind', 'taskRef', 'dryRun', ...required[kind]!, ...optional]);
|
|
45
|
+
allowed.add('overrideTicket'); allowed.add('overrideTarget'); allowed.add('overrideScope');
|
|
41
46
|
const unexpected = Object.keys(values).find((key) => !allowed.has(key));
|
|
42
47
|
const missing = required[kind]!.find((key) => values[key] === undefined);
|
|
43
48
|
if (unexpected) { usageFailure(`${kind} does not accept '${unexpected}'`); return; }
|
|
44
49
|
if (missing) { usageFailure(`${kind} requires '${missing}'`); return; }
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
const dryRunConflict = overrideDryRunConflict(values);
|
|
51
|
+
if (dryRunConflict) { usageFailure(dryRunConflict.message); return; }
|
|
52
|
+
const resolved = resolveTaskRef(taskRef);
|
|
53
|
+
if (!resolved.ok) { usageFailure(resolved.message); return; }
|
|
54
|
+
let result;
|
|
55
|
+
let humanOverride: unknown = null;
|
|
56
|
+
try {
|
|
57
|
+
result = withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-warning.${kind}`, () => {
|
|
58
|
+
let current = applyWorkflowWarningIntent(values as WorkflowWarningIntent);
|
|
59
|
+
if (current.status !== 'failed' || !values.overrideTicket) return current;
|
|
60
|
+
if (!values.overrideTarget || !values.overrideScope) { usageFailure('override ticket requires target and scope'); return current; }
|
|
61
|
+
const consumed = consumeHumanOverride({
|
|
62
|
+
taskRef, ticketId: String(values.overrideTicket),
|
|
63
|
+
failureId: failureId('workflow-warning', current.error?.code ?? 'TASK_STATE_MISMATCH'),
|
|
64
|
+
target: String(values.overrideTarget), scope: String(values.overrideScope)
|
|
65
|
+
}, {
|
|
66
|
+
effectExecutor: (capability) => {
|
|
67
|
+
const retried = applyWorkflowWarningIntent(values as WorkflowWarningIntent, { manualOverride: capability });
|
|
68
|
+
current = retried;
|
|
69
|
+
return retried.status === 'failed' || retried.status === 'planned'
|
|
70
|
+
? { code: 'OVERRIDE_EFFECT_FAILED', message: retried.status === 'planned' ? 'producer returned planned; no workflow warning effect was committed' : `${retried.error?.code ?? 'WARNING_FAILED'}: ${retried.error?.message ?? 'manual warning effect failed'}` }
|
|
71
|
+
: null;
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
humanOverride = consumed;
|
|
75
|
+
return current;
|
|
76
|
+
});
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (!(error instanceof TaskExecutionLockError)) throw error;
|
|
79
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: error.code, message: error.message } })}\n`);
|
|
80
|
+
process.exitCode = 1;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
process.stdout.write(`${JSON.stringify(humanOverride ? { ...result, humanOverride } : result)}\n`);
|
|
84
|
+
const overrideFailed = Boolean(
|
|
85
|
+
humanOverride && typeof humanOverride === 'object' &&
|
|
86
|
+
(humanOverride as { status?: unknown }).status === 'failed'
|
|
87
|
+
);
|
|
88
|
+
if (result.status === 'failed' || overrideFailed) process.exitCode = 1;
|
|
48
89
|
}
|
|
49
90
|
|
|
50
91
|
export { taskWarning };
|
|
@@ -3,6 +3,7 @@ import { execFileSync } from 'node:child_process';
|
|
|
3
3
|
|
|
4
4
|
import { enumerateArtifacts } from '../task/artifacts.ts';
|
|
5
5
|
import { parseTaskFrontmatter } from '../task/frontmatter.ts';
|
|
6
|
+
import { renderHumanOverrideAudit } from '../task/human-override.ts';
|
|
6
7
|
import { resolveTaskRef } from '../task/resolve-ref.ts';
|
|
7
8
|
import { resolvePlatformContext } from './context.ts';
|
|
8
9
|
import { createGitHubClient } from './github-client.ts';
|
|
@@ -10,19 +11,32 @@ import type { GitHubClient } from './github-client.ts';
|
|
|
10
11
|
import { listRemoteComments, normalizeCommentContent, writeComment } from './issue-comments.ts';
|
|
11
12
|
import { platformResult } from './types.ts';
|
|
12
13
|
import type { PlatformResult } from './types.ts';
|
|
14
|
+
import type { OperationWarning } from '../task/operation-outcome.ts';
|
|
13
15
|
|
|
14
16
|
type SummaryComment = { id: number; body: string };
|
|
15
17
|
type SummaryContextResult = PlatformResult & {
|
|
16
18
|
task: { id: string | null; prNumber: number | null };
|
|
17
19
|
artifacts: Array<{ family: string; name: string; path: string }>;
|
|
18
20
|
};
|
|
21
|
+
type PullRequestSummaryResult = PlatformResult & {
|
|
22
|
+
result: 'pr_created_with_warnings' | 'pr_reused_with_warnings' | 'no_op_with_warnings' | null;
|
|
23
|
+
warnings: readonly OperationWarning[];
|
|
24
|
+
};
|
|
25
|
+
type PullRequestPrimaryResult = 'pr_created' | 'pr_reused' | 'no_op';
|
|
26
|
+
|
|
27
|
+
function warningResultForPrimary(primaryResult: PullRequestPrimaryResult): NonNullable<PullRequestSummaryResult['result']> {
|
|
28
|
+
if (primaryResult === 'pr_created') return 'pr_created_with_warnings';
|
|
29
|
+
if (primaryResult === 'pr_reused') return 'pr_reused_with_warnings';
|
|
30
|
+
return 'no_op_with_warnings';
|
|
31
|
+
}
|
|
19
32
|
|
|
20
33
|
function summaryMarker(taskId: string): string {
|
|
21
34
|
return `<!-- sync-pr:${taskId}:summary -->`;
|
|
22
35
|
}
|
|
23
36
|
|
|
24
|
-
function buildPullRequestSummary(taskId: string, body: string, headSha: string): string {
|
|
25
|
-
|
|
37
|
+
function buildPullRequestSummary(taskId: string, body: string, headSha: string, humanOverrideAudit = ''): string {
|
|
38
|
+
const sections = [body.replace(/\s+$/, ''), humanOverrideAudit.trim()].filter(Boolean).join('\n\n');
|
|
39
|
+
return normalizeCommentContent(`${summaryMarker(taskId)}\n<!-- last-commit: ${headSha} -->\n\n${sections}\n`);
|
|
26
40
|
}
|
|
27
41
|
|
|
28
42
|
function reconcileSummaryComment(comments: SummaryComment[], taskId: string, desired: string):
|
|
@@ -59,32 +73,69 @@ function summaryContext(taskRef: string, options: { cwd?: string; client?: GitHu
|
|
|
59
73
|
return { ...platformResult('no-op'), task: { id: resolved.taskId, prNumber }, artifacts: canonicalArtifacts(resolved.taskDir) };
|
|
60
74
|
}
|
|
61
75
|
|
|
62
|
-
function syncPullRequestSummary(taskRef: string, options: { agent: string; body: string; cwd?: string; client?: GitHubClient; dryRun?: boolean }):
|
|
76
|
+
function syncPullRequestSummary(taskRef: string, options: { agent: string; body: string; cwd?: string; client?: GitHubClient; dryRun?: boolean; primaryResult: PullRequestPrimaryResult }): PullRequestSummaryResult {
|
|
77
|
+
const warningResult = warningResultForPrimary(options.primaryResult);
|
|
78
|
+
let knownPrNumber: number | null = null;
|
|
79
|
+
const softenFailure = (output: PlatformResult): PullRequestSummaryResult => {
|
|
80
|
+
const prNumber = output.resource.kind === 'pull-request' && output.resource.number
|
|
81
|
+
? output.resource.number
|
|
82
|
+
: knownPrNumber;
|
|
83
|
+
const warning = output.error && prNumber
|
|
84
|
+
&& output.error.code !== 'PR_NOT_LINKED'
|
|
85
|
+
? {
|
|
86
|
+
code: output.error.code,
|
|
87
|
+
message: output.error.message,
|
|
88
|
+
retryable: output.error.retryable,
|
|
89
|
+
step: 'pr-summary',
|
|
90
|
+
target: `pull-request:${prNumber}`,
|
|
91
|
+
severity: 'ACTION_REQUIRED' as const
|
|
92
|
+
}
|
|
93
|
+
: null;
|
|
94
|
+
return warning
|
|
95
|
+
? {
|
|
96
|
+
...output,
|
|
97
|
+
status: 'applied',
|
|
98
|
+
changed: false,
|
|
99
|
+
resource: { kind: 'pull-request', number: prNumber },
|
|
100
|
+
error: null,
|
|
101
|
+
result: warningResult,
|
|
102
|
+
warnings: [warning]
|
|
103
|
+
}
|
|
104
|
+
: { ...output, result: null, warnings: [] };
|
|
105
|
+
};
|
|
63
106
|
const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
|
|
64
|
-
if (!resolved.ok) return platformResult('failed', { error: { code: resolved.code, message: resolved.message, retryable: false } });
|
|
107
|
+
if (!resolved.ok) return softenFailure(platformResult('failed', { error: { code: resolved.code, message: resolved.message, retryable: false } }));
|
|
65
108
|
const frontmatter = parseTaskFrontmatter(fs.readFileSync(resolved.taskMdPath, 'utf8'));
|
|
66
109
|
const prNumber = Number(frontmatter.pr_number);
|
|
67
|
-
if (!Number.isInteger(prNumber) || prNumber <= 0) return platformResult('failed', { error: { code: 'PR_NOT_LINKED', message: 'Task has no valid pr_number', retryable: false } });
|
|
110
|
+
if (!Number.isInteger(prNumber) || prNumber <= 0) return softenFailure(platformResult('failed', { error: { code: 'PR_NOT_LINKED', message: 'Task has no valid pr_number', retryable: false } }));
|
|
111
|
+
knownPrNumber = prNumber;
|
|
68
112
|
const client = options.client || createGitHubClient();
|
|
69
113
|
const context = resolvePlatformContext({ cwd: resolved.repoRoot, client });
|
|
70
|
-
if (!context.platform.repository || !['no-op', 'degraded'].includes(context.status)) return context;
|
|
114
|
+
if (!context.platform.repository || !['no-op', 'degraded'].includes(context.status)) return softenFailure(context);
|
|
71
115
|
let headSha: string;
|
|
72
116
|
try {
|
|
73
117
|
headSha = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: resolved.repoRoot, encoding: 'utf8' }).trim();
|
|
74
118
|
} catch (error) {
|
|
75
|
-
return platformResult('failed', { platform: context.platform, capabilities: context.capabilities, error: { code: 'GIT_HEAD_UNRESOLVED', message: error instanceof Error ? error.message : String(error), retryable: false } });
|
|
119
|
+
return softenFailure(platformResult('failed', { platform: context.platform, capabilities: context.capabilities, error: { code: 'GIT_HEAD_UNRESOLVED', message: error instanceof Error ? error.message : String(error), retryable: false } }));
|
|
76
120
|
}
|
|
77
|
-
const desired = buildPullRequestSummary(
|
|
121
|
+
const desired = buildPullRequestSummary(
|
|
122
|
+
resolved.taskId,
|
|
123
|
+
options.body,
|
|
124
|
+
headSha,
|
|
125
|
+
renderHumanOverrideAudit(fs.readFileSync(resolved.taskMdPath, 'utf8'))
|
|
126
|
+
);
|
|
78
127
|
const listed = listRemoteComments(client, context.platform.repository, prNumber, resolved.repoRoot);
|
|
79
|
-
if (!listed.ok) return platformResult(listed.error.retryable ? 'blocked' : 'failed', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, error: listed.error });
|
|
128
|
+
if (!listed.ok) return softenFailure(platformResult(listed.error.retryable ? 'blocked' : 'failed', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, error: listed.error }));
|
|
80
129
|
const reconciliation = reconcileSummaryComment(listed.value, resolved.taskId, desired);
|
|
81
|
-
if (reconciliation.action === 'conflict') return platformResult('failed', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, error: { code: 'PR_SUMMARY_MARKER_AMBIGUOUS', message: 'Multiple PR comments contain the summary marker', retryable: false } });
|
|
82
|
-
if (reconciliation.action === 'no-op') return platformResult('no-op', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, comment: { kind: 'summary', marker: summaryMarker(resolved.taskId), ids: [reconciliation.commentId!], parts: 1 }, error: null });
|
|
83
|
-
if (options.dryRun) return platformResult('planned', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, operations: [{ name: `summary:${reconciliation.action}`, status: 'planned', reasonCode: null }], error: null });
|
|
130
|
+
if (reconciliation.action === 'conflict') return softenFailure(platformResult('failed', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, error: { code: 'PR_SUMMARY_MARKER_AMBIGUOUS', message: 'Multiple PR comments contain the summary marker', retryable: false } }));
|
|
131
|
+
if (reconciliation.action === 'no-op') return softenFailure(platformResult('no-op', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, comment: { kind: 'summary', marker: summaryMarker(resolved.taskId), ids: [reconciliation.commentId!], parts: 1 }, error: null }));
|
|
132
|
+
if (options.dryRun) return softenFailure(platformResult('planned', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, operations: [{ name: `summary:${reconciliation.action}`, status: 'planned', reasonCode: null }], error: null }));
|
|
84
133
|
const written = writeComment(client, context.platform.repository, prNumber, resolved.repoRoot, desired, reconciliation.commentId || undefined);
|
|
85
|
-
if (!written.ok) return platformResult(written.error.retryable ? 'blocked' : 'failed', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, error: written.error });
|
|
134
|
+
if (!written.ok) return softenFailure(platformResult(written.error.retryable ? 'blocked' : 'failed', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, error: written.error }));
|
|
86
135
|
const id = Number(written.value.id || reconciliation.commentId);
|
|
87
|
-
return platformResult('applied', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, comment: { kind: 'summary', marker: summaryMarker(resolved.taskId), ids: Number.isInteger(id) ? [id] : [], parts: 1 }, operations: [{ name: `summary:${reconciliation.action}`, status: 'applied', reasonCode: null }], error: null });
|
|
136
|
+
return softenFailure(platformResult('applied', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, comment: { kind: 'summary', marker: summaryMarker(resolved.taskId), ids: Number.isInteger(id) ? [id] : [], parts: 1 }, operations: [{ name: `summary:${reconciliation.action}`, status: 'applied', reasonCode: null }], error: null }));
|
|
88
137
|
}
|
|
89
138
|
|
|
90
139
|
export { buildPullRequestSummary, reconcileSummaryComment, summaryContext, summaryMarker, syncPullRequestSummary };
|
|
140
|
+
export type { PullRequestSummaryResult, SummaryContextResult };
|
|
141
|
+
export { warningResultForPrimary };
|