@fitlab-ai/agent-infra 0.9.6 → 0.9.8
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 +10 -0
- package/dist/lib/agent-clients/adapters/claude-code.js +3 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +47 -5
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +162 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +245 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +398 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +23 -6
- package/dist/lib/decide.js +68 -14
- package/dist/lib/defaults.json +10 -2
- package/dist/lib/git/workflow.js +30 -3
- package/dist/lib/init.js +1 -1
- package/dist/lib/internal/codex-lifecycle.js +69 -1
- package/dist/lib/internal/codex-sandbox-controller.js +106 -0
- package/dist/lib/internal/release-workflow.js +1 -3
- package/dist/lib/internal/task-activity.js +57 -7
- package/dist/lib/internal/task-event.js +57 -4
- 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 +48 -8
- 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/context.js +6 -4
- package/dist/lib/platform/github-client.js +55 -2
- package/dist/lib/platform/pr-summary.js +5 -3
- package/dist/lib/process-data/checkpoint.js +319 -0
- package/dist/lib/process-data/index.js +128 -60
- package/dist/lib/process-data/materialize.js +84 -0
- package/dist/lib/process-data/normalize.js +34 -1
- package/dist/lib/process-data/reconcile.js +55 -1
- package/dist/lib/process-data/sources.js +422 -1
- package/dist/lib/process-data/store.js +341 -11
- package/dist/lib/runtime/agent-runtime.js +56 -0
- package/dist/lib/sandbox/commands/create.js +394 -264
- package/dist/lib/sandbox/commands/rm.js +64 -15
- package/dist/lib/sandbox/commands/start.js +4 -3
- package/dist/lib/sandbox/control/container-identity.js +64 -0
- package/dist/lib/sandbox/control/executor.js +23 -9
- package/dist/lib/sandbox/control/lifecycle.js +590 -61
- package/dist/lib/sandbox/control/protocol.js +7 -0
- package/dist/lib/sandbox/control/server.js +202 -26
- package/dist/lib/sandbox/control/state.js +51 -25
- package/dist/lib/sandbox/control/timing.js +8 -0
- package/dist/lib/sandbox/engine.js +14 -7
- package/dist/lib/sandbox/mounts.js +7 -0
- package/dist/lib/sandbox/recovery.js +135 -50
- package/dist/lib/sandbox/workspace-view.js +59 -20
- package/dist/lib/server/daemon.js +1 -1
- package/dist/lib/server/process-control.js +11 -1
- package/dist/lib/server/process-state.js +107 -20
- package/dist/lib/task/activity-intent.js +4 -1
- package/dist/lib/task/artifact-lifecycle.js +82 -11
- package/dist/lib/task/artifact-receipts.js +128 -0
- package/dist/lib/task/codex-orchestration.js +81 -6
- package/dist/lib/task/commands/validate.js +1 -1
- package/dist/lib/task/create.js +10 -2
- package/dist/lib/task/decision-intents.js +4 -2
- package/dist/lib/task/delegation-receipts.js +120 -17
- package/dist/lib/task/events.js +148 -7
- 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/ledger.js +13 -1
- package/dist/lib/task/lifecycle.js +69 -14
- package/dist/lib/task/orchestration.js +293 -18
- package/dist/lib/task/review-finalization.js +3 -2
- package/dist/lib/task/sections.js +5 -1
- package/dist/lib/task/task-execution-lock.js +5 -4
- package/dist/lib/task/verification-engine.js +80 -22
- package/dist/lib/task/verification.js +13 -1
- package/dist/lib/task/workflow-warning-intents.js +4 -2
- package/dist/lib/task/write.js +2 -1
- package/dist/lifecycle-build-manifest.json +6 -0
- package/lib/agent-clients/adapters/claude-code.ts +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +62 -4
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +227 -0
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +322 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +26 -0
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +480 -0
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +35 -5
- package/lib/decide.ts +59 -14
- package/lib/defaults.json +10 -2
- package/lib/git/workflow.ts +25 -2
- package/lib/init.ts +1 -1
- package/lib/internal/codex-lifecycle.ts +68 -1
- package/lib/internal/codex-sandbox-controller.ts +109 -0
- package/lib/internal/release-workflow.ts +1 -3
- package/lib/internal/task-activity.ts +46 -6
- package/lib/internal/task-event.ts +53 -4
- package/lib/internal/task-ledger.ts +48 -4
- package/lib/internal/task-lifecycle.ts +56 -12
- package/lib/internal/task-orchestration.ts +43 -8
- 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/context.ts +8 -5
- package/lib/platform/github-client.ts +61 -3
- package/lib/platform/pr-summary.ts +10 -3
- package/lib/process-data/checkpoint.ts +335 -0
- package/lib/process-data/index.ts +112 -60
- package/lib/process-data/materialize.ts +83 -0
- package/lib/process-data/normalize.ts +29 -1
- package/lib/process-data/reconcile.ts +64 -2
- package/lib/process-data/sources.ts +449 -2
- package/lib/process-data/store.ts +304 -11
- package/lib/process-data/types.ts +124 -2
- package/lib/runtime/agent-runtime.ts +76 -0
- package/lib/sandbox/commands/create.ts +139 -6
- package/lib/sandbox/commands/rm.ts +64 -14
- package/lib/sandbox/commands/start.ts +4 -3
- package/lib/sandbox/config.ts +2 -1
- package/lib/sandbox/control/container-identity.ts +93 -0
- package/lib/sandbox/control/executor.ts +24 -12
- package/lib/sandbox/control/lifecycle.ts +685 -67
- package/lib/sandbox/control/protocol.ts +35 -6
- package/lib/sandbox/control/server.ts +184 -24
- package/lib/sandbox/control/state.ts +67 -33
- package/lib/sandbox/control/timing.ts +12 -0
- package/lib/sandbox/engine.ts +20 -9
- package/lib/sandbox/mounts.ts +8 -0
- package/lib/sandbox/recovery.ts +159 -49
- package/lib/sandbox/workspace-view.ts +73 -22
- package/lib/server/daemon.ts +1 -1
- package/lib/server/process-control.ts +13 -3
- package/lib/server/process-state.ts +129 -27
- package/lib/task/activity-intent.ts +4 -2
- package/lib/task/artifact-lifecycle.ts +73 -10
- package/lib/task/artifact-receipts.ts +161 -0
- package/lib/task/codex-orchestration.ts +91 -5
- package/lib/task/commands/validate.ts +1 -1
- package/lib/task/create.ts +11 -2
- package/lib/task/decision-intents.ts +4 -2
- package/lib/task/delegation-receipts.ts +205 -25
- package/lib/task/events.ts +132 -6
- 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/ledger.ts +16 -1
- package/lib/task/lifecycle.ts +71 -14
- package/lib/task/orchestration.ts +443 -22
- package/lib/task/review-finalization.ts +6 -2
- package/lib/task/sections.ts +6 -2
- package/lib/task/task-execution-lock.ts +11 -9
- package/lib/task/verification-engine.ts +93 -32
- package/lib/task/verification.ts +15 -1
- package/lib/task/workflow-warning-intents.ts +4 -2
- package/lib/task/write.ts +4 -1
- package/package.json +1 -1
- package/templates/.agents/README.en.md +7 -0
- package/templates/.agents/README.zh-CN.md +5 -0
- package/templates/.agents/hooks/lifecycle-delegation.js +33 -4
- package/templates/.agents/rules/lifecycle-orchestration.en.md +10 -3
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +10 -3
- package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
- package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +1 -1
- package/templates/.agents/rules/review-handshake.en.md +3 -1
- package/templates/.agents/rules/review-handshake.zh-CN.md +3 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +3 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +3 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +6 -6
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +14 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +14 -3
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +67 -0
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +67 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +25 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +25 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +9 -7
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -7
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +5 -1
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +5 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +75 -3
- package/templates/.agents/workspace/README.en.md +0 -26
- package/templates/.agents/workspace/README.zh-CN.md +0 -26
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { normalizeAgentToken, AGENT_USAGE_HINT } from '../agent-clients/tokens.ts';
|
|
2
|
+
import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.ts';
|
|
2
3
|
import { applyTaskLifecycle, lifecycleIntentCatalog } from '../task/lifecycle.ts';
|
|
3
4
|
import type { TaskLifecycleRequest } from '../task/lifecycle.ts';
|
|
4
|
-
import { resolveTaskRef } from '../task/resolve-ref.ts';
|
|
5
|
+
import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.ts';
|
|
5
6
|
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
|
|
6
7
|
|
|
7
|
-
const USAGE = `Usage: agent-infra-internal task-lifecycle <N | TASK-id> <intent> --agent <agent> [intent flags] [--dry-run]\n\nIntents: ${lifecycleIntentCatalog.join(', ')}\n`;
|
|
8
|
+
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`;
|
|
8
9
|
const FLAGS: Record<string, string> = {
|
|
9
10
|
'--agent': 'agent', '--reason': 'reason', '--unblock-condition': 'unblockCondition',
|
|
10
11
|
'--note': 'note', '--alert-number': 'alertNumber', '--staging-dir': 'stagingDir',
|
|
11
|
-
'--issue-number': 'issueNumber'
|
|
12
|
+
'--issue-number': 'issueNumber', '--override-ticket': 'overrideTicket',
|
|
13
|
+
'--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
|
|
12
14
|
};
|
|
13
15
|
|
|
14
16
|
function usageFailure(message: string): void {
|
|
@@ -39,19 +41,61 @@ function taskLifecycle(args: string[] = []): void {
|
|
|
39
41
|
const agent = normalizeAgentToken(String(values.agent ?? ''));
|
|
40
42
|
if (!agent) { usageFailure(`invalid --agent '${values.agent}': ${AGENT_USAGE_HINT}`); return; }
|
|
41
43
|
values.agent = agent;
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
const dryRunConflict = overrideDryRunConflict(values);
|
|
45
|
+
if (dryRunConflict) { usageFailure(dryRunConflict.message); return; }
|
|
46
|
+
let repoRoot: string;
|
|
47
|
+
let taskId: string;
|
|
48
|
+
if (values.intent === 'restore') {
|
|
49
|
+
// restore's precondition is that the task does NOT exist locally yet (it only lives
|
|
50
|
+
// in a staging dir), so resolveTaskRef's existence check is inapplicable here; identity
|
|
51
|
+
// is guaranteed by applyTaskLifecycle's own full-TASK-id check plus staging validation.
|
|
52
|
+
try {
|
|
53
|
+
repoRoot = detectRepoRoot();
|
|
54
|
+
} catch (error) {
|
|
55
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'REPO_ROOT_NOT_FOUND', message: error instanceof Error ? error.message : String(error) } })}\n`);
|
|
56
|
+
process.exitCode = 1;
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
taskId = String(values.taskRef);
|
|
60
|
+
} else {
|
|
61
|
+
const resolved = resolveTaskRef(String(values.taskRef));
|
|
62
|
+
if (!resolved.ok) {
|
|
63
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: resolved.code, message: resolved.message } })}\n`);
|
|
64
|
+
process.exitCode = 1;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
repoRoot = resolved.repoRoot;
|
|
68
|
+
taskId = resolved.taskId;
|
|
47
69
|
}
|
|
48
|
-
let result: ReturnType<typeof applyTaskLifecycle
|
|
70
|
+
let result: ReturnType<typeof applyTaskLifecycle> & { humanOverride?: unknown };
|
|
49
71
|
try {
|
|
50
72
|
result = withTaskExecutionLock(
|
|
51
|
-
|
|
52
|
-
|
|
73
|
+
repoRoot,
|
|
74
|
+
taskId,
|
|
53
75
|
`task-lifecycle.${String(values.intent)}`,
|
|
54
|
-
() =>
|
|
76
|
+
() => {
|
|
77
|
+
const lifecycleResult = applyTaskLifecycle(values as TaskLifecycleRequest);
|
|
78
|
+
if (lifecycleResult.status !== 'failed' || !values.overrideTicket) return lifecycleResult;
|
|
79
|
+
if (!values.overrideTarget || !values.overrideScope) {
|
|
80
|
+
return {
|
|
81
|
+
...lifecycleResult,
|
|
82
|
+
error: { code: 'OVERRIDE_PAYLOAD_INVALID', message: 'override ticket requires --override-target and --override-scope' }
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const override = consumeHumanOverride({
|
|
86
|
+
taskRef: String(values.taskRef),
|
|
87
|
+
ticketId: String(values.overrideTicket),
|
|
88
|
+
failureId: failureId('lifecycle.apply', lifecycleResult.error?.code ?? 'LIFECYCLE_FAILED'),
|
|
89
|
+
target: String(values.overrideTarget),
|
|
90
|
+
scope: String(values.overrideScope),
|
|
91
|
+
intent: String(values.intent) as TaskLifecycleRequest['intent'],
|
|
92
|
+
...(values.alertNumber ? { alertNumber: Number(values.alertNumber) } : {}),
|
|
93
|
+
...(values.issueNumber ? { issueNumber: Number(values.issueNumber) } : {}),
|
|
94
|
+
...(values.stagingDir ? { stagingDir: String(values.stagingDir) } : {})
|
|
95
|
+
});
|
|
96
|
+
if (override.status === 'failed') return { ...lifecycleResult, humanOverride: override };
|
|
97
|
+
return { ...lifecycleResult, ...override, humanOverride: override, error: null };
|
|
98
|
+
}
|
|
55
99
|
);
|
|
56
100
|
} catch (error) {
|
|
57
101
|
if (!(error instanceof TaskExecutionLockError)) throw error;
|
|
@@ -4,12 +4,15 @@ import {
|
|
|
4
4
|
activateMatchingOrchestrationDelegation,
|
|
5
5
|
activateOrchestrationDelegation,
|
|
6
6
|
advanceOrchestration,
|
|
7
|
+
awaitOrchestrationDelegationActivation,
|
|
7
8
|
beginCommitIntent,
|
|
8
9
|
beginOrResumeOrchestration,
|
|
9
10
|
checkpointCommitIntent,
|
|
10
11
|
completeCommitIntent,
|
|
12
|
+
dispatchOrchestrationDelegation,
|
|
11
13
|
pauseOrchestration,
|
|
12
14
|
recoverCommitIntent,
|
|
15
|
+
recoverPreparedOrchestrationDelegation,
|
|
13
16
|
routeOrchestration,
|
|
14
17
|
sealMatchingOrchestrationDelegation,
|
|
15
18
|
sealOrchestrationDelegation,
|
|
@@ -22,7 +25,7 @@ import { prepareCodexOrchestrationDelegation } from '../task/codex-orchestration
|
|
|
22
25
|
import { isAgentClientId } from '../agent-clients/types.ts';
|
|
23
26
|
import type { AgentClientId } from '../agent-clients/types.ts';
|
|
24
27
|
|
|
25
|
-
const USAGE = 'Usage: agent-infra-internal task-orchestration <task-ref|auto> <begin-or-resume|route|prepare|hook-start|hook-stop|advance|pause|status|commit-start|commit-begin|commit-checkpoint|commit-complete|commit-recover|commit-abort|commit-terminate|commit-status> [options]\n';
|
|
28
|
+
const USAGE = 'Usage: agent-infra-internal task-orchestration <task-ref|auto> <begin-or-resume|route|prepare|dispatch|await-activation|recover-prepared|hook-start|hook-stop|advance|pause|status|commit-start|commit-begin|commit-checkpoint|commit-complete|commit-recover|commit-abort|commit-terminate|commit-status> [options]\n';
|
|
26
29
|
|
|
27
30
|
function usageFailure(message: string): void {
|
|
28
31
|
process.stdout.write(`${JSON.stringify({
|
|
@@ -44,7 +47,8 @@ async function taskOrchestration(args: string[] = []): Promise<void> {
|
|
|
44
47
|
}
|
|
45
48
|
const [taskRef, intent] = args;
|
|
46
49
|
if (![
|
|
47
|
-
'begin-or-resume', 'route', 'prepare', '
|
|
50
|
+
'begin-or-resume', 'route', 'prepare', 'dispatch', 'await-activation', 'recover-prepared',
|
|
51
|
+
'hook-start', 'hook-stop', 'advance', 'pause', 'status',
|
|
48
52
|
'commit-start', 'commit-begin', 'commit-checkpoint', 'commit-complete', 'commit-recover', 'commit-abort', 'commit-terminate', 'commit-status'
|
|
49
53
|
].includes(intent!)) {
|
|
50
54
|
usageFailure(`unknown intent '${intent}'`);
|
|
@@ -64,7 +68,9 @@ async function taskOrchestration(args: string[] = []): Promise<void> {
|
|
|
64
68
|
if (![
|
|
65
69
|
'--max-steps', '--executor-model', '--executor-reasoning-effort',
|
|
66
70
|
'--reviewer-model', '--reviewer-reasoning-effort',
|
|
67
|
-
'--client', '--requested-model', '--requested-reasoning-effort', '--
|
|
71
|
+
'--client', '--requested-model', '--requested-reasoning-effort', '--capability-token',
|
|
72
|
+
'--parent-id', '--before-fingerprint',
|
|
73
|
+
'--stage', '--round', '--artifact', '--role',
|
|
68
74
|
'--native-agent', '--child-id', '--spawn-mode', '--actual-model', '--actual-reasoning-effort',
|
|
69
75
|
'--model-fallback-reason', '--reasoning-effort-fallback-reason',
|
|
70
76
|
'--exit-code', '--after-fingerprint', '--changed-paths', '--code', '--message', '--recoverable', '--agent',
|
|
@@ -215,17 +221,38 @@ async function taskOrchestration(args: string[] = []): Promise<void> {
|
|
|
215
221
|
result = await prepareCodexOrchestrationDelegation(taskRef!, {
|
|
216
222
|
client: values['--client'] as AgentClientId,
|
|
217
223
|
requestedModel: values['--requested-model'],
|
|
218
|
-
requestedReasoningEffort: values['--requested-reasoning-effort']
|
|
224
|
+
requestedReasoningEffort: values['--requested-reasoning-effort'],
|
|
225
|
+
capabilityToken: values['--capability-token']
|
|
219
226
|
}, { orchestrationOptions: coreOptions });
|
|
227
|
+
} else if (intent === 'dispatch') {
|
|
228
|
+
result = dispatchOrchestrationDelegation(taskRef!, coreOptions);
|
|
229
|
+
} else if (intent === 'await-activation') {
|
|
230
|
+
const missing = requireValues(['--stage', '--round', '--artifact', '--role']);
|
|
231
|
+
if (missing) { usageFailure(`intent 'await-activation' requires '${missing}'`); return; }
|
|
232
|
+
const round = Number(values['--round']);
|
|
233
|
+
if (!Number.isSafeInteger(round) || round < 1) {
|
|
234
|
+
usageFailure('--round must be a positive integer'); return;
|
|
235
|
+
}
|
|
236
|
+
if (!['executor', 'reviewer'].includes(values['--role']!)) {
|
|
237
|
+
usageFailure('--role must be executor or reviewer'); return;
|
|
238
|
+
}
|
|
239
|
+
result = await awaitOrchestrationDelegationActivation(taskRef!, {
|
|
240
|
+
stage: values['--stage'] as Parameters<typeof awaitOrchestrationDelegationActivation>[1]['stage'],
|
|
241
|
+
round,
|
|
242
|
+
artifact: values['--artifact']!,
|
|
243
|
+
role: values['--role'] as 'executor' | 'reviewer'
|
|
244
|
+
}, coreOptions);
|
|
245
|
+
} else if (intent === 'recover-prepared') {
|
|
246
|
+
result = recoverPreparedOrchestrationDelegation(taskRef!, coreOptions);
|
|
220
247
|
} else if (intent === 'hook-start') {
|
|
221
248
|
const missing = requireValues([
|
|
222
249
|
...(taskRef === 'auto' ? ['--client'] : []),
|
|
223
|
-
'--native-agent', '--child-id', '--parent-id'
|
|
250
|
+
'--native-agent', '--child-id', '--parent-id'
|
|
224
251
|
]);
|
|
225
252
|
if (missing) { usageFailure(`intent 'hook-start' requires '${missing}'`); return; }
|
|
226
253
|
const event = {
|
|
227
254
|
nativeAgent: values['--native-agent']!, childId: values['--child-id']!,
|
|
228
|
-
parentId: values['--parent-id']!, spawnMode: values['--spawn-mode']
|
|
255
|
+
parentId: values['--parent-id']!, spawnMode: values['--spawn-mode'],
|
|
229
256
|
actualModel: values['--actual-model'],
|
|
230
257
|
actualReasoningEffort: values['--actual-reasoning-effort'],
|
|
231
258
|
modelFallbackReason: values['--model-fallback-reason'],
|
|
@@ -239,7 +266,11 @@ async function taskOrchestration(args: string[] = []): Promise<void> {
|
|
|
239
266
|
const missing = requireValues(['--client', '--native-agent', '--child-id']);
|
|
240
267
|
if (missing) { usageFailure(`intent 'hook-stop' requires '${missing}'`); return; }
|
|
241
268
|
result = sealMatchingOrchestrationDelegation(values['--client'] as AgentClientId, {
|
|
242
|
-
nativeAgent: values['--native-agent']!, childId: values['--child-id']
|
|
269
|
+
nativeAgent: values['--native-agent']!, childId: values['--child-id']!,
|
|
270
|
+
actualModel: values['--actual-model'],
|
|
271
|
+
actualReasoningEffort: values['--actual-reasoning-effort'],
|
|
272
|
+
modelFallbackReason: values['--model-fallback-reason'],
|
|
273
|
+
reasoningEffortFallbackReason: values['--reasoning-effort-fallback-reason']
|
|
243
274
|
}, coreOptions);
|
|
244
275
|
} else {
|
|
245
276
|
const missing = requireValues(['--child-id', '--exit-code', '--after-fingerprint']);
|
|
@@ -249,7 +280,11 @@ async function taskOrchestration(args: string[] = []): Promise<void> {
|
|
|
249
280
|
result = sealOrchestrationDelegation(taskRef!, {
|
|
250
281
|
childId: values['--child-id']!, exitCode,
|
|
251
282
|
afterFingerprint: values['--after-fingerprint']!,
|
|
252
|
-
changedPaths: values['--changed-paths'] ? values['--changed-paths']!.split(',').filter(Boolean) : []
|
|
283
|
+
changedPaths: values['--changed-paths'] ? values['--changed-paths']!.split(',').filter(Boolean) : [],
|
|
284
|
+
actualModel: values['--actual-model'],
|
|
285
|
+
actualReasoningEffort: values['--actual-reasoning-effort'],
|
|
286
|
+
modelFallbackReason: values['--model-fallback-reason'],
|
|
287
|
+
reasoningEffortFallbackReason: values['--reasoning-effort-fallback-reason']
|
|
253
288
|
}, coreOptions);
|
|
254
289
|
}
|
|
255
290
|
} else if (intent === 'advance') {
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import {
|
|
2
|
+
consumeHumanOverride,
|
|
3
|
+
diagnoseHumanOverrideForTask,
|
|
4
|
+
issueHumanOverride
|
|
5
|
+
} from '../task/human-override.ts';
|
|
6
|
+
import type { ConsumeHumanOverrideRequest, HumanOverrideRequest } from '../task/human-override.ts';
|
|
7
|
+
import { resolveTaskRef } from '../task/resolve-ref.ts';
|
|
8
|
+
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
|
|
9
|
+
|
|
10
|
+
const USAGE = `Usage: agent-infra-internal task-override <task-ref> <diagnose|issue|consume> [options]
|
|
11
|
+
|
|
12
|
+
Options: --failure-id <id> --target <target> --operator <operator> --reason <reason> --scope <scope>
|
|
13
|
+
--intent <lifecycle-intent> --alert-number <N> --issue-number <N> --staging-dir <path>
|
|
14
|
+
--expires-at <timestamp> --ticket <ticket-id> --pr-number <N>
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
const FLAGS: Record<string, string> = {
|
|
18
|
+
'--failure-id': 'failureId',
|
|
19
|
+
'--target': 'target',
|
|
20
|
+
'--operator': 'operator',
|
|
21
|
+
'--reason': 'reason',
|
|
22
|
+
'--scope': 'scope',
|
|
23
|
+
'--intent': 'intent',
|
|
24
|
+
'--failure-digest': 'failureDigest',
|
|
25
|
+
'--expires-at': 'expiresAt',
|
|
26
|
+
'--ticket': 'ticketId',
|
|
27
|
+
'--facts': 'observedFacts',
|
|
28
|
+
'--alert-number': 'alertNumber',
|
|
29
|
+
'--issue-number': 'issueNumber',
|
|
30
|
+
'--pr-number': 'pullRequestNumber',
|
|
31
|
+
'--staging-dir': 'stagingDir'
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function usageFailure(message: string): void {
|
|
35
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'OVERRIDE_PAYLOAD_INVALID', message } })}\n`);
|
|
36
|
+
process.stderr.write(USAGE);
|
|
37
|
+
process.exitCode = 1;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function taskOverride(args: string[] = []): void {
|
|
41
|
+
if (args[0] === '--help' || args[0] === '-h') { process.stdout.write(USAGE); return; }
|
|
42
|
+
const [taskRef, operation] = args;
|
|
43
|
+
if (!taskRef || !operation || !['diagnose', 'issue', 'consume'].includes(operation)) {
|
|
44
|
+
usageFailure('task ref and a valid operation are required');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const values: Record<string, unknown> = { taskRef };
|
|
48
|
+
const seen = new Set<string>();
|
|
49
|
+
for (let index = 2; index < args.length; index += 1) {
|
|
50
|
+
const flag = args[index]!;
|
|
51
|
+
const key = FLAGS[flag];
|
|
52
|
+
if (!key) { usageFailure(`unknown option '${flag}'`); return; }
|
|
53
|
+
if (seen.has(flag)) { usageFailure(`duplicate option '${flag}'`); return; }
|
|
54
|
+
const value = args[++index];
|
|
55
|
+
if (value === undefined || value.startsWith('--')) { usageFailure(`option '${flag}' requires a value`); return; }
|
|
56
|
+
seen.add(flag);
|
|
57
|
+
values[key] = key === 'observedFacts' ? value.split(',').map((fact) => fact.trim()).filter(Boolean)
|
|
58
|
+
: key === 'alertNumber' || key === 'issueNumber' || key === 'pullRequestNumber' ? Number(value) : value;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const required: Record<string, string[]> = {
|
|
62
|
+
diagnose: [],
|
|
63
|
+
issue: ['failureId', 'target', 'operator', 'reason', 'scope', 'expiresAt'],
|
|
64
|
+
consume: ['failureId', 'target', 'scope', 'ticketId']
|
|
65
|
+
};
|
|
66
|
+
const missing = required[operation]!.find((key) => values[key] === undefined);
|
|
67
|
+
if (missing) { usageFailure(`${operation} requires '--${missing.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}'`); return; }
|
|
68
|
+
|
|
69
|
+
const resolved = resolveTaskRef(taskRef);
|
|
70
|
+
if (!resolved.ok) {
|
|
71
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: resolved.code, message: resolved.message } })}\n`);
|
|
72
|
+
process.exitCode = 1;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let result;
|
|
77
|
+
try {
|
|
78
|
+
if (operation === 'diagnose') {
|
|
79
|
+
result = diagnoseHumanOverrideForTask(
|
|
80
|
+
taskRef,
|
|
81
|
+
values.failureId ? String(values.failureId) : undefined,
|
|
82
|
+
values.target ? String(values.target) : undefined
|
|
83
|
+
);
|
|
84
|
+
} else if (operation === 'issue') {
|
|
85
|
+
result = withTaskExecutionLock(
|
|
86
|
+
resolved.repoRoot,
|
|
87
|
+
resolved.taskId,
|
|
88
|
+
'task-override.issue',
|
|
89
|
+
() => issueHumanOverride({
|
|
90
|
+
taskRef,
|
|
91
|
+
failureId: String(values.failureId),
|
|
92
|
+
target: String(values.target),
|
|
93
|
+
operator: String(values.operator),
|
|
94
|
+
reason: String(values.reason),
|
|
95
|
+
scope: String(values.scope),
|
|
96
|
+
expiresAt: String(values.expiresAt),
|
|
97
|
+
...(values.intent ? { intent: String(values.intent) as HumanOverrideRequest['intent'] } : {}),
|
|
98
|
+
...(values.alertNumber !== undefined ? { alertNumber: Number(values.alertNumber) } : {}),
|
|
99
|
+
...(values.issueNumber !== undefined ? { issueNumber: Number(values.issueNumber) } : {}),
|
|
100
|
+
...(values.pullRequestNumber !== undefined ? { pullRequestNumber: Number(values.pullRequestNumber) } : {}),
|
|
101
|
+
...(values.stagingDir ? { stagingDir: String(values.stagingDir) } : {}),
|
|
102
|
+
...(values.failureDigest ? { failureDigest: String(values.failureDigest) } : {})
|
|
103
|
+
} satisfies HumanOverrideRequest)
|
|
104
|
+
);
|
|
105
|
+
} else {
|
|
106
|
+
result = withTaskExecutionLock(
|
|
107
|
+
resolved.repoRoot,
|
|
108
|
+
resolved.taskId,
|
|
109
|
+
'task-override.consume',
|
|
110
|
+
() => consumeHumanOverride({
|
|
111
|
+
taskRef,
|
|
112
|
+
ticketId: String(values.ticketId),
|
|
113
|
+
failureId: String(values.failureId),
|
|
114
|
+
target: String(values.target),
|
|
115
|
+
scope: String(values.scope),
|
|
116
|
+
...(values.intent ? { intent: String(values.intent) as ConsumeHumanOverrideRequest['intent'] } : {}),
|
|
117
|
+
...(values.alertNumber !== undefined ? { alertNumber: Number(values.alertNumber) } : {}),
|
|
118
|
+
...(values.issueNumber !== undefined ? { issueNumber: Number(values.issueNumber) } : {}),
|
|
119
|
+
...(values.pullRequestNumber !== undefined ? { pullRequestNumber: Number(values.pullRequestNumber) } : {}),
|
|
120
|
+
...(values.stagingDir ? { stagingDir: String(values.stagingDir) } : {}),
|
|
121
|
+
...(values.failureDigest ? { failureDigest: String(values.failureDigest) } : {}),
|
|
122
|
+
...(values.observedFacts ? { observedFacts: values.observedFacts as string[] } : {})
|
|
123
|
+
} satisfies ConsumeHumanOverrideRequest)
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
} catch (cause) {
|
|
127
|
+
if (cause instanceof TaskExecutionLockError) {
|
|
128
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: cause.code, message: cause.message } })}\n`);
|
|
129
|
+
process.exitCode = 1;
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
throw cause;
|
|
133
|
+
}
|
|
134
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
135
|
+
if (result.status === 'failed') process.exitCode = 1;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export { taskOverride };
|
|
@@ -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 };
|
package/lib/platform/context.ts
CHANGED
|
@@ -20,6 +20,8 @@ type ContextOptions = {
|
|
|
20
20
|
platformType?: string;
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
+
const CURRENT_USER_QUERY = 'query { viewer { login } }';
|
|
24
|
+
|
|
23
25
|
function findRepoRoot(cwd: string): string {
|
|
24
26
|
try {
|
|
25
27
|
return execFileSync('git', ['rev-parse', '--show-toplevel'], {
|
|
@@ -105,16 +107,17 @@ function resolveGitHubContext(options: ContextOptions & { cwd: string }): Platfo
|
|
|
105
107
|
error: { code: 'UPSTREAM_UNRESOLVED', message: 'Unable to resolve the upstream repository', retryable: false }
|
|
106
108
|
});
|
|
107
109
|
}
|
|
108
|
-
const user = client.json(['api', '
|
|
110
|
+
const user = client.json(['api', 'graphql', '-f', `query=${CURRENT_USER_QUERY}`], { cwd });
|
|
109
111
|
if (!user.ok) return failure(user.error, upstream);
|
|
110
|
-
const userValue = user.value as { login?: string } | null;
|
|
112
|
+
const userValue = user.value as { data?: { viewer?: { login?: string } } } | null;
|
|
113
|
+
const currentUser = userValue?.data?.viewer?.login || null;
|
|
111
114
|
const permissions = client.json(['api', `repos/${upstream}`], { cwd });
|
|
112
115
|
if (!permissions.ok) return failure(permissions.error, upstream);
|
|
113
116
|
const permissionValue = permissions.value as { permissions?: Record<string, boolean> } | null;
|
|
114
117
|
const values = permissionValue?.permissions || {};
|
|
115
118
|
const capabilities = {
|
|
116
|
-
authenticated: Boolean(
|
|
117
|
-
comment: Boolean(
|
|
119
|
+
authenticated: Boolean(currentUser),
|
|
120
|
+
comment: Boolean(currentUser),
|
|
118
121
|
triage: Boolean(values.triage || values.push || values.admin),
|
|
119
122
|
push: Boolean(values.push || values.admin),
|
|
120
123
|
admin: Boolean(values.admin)
|
|
@@ -122,7 +125,7 @@ function resolveGitHubContext(options: ContextOptions & { cwd: string }): Platfo
|
|
|
122
125
|
const status = Object.values(capabilities).every(Boolean) ? 'no-op' : 'degraded';
|
|
123
126
|
return platformResult(status, {
|
|
124
127
|
changed: false,
|
|
125
|
-
platform: { type: platform, repository: upstream, currentUser
|
|
128
|
+
platform: { type: platform, repository: upstream, currentUser },
|
|
126
129
|
resource: { kind: 'repository', number: null },
|
|
127
130
|
capabilities,
|
|
128
131
|
operations: [{ name: 'resolve', status: 'no-op', reasonCode: null }]
|