@fitlab-ai/agent-infra 0.9.7 → 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 +5 -0
- package/dist/lib/agent-clients/adapters/claude-code.js +3 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +2 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +2 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +30 -2
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +2 -1
- package/dist/lib/decide.js +68 -14
- package/dist/lib/init.js +1 -1
- 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 +11 -3
- 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 +5 -3
- package/dist/lib/runtime/agent-runtime.js +56 -0
- package/dist/lib/sandbox/commands/create.js +389 -311
- package/dist/lib/sandbox/control/container-identity.js +1 -1
- package/dist/lib/sandbox/control/executor.js +10 -2
- package/dist/lib/sandbox/control/lifecycle.js +392 -72
- package/dist/lib/sandbox/control/state.js +27 -11
- package/dist/lib/sandbox/engine.js +14 -7
- package/dist/lib/sandbox/mounts.js +20 -5
- package/dist/lib/sandbox/recovery.js +78 -10
- package/dist/lib/sandbox/workspace-view.js +62 -40
- package/dist/lib/server/process-state.js +51 -1
- package/dist/lib/task/activity-intent.js +4 -1
- package/dist/lib/task/decision-intents.js +4 -2
- package/dist/lib/task/delegation-receipts.js +37 -16
- package/dist/lib/task/events.js +9 -5
- 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/orchestration.js +60 -6
- package/dist/lib/task/review-finalization.js +3 -2
- package/dist/lib/task/verification-engine.js +40 -14
- 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 +3 -3
- package/lib/agent-clients/adapters/claude-code.ts +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +2 -1
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +2 -1
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +29 -2
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +2 -1
- package/lib/decide.ts +59 -14
- package/lib/init.ts +1 -1
- 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 +12 -4
- 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 +10 -3
- package/lib/runtime/agent-runtime.ts +76 -0
- package/lib/sandbox/commands/create.ts +88 -4
- package/lib/sandbox/config.ts +2 -1
- package/lib/sandbox/control/container-identity.ts +3 -3
- package/lib/sandbox/control/executor.ts +10 -2
- package/lib/sandbox/control/lifecycle.ts +463 -76
- package/lib/sandbox/control/protocol.ts +5 -2
- package/lib/sandbox/control/state.ts +35 -18
- package/lib/sandbox/engine.ts +20 -9
- package/lib/sandbox/mounts.ts +23 -5
- package/lib/sandbox/recovery.ts +90 -9
- package/lib/sandbox/workspace-view.ts +70 -40
- package/lib/server/process-state.ts +48 -1
- package/lib/task/activity-intent.ts +4 -2
- package/lib/task/decision-intents.ts +4 -2
- package/lib/task/delegation-receipts.ts +46 -18
- package/lib/task/events.ts +10 -5
- 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/orchestration.ts +89 -7
- package/lib/task/review-finalization.ts +6 -2
- package/lib/task/verification-engine.ts +42 -16
- 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/hooks/lifecycle-delegation.js +8 -3
- package/templates/.agents/rules/lifecycle-orchestration.en.md +3 -2
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +3 -2
- 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
|
@@ -302,7 +302,7 @@ async function taskOrchestration(args = []) {
|
|
|
302
302
|
else if (intent === 'hook-start') {
|
|
303
303
|
const missing = requireValues([
|
|
304
304
|
...(taskRef === 'auto' ? ['--client'] : []),
|
|
305
|
-
'--native-agent', '--child-id', '--parent-id'
|
|
305
|
+
'--native-agent', '--child-id', '--parent-id'
|
|
306
306
|
]);
|
|
307
307
|
if (missing) {
|
|
308
308
|
usageFailure(`intent 'hook-start' requires '${missing}'`);
|
|
@@ -328,7 +328,11 @@ async function taskOrchestration(args = []) {
|
|
|
328
328
|
return;
|
|
329
329
|
}
|
|
330
330
|
result = sealMatchingOrchestrationDelegation(values['--client'], {
|
|
331
|
-
nativeAgent: values['--native-agent'], childId: values['--child-id']
|
|
331
|
+
nativeAgent: values['--native-agent'], childId: values['--child-id'],
|
|
332
|
+
actualModel: values['--actual-model'],
|
|
333
|
+
actualReasoningEffort: values['--actual-reasoning-effort'],
|
|
334
|
+
modelFallbackReason: values['--model-fallback-reason'],
|
|
335
|
+
reasoningEffortFallbackReason: values['--reasoning-effort-fallback-reason']
|
|
332
336
|
}, coreOptions);
|
|
333
337
|
}
|
|
334
338
|
else {
|
|
@@ -345,7 +349,11 @@ async function taskOrchestration(args = []) {
|
|
|
345
349
|
result = sealOrchestrationDelegation(taskRef, {
|
|
346
350
|
childId: values['--child-id'], exitCode,
|
|
347
351
|
afterFingerprint: values['--after-fingerprint'],
|
|
348
|
-
changedPaths: values['--changed-paths'] ? values['--changed-paths'].split(',').filter(Boolean) : []
|
|
352
|
+
changedPaths: values['--changed-paths'] ? values['--changed-paths'].split(',').filter(Boolean) : [],
|
|
353
|
+
actualModel: values['--actual-model'],
|
|
354
|
+
actualReasoningEffort: values['--actual-reasoning-effort'],
|
|
355
|
+
modelFallbackReason: values['--model-fallback-reason'],
|
|
356
|
+
reasoningEffortFallbackReason: values['--reasoning-effort-fallback-reason']
|
|
349
357
|
}, coreOptions);
|
|
350
358
|
}
|
|
351
359
|
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { consumeHumanOverride, diagnoseHumanOverrideForTask, issueHumanOverride } from '../task/human-override.js';
|
|
2
|
+
import { resolveTaskRef } from '../task/resolve-ref.js';
|
|
3
|
+
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
|
|
4
|
+
const USAGE = `Usage: agent-infra-internal task-override <task-ref> <diagnose|issue|consume> [options]
|
|
5
|
+
|
|
6
|
+
Options: --failure-id <id> --target <target> --operator <operator> --reason <reason> --scope <scope>
|
|
7
|
+
--intent <lifecycle-intent> --alert-number <N> --issue-number <N> --staging-dir <path>
|
|
8
|
+
--expires-at <timestamp> --ticket <ticket-id> --pr-number <N>
|
|
9
|
+
`;
|
|
10
|
+
const FLAGS = {
|
|
11
|
+
'--failure-id': 'failureId',
|
|
12
|
+
'--target': 'target',
|
|
13
|
+
'--operator': 'operator',
|
|
14
|
+
'--reason': 'reason',
|
|
15
|
+
'--scope': 'scope',
|
|
16
|
+
'--intent': 'intent',
|
|
17
|
+
'--failure-digest': 'failureDigest',
|
|
18
|
+
'--expires-at': 'expiresAt',
|
|
19
|
+
'--ticket': 'ticketId',
|
|
20
|
+
'--facts': 'observedFacts',
|
|
21
|
+
'--alert-number': 'alertNumber',
|
|
22
|
+
'--issue-number': 'issueNumber',
|
|
23
|
+
'--pr-number': 'pullRequestNumber',
|
|
24
|
+
'--staging-dir': 'stagingDir'
|
|
25
|
+
};
|
|
26
|
+
function usageFailure(message) {
|
|
27
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'OVERRIDE_PAYLOAD_INVALID', message } })}\n`);
|
|
28
|
+
process.stderr.write(USAGE);
|
|
29
|
+
process.exitCode = 1;
|
|
30
|
+
}
|
|
31
|
+
function taskOverride(args = []) {
|
|
32
|
+
if (args[0] === '--help' || args[0] === '-h') {
|
|
33
|
+
process.stdout.write(USAGE);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const [taskRef, operation] = args;
|
|
37
|
+
if (!taskRef || !operation || !['diagnose', 'issue', 'consume'].includes(operation)) {
|
|
38
|
+
usageFailure('task ref and a valid operation are required');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const values = { taskRef };
|
|
42
|
+
const seen = new Set();
|
|
43
|
+
for (let index = 2; index < args.length; index += 1) {
|
|
44
|
+
const flag = args[index];
|
|
45
|
+
const key = FLAGS[flag];
|
|
46
|
+
if (!key) {
|
|
47
|
+
usageFailure(`unknown option '${flag}'`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (seen.has(flag)) {
|
|
51
|
+
usageFailure(`duplicate option '${flag}'`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const value = args[++index];
|
|
55
|
+
if (value === undefined || value.startsWith('--')) {
|
|
56
|
+
usageFailure(`option '${flag}' requires a value`);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
seen.add(flag);
|
|
60
|
+
values[key] = key === 'observedFacts' ? value.split(',').map((fact) => fact.trim()).filter(Boolean)
|
|
61
|
+
: key === 'alertNumber' || key === 'issueNumber' || key === 'pullRequestNumber' ? Number(value) : value;
|
|
62
|
+
}
|
|
63
|
+
const required = {
|
|
64
|
+
diagnose: [],
|
|
65
|
+
issue: ['failureId', 'target', 'operator', 'reason', 'scope', 'expiresAt'],
|
|
66
|
+
consume: ['failureId', 'target', 'scope', 'ticketId']
|
|
67
|
+
};
|
|
68
|
+
const missing = required[operation].find((key) => values[key] === undefined);
|
|
69
|
+
if (missing) {
|
|
70
|
+
usageFailure(`${operation} requires '--${missing.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}'`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const resolved = resolveTaskRef(taskRef);
|
|
74
|
+
if (!resolved.ok) {
|
|
75
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: resolved.code, message: resolved.message } })}\n`);
|
|
76
|
+
process.exitCode = 1;
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
let result;
|
|
80
|
+
try {
|
|
81
|
+
if (operation === 'diagnose') {
|
|
82
|
+
result = diagnoseHumanOverrideForTask(taskRef, values.failureId ? String(values.failureId) : undefined, values.target ? String(values.target) : undefined);
|
|
83
|
+
}
|
|
84
|
+
else if (operation === 'issue') {
|
|
85
|
+
result = withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-override.issue', () => issueHumanOverride({
|
|
86
|
+
taskRef,
|
|
87
|
+
failureId: String(values.failureId),
|
|
88
|
+
target: String(values.target),
|
|
89
|
+
operator: String(values.operator),
|
|
90
|
+
reason: String(values.reason),
|
|
91
|
+
scope: String(values.scope),
|
|
92
|
+
expiresAt: String(values.expiresAt),
|
|
93
|
+
...(values.intent ? { intent: String(values.intent) } : {}),
|
|
94
|
+
...(values.alertNumber !== undefined ? { alertNumber: Number(values.alertNumber) } : {}),
|
|
95
|
+
...(values.issueNumber !== undefined ? { issueNumber: Number(values.issueNumber) } : {}),
|
|
96
|
+
...(values.pullRequestNumber !== undefined ? { pullRequestNumber: Number(values.pullRequestNumber) } : {}),
|
|
97
|
+
...(values.stagingDir ? { stagingDir: String(values.stagingDir) } : {}),
|
|
98
|
+
...(values.failureDigest ? { failureDigest: String(values.failureDigest) } : {})
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
result = withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-override.consume', () => consumeHumanOverride({
|
|
103
|
+
taskRef,
|
|
104
|
+
ticketId: String(values.ticketId),
|
|
105
|
+
failureId: String(values.failureId),
|
|
106
|
+
target: String(values.target),
|
|
107
|
+
scope: String(values.scope),
|
|
108
|
+
...(values.intent ? { intent: String(values.intent) } : {}),
|
|
109
|
+
...(values.alertNumber !== undefined ? { alertNumber: Number(values.alertNumber) } : {}),
|
|
110
|
+
...(values.issueNumber !== undefined ? { issueNumber: Number(values.issueNumber) } : {}),
|
|
111
|
+
...(values.pullRequestNumber !== undefined ? { pullRequestNumber: Number(values.pullRequestNumber) } : {}),
|
|
112
|
+
...(values.stagingDir ? { stagingDir: String(values.stagingDir) } : {}),
|
|
113
|
+
...(values.failureDigest ? { failureDigest: String(values.failureDigest) } : {}),
|
|
114
|
+
...(values.observedFacts ? { observedFacts: values.observedFacts } : {})
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch (cause) {
|
|
119
|
+
if (cause instanceof TaskExecutionLockError) {
|
|
120
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: cause.code, message: cause.message } })}\n`);
|
|
121
|
+
process.exitCode = 1;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
throw cause;
|
|
125
|
+
}
|
|
126
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
127
|
+
if (result.status === 'failed')
|
|
128
|
+
process.exitCode = 1;
|
|
129
|
+
}
|
|
130
|
+
export { taskOverride };
|
|
131
|
+
//# sourceMappingURL=task-override.js.map
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { finalizeReviewSummary } from '../task/review-finalization.js';
|
|
2
|
-
|
|
2
|
+
import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.js';
|
|
3
|
+
import { resolveTaskRef } from '../task/resolve-ref.js';
|
|
4
|
+
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
|
|
5
|
+
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';
|
|
3
6
|
function failUsage(message) {
|
|
4
7
|
process.stdout.write(`${JSON.stringify({
|
|
5
8
|
status: 'failed',
|
|
@@ -27,10 +30,13 @@ function taskReview(args = []) {
|
|
|
27
30
|
let artifact = '';
|
|
28
31
|
let dryRun = false;
|
|
29
32
|
let orchestrated = false;
|
|
33
|
+
let overrideTicket = '';
|
|
34
|
+
let overrideTarget = '';
|
|
35
|
+
let overrideScope = '';
|
|
30
36
|
const seen = new Set();
|
|
31
37
|
for (let index = 2; index < args.length; index += 1) {
|
|
32
38
|
const flag = args[index];
|
|
33
|
-
if (!['--stage', '--artifact', '--orchestrated', '--dry-run'].includes(flag)) {
|
|
39
|
+
if (!['--stage', '--artifact', '--orchestrated', '--dry-run', '--override-ticket', '--override-target', '--override-scope'].includes(flag)) {
|
|
34
40
|
failUsage(`unknown option '${flag}'`);
|
|
35
41
|
return;
|
|
36
42
|
}
|
|
@@ -54,16 +60,68 @@ function taskReview(args = []) {
|
|
|
54
60
|
}
|
|
55
61
|
if (flag === '--stage')
|
|
56
62
|
stage = value;
|
|
57
|
-
else
|
|
63
|
+
else if (flag === '--artifact')
|
|
58
64
|
artifact = value;
|
|
65
|
+
else if (flag === '--override-ticket')
|
|
66
|
+
overrideTicket = value;
|
|
67
|
+
else if (flag === '--override-target')
|
|
68
|
+
overrideTarget = value;
|
|
69
|
+
else if (flag === '--override-scope')
|
|
70
|
+
overrideScope = value;
|
|
59
71
|
}
|
|
60
72
|
if (!stage || !artifact) {
|
|
61
73
|
failUsage("options '--stage' and '--artifact' are required");
|
|
62
74
|
return;
|
|
63
75
|
}
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
76
|
+
const dryRunConflict = overrideDryRunConflict({ dryRun, overrideTicket, overrideTarget, overrideScope });
|
|
77
|
+
if (dryRunConflict) {
|
|
78
|
+
failUsage(dryRunConflict.message);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const resolved = resolveTaskRef(args[0]);
|
|
82
|
+
if (!resolved.ok) {
|
|
83
|
+
failUsage(resolved.message);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
let result;
|
|
87
|
+
let humanOverride = null;
|
|
88
|
+
try {
|
|
89
|
+
result = withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-review.finalize-summary', () => {
|
|
90
|
+
let current = finalizeReviewSummary({ taskRef: args[0], stage, artifact, orchestrated, dryRun }, { lockAlreadyHeld: true });
|
|
91
|
+
if (current.status !== 'failed' || !overrideTicket)
|
|
92
|
+
return current;
|
|
93
|
+
if (!overrideTarget || !overrideScope) {
|
|
94
|
+
failUsage('override ticket requires target and scope');
|
|
95
|
+
return current;
|
|
96
|
+
}
|
|
97
|
+
const consumed = consumeHumanOverride({
|
|
98
|
+
taskRef: args[0], ticketId: overrideTicket,
|
|
99
|
+
failureId: failureId('review-finalization', current.error?.code ?? 'TASK_STATE_MISMATCH'),
|
|
100
|
+
target: overrideTarget, scope: overrideScope
|
|
101
|
+
}, {
|
|
102
|
+
effectExecutor: (capability) => {
|
|
103
|
+
const retried = finalizeReviewSummary({ taskRef: args[0], stage, artifact, orchestrated, dryRun }, { lockAlreadyHeld: true, manualOverride: capability });
|
|
104
|
+
current = retried;
|
|
105
|
+
return retried.status === 'failed' || retried.status === 'planned'
|
|
106
|
+
? { 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'}` }
|
|
107
|
+
: null;
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
humanOverride = consumed;
|
|
111
|
+
return current;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
if (!(error instanceof TaskExecutionLockError))
|
|
116
|
+
throw error;
|
|
117
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: error.code, message: error.message } })}\n`);
|
|
118
|
+
process.exitCode = 1;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
process.stdout.write(`${JSON.stringify(humanOverride ? { ...result, humanOverride } : result)}\n`);
|
|
122
|
+
const overrideFailed = Boolean(humanOverride && typeof humanOverride === 'object' &&
|
|
123
|
+
humanOverride.status === 'failed');
|
|
124
|
+
if (result.status === 'failed' || overrideFailed)
|
|
67
125
|
process.exitCode = 1;
|
|
68
126
|
}
|
|
69
127
|
export { taskReview };
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { renderTaskVerification, verifyTaskEvent } from '../task/verification.js';
|
|
2
|
-
|
|
2
|
+
import { consumeHumanOverride, failureId } from '../task/human-override.js';
|
|
3
|
+
import { resolveTaskRef } from '../task/resolve-ref.js';
|
|
4
|
+
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
|
|
5
|
+
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';
|
|
3
6
|
function fail(message) {
|
|
4
7
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'VERIFY_PAYLOAD_INVALID', message } })}\n`);
|
|
5
8
|
process.stderr.write(USAGE);
|
|
@@ -14,12 +17,17 @@ function taskVerify(args = []) {
|
|
|
14
17
|
fail('task ref and verification event are required');
|
|
15
18
|
return;
|
|
16
19
|
}
|
|
20
|
+
const taskRef = args[0];
|
|
21
|
+
const event = args[1];
|
|
17
22
|
let artifact;
|
|
18
23
|
let format = 'json';
|
|
24
|
+
let overrideTicket;
|
|
25
|
+
let overrideTarget;
|
|
26
|
+
let overrideScope;
|
|
19
27
|
const seen = new Set();
|
|
20
28
|
for (let index = 2; index < args.length; index += 1) {
|
|
21
29
|
const flag = args[index];
|
|
22
|
-
if (
|
|
30
|
+
if (!['--artifact', '--format', '--override-ticket', '--override-target', '--override-scope'].includes(flag)) {
|
|
23
31
|
fail(`unknown option '${flag}'`);
|
|
24
32
|
return;
|
|
25
33
|
}
|
|
@@ -35,6 +43,12 @@ function taskVerify(args = []) {
|
|
|
35
43
|
seen.add(flag);
|
|
36
44
|
if (flag === '--artifact')
|
|
37
45
|
artifact = value;
|
|
46
|
+
else if (flag === '--override-ticket')
|
|
47
|
+
overrideTicket = value;
|
|
48
|
+
else if (flag === '--override-target')
|
|
49
|
+
overrideTarget = value;
|
|
50
|
+
else if (flag === '--override-scope')
|
|
51
|
+
overrideScope = value;
|
|
38
52
|
else if (value === 'json' || value === 'text')
|
|
39
53
|
format = value;
|
|
40
54
|
else {
|
|
@@ -42,7 +56,55 @@ function taskVerify(args = []) {
|
|
|
42
56
|
return;
|
|
43
57
|
}
|
|
44
58
|
}
|
|
45
|
-
const
|
|
59
|
+
const resolved = resolveTaskRef(taskRef);
|
|
60
|
+
if (!resolved.ok) {
|
|
61
|
+
fail(resolved.message);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
let result;
|
|
65
|
+
let humanOverride = null;
|
|
66
|
+
try {
|
|
67
|
+
result = withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-verify.${event}`, () => {
|
|
68
|
+
let current = verifyTaskEvent({ taskRef, event, ...(artifact ? { artifact } : {}) });
|
|
69
|
+
if (current.status === 'pass' || !overrideTicket)
|
|
70
|
+
return current;
|
|
71
|
+
if (!overrideTarget || !overrideScope) {
|
|
72
|
+
fail('override ticket requires target and scope');
|
|
73
|
+
return current;
|
|
74
|
+
}
|
|
75
|
+
const failureProducer = current.status === 'blocked' || current.status === 'fail'
|
|
76
|
+
? 'verification-engine'
|
|
77
|
+
: 'task-verify';
|
|
78
|
+
const failureCode = current.status === 'blocked'
|
|
79
|
+
? 'CHECK_BLOCKED'
|
|
80
|
+
: current.status === 'fail'
|
|
81
|
+
? 'CHECK_FAILED'
|
|
82
|
+
: current.error?.code ?? 'VERIFY_TASK_STATE_MISMATCH';
|
|
83
|
+
const consumed = consumeHumanOverride({
|
|
84
|
+
taskRef, ticketId: overrideTicket,
|
|
85
|
+
failureId: failureId(failureProducer, failureCode),
|
|
86
|
+
target: overrideTarget, scope: overrideScope
|
|
87
|
+
}, {
|
|
88
|
+
effectExecutor: (capability) => {
|
|
89
|
+
const retried = verifyTaskEvent({ taskRef, event, ...(artifact ? { artifact } : {}) }, { manualOverride: capability });
|
|
90
|
+
current = retried;
|
|
91
|
+
return retried.status !== 'pass'
|
|
92
|
+
? { code: 'OVERRIDE_EFFECT_FAILED', message: `${retried.error?.code ?? 'VERIFY_FAILED'}: ${retried.error?.message ?? 'manual verification effect failed'}` }
|
|
93
|
+
: null;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
humanOverride = consumed;
|
|
97
|
+
return current;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
if (!(error instanceof TaskExecutionLockError))
|
|
102
|
+
throw error;
|
|
103
|
+
fail(`${error.code}: ${error.message}`);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (humanOverride)
|
|
107
|
+
result = { ...result, humanOverride };
|
|
46
108
|
process.stdout.write(format === 'text' ? renderTaskVerification(result) : `${JSON.stringify(result)}\n`);
|
|
47
109
|
process.exitCode = result.status === 'pass' ? 0 : result.status === 'blocked' ? 2 : 1;
|
|
48
110
|
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { applyWorkflowWarningIntent } from '../task/workflow-warning-intents.js';
|
|
2
|
+
import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.js';
|
|
3
|
+
import { resolveTaskRef } from '../task/resolve-ref.js';
|
|
4
|
+
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
|
|
2
5
|
const USAGE = `Usage: agent-infra-internal task-warning <task-ref> <add|set-status|list> [intent flags] [--dry-run]\n`;
|
|
3
6
|
const FLAGS = {
|
|
4
7
|
'--step': 'step', '--severity': 'severity', '--code': 'code', '--target': 'target',
|
|
5
|
-
'--message': 'message', '--action': 'action', '--id': 'id', '--status': 'status', '--resolution': 'resolution'
|
|
8
|
+
'--message': 'message', '--action': 'action', '--id': 'id', '--status': 'status', '--resolution': 'resolution',
|
|
9
|
+
'--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
|
|
6
10
|
};
|
|
7
11
|
function usageFailure(message) {
|
|
8
12
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'WARNING_PAYLOAD_INVALID', message } })}\n`);
|
|
@@ -55,6 +59,9 @@ function taskWarning(args = []) {
|
|
|
55
59
|
};
|
|
56
60
|
const optional = kind === 'list' ? ['status'] : [];
|
|
57
61
|
const allowed = new Set(['kind', 'taskRef', 'dryRun', ...required[kind], ...optional]);
|
|
62
|
+
allowed.add('overrideTicket');
|
|
63
|
+
allowed.add('overrideTarget');
|
|
64
|
+
allowed.add('overrideScope');
|
|
58
65
|
const unexpected = Object.keys(values).find((key) => !allowed.has(key));
|
|
59
66
|
const missing = required[kind].find((key) => values[key] === undefined);
|
|
60
67
|
if (unexpected) {
|
|
@@ -65,9 +72,55 @@ function taskWarning(args = []) {
|
|
|
65
72
|
usageFailure(`${kind} requires '${missing}'`);
|
|
66
73
|
return;
|
|
67
74
|
}
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
75
|
+
const dryRunConflict = overrideDryRunConflict(values);
|
|
76
|
+
if (dryRunConflict) {
|
|
77
|
+
usageFailure(dryRunConflict.message);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const resolved = resolveTaskRef(taskRef);
|
|
81
|
+
if (!resolved.ok) {
|
|
82
|
+
usageFailure(resolved.message);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
let result;
|
|
86
|
+
let humanOverride = null;
|
|
87
|
+
try {
|
|
88
|
+
result = withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-warning.${kind}`, () => {
|
|
89
|
+
let current = applyWorkflowWarningIntent(values);
|
|
90
|
+
if (current.status !== 'failed' || !values.overrideTicket)
|
|
91
|
+
return current;
|
|
92
|
+
if (!values.overrideTarget || !values.overrideScope) {
|
|
93
|
+
usageFailure('override ticket requires target and scope');
|
|
94
|
+
return current;
|
|
95
|
+
}
|
|
96
|
+
const consumed = consumeHumanOverride({
|
|
97
|
+
taskRef, ticketId: String(values.overrideTicket),
|
|
98
|
+
failureId: failureId('workflow-warning', current.error?.code ?? 'TASK_STATE_MISMATCH'),
|
|
99
|
+
target: String(values.overrideTarget), scope: String(values.overrideScope)
|
|
100
|
+
}, {
|
|
101
|
+
effectExecutor: (capability) => {
|
|
102
|
+
const retried = applyWorkflowWarningIntent(values, { manualOverride: capability });
|
|
103
|
+
current = retried;
|
|
104
|
+
return retried.status === 'failed' || retried.status === 'planned'
|
|
105
|
+
? { 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'}` }
|
|
106
|
+
: null;
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
humanOverride = consumed;
|
|
110
|
+
return current;
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
if (!(error instanceof TaskExecutionLockError))
|
|
115
|
+
throw error;
|
|
116
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: error.code, message: error.message } })}\n`);
|
|
117
|
+
process.exitCode = 1;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
process.stdout.write(`${JSON.stringify(humanOverride ? { ...result, humanOverride } : result)}\n`);
|
|
121
|
+
const overrideFailed = Boolean(humanOverride && typeof humanOverride === 'object' &&
|
|
122
|
+
humanOverride.status === 'failed');
|
|
123
|
+
if (result.status === 'failed' || overrideFailed)
|
|
71
124
|
process.exitCode = 1;
|
|
72
125
|
}
|
|
73
126
|
export { taskWarning };
|
|
@@ -2,6 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import { execFileSync } from 'node:child_process';
|
|
3
3
|
import { enumerateArtifacts } from '../task/artifacts.js';
|
|
4
4
|
import { parseTaskFrontmatter } from '../task/frontmatter.js';
|
|
5
|
+
import { renderHumanOverrideAudit } from '../task/human-override.js';
|
|
5
6
|
import { resolveTaskRef } from '../task/resolve-ref.js';
|
|
6
7
|
import { resolvePlatformContext } from './context.js';
|
|
7
8
|
import { createGitHubClient } from './github-client.js';
|
|
@@ -10,8 +11,9 @@ import { platformResult } from './types.js';
|
|
|
10
11
|
function summaryMarker(taskId) {
|
|
11
12
|
return `<!-- sync-pr:${taskId}:summary -->`;
|
|
12
13
|
}
|
|
13
|
-
function buildPullRequestSummary(taskId, body, headSha) {
|
|
14
|
-
|
|
14
|
+
function buildPullRequestSummary(taskId, body, headSha, humanOverrideAudit = '') {
|
|
15
|
+
const sections = [body.replace(/\s+$/, ''), humanOverrideAudit.trim()].filter(Boolean).join('\n\n');
|
|
16
|
+
return normalizeCommentContent(`${summaryMarker(taskId)}\n<!-- last-commit: ${headSha} -->\n\n${sections}\n`);
|
|
15
17
|
}
|
|
16
18
|
function reconcileSummaryComment(comments, taskId, desired) {
|
|
17
19
|
const marker = summaryMarker(taskId);
|
|
@@ -67,7 +69,7 @@ function syncPullRequestSummary(taskRef, options) {
|
|
|
67
69
|
catch (error) {
|
|
68
70
|
return platformResult('failed', { platform: context.platform, capabilities: context.capabilities, error: { code: 'GIT_HEAD_UNRESOLVED', message: error instanceof Error ? error.message : String(error), retryable: false } });
|
|
69
71
|
}
|
|
70
|
-
const desired = buildPullRequestSummary(resolved.taskId, options.body, headSha);
|
|
72
|
+
const desired = buildPullRequestSummary(resolved.taskId, options.body, headSha, renderHumanOverrideAudit(fs.readFileSync(resolved.taskMdPath, 'utf8')));
|
|
71
73
|
const listed = listRemoteComments(client, context.platform.repository, prNumber, resolved.repoRoot);
|
|
72
74
|
if (!listed.ok)
|
|
73
75
|
return platformResult(listed.error.retryable ? 'blocked' : 'failed', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, error: listed.error });
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
const CLIENT_NAMESPACE = /^[a-z][a-z0-9-]{0,31}$/u;
|
|
3
|
+
function runtimeError(code, message) {
|
|
4
|
+
const error = new Error(`${code}: ${message}`);
|
|
5
|
+
error.name = code;
|
|
6
|
+
return error;
|
|
7
|
+
}
|
|
8
|
+
function boundControlContext(env) {
|
|
9
|
+
return [
|
|
10
|
+
'AGENT_INFRA_CONTROL_TOKEN',
|
|
11
|
+
'AGENT_INFRA_CONTROL_GENERATION',
|
|
12
|
+
'AGENT_INFRA_EXECUTOR_MANIFEST',
|
|
13
|
+
'AGENT_INFRA_CODEX_CONTROLLER_CONTEXT'
|
|
14
|
+
].some((key) => typeof env[key] === 'string' && env[key].length > 0);
|
|
15
|
+
}
|
|
16
|
+
function clientNamespace(client) {
|
|
17
|
+
if (!CLIENT_NAMESPACE.test(client)) {
|
|
18
|
+
throw runtimeError('AGENT_INFRA_RUNTIME_CLIENT_INVALID', `invalid runtime client namespace '${client}'`);
|
|
19
|
+
}
|
|
20
|
+
return client;
|
|
21
|
+
}
|
|
22
|
+
export function resolveAgentRuntimeRoot(options = {}) {
|
|
23
|
+
const env = options.env ?? process.env;
|
|
24
|
+
const configured = env.AGENT_INFRA_RUNTIME_DIR;
|
|
25
|
+
if (configured !== undefined && configured.length > 0) {
|
|
26
|
+
if (!path.isAbsolute(configured)) {
|
|
27
|
+
throw runtimeError('AGENT_INFRA_RUNTIME_DIR_INVALID', 'runtime directory must be absolute');
|
|
28
|
+
}
|
|
29
|
+
return path.resolve(configured);
|
|
30
|
+
}
|
|
31
|
+
if (boundControlContext(env)) {
|
|
32
|
+
throw runtimeError('AGENT_INFRA_RUNTIME_DIR_REQUIRED', 'task-bound control context requires AGENT_INFRA_RUNTIME_DIR');
|
|
33
|
+
}
|
|
34
|
+
return path.join(path.resolve(options.repoRoot ?? process.cwd()), '.agents', 'workspace', '.runtime');
|
|
35
|
+
}
|
|
36
|
+
export function resolveAgentRuntimeStoreRoot(options = {}) {
|
|
37
|
+
const client = clientNamespace(options.client ?? 'codex');
|
|
38
|
+
if (options.explicitRoot)
|
|
39
|
+
return path.resolve(options.explicitRoot);
|
|
40
|
+
const env = options.env ?? process.env;
|
|
41
|
+
const runtimeRoot = resolveAgentRuntimeRoot({ repoRoot: options.repoRoot, env });
|
|
42
|
+
if (env.AGENT_INFRA_RUNTIME_DIR) {
|
|
43
|
+
if (!options.store) {
|
|
44
|
+
throw runtimeError('AGENT_INFRA_RUNTIME_STORE_INVALID', 'store kind is required for task-bound runtime');
|
|
45
|
+
}
|
|
46
|
+
return path.join(runtimeRoot, 'clients', client, options.store);
|
|
47
|
+
}
|
|
48
|
+
if (client === 'codex' && options.store) {
|
|
49
|
+
return path.join(runtimeRoot, `codex-${options.store}`);
|
|
50
|
+
}
|
|
51
|
+
if (!options.store) {
|
|
52
|
+
throw runtimeError('AGENT_INFRA_RUNTIME_STORE_INVALID', 'store kind is required');
|
|
53
|
+
}
|
|
54
|
+
return path.join(runtimeRoot, 'clients', client, options.store);
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=agent-runtime.js.map
|