@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
package/dist/bin/internal-cli.js
CHANGED
|
@@ -132,6 +132,11 @@ else
|
|
|
132
132
|
taskLifecycle(process.argv.slice(3));
|
|
133
133
|
break;
|
|
134
134
|
}
|
|
135
|
+
case 'task-override': {
|
|
136
|
+
const { taskOverride } = await import('../lib/internal/task-override.js');
|
|
137
|
+
taskOverride(process.argv.slice(3));
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
135
140
|
case 'task-short-id': {
|
|
136
141
|
const { taskShortId } = await import('../lib/internal/task-short-id.js');
|
|
137
142
|
taskShortId(process.argv.slice(3));
|
|
@@ -11,7 +11,7 @@ const claudeCodeAdapter = defineAgentClientAdapter({
|
|
|
11
11
|
commands: { level: 'integrated' },
|
|
12
12
|
hooks: { level: 'integrated' },
|
|
13
13
|
subagents: { level: 'experimental' },
|
|
14
|
-
orchestration: { level: '
|
|
14
|
+
orchestration: { level: 'experimental' },
|
|
15
15
|
sandbox: { level: 'integrated' },
|
|
16
16
|
verification: { level: 'compatible' }
|
|
17
17
|
},
|
|
@@ -21,8 +21,8 @@ const claudeCodeAdapter = defineAgentClientAdapter({
|
|
|
21
21
|
guidance: 'Use the host model picker for the complete model and reasoning-effort catalog.'
|
|
22
22
|
},
|
|
23
23
|
delegationEvidence: {
|
|
24
|
-
actualModel: '
|
|
25
|
-
actualReasoningEffort: '
|
|
24
|
+
actualModel: 'host-event',
|
|
25
|
+
actualReasoningEffort: 'spawn-ack'
|
|
26
26
|
},
|
|
27
27
|
project: {
|
|
28
28
|
ownedPathPrefixes: ['.claude/'],
|
|
@@ -4,6 +4,7 @@ import fs from 'node:fs';
|
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import semver from 'semver';
|
|
7
|
+
import { resolveAgentRuntimeStoreRoot } from '../../../runtime/agent-runtime.js';
|
|
7
8
|
const LIFECYCLE_HOOKS = Object.freeze([
|
|
8
9
|
Object.freeze({ event: 'PreToolUse', matcher: '^collaborationspawn_agent$', phase: 'pre-tool' }),
|
|
9
10
|
Object.freeze({ event: 'PostToolUse', matcher: '', phase: 'post-tool' }),
|
|
@@ -482,7 +483,7 @@ async function preflightCodexLifecycleEvidence(repoRoot = process.cwd(), runtime
|
|
|
482
483
|
fs.rmSync(schemaDir, { recursive: true, force: true });
|
|
483
484
|
}
|
|
484
485
|
const hookDefinitionHash = crypto.createHash('sha256').update(hooksRaw).digest('hex');
|
|
485
|
-
const runtimeRoot =
|
|
486
|
+
const runtimeRoot = resolveAgentRuntimeStoreRoot({ repoRoot, store: 'lifecycle' });
|
|
486
487
|
const runtimeLiveness = hasCodexRuntimeLiveness(runtimeRoot, hookDefinitionHash, runtimeIdentity);
|
|
487
488
|
const transport = new CodexAppServerTransport();
|
|
488
489
|
transport.start();
|
|
@@ -2,6 +2,7 @@ import crypto from 'node:crypto';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { verifyLifecycleBuildIdentity } from './build-identity.js';
|
|
5
|
+
import { resolveAgentRuntimeStoreRoot } from '../../../runtime/agent-runtime.js';
|
|
5
6
|
const DEFAULT_TTL_MS = 30_000;
|
|
6
7
|
const DEFAULT_TOMBSTONE_MS = 86_400_000;
|
|
7
8
|
const DEFAULT_MAX_RECORDS = 128;
|
|
@@ -22,7 +23,7 @@ function sameController(left, right) {
|
|
|
22
23
|
}
|
|
23
24
|
function createCodexCapabilityStore(options = {}) {
|
|
24
25
|
const root = options.root
|
|
25
|
-
??
|
|
26
|
+
?? resolveAgentRuntimeStoreRoot({ store: 'capabilities' });
|
|
26
27
|
const now = options.now ?? Date.now;
|
|
27
28
|
const createToken = options.token ?? (() => crypto.randomBytes(32).toString('base64url'));
|
|
28
29
|
const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
|
|
@@ -38,12 +38,39 @@ function controlFromEnvironment() {
|
|
|
38
38
|
const generation = process.env.AGENT_INFRA_CONTROL_GENERATION;
|
|
39
39
|
const channelDir = process.env.AGENT_INFRA_CONTROL_DIR;
|
|
40
40
|
const statusDir = process.env.AGENT_INFRA_CONTROL_STATUS_DIR;
|
|
41
|
-
|
|
41
|
+
const runtimeDir = process.env.AGENT_INFRA_RUNTIME_DIR;
|
|
42
|
+
if (!token || !generation || !channelDir || !statusDir || !runtimeDir) {
|
|
42
43
|
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTROL_MISSING');
|
|
43
44
|
}
|
|
44
|
-
return { token, generation, channelDir, statusDir };
|
|
45
|
+
return { token, generation, channelDir, statusDir, runtimeDir };
|
|
46
|
+
}
|
|
47
|
+
function verifyRuntime(runtimeDir) {
|
|
48
|
+
let stat;
|
|
49
|
+
try {
|
|
50
|
+
stat = fs.lstatSync(runtimeDir);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
throw new Error('CODEX_SANDBOX_CONTROLLER_RUNTIME_UNAVAILABLE');
|
|
54
|
+
}
|
|
55
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
56
|
+
throw new Error('CODEX_SANDBOX_CONTROLLER_RUNTIME_UNAVAILABLE');
|
|
57
|
+
}
|
|
58
|
+
const probe = path.join(runtimeDir, `.controller-${process.pid}-${crypto.randomUUID()}.probe`);
|
|
59
|
+
try {
|
|
60
|
+
fs.writeFileSync(probe, 'runtime-probe\n', { encoding: 'utf8', flag: 'wx', mode: 0o600 });
|
|
61
|
+
if (fs.readFileSync(probe, 'utf8') !== 'runtime-probe\n') {
|
|
62
|
+
throw new Error('probe mismatch');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
throw new Error('CODEX_SANDBOX_CONTROLLER_RUNTIME_UNAVAILABLE');
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
fs.rmSync(probe, { force: true });
|
|
70
|
+
}
|
|
45
71
|
}
|
|
46
72
|
function verifyControl(taskId, control) {
|
|
73
|
+
verifyRuntime(control.runtimeDir);
|
|
47
74
|
let status = null;
|
|
48
75
|
let lastError = null;
|
|
49
76
|
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
@@ -153,6 +180,7 @@ function isolatedEnvironment(home, shimDir, contextPath, taskId, control, provid
|
|
|
153
180
|
AGENT_INFRA_CONTROL_GENERATION: control.generation,
|
|
154
181
|
AGENT_INFRA_CONTROL_DIR: control.channelDir,
|
|
155
182
|
AGENT_INFRA_CONTROL_STATUS_DIR: control.statusDir,
|
|
183
|
+
AGENT_INFRA_RUNTIME_DIR: control.runtimeDir,
|
|
156
184
|
AGENT_INFRA_CODEX_CONTROLLER_CONTEXT: contextPath
|
|
157
185
|
};
|
|
158
186
|
}
|
|
@@ -2,6 +2,7 @@ import crypto from 'node:crypto';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { createCodexLifecycleState, expireCodexLifecycleState, reduceCodexLifecycleEvent } from './evidence.js';
|
|
5
|
+
import { resolveAgentRuntimeStoreRoot } from '../../../runtime/agent-runtime.js';
|
|
5
6
|
const LOCK_RETRY_MS = 10;
|
|
6
7
|
const LOCK_TIMEOUT_MS = 1_000;
|
|
7
8
|
const LOCK_STALE_MS = 30_000;
|
|
@@ -58,7 +59,7 @@ function writeRecord(file, record, expectedRevision) {
|
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
function createCodexLifecycleStore(options) {
|
|
61
|
-
const root = options.root ??
|
|
62
|
+
const root = options.root ?? resolveAgentRuntimeStoreRoot({ store: 'lifecycle' });
|
|
62
63
|
const now = options.now ?? (() => new Date().toISOString());
|
|
63
64
|
fs.mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
64
65
|
fs.chmodSync(root, 0o700);
|
package/dist/lib/decide.js
CHANGED
|
@@ -2,25 +2,44 @@ import { VERSION } from './version.js';
|
|
|
2
2
|
import { parseTaskScope } from './task/command-options.js';
|
|
3
3
|
import { applyHumanDecision } from './task/decision-intents.js';
|
|
4
4
|
import { canonicalTimestamp } from './task/write.js';
|
|
5
|
+
import { consumeHumanOverride, failureId } from './task/human-override.js';
|
|
6
|
+
import { resolveTaskRef } from './task/resolve-ref.js';
|
|
7
|
+
import { TaskExecutionLockError, withTaskExecutionLock } from './task/task-execution-lock.js';
|
|
5
8
|
function parseDecisionParts(parts) {
|
|
6
9
|
const decision = [];
|
|
7
10
|
let needsImplementation;
|
|
11
|
+
let overrideTicket;
|
|
12
|
+
let overrideTarget;
|
|
13
|
+
let overrideScope;
|
|
8
14
|
for (let index = 0; index < parts.length; index += 1) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
15
|
+
const part = parts[index];
|
|
16
|
+
if (part === '--needs-implementation') {
|
|
17
|
+
if (needsImplementation !== undefined)
|
|
18
|
+
throw new Error("duplicate option '--needs-implementation'");
|
|
19
|
+
const value = parts[++index];
|
|
20
|
+
if (value !== 'true' && value !== 'false') {
|
|
21
|
+
throw new Error("--needs-implementation must be 'true' or 'false'");
|
|
22
|
+
}
|
|
23
|
+
needsImplementation = value === 'true';
|
|
12
24
|
}
|
|
13
|
-
if (
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
25
|
+
else if (part === '--override-ticket' || part === '--override-target' || part === '--override-scope') {
|
|
26
|
+
const value = parts[++index];
|
|
27
|
+
if (!value)
|
|
28
|
+
throw new Error(`${part} requires a value`);
|
|
29
|
+
if (part === '--override-ticket')
|
|
30
|
+
overrideTicket = value;
|
|
31
|
+
else if (part === '--override-target')
|
|
32
|
+
overrideTarget = value;
|
|
33
|
+
else
|
|
34
|
+
overrideScope = value;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
decision.push(parts[index]);
|
|
18
38
|
}
|
|
19
|
-
needsImplementation = value === 'true';
|
|
20
39
|
}
|
|
21
40
|
if (decision.length === 0)
|
|
22
41
|
throw new Error('decision content is required');
|
|
23
|
-
return { decision: decision.join(' '), needsImplementation };
|
|
42
|
+
return { decision: decision.join(' '), needsImplementation, overrideTicket, overrideTarget, overrideScope };
|
|
24
43
|
}
|
|
25
44
|
export async function decide(args, options = {}) {
|
|
26
45
|
let scope;
|
|
@@ -79,19 +98,54 @@ export async function decide(args, options = {}) {
|
|
|
79
98
|
try {
|
|
80
99
|
const parsedDecision = parseDecisionParts(decisionParts);
|
|
81
100
|
const now = (options.now ?? canonicalTimestamp)();
|
|
82
|
-
const
|
|
101
|
+
const request = {
|
|
83
102
|
taskRef, selector, decision: parsedDecision.decision,
|
|
84
103
|
needsImplementation: parsedDecision.needsImplementation
|
|
85
|
-
}
|
|
104
|
+
};
|
|
105
|
+
const writeOptions = {
|
|
86
106
|
repoRoot: options.repoRoot,
|
|
87
107
|
metadataProvider: () => ({ timestamp: now, agentInfraVersion: options.version ?? VERSION })
|
|
88
|
-
}
|
|
108
|
+
};
|
|
109
|
+
const resolved = taskRef ? resolveTaskRef(taskRef, { repoRoot: options.repoRoot }) : null;
|
|
110
|
+
let result;
|
|
111
|
+
const execute = () => {
|
|
112
|
+
let current = applyHumanDecision(request, writeOptions);
|
|
113
|
+
if (current.status !== 'failed' || !parsedDecision.overrideTicket)
|
|
114
|
+
return current;
|
|
115
|
+
if (!parsedDecision.overrideTarget || !parsedDecision.overrideScope)
|
|
116
|
+
throw new Error('override ticket requires --override-target and --override-scope');
|
|
117
|
+
const consumed = consumeHumanOverride({
|
|
118
|
+
taskRef: resolved?.ok ? resolved.taskId : String(taskRef ?? ''),
|
|
119
|
+
ticketId: parsedDecision.overrideTicket,
|
|
120
|
+
failureId: failureId('decision-intent', current.error?.code ?? 'TASK_STATE_MISMATCH'),
|
|
121
|
+
target: parsedDecision.overrideTarget,
|
|
122
|
+
scope: parsedDecision.overrideScope
|
|
123
|
+
}, {
|
|
124
|
+
...writeOptions,
|
|
125
|
+
effectExecutor: (capability) => {
|
|
126
|
+
const retried = applyHumanDecision(request, { ...writeOptions, manualOverride: capability });
|
|
127
|
+
current = retried;
|
|
128
|
+
return retried.status === 'failed' || retried.status === 'planned'
|
|
129
|
+
? { code: 'OVERRIDE_EFFECT_FAILED', message: retried.status === 'planned' ? 'producer returned planned; no decision effect was committed' : `${retried.error?.code ?? 'DECISION_FAILED'}: ${retried.error?.message ?? 'manual decision effect failed'}` }
|
|
130
|
+
: null;
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
if (consumed.status === 'failed')
|
|
134
|
+
throw new Error(consumed.error.message);
|
|
135
|
+
return current;
|
|
136
|
+
};
|
|
137
|
+
result = resolved?.ok
|
|
138
|
+
? withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-decision', execute)
|
|
139
|
+
: execute();
|
|
89
140
|
if (result.error)
|
|
90
141
|
throw new Error(result.error.message);
|
|
91
142
|
return 0;
|
|
92
143
|
}
|
|
93
144
|
catch (error) {
|
|
94
|
-
|
|
145
|
+
const message = error instanceof TaskExecutionLockError
|
|
146
|
+
? `${error.code}: ${error.message}`
|
|
147
|
+
: error instanceof Error ? error.message : String(error);
|
|
148
|
+
process.stderr.write(`Error: ${message}\n`);
|
|
95
149
|
return 1;
|
|
96
150
|
}
|
|
97
151
|
}
|
package/dist/lib/init.js
CHANGED
|
@@ -180,7 +180,7 @@ async function cmdInit() {
|
|
|
180
180
|
agentClients: serializeAgentClients(defaultState)
|
|
181
181
|
};
|
|
182
182
|
if (sandboxEngine)
|
|
183
|
-
config.sandbox.engine = sandboxEngine;
|
|
183
|
+
config.sandbox.engine = { [currentPlatform]: sandboxEngine };
|
|
184
184
|
if (templateSources.length > 0)
|
|
185
185
|
config.templates = { sources: templateSources };
|
|
186
186
|
if (skillSources.length > 0)
|
|
@@ -21,9 +21,7 @@ const DEMO_INPUT_PATHS = [
|
|
|
21
21
|
'lib/paths.ts',
|
|
22
22
|
'lib/render.ts',
|
|
23
23
|
'lib/builtin-tuis.ts',
|
|
24
|
-
'lib/sandbox/engines/'
|
|
25
|
-
'src/sync-templates.js',
|
|
26
|
-
'templates/'
|
|
24
|
+
'lib/sandbox/engines/'
|
|
27
25
|
];
|
|
28
26
|
const DEMO_DIGEST_PATH = 'assets/demo-init.inputs.sha256';
|
|
29
27
|
const DEMO_OUTPUT_PATH = 'assets/demo-init.gif';
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { applyPrReviewActivityIntent, inspectPrReviewActivity } from '../task/activity-intent.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-activity <task-ref> pr-review-inspect
|
|
3
6
|
agent-infra-internal task-activity <task-ref> pr-review-start --agent <agent> --artifact <canonical.md> --head <40hex> [--dry-run]
|
|
4
7
|
agent-infra-internal task-activity <task-ref> pr-review-complete --agent <agent> --artifact <canonical.md> --head <40hex> --verdict <approved|changes-requested|commented> --blockers <N> --major <N> --minor <N> [--dry-run]
|
|
@@ -14,14 +17,15 @@ const FLAGS = {
|
|
|
14
17
|
'--major': 'major',
|
|
15
18
|
'--minor': 'minor',
|
|
16
19
|
'--outcome': 'outcome',
|
|
17
|
-
'--reason': 'reason'
|
|
20
|
+
'--reason': 'reason',
|
|
21
|
+
'--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
|
|
18
22
|
};
|
|
19
23
|
const COMMON = ['agent', 'artifact', 'head'];
|
|
20
24
|
const ALLOWED = {
|
|
21
25
|
'pr-review-inspect': new Set(['kind', 'taskRef']),
|
|
22
|
-
'pr-review-start': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'dryRun']),
|
|
23
|
-
'pr-review-complete': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'verdict', 'blockers', 'major', 'minor', 'dryRun']),
|
|
24
|
-
'pr-review-terminate': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'outcome', 'reason', 'dryRun'])
|
|
26
|
+
'pr-review-start': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'dryRun', 'overrideTicket', 'overrideTarget', 'overrideScope']),
|
|
27
|
+
'pr-review-complete': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'verdict', 'blockers', 'major', 'minor', 'dryRun', 'overrideTicket', 'overrideTarget', 'overrideScope']),
|
|
28
|
+
'pr-review-terminate': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'outcome', 'reason', 'dryRun', 'overrideTicket', 'overrideTarget', 'overrideScope'])
|
|
25
29
|
};
|
|
26
30
|
function usageFailure(message) {
|
|
27
31
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'ACTIVITY_PAYLOAD_INVALID', message } })}\n`);
|
|
@@ -96,9 +100,55 @@ function taskActivity(args = []) {
|
|
|
96
100
|
return;
|
|
97
101
|
}
|
|
98
102
|
}
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
103
|
+
const dryRunConflict = overrideDryRunConflict(values);
|
|
104
|
+
if (dryRunConflict) {
|
|
105
|
+
usageFailure(dryRunConflict.message);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const resolved = resolveTaskRef(taskRef);
|
|
109
|
+
if (!resolved.ok) {
|
|
110
|
+
usageFailure(resolved.message);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
let result;
|
|
114
|
+
let humanOverride = null;
|
|
115
|
+
try {
|
|
116
|
+
result = withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-activity.${kind}`, () => {
|
|
117
|
+
let current = applyPrReviewActivityIntent(values, { lockAlreadyHeld: true });
|
|
118
|
+
if (current.status !== 'failed' || !values.overrideTicket)
|
|
119
|
+
return current;
|
|
120
|
+
if (!values.overrideTarget || !values.overrideScope) {
|
|
121
|
+
usageFailure('override ticket requires target and scope');
|
|
122
|
+
return current;
|
|
123
|
+
}
|
|
124
|
+
const consumed = consumeHumanOverride({
|
|
125
|
+
taskRef, ticketId: String(values.overrideTicket),
|
|
126
|
+
failureId: failureId('activity-intent', current.error?.code ?? 'TASK_STATE_MISMATCH'),
|
|
127
|
+
target: String(values.overrideTarget), scope: String(values.overrideScope)
|
|
128
|
+
}, {
|
|
129
|
+
effectExecutor: (capability) => {
|
|
130
|
+
const retried = applyPrReviewActivityIntent(values, { lockAlreadyHeld: true, manualOverride: capability });
|
|
131
|
+
current = retried;
|
|
132
|
+
return retried.status === 'failed' || retried.status === 'planned'
|
|
133
|
+
? { code: 'OVERRIDE_EFFECT_FAILED', message: retried.status === 'planned' ? 'producer returned planned; no activity effect was committed' : `${retried.error?.code ?? 'ACTIVITY_FAILED'}: ${retried.error?.message ?? 'manual activity effect failed'}` }
|
|
134
|
+
: null;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
humanOverride = consumed;
|
|
138
|
+
return current;
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
if (!(error instanceof TaskExecutionLockError))
|
|
143
|
+
throw error;
|
|
144
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: error.code, message: error.message } })}\n`);
|
|
145
|
+
process.exitCode = 1;
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
process.stdout.write(`${JSON.stringify(humanOverride ? { ...result, humanOverride } : result)}\n`);
|
|
149
|
+
const overrideFailed = Boolean(humanOverride && typeof humanOverride === 'object' &&
|
|
150
|
+
humanOverride.status === 'failed');
|
|
151
|
+
if (result.status === 'failed' || overrideFailed)
|
|
102
152
|
process.exitCode = 1;
|
|
103
153
|
}
|
|
104
154
|
export { taskActivity };
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { normalizeAgentToken, AGENT_USAGE_HINT } from '../agent-clients/tokens.js';
|
|
2
2
|
import { applyTaskEvent, eventCatalog } from '../task/events.js';
|
|
3
|
+
import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.js';
|
|
4
|
+
import { resolveTaskRef } from '../task/resolve-ref.js';
|
|
5
|
+
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
|
|
3
6
|
const USAGE = `Usage: agent-infra-internal task-event <N | TASK-id> <event> --agent <agent> [event options] [--orchestrated] [--dry-run]
|
|
4
7
|
|
|
5
8
|
Apply one closed-set task lifecycle event and print a structured JSON result.
|
|
@@ -10,7 +13,8 @@ const FLAGS = {
|
|
|
10
13
|
'--fix-for': 'fixFor', '--implementation-input': 'implementationInput',
|
|
11
14
|
'--verdict': 'verdict', '--blockers': 'blockers', '--major': 'major',
|
|
12
15
|
'--minor': 'minor', '--manual-validation': 'manualValidation', '--files-modified': 'filesModified',
|
|
13
|
-
'--tests-passed': 'testsPassed', '--summary-result': 'summaryResult'
|
|
16
|
+
'--tests-passed': 'testsPassed', '--summary-result': 'summaryResult',
|
|
17
|
+
'--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
|
|
14
18
|
};
|
|
15
19
|
const NUMERIC = new Set(['round', 'question', 'blockers', 'major', 'minor', 'manualValidation', 'filesModified', 'testsPassed']);
|
|
16
20
|
function usageFailure(message) {
|
|
@@ -77,9 +81,58 @@ function taskEvent(args = []) {
|
|
|
77
81
|
return;
|
|
78
82
|
}
|
|
79
83
|
request.agent = agent;
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
84
|
+
const dryRunConflict = overrideDryRunConflict(request);
|
|
85
|
+
if (dryRunConflict) {
|
|
86
|
+
usageFailure(dryRunConflict.message);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const resolved = resolveTaskRef(request.taskRef);
|
|
90
|
+
if (!resolved.ok) {
|
|
91
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: resolved.code, message: resolved.message } })}\n`);
|
|
92
|
+
process.exitCode = 1;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
let result;
|
|
96
|
+
let humanOverride = null;
|
|
97
|
+
try {
|
|
98
|
+
result = withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-event.${request.event}`, () => {
|
|
99
|
+
let current = applyTaskEvent(request, { lockAlreadyHeld: true });
|
|
100
|
+
const values = request;
|
|
101
|
+
if (current.status !== 'failed' || !values.overrideTicket)
|
|
102
|
+
return current;
|
|
103
|
+
if (!values.overrideTarget || !values.overrideScope) {
|
|
104
|
+
return { ...current, humanOverride: { status: 'failed', error: { code: 'OVERRIDE_PAYLOAD_INVALID', message: 'override ticket requires target and scope' } } };
|
|
105
|
+
}
|
|
106
|
+
const consumed = consumeHumanOverride({
|
|
107
|
+
taskRef: request.taskRef,
|
|
108
|
+
ticketId: String(values.overrideTicket),
|
|
109
|
+
failureId: failureId('task-event', current.error?.code ?? 'EVENT_TRANSITION_INVALID'),
|
|
110
|
+
target: String(values.overrideTarget),
|
|
111
|
+
scope: String(values.overrideScope)
|
|
112
|
+
}, {
|
|
113
|
+
effectExecutor: (capability) => {
|
|
114
|
+
const retried = applyTaskEvent(request, { lockAlreadyHeld: true, manualOverride: capability });
|
|
115
|
+
current = retried;
|
|
116
|
+
return retried.status === 'failed' || retried.status === 'planned'
|
|
117
|
+
? { code: 'OVERRIDE_EFFECT_FAILED', message: retried.status === 'planned' ? 'producer returned planned; no task event was committed' : `${retried.error?.code ?? 'EVENT_FAILED'}: ${retried.error?.message ?? 'manual task event effect failed'}` }
|
|
118
|
+
: null;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
humanOverride = consumed;
|
|
122
|
+
return current;
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (!(error instanceof TaskExecutionLockError))
|
|
127
|
+
throw error;
|
|
128
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: error.code, message: error.message } })}\n`);
|
|
129
|
+
process.exitCode = 1;
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
process.stdout.write(`${JSON.stringify(humanOverride ? { ...result, humanOverride } : result)}\n`);
|
|
133
|
+
const overrideFailed = Boolean(humanOverride && typeof humanOverride === 'object' &&
|
|
134
|
+
humanOverride.status === 'failed');
|
|
135
|
+
if (result.status === 'failed' || overrideFailed)
|
|
83
136
|
process.exitCode = 1;
|
|
84
137
|
}
|
|
85
138
|
export { taskEvent };
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import { applyLedgerIntent } from '../task/ledger-intents.js';
|
|
3
|
+
import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.js';
|
|
3
4
|
import { isReviewStage, parseLedger, summarizeLedgerStage, validateLedgerRows } from '../task/ledger.js';
|
|
4
5
|
import { resolveTaskRef } from '../task/resolve-ref.js';
|
|
6
|
+
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
|
|
5
7
|
const USAGE = `Usage: agent-infra-internal task-ledger <task-ref> <intent> [intent flags] [--dry-run]\n\nIntents: finding-upsert, finding-respond, finding-review, decision-next-id, decision-upsert, stage-status\n`;
|
|
6
8
|
const FLAGS = {
|
|
7
9
|
'--stage': 'stage', '--review-artifact': 'reviewArtifact', '--ordinal': 'ordinal',
|
|
8
10
|
'--severity': 'severity', '--evidence': 'evidence', '--id': 'id', '--round': 'round',
|
|
9
|
-
'--status': 'status', '--artifact': 'artifact', '--needs-implementation': 'needsImplementation'
|
|
11
|
+
'--status': 'status', '--artifact': 'artifact', '--needs-implementation': 'needsImplementation',
|
|
12
|
+
'--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
|
|
10
13
|
};
|
|
11
14
|
const NUMERIC = new Set(['ordinal', 'round']);
|
|
12
15
|
const BOOLEAN = new Set(['needsImplementation']);
|
|
@@ -75,7 +78,10 @@ function taskLedger(args = []) {
|
|
|
75
78
|
'finding-review': ['needsImplementation'],
|
|
76
79
|
'decision-upsert': ['needsImplementation']
|
|
77
80
|
};
|
|
78
|
-
const
|
|
81
|
+
const overrideFields = kind === 'stage-status' || kind === 'decision-next-id'
|
|
82
|
+
? []
|
|
83
|
+
: ['overrideTicket', 'overrideTarget', 'overrideScope'];
|
|
84
|
+
const allowed = new Set(['kind', 'taskRef', 'dryRun', ...required[kind], ...(optional[kind] ?? []), ...overrideFields]);
|
|
79
85
|
const unexpected = Object.keys(values).find((key) => !allowed.has(key));
|
|
80
86
|
const missing = required[kind].find((key) => values[key] === undefined);
|
|
81
87
|
if (unexpected) {
|
|
@@ -86,6 +92,11 @@ function taskLedger(args = []) {
|
|
|
86
92
|
usageFailure(`${kind} requires '${missing}'`);
|
|
87
93
|
return;
|
|
88
94
|
}
|
|
95
|
+
const dryRunConflict = overrideDryRunConflict(values);
|
|
96
|
+
if (dryRunConflict) {
|
|
97
|
+
usageFailure(dryRunConflict.message);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
89
100
|
if (kind === 'stage-status') {
|
|
90
101
|
if (!isReviewStage(String(values.stage))) {
|
|
91
102
|
usageFailure(`stage-status requires a valid review stage`);
|
|
@@ -114,9 +125,52 @@ function taskLedger(args = []) {
|
|
|
114
125
|
}
|
|
115
126
|
return;
|
|
116
127
|
}
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
128
|
+
const lockResolved = resolveTaskRef(taskRef);
|
|
129
|
+
if (!lockResolved.ok) {
|
|
130
|
+
usageFailure(lockResolved.message);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
let result;
|
|
134
|
+
let humanOverride = null;
|
|
135
|
+
try {
|
|
136
|
+
result = withTaskExecutionLock(lockResolved.repoRoot, lockResolved.taskId, `task-ledger.${kind}`, () => {
|
|
137
|
+
let current = applyLedgerIntent(values);
|
|
138
|
+
if (current.status !== 'failed' || !values.overrideTicket)
|
|
139
|
+
return current;
|
|
140
|
+
if (!values.overrideTarget || !values.overrideScope) {
|
|
141
|
+
usageFailure('override ticket requires target and scope');
|
|
142
|
+
return current;
|
|
143
|
+
}
|
|
144
|
+
const consumed = consumeHumanOverride({
|
|
145
|
+
taskRef,
|
|
146
|
+
ticketId: String(values.overrideTicket),
|
|
147
|
+
failureId: failureId('ledger-intent', current.error?.code ?? 'TASK_STATE_MISMATCH'),
|
|
148
|
+
target: String(values.overrideTarget),
|
|
149
|
+
scope: String(values.overrideScope)
|
|
150
|
+
}, {
|
|
151
|
+
effectExecutor: (capability) => {
|
|
152
|
+
const retried = applyLedgerIntent(values, { manualOverride: capability });
|
|
153
|
+
current = retried;
|
|
154
|
+
return retried.status === 'failed'
|
|
155
|
+
? { code: 'OVERRIDE_EFFECT_FAILED', message: `${retried.error?.code ?? 'LEDGER_FAILED'}: ${retried.error?.message ?? 'manual ledger effect failed'}` }
|
|
156
|
+
: null;
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
humanOverride = consumed;
|
|
160
|
+
return current;
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
if (!(error instanceof TaskExecutionLockError))
|
|
165
|
+
throw error;
|
|
166
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: error.code, message: error.message } })}\n`);
|
|
167
|
+
process.exitCode = 1;
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
process.stdout.write(`${JSON.stringify(humanOverride ? { ...result, humanOverride } : result)}\n`);
|
|
171
|
+
const overrideFailed = Boolean(humanOverride && typeof humanOverride === 'object' &&
|
|
172
|
+
humanOverride.status === 'failed');
|
|
173
|
+
if (result.status === 'failed' || overrideFailed)
|
|
120
174
|
process.exitCode = 1;
|
|
121
175
|
}
|
|
122
176
|
export { taskLedger };
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { normalizeAgentToken, AGENT_USAGE_HINT } from '../agent-clients/tokens.js';
|
|
2
|
+
import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.js';
|
|
2
3
|
import { applyTaskLifecycle, lifecycleIntentCatalog } from '../task/lifecycle.js';
|
|
3
|
-
import { resolveTaskRef } from '../task/resolve-ref.js';
|
|
4
|
+
import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.js';
|
|
4
5
|
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
|
|
5
|
-
const USAGE = `Usage: agent-infra-internal task-lifecycle <N | TASK-id> <intent> --agent <agent> [intent flags] [--dry-run]\n\nIntents: ${lifecycleIntentCatalog.join(', ')}\n`;
|
|
6
|
+
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`;
|
|
6
7
|
const FLAGS = {
|
|
7
8
|
'--agent': 'agent', '--reason': 'reason', '--unblock-condition': 'unblockCondition',
|
|
8
9
|
'--note': 'note', '--alert-number': 'alertNumber', '--staging-dir': 'stagingDir',
|
|
9
|
-
'--issue-number': 'issueNumber'
|
|
10
|
+
'--issue-number': 'issueNumber', '--override-ticket': 'overrideTicket',
|
|
11
|
+
'--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
|
|
10
12
|
};
|
|
11
13
|
function usageFailure(message) {
|
|
12
14
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'LIFECYCLE_PAYLOAD_INVALID', message } })}\n`);
|
|
@@ -58,15 +60,64 @@ function taskLifecycle(args = []) {
|
|
|
58
60
|
return;
|
|
59
61
|
}
|
|
60
62
|
values.agent = agent;
|
|
61
|
-
const
|
|
62
|
-
if (
|
|
63
|
-
|
|
64
|
-
process.exitCode = 1;
|
|
63
|
+
const dryRunConflict = overrideDryRunConflict(values);
|
|
64
|
+
if (dryRunConflict) {
|
|
65
|
+
usageFailure(dryRunConflict.message);
|
|
65
66
|
return;
|
|
66
67
|
}
|
|
68
|
+
let repoRoot;
|
|
69
|
+
let taskId;
|
|
70
|
+
if (values.intent === 'restore') {
|
|
71
|
+
// restore's precondition is that the task does NOT exist locally yet (it only lives
|
|
72
|
+
// in a staging dir), so resolveTaskRef's existence check is inapplicable here; identity
|
|
73
|
+
// is guaranteed by applyTaskLifecycle's own full-TASK-id check plus staging validation.
|
|
74
|
+
try {
|
|
75
|
+
repoRoot = detectRepoRoot();
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'REPO_ROOT_NOT_FOUND', message: error instanceof Error ? error.message : String(error) } })}\n`);
|
|
79
|
+
process.exitCode = 1;
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
taskId = String(values.taskRef);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
const resolved = resolveTaskRef(String(values.taskRef));
|
|
86
|
+
if (!resolved.ok) {
|
|
87
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: resolved.code, message: resolved.message } })}\n`);
|
|
88
|
+
process.exitCode = 1;
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
repoRoot = resolved.repoRoot;
|
|
92
|
+
taskId = resolved.taskId;
|
|
93
|
+
}
|
|
67
94
|
let result;
|
|
68
95
|
try {
|
|
69
|
-
result = withTaskExecutionLock(
|
|
96
|
+
result = withTaskExecutionLock(repoRoot, taskId, `task-lifecycle.${String(values.intent)}`, () => {
|
|
97
|
+
const lifecycleResult = applyTaskLifecycle(values);
|
|
98
|
+
if (lifecycleResult.status !== 'failed' || !values.overrideTicket)
|
|
99
|
+
return lifecycleResult;
|
|
100
|
+
if (!values.overrideTarget || !values.overrideScope) {
|
|
101
|
+
return {
|
|
102
|
+
...lifecycleResult,
|
|
103
|
+
error: { code: 'OVERRIDE_PAYLOAD_INVALID', message: 'override ticket requires --override-target and --override-scope' }
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const override = consumeHumanOverride({
|
|
107
|
+
taskRef: String(values.taskRef),
|
|
108
|
+
ticketId: String(values.overrideTicket),
|
|
109
|
+
failureId: failureId('lifecycle.apply', lifecycleResult.error?.code ?? 'LIFECYCLE_FAILED'),
|
|
110
|
+
target: String(values.overrideTarget),
|
|
111
|
+
scope: String(values.overrideScope),
|
|
112
|
+
intent: String(values.intent),
|
|
113
|
+
...(values.alertNumber ? { alertNumber: Number(values.alertNumber) } : {}),
|
|
114
|
+
...(values.issueNumber ? { issueNumber: Number(values.issueNumber) } : {}),
|
|
115
|
+
...(values.stagingDir ? { stagingDir: String(values.stagingDir) } : {})
|
|
116
|
+
});
|
|
117
|
+
if (override.status === 'failed')
|
|
118
|
+
return { ...lifecycleResult, humanOverride: override };
|
|
119
|
+
return { ...lifecycleResult, ...override, humanOverride: override, error: null };
|
|
120
|
+
});
|
|
70
121
|
}
|
|
71
122
|
catch (error) {
|
|
72
123
|
if (!(error instanceof TaskExecutionLockError))
|