@fitlab-ai/agent-infra 0.9.7 → 0.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/bin/internal-cli.js +11 -1
- package/dist/lib/agent-clients/adapters/claude-code.js +3 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +60 -22
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +8 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +175 -11
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-context.js +147 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +14 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +86 -69
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +2 -1
- package/dist/lib/decide.js +68 -14
- package/dist/lib/git/workflow.js +10 -1
- package/dist/lib/init.js +1 -1
- package/dist/lib/internal/codex-lifecycle.js +19 -5
- package/dist/lib/internal/codex-sandbox-controller.js +6 -13
- package/dist/lib/internal/git-workflow.js +5 -7
- package/dist/lib/internal/platform-pr.js +20 -7
- package/dist/lib/internal/release-workflow.js +3 -4
- package/dist/lib/internal/sandbox-control.js +79 -2
- package/dist/lib/internal/task-activity.js +57 -7
- package/dist/lib/internal/task-event.js +57 -4
- package/dist/lib/internal/task-finalization.js +54 -0
- package/dist/lib/internal/task-ledger.js +59 -5
- package/dist/lib/internal/task-lifecycle.js +59 -8
- package/dist/lib/internal/task-orchestration.js +161 -288
- package/dist/lib/internal/task-override.js +131 -0
- package/dist/lib/internal/task-review.js +64 -6
- package/dist/lib/internal/task-verify.js +65 -3
- package/dist/lib/internal/task-warning.js +57 -4
- package/dist/lib/platform/pr-summary.js +53 -13
- package/dist/lib/platform/pull-requests.js +205 -41
- package/dist/lib/runtime/agent-runtime.js +56 -0
- package/dist/lib/sandbox/commands/create.js +389 -311
- package/dist/lib/sandbox/commands/rm.js +316 -55
- package/dist/lib/sandbox/control/client.js +190 -6
- package/dist/lib/sandbox/control/container-identity.js +1 -1
- package/dist/lib/sandbox/control/controller-registration.js +273 -0
- package/dist/lib/sandbox/control/executor.js +145 -5
- package/dist/lib/sandbox/control/lifecycle.js +395 -73
- package/dist/lib/sandbox/control/protocol.js +67 -6
- package/dist/lib/sandbox/control/state.js +27 -11
- package/dist/lib/sandbox/engine.js +14 -7
- package/dist/lib/sandbox/index.js +5 -3
- package/dist/lib/sandbox/managed-fs.js +5 -0
- package/dist/lib/sandbox/mounts.js +20 -5
- package/dist/lib/sandbox/recovery.js +78 -10
- package/dist/lib/sandbox/task-resolver.js +50 -6
- package/dist/lib/sandbox/workspace-identity.js +27 -0
- package/dist/lib/sandbox/workspace-view.js +62 -40
- package/dist/lib/sandbox/worktree-safety.js +215 -2
- package/dist/lib/server/process-state.js +51 -1
- package/dist/lib/task/activity-intent.js +4 -1
- package/dist/lib/task/codex-orchestration.js +158 -33
- package/dist/lib/task/commands/status.js +59 -1
- package/dist/lib/task/commit-operation.js +335 -0
- package/dist/lib/task/commit-policy.js +18 -0
- package/dist/lib/task/decision-intents.js +4 -2
- package/dist/lib/task/delegation-receipts.js +279 -17
- package/dist/lib/task/events.js +9 -5
- package/dist/lib/task/finalization.js +567 -0
- package/dist/lib/task/guard-override.js +31 -0
- package/dist/lib/task/human-override.js +1240 -0
- package/dist/lib/task/ledger-intents.js +6 -3
- package/dist/lib/task/lifecycle.js +69 -14
- package/dist/lib/task/operation-outcome.js +33 -0
- package/dist/lib/task/orchestration.js +199 -719
- package/dist/lib/task/review-finalization.js +3 -2
- package/dist/lib/task/short-id.js +59 -1
- package/dist/lib/task/task-execution-lock.js +6 -1
- package/dist/lib/task/verification-engine.js +138 -59
- package/dist/lib/task/verification.js +15 -2
- package/dist/lib/task/workflow-warning-intents.js +54 -3
- package/dist/lib/task/write.js +2 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/claude-code.ts +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +63 -20
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +12 -0
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +278 -13
- package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +180 -0
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +19 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +9 -1
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +112 -93
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +2 -1
- package/lib/decide.ts +59 -14
- package/lib/git/workflow.ts +11 -2
- package/lib/init.ts +1 -1
- package/lib/internal/codex-lifecycle.ts +18 -5
- package/lib/internal/codex-sandbox-controller.ts +6 -12
- package/lib/internal/git-workflow.ts +5 -6
- package/lib/internal/platform-pr.ts +14 -7
- package/lib/internal/release-workflow.ts +3 -4
- package/lib/internal/sandbox-control.ts +92 -2
- package/lib/internal/task-activity.ts +46 -6
- package/lib/internal/task-event.ts +53 -4
- package/lib/internal/task-finalization.ts +68 -0
- package/lib/internal/task-ledger.ts +48 -4
- package/lib/internal/task-lifecycle.ts +56 -12
- package/lib/internal/task-orchestration.ts +32 -107
- package/lib/internal/task-override.ts +138 -0
- package/lib/internal/task-review.ts +51 -6
- package/lib/internal/task-verify.ts +52 -3
- package/lib/internal/task-warning.ts +44 -3
- package/lib/platform/pr-summary.ts +65 -14
- package/lib/platform/pull-requests.ts +211 -41
- package/lib/runtime/agent-runtime.ts +76 -0
- package/lib/sandbox/commands/create.ts +88 -4
- package/lib/sandbox/commands/rm.ts +413 -61
- package/lib/sandbox/config.ts +2 -1
- package/lib/sandbox/control/client.ts +255 -6
- package/lib/sandbox/control/container-identity.ts +3 -3
- package/lib/sandbox/control/controller-registration.ts +347 -0
- package/lib/sandbox/control/executor.ts +172 -6
- package/lib/sandbox/control/lifecycle.ts +465 -77
- package/lib/sandbox/control/protocol.ts +85 -9
- package/lib/sandbox/control/state.ts +35 -18
- package/lib/sandbox/engine.ts +20 -9
- package/lib/sandbox/index.ts +5 -3
- package/lib/sandbox/managed-fs.ts +5 -0
- package/lib/sandbox/mounts.ts +23 -5
- package/lib/sandbox/recovery.ts +90 -9
- package/lib/sandbox/task-resolver.ts +58 -7
- package/lib/sandbox/workspace-identity.ts +50 -2
- package/lib/sandbox/workspace-view.ts +73 -43
- package/lib/sandbox/worktree-safety.ts +236 -2
- package/lib/server/process-state.ts +48 -1
- package/lib/task/activity-intent.ts +4 -2
- package/lib/task/codex-orchestration.ts +167 -33
- package/lib/task/commands/status.ts +78 -1
- package/lib/task/commit-operation.ts +430 -0
- package/lib/task/commit-policy.ts +30 -0
- package/lib/task/decision-intents.ts +4 -2
- package/lib/task/delegation-receipts.ts +304 -19
- package/lib/task/events.ts +10 -5
- package/lib/task/finalization.ts +719 -0
- package/lib/task/guard-override.ts +50 -0
- package/lib/task/human-override.ts +1503 -0
- package/lib/task/ledger-intents.ts +6 -3
- package/lib/task/lifecycle.ts +71 -14
- package/lib/task/operation-outcome.ts +48 -0
- package/lib/task/orchestration.ts +239 -922
- package/lib/task/review-finalization.ts +6 -2
- package/lib/task/short-id.ts +65 -1
- package/lib/task/task-execution-lock.ts +10 -1
- package/lib/task/verification-engine.ts +145 -64
- package/lib/task/verification.ts +18 -3
- package/lib/task/workflow-warning-intents.ts +61 -4
- package/lib/task/write.ts +4 -1
- package/package.json +2 -2
- package/templates/.agents/QUICKSTART.en.md +2 -2
- package/templates/.agents/QUICKSTART.zh-CN.md +2 -2
- package/templates/.agents/hooks/lifecycle-delegation.js +9 -24
- package/templates/.agents/rules/lifecycle-orchestration.en.md +4 -3
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +4 -3
- package/templates/.agents/rules/task-management.en.md +2 -2
- package/templates/.agents/rules/task-management.zh-CN.md +2 -2
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -4
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -4
- package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/close-codescan/SKILL.en.md +6 -2
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +6 -2
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/commit/SKILL.en.md +41 -125
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +52 -123
- package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -1
- package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +14 -43
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +14 -49
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +2 -35
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +2 -35
- package/templates/.agents/skills/complete-task/SKILL.en.md +31 -38
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +31 -38
- package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +6 -4
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +6 -4
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +2 -2
- package/templates/.agents/skills/run-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +3 -1
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +3 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +1 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +1 -1
- package/templates/.codex/hooks.json +4 -4
- package/dist/lib/task/commit-finalization.js +0 -305
- package/dist/lib/task/commit-intent.js +0 -165
- package/lib/task/commit-finalization.ts +0 -419
- package/lib/task/commit-intent.ts +0 -230
package/lib/decide.ts
CHANGED
|
@@ -2,6 +2,9 @@ import { VERSION } from './version.ts';
|
|
|
2
2
|
import { parseTaskScope } from './task/command-options.ts';
|
|
3
3
|
import { applyHumanDecision } from './task/decision-intents.ts';
|
|
4
4
|
import { canonicalTimestamp } from './task/write.ts';
|
|
5
|
+
import { consumeHumanOverride, failureId } from './task/human-override.ts';
|
|
6
|
+
import { resolveTaskRef } from './task/resolve-ref.ts';
|
|
7
|
+
import { TaskExecutionLockError, withTaskExecutionLock } from './task/task-execution-lock.ts';
|
|
5
8
|
|
|
6
9
|
type DecideOptions = {
|
|
7
10
|
repoRoot?: string;
|
|
@@ -9,23 +12,33 @@ type DecideOptions = {
|
|
|
9
12
|
version?: string;
|
|
10
13
|
};
|
|
11
14
|
|
|
12
|
-
function parseDecisionParts(parts: string[]): { decision: string; needsImplementation: boolean | undefined } {
|
|
15
|
+
function parseDecisionParts(parts: string[]): { decision: string; needsImplementation: boolean | undefined; overrideTicket?: string; overrideTarget?: string; overrideScope?: string } {
|
|
13
16
|
const decision: string[] = [];
|
|
14
17
|
let needsImplementation: boolean | undefined;
|
|
18
|
+
let overrideTicket: string | undefined;
|
|
19
|
+
let overrideTarget: string | undefined;
|
|
20
|
+
let overrideScope: string | undefined;
|
|
15
21
|
for (let index = 0; index < parts.length; index += 1) {
|
|
16
|
-
|
|
22
|
+
const part = parts[index];
|
|
23
|
+
if (part === '--needs-implementation') {
|
|
24
|
+
if (needsImplementation !== undefined) throw new Error("duplicate option '--needs-implementation'");
|
|
25
|
+
const value = parts[++index];
|
|
26
|
+
if (value !== 'true' && value !== 'false') {
|
|
27
|
+
throw new Error("--needs-implementation must be 'true' or 'false'");
|
|
28
|
+
}
|
|
29
|
+
needsImplementation = value === 'true';
|
|
30
|
+
} else if (part === '--override-ticket' || part === '--override-target' || part === '--override-scope') {
|
|
31
|
+
const value = parts[++index];
|
|
32
|
+
if (!value) throw new Error(`${part} requires a value`);
|
|
33
|
+
if (part === '--override-ticket') overrideTicket = value;
|
|
34
|
+
else if (part === '--override-target') overrideTarget = value;
|
|
35
|
+
else overrideScope = value;
|
|
36
|
+
} else {
|
|
17
37
|
decision.push(parts[index]!);
|
|
18
|
-
continue;
|
|
19
38
|
}
|
|
20
|
-
if (needsImplementation !== undefined) throw new Error("duplicate option '--needs-implementation'");
|
|
21
|
-
const value = parts[++index];
|
|
22
|
-
if (value !== 'true' && value !== 'false') {
|
|
23
|
-
throw new Error("--needs-implementation must be 'true' or 'false'");
|
|
24
|
-
}
|
|
25
|
-
needsImplementation = value === 'true';
|
|
26
39
|
}
|
|
27
40
|
if (decision.length === 0) throw new Error('decision content is required');
|
|
28
|
-
return { decision: decision.join(' '), needsImplementation };
|
|
41
|
+
return { decision: decision.join(' '), needsImplementation, overrideTicket, overrideTarget, overrideScope };
|
|
29
42
|
}
|
|
30
43
|
|
|
31
44
|
export async function decide(args: string[], options: DecideOptions = {}): Promise<number> {
|
|
@@ -64,17 +77,49 @@ export async function decide(args: string[], options: DecideOptions = {}): Promi
|
|
|
64
77
|
try {
|
|
65
78
|
const parsedDecision = parseDecisionParts(decisionParts);
|
|
66
79
|
const now = (options.now ?? canonicalTimestamp)();
|
|
67
|
-
const
|
|
80
|
+
const request = {
|
|
68
81
|
taskRef, selector, decision: parsedDecision.decision,
|
|
69
82
|
needsImplementation: parsedDecision.needsImplementation
|
|
70
|
-
}
|
|
83
|
+
};
|
|
84
|
+
const writeOptions = {
|
|
71
85
|
repoRoot: options.repoRoot,
|
|
72
86
|
metadataProvider: () => ({ timestamp: now, agentInfraVersion: options.version ?? VERSION })
|
|
73
|
-
}
|
|
87
|
+
};
|
|
88
|
+
const resolved = taskRef ? resolveTaskRef(taskRef, { repoRoot: options.repoRoot }) : null;
|
|
89
|
+
let result;
|
|
90
|
+
const execute = () => {
|
|
91
|
+
let current = applyHumanDecision(request, writeOptions);
|
|
92
|
+
if (current.status !== 'failed' || !parsedDecision.overrideTicket) return current;
|
|
93
|
+
if (!parsedDecision.overrideTarget || !parsedDecision.overrideScope) throw new Error('override ticket requires --override-target and --override-scope');
|
|
94
|
+
const consumed = consumeHumanOverride({
|
|
95
|
+
taskRef: resolved?.ok ? resolved.taskId : String(taskRef ?? ''),
|
|
96
|
+
ticketId: parsedDecision.overrideTicket,
|
|
97
|
+
failureId: failureId('decision-intent', current.error?.code ?? 'TASK_STATE_MISMATCH'),
|
|
98
|
+
target: parsedDecision.overrideTarget,
|
|
99
|
+
scope: parsedDecision.overrideScope
|
|
100
|
+
}, {
|
|
101
|
+
...writeOptions,
|
|
102
|
+
effectExecutor: (capability) => {
|
|
103
|
+
const retried = applyHumanDecision(request, { ...writeOptions, 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 decision effect was committed' : `${retried.error?.code ?? 'DECISION_FAILED'}: ${retried.error?.message ?? 'manual decision effect failed'}` }
|
|
107
|
+
: null;
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
if (consumed.status === 'failed') throw new Error(consumed.error.message);
|
|
111
|
+
return current;
|
|
112
|
+
};
|
|
113
|
+
result = resolved?.ok
|
|
114
|
+
? withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-decision', execute)
|
|
115
|
+
: execute();
|
|
74
116
|
if (result.error) throw new Error(result.error.message);
|
|
75
117
|
return 0;
|
|
76
118
|
} catch (error) {
|
|
77
|
-
|
|
119
|
+
const message = error instanceof TaskExecutionLockError
|
|
120
|
+
? `${error.code}: ${error.message}`
|
|
121
|
+
: error instanceof Error ? error.message : String(error);
|
|
122
|
+
process.stderr.write(`Error: ${message}\n`);
|
|
78
123
|
return 1;
|
|
79
124
|
}
|
|
80
125
|
}
|
package/lib/git/workflow.ts
CHANGED
|
@@ -105,7 +105,7 @@ function commitExplicitPaths(input: { cwd: string; paths: readonly string[]; mes
|
|
|
105
105
|
return { status: 'applied' as const, changed: true, snapshot: after.snapshot, operations: [{ name: 'stage', status: 'applied' as const }, { name: 'commit', status: 'applied' as const }], error: null };
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
function pushGitRefs(input: { cwd: string; remote: string; refs: readonly string[] }, runner: GitRunner = defaultRunner) {
|
|
108
|
+
function pushGitRefs(input: { cwd: string; remote: string; refs: readonly string[]; expectedSha?: string }, runner: GitRunner = defaultRunner) {
|
|
109
109
|
if (!input.remote || input.refs.length === 0) return { status: 'failed' as const, changed: false, snapshot: inspectGitWorkflow(input.cwd, runner).snapshot, operations: [], error: { code: 'GIT_PUSH_INPUT_INVALID', message: 'Remote and refs are required' } };
|
|
110
110
|
const operations: GitOperation[] = [];
|
|
111
111
|
for (const ref of input.refs) {
|
|
@@ -120,7 +120,16 @@ function pushGitRefs(input: { cwd: string; remote: string; refs: readonly string
|
|
|
120
120
|
}
|
|
121
121
|
const canonical = ref.startsWith('refs/') ? ref : `refs/heads/${ref}`;
|
|
122
122
|
const remoteFact = run(runner, input.cwd, ['ls-remote', '--exit-code', input.remote, canonical]);
|
|
123
|
-
|
|
123
|
+
const remoteSha = remoteFact.status === 0 ? remoteFact.stdout.trim().split(/\s+/)[0] ?? null : null;
|
|
124
|
+
const verified = remoteFact.status === 0 && (!input.expectedSha || remoteSha === input.expectedSha);
|
|
125
|
+
operations.push({
|
|
126
|
+
name: 'push',
|
|
127
|
+
ref,
|
|
128
|
+
status: verified ? 'applied' : 'failed',
|
|
129
|
+
message: verified ? undefined : input.expectedSha
|
|
130
|
+
? `Remote ref verification failed: expected ${input.expectedSha}, received ${remoteSha ?? 'unavailable'}`
|
|
131
|
+
: 'Remote ref verification failed'
|
|
132
|
+
});
|
|
124
133
|
}
|
|
125
134
|
const failures = operations.filter((item) => item.status === 'failed').length;
|
|
126
135
|
return { status: failures === 0 ? 'applied' as const : failures === operations.length ? 'failed' as const : 'degraded' as const, changed: failures < operations.length, snapshot: inspectGitWorkflow(input.cwd, runner).snapshot, operations, error: failures ? { code: 'GIT_PUSH_PARTIAL', message: `${failures} ref push operation(s) failed` } : null };
|
package/lib/init.ts
CHANGED
|
@@ -249,7 +249,7 @@ async function cmdInit(): Promise<void> {
|
|
|
249
249
|
agentClients: serializeAgentClients(defaultState)
|
|
250
250
|
};
|
|
251
251
|
|
|
252
|
-
if (sandboxEngine) config.sandbox.engine = sandboxEngine;
|
|
252
|
+
if (sandboxEngine) config.sandbox.engine = { [currentPlatform]: sandboxEngine };
|
|
253
253
|
if (templateSources.length > 0) config.templates = { sources: templateSources };
|
|
254
254
|
if (skillSources.length > 0) config.skills = { sources: skillSources };
|
|
255
255
|
|
|
@@ -14,6 +14,7 @@ import { computeLifecycleBuildIdentity } from '../agent-clients/adapters/codex-l
|
|
|
14
14
|
import { verifyCodexSandboxControllerContext } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.ts';
|
|
15
15
|
import type { CodexLifecycleEvent } from '../agent-clients/adapters/codex-lifecycle/evidence.ts';
|
|
16
16
|
import { resolveTaskRef } from '../task/resolve-ref.ts';
|
|
17
|
+
import { hasSealableOrchestrationDelegation } from '../task/orchestration.ts';
|
|
17
18
|
import {
|
|
18
19
|
activateCodexOrchestrationDelegation,
|
|
19
20
|
activateCodexSpawnDelegation,
|
|
@@ -100,7 +101,7 @@ function hookDefinitionHash(): string {
|
|
|
100
101
|
function controllerBinding(taskId: string) {
|
|
101
102
|
const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
|
|
102
103
|
if (!contextPath) return undefined;
|
|
103
|
-
const context = verifyCodexSandboxControllerContext(contextPath,
|
|
104
|
+
const context = verifyCodexSandboxControllerContext(contextPath, { repoRoot: process.cwd() });
|
|
104
105
|
return {
|
|
105
106
|
instanceDigest: context.controllerInstanceDigest,
|
|
106
107
|
controlGeneration: context.controlGeneration
|
|
@@ -139,7 +140,8 @@ function recordFromPayload(phase: string, payload: unknown): CodexLifecycleEvent
|
|
|
139
140
|
sessionId: text('sessionId'),
|
|
140
141
|
turnId: text('turnId'),
|
|
141
142
|
childThreadId: text('childThreadId'),
|
|
142
|
-
nativeAgent: text('nativeAgent')
|
|
143
|
+
nativeAgent: text('nativeAgent'),
|
|
144
|
+
source: 'hook'
|
|
143
145
|
};
|
|
144
146
|
if (phase === 'subagent-stop') return {
|
|
145
147
|
type: 'hook-stop',
|
|
@@ -326,8 +328,17 @@ async function codexLifecycle(args: string[] = []): Promise<void> {
|
|
|
326
328
|
}
|
|
327
329
|
const result = store.apply(event);
|
|
328
330
|
if (parsed.values['--bridge'] === 'true' && event.type === 'hook-stop') {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
+
if (!hasSealableOrchestrationDelegation('codex', event.childThreadId, { repoRoot: process.cwd() })) {
|
|
332
|
+
output({ status: 'ignored', changed: false, evidence: null, diagnostics: [], error: null });
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
output({
|
|
336
|
+
status: result.state.status,
|
|
337
|
+
changed: true,
|
|
338
|
+
evidence: result.state,
|
|
339
|
+
diagnostics: [],
|
|
340
|
+
error: result.state.error
|
|
341
|
+
});
|
|
331
342
|
return;
|
|
332
343
|
}
|
|
333
344
|
output({ status: result.state.status, changed: true, evidence: result.state, diagnostics: [], error: result.state.error });
|
|
@@ -350,7 +361,9 @@ async function codexLifecycle(args: string[] = []): Promise<void> {
|
|
|
350
361
|
return;
|
|
351
362
|
}
|
|
352
363
|
if (parsed.operation === 'resolve-stop') {
|
|
353
|
-
const
|
|
364
|
+
const stopTurnId = store.read(childThreadId).state.stop?.turnId;
|
|
365
|
+
if (!stopTurnId) throw new Error('Codex lifecycle stop hook is not available');
|
|
366
|
+
const terminal = await resolveCodexTerminal(childThreadId, stopTurnId);
|
|
354
367
|
const latest = store.apply(terminal);
|
|
355
368
|
if (latest.state.status !== 'stop-ready') {
|
|
356
369
|
throw new Error(`Codex lifecycle stop evidence is not ready (status=${latest.state.status})`);
|
|
@@ -26,8 +26,6 @@ function parse(args: string[]): Readonly<{ operation: string; values: Readonly<R
|
|
|
26
26
|
return null;
|
|
27
27
|
}
|
|
28
28
|
const allowed = new Set([
|
|
29
|
-
'--task-id',
|
|
30
|
-
'--task-ref',
|
|
31
29
|
'--context',
|
|
32
30
|
'--executor-model',
|
|
33
31
|
'--executor-reasoning-effort',
|
|
@@ -37,6 +35,11 @@ function parse(args: string[]): Readonly<{ operation: string; values: Readonly<R
|
|
|
37
35
|
const values: Record<string, string> = {};
|
|
38
36
|
for (let index = 1; index < args.length; index += 1) {
|
|
39
37
|
const flag = args[index]!;
|
|
38
|
+
if (flag === '--task-id' || flag.startsWith('--task-id=')
|
|
39
|
+
|| flag === '--task-ref' || flag.startsWith('--task-ref=')) {
|
|
40
|
+
fail('CODEX_SANDBOX_CONTROLLER_IDENTITY_OPTION_UNSUPPORTED', 'controller task identity options are not supported', 2);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
40
43
|
const value = args[++index];
|
|
41
44
|
if (!allowed.has(flag) || Object.hasOwn(values, flag) || !value || value.startsWith('--')) {
|
|
42
45
|
fail('CODEX_SANDBOX_CONTROLLER_PAYLOAD_INVALID', `invalid option '${flag}'`, 2);
|
|
@@ -50,17 +53,12 @@ function parse(args: string[]): Readonly<{ operation: string; values: Readonly<R
|
|
|
50
53
|
async function codexSandboxController(args: string[] = []): Promise<void> {
|
|
51
54
|
const parsed = parse(args);
|
|
52
55
|
if (!parsed || process.exitCode) return;
|
|
53
|
-
const taskId = parsed.values['--task-id'];
|
|
54
|
-
if (!taskId) {
|
|
55
|
-
fail('CODEX_SANDBOX_CONTROLLER_PAYLOAD_INVALID', '--task-id is required', 2);
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
56
|
try {
|
|
59
57
|
if (parsed.operation === 'verify-context') {
|
|
60
58
|
const contextPath = parsed.values['--context']
|
|
61
59
|
?? process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
|
|
62
60
|
if (!contextPath) throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_MISSING');
|
|
63
|
-
const context = verifyCodexSandboxControllerContext(contextPath
|
|
61
|
+
const context = verifyCodexSandboxControllerContext(contextPath);
|
|
64
62
|
process.stdout.write(`${JSON.stringify({
|
|
65
63
|
status: 'ready',
|
|
66
64
|
changed: false,
|
|
@@ -77,8 +75,6 @@ async function codexSandboxController(args: string[] = []): Promise<void> {
|
|
|
77
75
|
})}\n`);
|
|
78
76
|
return;
|
|
79
77
|
}
|
|
80
|
-
const taskRef = parsed.values['--task-ref'];
|
|
81
|
-
if (!taskRef) throw new Error('CODEX_SANDBOX_CONTROLLER_TASK_REF_REQUIRED');
|
|
82
78
|
const policy = [
|
|
83
79
|
'--executor-model',
|
|
84
80
|
'--executor-reasoning-effort',
|
|
@@ -90,8 +86,6 @@ async function codexSandboxController(args: string[] = []): Promise<void> {
|
|
|
90
86
|
throw new Error('CODEX_SANDBOX_CONTROLLER_MODEL_POLICY_INVALID');
|
|
91
87
|
}
|
|
92
88
|
const exitCode = await runCodexSandboxController({
|
|
93
|
-
taskId,
|
|
94
|
-
taskRef,
|
|
95
89
|
executorModel: parsed.values['--executor-model'],
|
|
96
90
|
executorReasoningEffort: parsed.values['--executor-reasoning-effort'],
|
|
97
91
|
reviewerModel: parsed.values['--reviewer-model'],
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import {
|
|
2
|
+
import { inspectGitWorkflow, pushRebasedBranch } from '../git/workflow.ts';
|
|
3
3
|
import { compareReviewTrees, snapshotReview } from '../git/review-snapshot.ts';
|
|
4
4
|
import { resolvePostReviewGlobs } from '../task/review-fingerprint.ts';
|
|
5
|
+
import { executeCommitOperation } from '../task/commit-operation.ts';
|
|
5
6
|
|
|
6
7
|
function output(value: unknown): void { process.stdout.write(`${JSON.stringify(value)}\n`); }
|
|
7
8
|
|
|
@@ -18,11 +19,9 @@ function gitWorkflow(args: string[] = []): void {
|
|
|
18
19
|
else if (action === 'commit') {
|
|
19
20
|
const inputIndex = rest.indexOf('--input');
|
|
20
21
|
const input = inputIndex >= 0 && rest[inputIndex + 1] ? JSON.parse(fs.readFileSync(rest[inputIndex + 1]!, 'utf8')) : null;
|
|
21
|
-
result = input
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const input = inputIndex >= 0 && rest[inputIndex + 1] ? JSON.parse(fs.readFileSync(rest[inputIndex + 1]!, 'utf8')) : null;
|
|
25
|
-
result = input ? pushGitRefs({ cwd: cwd ?? process.cwd(), ...input }) : { status: 'failed', changed: false, error: { code: 'GIT_INPUT_REQUIRED', message: '--input JSON file is required' } };
|
|
22
|
+
result = input
|
|
23
|
+
? executeCommitOperation({ cwd: cwd ?? process.cwd(), ...input })
|
|
24
|
+
: { status: 'failed', changed: false, error: { code: 'GIT_INPUT_REQUIRED', message: '--input JSON file is required' } };
|
|
26
25
|
} else if (action === 'push-rebased') {
|
|
27
26
|
const inputIndex = rest.indexOf('--input');
|
|
28
27
|
const input = inputIndex >= 0 && rest[inputIndex + 1] ? JSON.parse(fs.readFileSync(rest[inputIndex + 1]!, 'utf8')) : null;
|
|
@@ -17,13 +17,13 @@ const USAGE = `Usage: agent-infra-internal platform-pr inspect <task-ref> [--cwd
|
|
|
17
17
|
agent-infra-internal platform-pr resolve-external <task-ref> --agent <agent> [--pr <N>] [--dry-run] [--cwd <path>]
|
|
18
18
|
agent-infra-internal platform-pr create <task-ref> --agent <agent> --base <branch> --head <branch> --title-file <path|-> --body-file <path|-> [--draft] [--dry-run] [--cwd <path>]
|
|
19
19
|
agent-infra-internal platform-pr bind <task-ref> --pr <N> --agent <agent> [--dry-run] [--cwd <path>]
|
|
20
|
-
agent-infra-internal platform-pr sync <task-ref> --agent <agent> [--metadata] [--closing-issue] [--dry-run] [--cwd <path>]
|
|
20
|
+
agent-infra-internal platform-pr sync <task-ref> --agent <agent> [--metadata] [--closing-issue] --result <pr_created|pr_reused|no_op> [--dry-run] [--cwd <path>]
|
|
21
21
|
agent-infra-internal platform-pr summary-context <task-ref> [--cwd <path>]
|
|
22
|
-
agent-infra-internal platform-pr summary-sync <task-ref> --agent <agent> --body-file <path|-> [--dry-run] [--cwd <path>]
|
|
22
|
+
agent-infra-internal platform-pr summary-sync <task-ref> --agent <agent> --body-file <path|-> --result <pr_created|pr_reused|no_op> [--dry-run] [--cwd <path>]
|
|
23
23
|
`;
|
|
24
24
|
|
|
25
25
|
const BOOLEAN_FLAGS = new Set(['--draft', '--dry-run', '--metadata', '--closing-issue']);
|
|
26
|
-
const VALUE_FLAGS = new Set(['--cwd', '--agent', '--base', '--head', '--title-file', '--body-file', '--pr']);
|
|
26
|
+
const VALUE_FLAGS = new Set(['--cwd', '--agent', '--base', '--head', '--title-file', '--body-file', '--pr', '--result']);
|
|
27
27
|
|
|
28
28
|
function key(flag: string): string {
|
|
29
29
|
return flag.slice(2).replace(/-([a-z])/g, (_, letter: string) => letter.toUpperCase());
|
|
@@ -76,9 +76,9 @@ function platformPr(args: string[] = []): void {
|
|
|
76
76
|
'resolve-external': ['cwd', 'agent', 'pr', 'dryRun'],
|
|
77
77
|
create: ['cwd', 'agent', 'base', 'head', 'titleFile', 'bodyFile', 'draft', 'dryRun'],
|
|
78
78
|
bind: ['cwd', 'agent', 'pr', 'dryRun'],
|
|
79
|
-
sync: ['cwd', 'agent', 'metadata', 'closingIssue', 'dryRun'],
|
|
79
|
+
sync: ['cwd', 'agent', 'metadata', 'closingIssue', 'result', 'dryRun'],
|
|
80
80
|
'summary-context': ['cwd'],
|
|
81
|
-
'summary-sync': ['cwd', 'agent', 'bodyFile', 'dryRun']
|
|
81
|
+
'summary-sync': ['cwd', 'agent', 'bodyFile', 'result', 'dryRun']
|
|
82
82
|
};
|
|
83
83
|
const unexpected = Object.keys(values).find((name) => !allowed[operation]!.includes(name));
|
|
84
84
|
if (unexpected) { fail(`${operation} does not accept --${unexpected}`); return; }
|
|
@@ -88,6 +88,11 @@ function platformPr(args: string[] = []): void {
|
|
|
88
88
|
const agent = normalizeAgentToken(values.agent);
|
|
89
89
|
if (!agent) { fail(`invalid --agent '${values.agent}': ${AGENT_USAGE_HINT}`); return; }
|
|
90
90
|
values.agent = agent;
|
|
91
|
+
const primaryResult = values.result === undefined ? undefined : values.result;
|
|
92
|
+
if (primaryResult !== undefined && !['pr_created', 'pr_reused', 'no_op'].includes(primaryResult as string)) {
|
|
93
|
+
fail('--result must be pr_created, pr_reused, or no_op');
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
91
96
|
if (operation === 'resolve-external') {
|
|
92
97
|
const pr = values.pr === undefined ? undefined : Number(values.pr);
|
|
93
98
|
if (pr !== undefined && (!Number.isInteger(pr) || pr <= 0)) { fail('resolve-external requires a positive --pr'); return; }
|
|
@@ -102,13 +107,15 @@ function platformPr(args: string[] = []): void {
|
|
|
102
107
|
}
|
|
103
108
|
if (operation === 'sync') {
|
|
104
109
|
if (values.metadata !== true && values.closingIssue !== true) { fail('sync requires --metadata or --closing-issue'); return; }
|
|
105
|
-
|
|
110
|
+
if (!primaryResult) { fail('sync requires --result pr_created, pr_reused, or no_op'); return; }
|
|
111
|
+
finish(syncPlatformPullRequest(taskRef, { cwd, agent: values.agent, metadata: values.metadata === true, closingIssue: values.closingIssue === true, primaryResult: primaryResult as 'pr_created' | 'pr_reused' | 'no_op', dryRun: values.dryRun === true }));
|
|
106
112
|
return;
|
|
107
113
|
}
|
|
108
114
|
if (operation === 'summary-sync') {
|
|
109
115
|
if (typeof values.bodyFile !== 'string') { fail('summary-sync requires --body-file'); return; }
|
|
116
|
+
if (!primaryResult) { fail('summary-sync requires --result pr_created, pr_reused, or no_op'); return; }
|
|
110
117
|
try {
|
|
111
|
-
finish(syncPullRequestSummary(taskRef, { cwd, agent: values.agent, body: readFile(values.bodyFile, cwd), dryRun: values.dryRun === true }));
|
|
118
|
+
finish(syncPullRequestSummary(taskRef, { cwd, agent: values.agent, body: readFile(values.bodyFile, cwd), primaryResult: primaryResult as 'pr_created' | 'pr_reused' | 'no_op', dryRun: values.dryRun === true }));
|
|
112
119
|
} catch (error) {
|
|
113
120
|
fail(`unable to read body file: ${error instanceof Error ? error.message : String(error)}`);
|
|
114
121
|
}
|
|
@@ -25,20 +25,19 @@ type DemoResult = {
|
|
|
25
25
|
outputPath: string | null;
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
+
// The tape is the canonical init interaction contract. Do not hash all of
|
|
29
|
+
// lib/init.ts: non-visual config serialization changes must not re-record it.
|
|
28
30
|
const DEMO_INPUT_PATHS = [
|
|
29
31
|
'assets/demo-init.tape',
|
|
30
32
|
'scripts/demo-regen.sh',
|
|
31
33
|
'scripts/normalize-gif-duration.py',
|
|
32
34
|
'bin/cli.ts',
|
|
33
|
-
'lib/init.ts',
|
|
34
35
|
'lib/log.ts',
|
|
35
36
|
'lib/prompt.ts',
|
|
36
37
|
'lib/paths.ts',
|
|
37
38
|
'lib/render.ts',
|
|
38
39
|
'lib/builtin-tuis.ts',
|
|
39
|
-
'lib/sandbox/engines/'
|
|
40
|
-
'src/sync-templates.js',
|
|
41
|
-
'templates/'
|
|
40
|
+
'lib/sandbox/engines/'
|
|
42
41
|
] as const;
|
|
43
42
|
const DEMO_DIGEST_PATH = 'assets/demo-init.inputs.sha256';
|
|
44
43
|
const DEMO_OUTPUT_PATH = 'assets/demo-init.gif';
|
|
@@ -1,6 +1,84 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
requestSandboxControl,
|
|
3
|
+
requestSandboxTaskFinalization,
|
|
4
|
+
requestSandboxTaskControl,
|
|
5
|
+
SandboxControlClientError
|
|
6
|
+
} from '../sandbox/control/client.ts';
|
|
7
|
+
import { normalizeAgentToken, AGENT_USAGE_HINT } from '../agent-clients/tokens.ts';
|
|
2
8
|
import { serveSandboxControl } from '../sandbox/control/server.ts';
|
|
3
9
|
import { runSandboxControlExecutor } from '../sandbox/control/executor.ts';
|
|
10
|
+
import {
|
|
11
|
+
controllerProofFromContext,
|
|
12
|
+
verifyCodexSandboxControllerContext
|
|
13
|
+
} from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.ts';
|
|
14
|
+
|
|
15
|
+
function isCanonicalCodexPrepare(args: readonly string[]): boolean {
|
|
16
|
+
if (args[1] !== 'prepare') return false;
|
|
17
|
+
const clients: string[] = [];
|
|
18
|
+
for (let index = 2; index < args.length; index += 1) {
|
|
19
|
+
if (args[index] === '--client') clients.push(args[index + 1] ?? '');
|
|
20
|
+
else if (args[index]?.startsWith('--client=')) clients.push(args[index]!.slice('--client='.length));
|
|
21
|
+
}
|
|
22
|
+
return clients.length === 1 && clients[0] === 'codex';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type FinalizationStatus = 'completed' | 'failed' | 'blocked' | 'unknown';
|
|
26
|
+
|
|
27
|
+
function writeFinalizationEnvelope(
|
|
28
|
+
status: FinalizationStatus,
|
|
29
|
+
accepted: boolean,
|
|
30
|
+
error: { code: string; message: string; retryable: boolean },
|
|
31
|
+
changed = false,
|
|
32
|
+
result: unknown = null
|
|
33
|
+
): void {
|
|
34
|
+
process.stdout.write(`${JSON.stringify({ version: 1, status, changed, accepted, result, error })}\n`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function finalizationErrorStatus(error: { retryable: boolean; code: string }): FinalizationStatus {
|
|
38
|
+
if (error.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN') return 'unknown';
|
|
39
|
+
return error.retryable ? 'blocked' : 'failed';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function sandboxFinalizationClient(args: string[]): void {
|
|
43
|
+
if (args.length !== 4 || !args[0] || args[1] !== 'complete' || args[2] !== '--agent' || !args[3]) {
|
|
44
|
+
const error = { code: 'TASK_FINALIZATION_PAYLOAD_INVALID', message: 'task ref, complete intent, and --agent are required', retryable: false };
|
|
45
|
+
writeFinalizationEnvelope('failed', false, error);
|
|
46
|
+
process.stderr.write('Usage: agent-infra-internal task-finalization <N | TASK-id> complete --agent <agent>\n');
|
|
47
|
+
process.exitCode = 1;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const agent = normalizeAgentToken(args[3]);
|
|
51
|
+
if (!agent) {
|
|
52
|
+
const error = { code: 'TASK_FINALIZATION_PAYLOAD_INVALID', message: `invalid --agent: ${AGENT_USAGE_HINT}`, retryable: false };
|
|
53
|
+
writeFinalizationEnvelope('failed', false, error);
|
|
54
|
+
process.exitCode = 1;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
let response;
|
|
58
|
+
try {
|
|
59
|
+
response = requestSandboxTaskFinalization({ agent });
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (!(error instanceof SandboxControlClientError)) throw error;
|
|
62
|
+
writeFinalizationEnvelope(finalizationErrorStatus(error.detail), error.accepted, error.detail);
|
|
63
|
+
process.stderr.write(`${error.detail.message}\n`);
|
|
64
|
+
process.exitCode = error.detail.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN' ? 1 : error.detail.retryable ? 2 : 1;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (response.phase === 'rejected') {
|
|
68
|
+
const error = response.error ?? {
|
|
69
|
+
code: 'SANDBOX_CONTROL_REJECTED',
|
|
70
|
+
message: 'sandbox control rejected the finalization request',
|
|
71
|
+
retryable: false
|
|
72
|
+
};
|
|
73
|
+
writeFinalizationEnvelope(finalizationErrorStatus(error), error.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN', error);
|
|
74
|
+
process.stderr.write(`${error.message}\n`);
|
|
75
|
+
process.exitCode = error.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN' ? 1 : error.retryable ? 2 : 1;
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
process.stdout.write(response.stdout);
|
|
79
|
+
process.stderr.write(response.stderr);
|
|
80
|
+
process.exitCode = response.exitCode ?? 1;
|
|
81
|
+
}
|
|
4
82
|
|
|
5
83
|
async function sandboxControl(args: string[]): Promise<void> {
|
|
6
84
|
const [operation, ...rest] = args;
|
|
@@ -31,9 +109,21 @@ async function sandboxControl(args: string[]): Promise<void> {
|
|
|
31
109
|
}
|
|
32
110
|
if (operation === 'client') {
|
|
33
111
|
const [family = '', ...commandArgs] = rest;
|
|
112
|
+
if (family === 'task-finalization') {
|
|
113
|
+
sandboxFinalizationClient(commandArgs);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
34
116
|
let response;
|
|
35
117
|
try {
|
|
36
|
-
|
|
118
|
+
if (family === 'task-orchestration' && isCanonicalCodexPrepare(commandArgs)) {
|
|
119
|
+
const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
|
|
120
|
+
const proof = contextPath
|
|
121
|
+
? controllerProofFromContext(verifyCodexSandboxControllerContext(contextPath))
|
|
122
|
+
: null;
|
|
123
|
+
response = requestSandboxTaskControl({ family, args: commandArgs, controllerProof: proof });
|
|
124
|
+
} else {
|
|
125
|
+
response = requestSandboxControl({ family, args: commandArgs });
|
|
126
|
+
}
|
|
37
127
|
} catch (error) {
|
|
38
128
|
if (!(error instanceof SandboxControlClientError)) throw error;
|
|
39
129
|
process.stderr.write(`${error.detail.message}\n`);
|
|
@@ -6,6 +6,9 @@ import type {
|
|
|
6
6
|
PrReviewActivityIntent,
|
|
7
7
|
PrReviewInspectIntent
|
|
8
8
|
} from '../task/activity-intent.ts';
|
|
9
|
+
import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.ts';
|
|
10
|
+
import { resolveTaskRef } from '../task/resolve-ref.ts';
|
|
11
|
+
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
|
|
9
12
|
|
|
10
13
|
const USAGE = `Usage: agent-infra-internal task-activity <task-ref> pr-review-inspect
|
|
11
14
|
agent-infra-internal task-activity <task-ref> pr-review-start --agent <agent> --artifact <canonical.md> --head <40hex> [--dry-run]
|
|
@@ -24,13 +27,14 @@ const FLAGS: Record<string, string> = {
|
|
|
24
27
|
'--minor': 'minor',
|
|
25
28
|
'--outcome': 'outcome',
|
|
26
29
|
'--reason': 'reason'
|
|
30
|
+
, '--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
|
|
27
31
|
};
|
|
28
32
|
const COMMON = ['agent', 'artifact', 'head'] as const;
|
|
29
33
|
const ALLOWED: Record<string, ReadonlySet<string>> = {
|
|
30
34
|
'pr-review-inspect': new Set(['kind', 'taskRef']),
|
|
31
|
-
'pr-review-start': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'dryRun']),
|
|
32
|
-
'pr-review-complete': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'verdict', 'blockers', 'major', 'minor', 'dryRun']),
|
|
33
|
-
'pr-review-terminate': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'outcome', 'reason', 'dryRun'])
|
|
35
|
+
'pr-review-start': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'dryRun', 'overrideTicket', 'overrideTarget', 'overrideScope']),
|
|
36
|
+
'pr-review-complete': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'verdict', 'blockers', 'major', 'minor', 'dryRun', 'overrideTicket', 'overrideTarget', 'overrideScope']),
|
|
37
|
+
'pr-review-terminate': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'outcome', 'reason', 'dryRun', 'overrideTicket', 'overrideTarget', 'overrideScope'])
|
|
34
38
|
};
|
|
35
39
|
|
|
36
40
|
function usageFailure(message: string): void {
|
|
@@ -83,9 +87,45 @@ function taskActivity(args: string[] = []): void {
|
|
|
83
87
|
for (const key of operationRequired) {
|
|
84
88
|
if (values[key] === undefined) { usageFailure(`${kind} requires '${key}'`); return; }
|
|
85
89
|
}
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
90
|
+
const dryRunConflict = overrideDryRunConflict(values);
|
|
91
|
+
if (dryRunConflict) { usageFailure(dryRunConflict.message); return; }
|
|
92
|
+
const resolved = resolveTaskRef(taskRef);
|
|
93
|
+
if (!resolved.ok) { usageFailure(resolved.message); return; }
|
|
94
|
+
let result;
|
|
95
|
+
let humanOverride: unknown = null;
|
|
96
|
+
try {
|
|
97
|
+
result = withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-activity.${kind}`, () => {
|
|
98
|
+
let current = applyPrReviewActivityIntent(values as PrReviewActivityIntent, { lockAlreadyHeld: true });
|
|
99
|
+
if (current.status !== 'failed' || !values.overrideTicket) return current;
|
|
100
|
+
if (!values.overrideTarget || !values.overrideScope) { usageFailure('override ticket requires target and scope'); return current; }
|
|
101
|
+
const consumed = consumeHumanOverride({
|
|
102
|
+
taskRef, ticketId: String(values.overrideTicket),
|
|
103
|
+
failureId: failureId('activity-intent', current.error?.code ?? 'TASK_STATE_MISMATCH'),
|
|
104
|
+
target: String(values.overrideTarget), scope: String(values.overrideScope)
|
|
105
|
+
}, {
|
|
106
|
+
effectExecutor: (capability) => {
|
|
107
|
+
const retried = applyPrReviewActivityIntent(values as PrReviewActivityIntent, { lockAlreadyHeld: true, manualOverride: capability });
|
|
108
|
+
current = retried;
|
|
109
|
+
return retried.status === 'failed' || retried.status === 'planned'
|
|
110
|
+
? { 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'}` }
|
|
111
|
+
: null;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
humanOverride = consumed;
|
|
115
|
+
return current;
|
|
116
|
+
});
|
|
117
|
+
} catch (error) {
|
|
118
|
+
if (!(error instanceof TaskExecutionLockError)) throw error;
|
|
119
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: error.code, message: error.message } })}\n`);
|
|
120
|
+
process.exitCode = 1;
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
process.stdout.write(`${JSON.stringify(humanOverride ? { ...result, humanOverride } : result)}\n`);
|
|
124
|
+
const overrideFailed = Boolean(
|
|
125
|
+
humanOverride && typeof humanOverride === 'object' &&
|
|
126
|
+
(humanOverride as { status?: unknown }).status === 'failed'
|
|
127
|
+
);
|
|
128
|
+
if (result.status === 'failed' || overrideFailed) process.exitCode = 1;
|
|
89
129
|
}
|
|
90
130
|
|
|
91
131
|
export { taskActivity };
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { normalizeAgentToken, AGENT_USAGE_HINT } from '../agent-clients/tokens.ts';
|
|
2
2
|
import { applyTaskEvent, eventCatalog } from '../task/events.ts';
|
|
3
3
|
import type { TaskEventRequest, Verdict } from '../task/events.ts';
|
|
4
|
+
import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.ts';
|
|
5
|
+
import { resolveTaskRef } from '../task/resolve-ref.ts';
|
|
6
|
+
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
|
|
4
7
|
|
|
5
8
|
const USAGE = `Usage: agent-infra-internal task-event <N | TASK-id> <event> --agent <agent> [event options] [--orchestrated] [--dry-run]
|
|
6
9
|
|
|
@@ -13,7 +16,8 @@ const FLAGS: Record<string, keyof TaskEventRequest> = {
|
|
|
13
16
|
'--fix-for': 'fixFor', '--implementation-input': 'implementationInput',
|
|
14
17
|
'--verdict': 'verdict', '--blockers': 'blockers', '--major': 'major',
|
|
15
18
|
'--minor': 'minor', '--manual-validation': 'manualValidation', '--files-modified': 'filesModified',
|
|
16
|
-
'--tests-passed': 'testsPassed', '--summary-result': 'summaryResult'
|
|
19
|
+
'--tests-passed': 'testsPassed', '--summary-result': 'summaryResult',
|
|
20
|
+
'--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
|
|
17
21
|
};
|
|
18
22
|
const NUMERIC = new Set(['round', 'question', 'blockers', 'major', 'minor', 'manualValidation', 'filesModified', 'testsPassed']);
|
|
19
23
|
|
|
@@ -51,9 +55,54 @@ function taskEvent(args: string[] = []): void {
|
|
|
51
55
|
const agent = normalizeAgentToken(String(request.agent ?? ''));
|
|
52
56
|
if (!agent) { usageFailure(`invalid --agent '${request.agent}': ${AGENT_USAGE_HINT}`); return; }
|
|
53
57
|
request.agent = agent;
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
const dryRunConflict = overrideDryRunConflict(request as unknown as Record<string, unknown>);
|
|
59
|
+
if (dryRunConflict) { usageFailure(dryRunConflict.message); return; }
|
|
60
|
+
const resolved = resolveTaskRef(request.taskRef);
|
|
61
|
+
if (!resolved.ok) {
|
|
62
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: resolved.code, message: resolved.message } })}\n`);
|
|
63
|
+
process.exitCode = 1;
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
let result;
|
|
67
|
+
let humanOverride: unknown = null;
|
|
68
|
+
try {
|
|
69
|
+
result = withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-event.${request.event}`, () => {
|
|
70
|
+
let current = applyTaskEvent(request, { lockAlreadyHeld: true });
|
|
71
|
+
const values = request as Record<string, unknown>;
|
|
72
|
+
if (current.status !== 'failed' || !values.overrideTicket) return current;
|
|
73
|
+
if (!values.overrideTarget || !values.overrideScope) {
|
|
74
|
+
return { ...current, humanOverride: { status: 'failed', error: { code: 'OVERRIDE_PAYLOAD_INVALID', message: 'override ticket requires target and scope' } } } as typeof current & { humanOverride: unknown };
|
|
75
|
+
}
|
|
76
|
+
const consumed = consumeHumanOverride({
|
|
77
|
+
taskRef: request.taskRef,
|
|
78
|
+
ticketId: String(values.overrideTicket),
|
|
79
|
+
failureId: failureId('task-event', current.error?.code ?? 'EVENT_TRANSITION_INVALID'),
|
|
80
|
+
target: String(values.overrideTarget),
|
|
81
|
+
scope: String(values.overrideScope)
|
|
82
|
+
}, {
|
|
83
|
+
effectExecutor: (capability) => {
|
|
84
|
+
const retried = applyTaskEvent(request, { lockAlreadyHeld: true, manualOverride: capability });
|
|
85
|
+
current = retried;
|
|
86
|
+
return retried.status === 'failed' || retried.status === 'planned'
|
|
87
|
+
? { 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'}` }
|
|
88
|
+
: null;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
humanOverride = consumed;
|
|
92
|
+
return current;
|
|
93
|
+
});
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (!(error instanceof TaskExecutionLockError)) throw error;
|
|
96
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: error.code, message: error.message } })}\n`);
|
|
97
|
+
process.exitCode = 1;
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
process.stdout.write(`${JSON.stringify(humanOverride ? { ...result, humanOverride } : result)}\n`);
|
|
101
|
+
const overrideFailed = Boolean(
|
|
102
|
+
humanOverride && typeof humanOverride === 'object' &&
|
|
103
|
+
(humanOverride as { status?: unknown }).status === 'failed'
|
|
104
|
+
);
|
|
105
|
+
if (result.status === 'failed' || overrideFailed) process.exitCode = 1;
|
|
57
106
|
}
|
|
58
107
|
|
|
59
108
|
export { taskEvent };
|