@fitlab-ai/agent-infra 0.11.11 → 0.11.13
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 +2 -2
- package/dist/lib/agent-clients/adapter.js +7 -1
- package/dist/lib/agent-clients/adapters/claude-code.js +5 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +78 -63
- package/dist/lib/agent-clients/adapters/codex-lifecycle/binding.js +114 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/cli.js +447 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-client.js +130 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-operation.js +131 -0
- package/dist/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.js +2 -2
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +48 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +124 -62
- package/dist/lib/{internal/codex-sandbox-controller.js → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.js} +3 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -5
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +211 -213
- package/dist/lib/agent-clients/adapters/codex-orchestration.js +468 -0
- package/dist/lib/agent-clients/adapters/codex.js +40 -1
- package/dist/lib/agent-clients/adapters/index.js +2 -10
- package/dist/lib/agent-clients/tokens.js +3 -2
- package/dist/lib/internal/agent-client.js +132 -72
- package/dist/lib/internal/cli-route-inventory.js +2 -2
- package/dist/lib/internal/sandbox-control.js +1 -81
- package/dist/lib/internal/task-finalization.js +26 -19
- package/dist/lib/internal/task-lifecycle.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +1 -1
- package/dist/lib/internal/task-validate.js +1 -1
- package/dist/lib/platform/github-client.js +33 -3
- package/dist/lib/platform/issues.js +18 -6
- package/dist/lib/runtime/agent-runtime.js +6 -29
- package/dist/lib/sandbox/commands/control-status.js +1 -0
- package/dist/lib/sandbox/commands/create.js +7 -2
- package/dist/lib/sandbox/commands/ls.js +4 -5
- package/dist/lib/sandbox/commands/rm.js +1 -1
- package/dist/lib/sandbox/control/agent-operation.js +34 -0
- package/dist/lib/sandbox/control/client.js +14 -201
- package/dist/lib/sandbox/control/executor.js +12 -125
- package/dist/lib/sandbox/control/lifecycle.js +66 -4
- package/dist/lib/sandbox/control/protocol.js +15 -54
- package/dist/lib/sandbox/control/server.js +216 -281
- package/dist/lib/sandbox/control/task-view.js +0 -13
- package/dist/lib/sandbox/mounts.js +9 -1
- package/dist/lib/sandbox/recovery.js +31 -10
- package/dist/lib/sandbox/removal.js +28 -5
- package/dist/lib/sandbox/workspace-identity.js +18 -6
- package/dist/lib/sandbox/workspace-view.js +56 -19
- package/dist/lib/task/commands/ls.js +3 -3
- package/dist/lib/task/control-authority.js +56 -264
- package/dist/lib/task/control-recovery.js +2 -3
- package/dist/lib/task/create.js +2 -2
- package/dist/lib/task/delegation-receipts.js +74 -161
- package/dist/lib/task/events.js +88 -44
- package/dist/lib/task/finalization.js +35 -6
- package/dist/lib/task/lifecycle.js +274 -115
- package/dist/lib/task/orchestration.js +201 -194
- package/dist/lib/task/runtime-paths.js +10 -0
- package/dist/lib/task/short-id.js +37 -27
- package/dist/lib/task/verification-engine.js +32 -26
- package/dist/lib/task/workspace-snapshot.js +1 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapter.ts +39 -0
- package/lib/agent-clients/adapters/claude-code.ts +5 -1
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +105 -80
- package/lib/agent-clients/adapters/codex-lifecycle/binding.ts +147 -0
- package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +510 -0
- package/lib/agent-clients/adapters/codex-lifecycle/controller-client.ts +158 -0
- package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +1 -1
- package/lib/agent-clients/adapters/codex-lifecycle/controller-operation.ts +151 -0
- package/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.ts +4 -4
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +43 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +8 -5
- package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +190 -65
- package/lib/{internal/codex-sandbox-controller.ts → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.ts} +2 -2
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -5
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +270 -224
- package/lib/agent-clients/adapters/codex-orchestration.ts +632 -0
- package/lib/agent-clients/adapters/codex.ts +44 -1
- package/lib/agent-clients/adapters/index.ts +7 -20
- package/lib/agent-clients/tokens.ts +4 -3
- package/lib/internal/agent-client.ts +142 -73
- package/lib/internal/cli-route-inventory.ts +2 -2
- package/lib/internal/sandbox-control.ts +0 -97
- package/lib/internal/task-finalization.ts +30 -18
- package/lib/internal/task-lifecycle.ts +1 -1
- package/lib/internal/task-operation-registry.ts +1 -1
- package/lib/internal/task-validate.ts +1 -1
- package/lib/platform/github-client.ts +34 -3
- package/lib/platform/issues.ts +18 -6
- package/lib/runtime/agent-runtime.ts +6 -41
- package/lib/sandbox/commands/control-status.ts +1 -0
- package/lib/sandbox/commands/create.ts +7 -1
- package/lib/sandbox/commands/ls.ts +4 -5
- package/lib/sandbox/commands/rm.ts +1 -1
- package/lib/sandbox/control/agent-operation.ts +49 -0
- package/lib/sandbox/control/client.ts +20 -254
- package/lib/sandbox/control/executor.ts +13 -136
- package/lib/sandbox/control/lifecycle.ts +67 -4
- package/lib/sandbox/control/protocol.ts +22 -66
- package/lib/sandbox/control/server.ts +21 -95
- package/lib/sandbox/control/task-view.ts +0 -19
- package/lib/sandbox/mounts.ts +10 -1
- package/lib/sandbox/recovery.ts +38 -17
- package/lib/sandbox/removal.ts +33 -7
- package/lib/sandbox/workspace-identity.ts +16 -6
- package/lib/sandbox/workspace-view.ts +50 -18
- package/lib/task/commands/ls.ts +3 -3
- package/lib/task/control-authority.ts +66 -378
- package/lib/task/control-recovery.ts +3 -4
- package/lib/task/create.ts +3 -3
- package/lib/task/delegation-receipts.ts +87 -231
- package/lib/task/events.ts +100 -42
- package/lib/task/finalization.ts +37 -7
- package/lib/task/lifecycle.ts +257 -109
- package/lib/task/orchestration.ts +260 -237
- package/lib/task/runtime-paths.ts +12 -0
- package/lib/task/short-id.ts +33 -40
- package/lib/task/verification-engine.ts +30 -25
- package/lib/task/workspace-snapshot.ts +1 -1
- package/package.json +1 -1
- package/templates/.agents/hooks/lifecycle-delegation.js +1 -25
- package/templates/.agents/rules/README.en.md +2 -1
- package/templates/.agents/rules/README.zh-CN.md +2 -1
- package/templates/.agents/rules/next-step-output.en.md +6 -19
- package/templates/.agents/rules/next-step-output.zh-CN.md +6 -19
- package/templates/.agents/rules/strategy-pattern.en.md +25 -0
- package/templates/.agents/rules/strategy-pattern.zh-CN.md +25 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/check-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/code-rules.en.md +4 -0
- package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +4 -0
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +0 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +0 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +5 -29
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +5 -26
- package/templates/.agents/skills/plan-task/SKILL.en.md +7 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +7 -4
- package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +0 -546
- package/dist/lib/internal/codex-lifecycle.js +0 -385
- package/dist/lib/task/codex-orchestration.js +0 -206
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +0 -776
- package/lib/internal/codex-lifecycle.ts +0 -390
- package/lib/task/codex-orchestration.ts +0 -276
package/lib/task/commands/ls.ts
CHANGED
|
@@ -75,9 +75,9 @@ type TaskRow = {
|
|
|
75
75
|
function collectTasks(repoRoot: string, state: 'active' | 'blocked' | 'completed'): TaskRow[] {
|
|
76
76
|
const dir = path.join(repoRoot, '.agents', 'workspace', state);
|
|
77
77
|
if (!fs.existsSync(dir)) return [];
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
const shortIdByTaskId = state === 'active' || state === 'completed'
|
|
79
|
+
? loadShortIdByTaskId(repoRoot)
|
|
80
|
+
: new Map<string, string>();
|
|
81
81
|
const rows: TaskRow[] = [];
|
|
82
82
|
for (const entry of fs.readdirSync(dir).sort()) {
|
|
83
83
|
if (!TASK_ID_RE.test(entry)) continue;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import crypto from 'node:crypto';
|
|
3
2
|
|
|
4
3
|
import { normalizeAgentToken } from '../agent-clients/tokens.ts';
|
|
5
4
|
import { isAgentClientId } from '../agent-clients/types.ts';
|
|
@@ -29,6 +28,7 @@ import {
|
|
|
29
28
|
} from './finalization.ts';
|
|
30
29
|
import {
|
|
31
30
|
applyTaskLifecycle,
|
|
31
|
+
applyTaskLifecycleWithinTaskLock,
|
|
32
32
|
taskLifecycleFailure,
|
|
33
33
|
type TaskLifecycleRequest,
|
|
34
34
|
type TaskLifecycleResult
|
|
@@ -37,331 +37,9 @@ import type {
|
|
|
37
37
|
AgentClientLifecycleRecoveryRequest as LifecycleRecoveryRequest,
|
|
38
38
|
AgentClientLifecycleRecoveryResult as LifecycleRecoveryResult
|
|
39
39
|
} from '../agent-clients/adapter.ts';
|
|
40
|
-
import { resolveTaskRef, TASK_ID_RE } from './resolve-ref.ts';
|
|
40
|
+
import { resolveTaskContext, resolveTaskRef, TASK_ID_RE } from './resolve-ref.ts';
|
|
41
41
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
42
42
|
import { verifyTaskEvent } from './verification.ts';
|
|
43
|
-
import { createCodexCapabilityStore } from '../agent-clients/adapters/codex-lifecycle/capability-store.ts';
|
|
44
|
-
import {
|
|
45
|
-
computeLifecycleBuildIdentity,
|
|
46
|
-
type LifecycleBuildIdentity
|
|
47
|
-
} from '../agent-clients/adapters/codex-lifecycle/build-identity.ts';
|
|
48
|
-
|
|
49
|
-
export type LifecycleAuthorityPhase =
|
|
50
|
-
| 'orchestration.prepare'
|
|
51
|
-
| 'artifact.finalize-local'
|
|
52
|
-
| 'task-event.completed';
|
|
53
|
-
|
|
54
|
-
export type LifecycleAuthorityRequestV1 = Readonly<{
|
|
55
|
-
version: 1;
|
|
56
|
-
requestId: string;
|
|
57
|
-
operationId: string;
|
|
58
|
-
phase: LifecycleAuthorityPhase;
|
|
59
|
-
taskId: string;
|
|
60
|
-
family: 'analysis' | 'plan' | 'code';
|
|
61
|
-
artifact: string;
|
|
62
|
-
round: number;
|
|
63
|
-
lifecycleRequestId: string;
|
|
64
|
-
authorityRef: string;
|
|
65
|
-
expectedControlGeneration: string;
|
|
66
|
-
expectedControllerInstanceDigest: string;
|
|
67
|
-
expectedBuildIdentityDigest: string;
|
|
68
|
-
expectedHookDefinitionHash: string;
|
|
69
|
-
}>;
|
|
70
|
-
|
|
71
|
-
export type LifecycleRecoveryAttestationV1 = Readonly<{
|
|
72
|
-
version: 1;
|
|
73
|
-
attestationId: string;
|
|
74
|
-
authorityRefDigest: string;
|
|
75
|
-
operationId: string;
|
|
76
|
-
phase: LifecycleAuthorityPhase;
|
|
77
|
-
requestId: string;
|
|
78
|
-
lifecycleRequestId: string;
|
|
79
|
-
taskId: string;
|
|
80
|
-
family: 'analysis' | 'plan' | 'code';
|
|
81
|
-
artifact: string;
|
|
82
|
-
round: number;
|
|
83
|
-
controlGeneration: string;
|
|
84
|
-
controllerInstanceDigest: string;
|
|
85
|
-
sessionId: string;
|
|
86
|
-
turnId: string;
|
|
87
|
-
toolUseId: string;
|
|
88
|
-
buildIdentityDigest: string;
|
|
89
|
-
hookDefinitionHash: string;
|
|
90
|
-
issuedAt: number;
|
|
91
|
-
expiresAt: number;
|
|
92
|
-
}>;
|
|
93
|
-
|
|
94
|
-
export type LifecycleAuthorityResponseV1 = Readonly<{
|
|
95
|
-
version: 1;
|
|
96
|
-
requestId: string;
|
|
97
|
-
operationId: string;
|
|
98
|
-
phase: LifecycleAuthorityPhase;
|
|
99
|
-
status: 'issued' | 'already-completed' | 'rejected';
|
|
100
|
-
attestation: LifecycleRecoveryAttestationV1 | null;
|
|
101
|
-
error: { code: string; message: string } | null;
|
|
102
|
-
}>;
|
|
103
|
-
|
|
104
|
-
type LifecycleAuthorityIssuerOptions = Readonly<{
|
|
105
|
-
controllerBinding?: TaskControlControllerBinding | null;
|
|
106
|
-
capabilityStore?: ReturnType<typeof createCodexCapabilityStore>;
|
|
107
|
-
buildIdentity?: LifecycleBuildIdentity;
|
|
108
|
-
now?: () => number;
|
|
109
|
-
operationAttestations?: Map<string, LifecycleRecoveryAttestationV1>;
|
|
110
|
-
}>;
|
|
111
|
-
|
|
112
|
-
export type LifecycleRecoveryOperationQueryV1 = Readonly<{
|
|
113
|
-
version: 1;
|
|
114
|
-
operationId: string;
|
|
115
|
-
status: 'unknown' | 'in-progress' | 'committed';
|
|
116
|
-
capabilityState: 'unknown' | 'reserved' | 'consumed';
|
|
117
|
-
phases: readonly Readonly<{
|
|
118
|
-
phase: LifecycleAuthorityPhase;
|
|
119
|
-
requestId: string;
|
|
120
|
-
attestationId: string | null;
|
|
121
|
-
state: 'issued' | 'consumed' | 'observed';
|
|
122
|
-
}>[];
|
|
123
|
-
}>;
|
|
124
|
-
|
|
125
|
-
const lifecycleAuthorityPhases: readonly LifecycleAuthorityPhase[] = [
|
|
126
|
-
'orchestration.prepare', 'artifact.finalize-local', 'task-event.completed'
|
|
127
|
-
];
|
|
128
|
-
const lifecycleAuthorityFamilies = ['analysis', 'plan', 'code'] as const;
|
|
129
|
-
const lifecycleAuthorityArtifact = /^(?:analysis|plan|code)(?:-r[1-9]\d*)?\.md$/u;
|
|
130
|
-
const lifecycleAuthorityDigest = (value: string): string => crypto.createHash('sha256').update(value, 'utf8').digest('hex');
|
|
131
|
-
const lifecycleAuthorityBuildDigest = (value: LifecycleBuildIdentity): string => lifecycleAuthorityDigest(JSON.stringify(value));
|
|
132
|
-
|
|
133
|
-
export function lifecycleRecoveryAttestationDigest(value: LifecycleRecoveryAttestationV1): string {
|
|
134
|
-
return lifecycleAuthorityDigest(JSON.stringify(value));
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function authorityExactKeys(value: Record<string, unknown>, keys: readonly string[]): boolean {
|
|
138
|
-
return Object.keys(value).sort().join(',') === [...keys].sort().join(',');
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export function validateLifecycleAuthorityRequest(value: unknown): LifecycleAuthorityRequestV1 {
|
|
142
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('LIFECYCLE_AUTHORITY_REQUEST_INVALID');
|
|
143
|
-
const request = value as Record<string, unknown>;
|
|
144
|
-
if (!authorityExactKeys(request, [
|
|
145
|
-
'artifact', 'authorityRef', 'expectedBuildIdentityDigest', 'expectedControlGeneration',
|
|
146
|
-
'expectedControllerInstanceDigest', 'expectedHookDefinitionHash', 'family', 'lifecycleRequestId',
|
|
147
|
-
'operationId', 'phase', 'requestId', 'round', 'taskId', 'version'
|
|
148
|
-
])
|
|
149
|
-
|| request.version !== 1
|
|
150
|
-
|| typeof request.requestId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(request.requestId)
|
|
151
|
-
|| typeof request.operationId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(request.operationId)
|
|
152
|
-
|| !lifecycleAuthorityPhases.includes(request.phase as LifecycleAuthorityPhase)
|
|
153
|
-
|| typeof request.taskId !== 'string' || !/^TASK-\d{8}-\d{6}$/u.test(request.taskId)
|
|
154
|
-
|| !lifecycleAuthorityFamilies.includes(request.family as typeof lifecycleAuthorityFamilies[number])
|
|
155
|
-
|| typeof request.artifact !== 'string' || !lifecycleAuthorityArtifact.test(request.artifact)
|
|
156
|
-
|| !Number.isSafeInteger(request.round) || (request.round as number) < 1
|
|
157
|
-
|| typeof request.lifecycleRequestId !== 'string' || !request.lifecycleRequestId || /[\r\n]/u.test(request.lifecycleRequestId)
|
|
158
|
-
|| typeof request.authorityRef !== 'string' || !request.authorityRef || /[\r\n]/u.test(request.authorityRef)
|
|
159
|
-
|| typeof request.expectedControlGeneration !== 'string' || !request.expectedControlGeneration
|
|
160
|
-
|| typeof request.expectedControllerInstanceDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(request.expectedControllerInstanceDigest)
|
|
161
|
-
|| typeof request.expectedBuildIdentityDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(request.expectedBuildIdentityDigest)
|
|
162
|
-
|| typeof request.expectedHookDefinitionHash !== 'string' || !/^[a-f0-9]{64}$/u.test(request.expectedHookDefinitionHash)) {
|
|
163
|
-
throw new Error('LIFECYCLE_AUTHORITY_REQUEST_INVALID');
|
|
164
|
-
}
|
|
165
|
-
const expectedArtifactFamily = request.artifact as string;
|
|
166
|
-
if (!expectedArtifactFamily.startsWith(request.family as string)) throw new Error('LIFECYCLE_AUTHORITY_REQUEST_INVALID');
|
|
167
|
-
return request as LifecycleAuthorityRequestV1;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
export function validateLifecycleRecoveryAttestation(
|
|
171
|
-
value: unknown,
|
|
172
|
-
now = Date.now()
|
|
173
|
-
): LifecycleRecoveryAttestationV1 {
|
|
174
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('LIFECYCLE_AUTHORITY_ATTESTATION_INVALID');
|
|
175
|
-
const attestation = value as Record<string, unknown>;
|
|
176
|
-
if (!authorityExactKeys(attestation, [
|
|
177
|
-
'artifact', 'attestationId', 'authorityRefDigest', 'buildIdentityDigest', 'controlGeneration',
|
|
178
|
-
'controllerInstanceDigest', 'expiresAt', 'family', 'hookDefinitionHash', 'issuedAt',
|
|
179
|
-
'lifecycleRequestId', 'operationId', 'phase', 'requestId', 'round', 'sessionId',
|
|
180
|
-
'taskId', 'toolUseId', 'turnId', 'version'
|
|
181
|
-
])
|
|
182
|
-
|| attestation.version !== 1
|
|
183
|
-
|| typeof attestation.attestationId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(attestation.attestationId)
|
|
184
|
-
|| typeof attestation.authorityRefDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(attestation.authorityRefDigest)
|
|
185
|
-
|| typeof attestation.operationId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(attestation.operationId)
|
|
186
|
-
|| !lifecycleAuthorityPhases.includes(attestation.phase as LifecycleAuthorityPhase)
|
|
187
|
-
|| typeof attestation.requestId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(attestation.requestId)
|
|
188
|
-
|| typeof attestation.lifecycleRequestId !== 'string' || !attestation.lifecycleRequestId
|
|
189
|
-
|| typeof attestation.taskId !== 'string' || !/^TASK-\d{8}-\d{6}$/u.test(attestation.taskId)
|
|
190
|
-
|| !lifecycleAuthorityFamilies.includes(attestation.family as typeof lifecycleAuthorityFamilies[number])
|
|
191
|
-
|| typeof attestation.artifact !== 'string' || !lifecycleAuthorityArtifact.test(attestation.artifact)
|
|
192
|
-
|| !Number.isSafeInteger(attestation.round) || (attestation.round as number) < 1
|
|
193
|
-
|| typeof attestation.controlGeneration !== 'string' || !attestation.controlGeneration
|
|
194
|
-
|| typeof attestation.controllerInstanceDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(attestation.controllerInstanceDigest)
|
|
195
|
-
|| typeof attestation.sessionId !== 'string' || !attestation.sessionId
|
|
196
|
-
|| typeof attestation.turnId !== 'string' || !attestation.turnId
|
|
197
|
-
|| typeof attestation.toolUseId !== 'string' || !attestation.toolUseId
|
|
198
|
-
|| typeof attestation.buildIdentityDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(attestation.buildIdentityDigest)
|
|
199
|
-
|| typeof attestation.hookDefinitionHash !== 'string' || !/^[a-f0-9]{64}$/u.test(attestation.hookDefinitionHash)
|
|
200
|
-
|| !Number.isSafeInteger(attestation.issuedAt) || !Number.isSafeInteger(attestation.expiresAt)
|
|
201
|
-
|| (attestation.expiresAt as number) <= (attestation.issuedAt as number)
|
|
202
|
-
|| (attestation.expiresAt as number) <= now) {
|
|
203
|
-
throw new Error('LIFECYCLE_AUTHORITY_ATTESTATION_INVALID');
|
|
204
|
-
}
|
|
205
|
-
return attestation as LifecycleRecoveryAttestationV1;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const authorityOperationMap = new WeakMap<object, Map<string, LifecycleRecoveryAttestationV1>>();
|
|
209
|
-
const authorityRecords = new Map<string, Readonly<{
|
|
210
|
-
store: ReturnType<typeof createCodexCapabilityStore>;
|
|
211
|
-
capabilityRef: string;
|
|
212
|
-
expected: Readonly<{
|
|
213
|
-
taskId: string;
|
|
214
|
-
hookDefinitionHash: string;
|
|
215
|
-
buildIdentity: LifecycleBuildIdentity;
|
|
216
|
-
controller: TaskControlControllerBinding;
|
|
217
|
-
}>;
|
|
218
|
-
}>>();
|
|
219
|
-
const consumedAuthorityPhases = new Set<string>();
|
|
220
|
-
|
|
221
|
-
export function issueLifecycleRecoveryAttestation(
|
|
222
|
-
input: LifecycleAuthorityRequestV1,
|
|
223
|
-
options: LifecycleAuthorityIssuerOptions = {}
|
|
224
|
-
): LifecycleAuthorityResponseV1 {
|
|
225
|
-
let request: LifecycleAuthorityRequestV1;
|
|
226
|
-
try { request = validateLifecycleAuthorityRequest(input); }
|
|
227
|
-
catch (error) {
|
|
228
|
-
return { version: 1, requestId: String((input as { requestId?: unknown })?.requestId ?? ''), operationId: String((input as { operationId?: unknown })?.operationId ?? ''), phase: (input as { phase?: LifecycleAuthorityPhase })?.phase ?? 'artifact.finalize-local', status: 'rejected', attestation: null, error: { code: 'LIFECYCLE_AUTHORITY_REQUEST_INVALID', message: error instanceof Error ? error.message : String(error) } };
|
|
229
|
-
}
|
|
230
|
-
const binding = options.controllerBinding ?? null;
|
|
231
|
-
if (!binding) return authorityRejected(request, 'LOCAL_EXECUTION_AUTHORITY_UNAVAILABLE', 'current controller authority is unavailable');
|
|
232
|
-
if (binding.controlGeneration !== request.expectedControlGeneration
|
|
233
|
-
|| binding.instanceDigest !== request.expectedControllerInstanceDigest) {
|
|
234
|
-
return authorityRejected(request, 'LIFECYCLE_AUTHORITY_CONTROLLER_MISMATCH', 'current controller binding does not match the request');
|
|
235
|
-
}
|
|
236
|
-
const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(process.cwd());
|
|
237
|
-
if (lifecycleAuthorityBuildDigest(buildIdentity) !== request.expectedBuildIdentityDigest) {
|
|
238
|
-
return authorityRejected(request, 'LIFECYCLE_AUTHORITY_BUILD_MISMATCH', 'current lifecycle build does not match the request');
|
|
239
|
-
}
|
|
240
|
-
const store = options.capabilityStore ?? createCodexCapabilityStore();
|
|
241
|
-
const registry = options.operationAttestations ?? authorityOperationMap.get(store) ?? new Map<string, LifecycleRecoveryAttestationV1>();
|
|
242
|
-
authorityOperationMap.set(store, registry);
|
|
243
|
-
const key = `${request.requestId}\0${request.operationId}\0${request.phase}`;
|
|
244
|
-
const prior = registry.get(key);
|
|
245
|
-
if (prior) return { version: 1, requestId: request.requestId, operationId: request.operationId, phase: request.phase, status: 'already-completed', attestation: prior, error: null };
|
|
246
|
-
try {
|
|
247
|
-
const expected = {
|
|
248
|
-
taskId: request.taskId,
|
|
249
|
-
hookDefinitionHash: request.expectedHookDefinitionHash,
|
|
250
|
-
buildIdentity,
|
|
251
|
-
controller: binding
|
|
252
|
-
};
|
|
253
|
-
const record = store.reserveReference(request.authorityRef, request.operationId, expected);
|
|
254
|
-
if (!record.sessionId || !record.turnId || !record.toolUseId) throw new Error('CODEX_CAPABILITY_IDENTITY_INVALID: capability hook attestation is incomplete');
|
|
255
|
-
const now = (options.now ?? Date.now)();
|
|
256
|
-
const attestation = validateLifecycleRecoveryAttestation({
|
|
257
|
-
version: 1,
|
|
258
|
-
attestationId: crypto.randomUUID(),
|
|
259
|
-
authorityRefDigest: lifecycleAuthorityDigest(request.authorityRef),
|
|
260
|
-
operationId: request.operationId,
|
|
261
|
-
phase: request.phase,
|
|
262
|
-
requestId: request.requestId,
|
|
263
|
-
lifecycleRequestId: request.lifecycleRequestId,
|
|
264
|
-
taskId: request.taskId,
|
|
265
|
-
family: request.family,
|
|
266
|
-
artifact: request.artifact,
|
|
267
|
-
round: request.round,
|
|
268
|
-
controlGeneration: binding.controlGeneration,
|
|
269
|
-
controllerInstanceDigest: binding.instanceDigest,
|
|
270
|
-
sessionId: record.sessionId,
|
|
271
|
-
turnId: record.turnId,
|
|
272
|
-
toolUseId: record.toolUseId,
|
|
273
|
-
buildIdentityDigest: lifecycleAuthorityBuildDigest(buildIdentity),
|
|
274
|
-
hookDefinitionHash: request.expectedHookDefinitionHash,
|
|
275
|
-
issuedAt: now,
|
|
276
|
-
expiresAt: Math.min(record.expiresAt, now + 30_000)
|
|
277
|
-
}, now);
|
|
278
|
-
store.reserveRecoveryPhase(
|
|
279
|
-
request.authorityRef,
|
|
280
|
-
request.operationId,
|
|
281
|
-
request.phase,
|
|
282
|
-
request.requestId,
|
|
283
|
-
expected
|
|
284
|
-
);
|
|
285
|
-
registry.set(key, attestation);
|
|
286
|
-
authorityRecords.set(attestation.attestationId, { store, capabilityRef: request.authorityRef, expected });
|
|
287
|
-
return { version: 1, requestId: request.requestId, operationId: request.operationId, phase: request.phase, status: 'issued', attestation, error: null };
|
|
288
|
-
} catch (error) {
|
|
289
|
-
return authorityRejected(request, error instanceof Error && error.name.startsWith('CODEX_') ? error.name : 'LIFECYCLE_AUTHORITY_REJECTED', error instanceof Error ? error.message : String(error));
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
export function consumeLifecycleRecoveryAttestation(
|
|
294
|
-
value: LifecycleRecoveryAttestationV1,
|
|
295
|
-
now = Date.now()
|
|
296
|
-
): void {
|
|
297
|
-
const attestation = validateLifecycleRecoveryAttestation(value, now);
|
|
298
|
-
if (consumedAuthorityPhases.has(attestation.attestationId)) return;
|
|
299
|
-
const record = authorityRecords.get(attestation.attestationId);
|
|
300
|
-
if (!record) throw new Error('LIFECYCLE_AUTHORITY_ATTESTATION_UNKNOWN');
|
|
301
|
-
record.store.consumeRecoveryPhase(
|
|
302
|
-
record.capabilityRef,
|
|
303
|
-
attestation.operationId,
|
|
304
|
-
attestation.phase,
|
|
305
|
-
attestation.requestId,
|
|
306
|
-
record.expected
|
|
307
|
-
);
|
|
308
|
-
if (attestation.phase === 'task-event.completed') {
|
|
309
|
-
record.store.consumeReference(record.capabilityRef, attestation.operationId, record.expected);
|
|
310
|
-
}
|
|
311
|
-
consumedAuthorityPhases.add(attestation.attestationId);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
export function queryLifecycleRecoveryOperation(
|
|
315
|
-
operationId: string,
|
|
316
|
-
options: Readonly<{
|
|
317
|
-
capabilityStore?: ReturnType<typeof createCodexCapabilityStore>;
|
|
318
|
-
}> = {}
|
|
319
|
-
): LifecycleRecoveryOperationQueryV1 {
|
|
320
|
-
if (!/^[a-f0-9-]{16,64}$/u.test(operationId)) {
|
|
321
|
-
throw new Error('LIFECYCLE_RECOVERY_OPERATION_INVALID');
|
|
322
|
-
}
|
|
323
|
-
const store = options.capabilityStore ?? createCodexCapabilityStore();
|
|
324
|
-
const registry = authorityOperationMap.get(store);
|
|
325
|
-
const known = [...(registry?.values() ?? [])].filter((value) => value.operationId === operationId);
|
|
326
|
-
const capabilities = store.findByRecoveryOperation(operationId);
|
|
327
|
-
const phaseMap = new Map<string, LifecycleRecoveryOperationQueryV1['phases'][number]>();
|
|
328
|
-
for (const capability of capabilities) {
|
|
329
|
-
for (const phase of capability.recoveryPhases) {
|
|
330
|
-
phaseMap.set(`${phase.phase}\0${phase.requestId}`, {
|
|
331
|
-
phase: phase.phase,
|
|
332
|
-
requestId: phase.requestId,
|
|
333
|
-
attestationId: null,
|
|
334
|
-
state: phase.state === 'consumed' ? 'consumed' : 'observed'
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
for (const attestation of known) {
|
|
339
|
-
const key = `${attestation.phase}\0${attestation.requestId}`;
|
|
340
|
-
const durable = phaseMap.get(key);
|
|
341
|
-
phaseMap.set(key, {
|
|
342
|
-
phase: attestation.phase,
|
|
343
|
-
requestId: attestation.requestId,
|
|
344
|
-
attestationId: attestation.attestationId,
|
|
345
|
-
state: durable?.state === 'consumed' || consumedAuthorityPhases.has(attestation.attestationId)
|
|
346
|
-
? 'consumed' : 'issued'
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
const capabilityState = capabilities.some((record) => record.recoveryState === 'consumed')
|
|
350
|
-
? 'consumed'
|
|
351
|
-
: capabilities.some((record) => record.recoveryState === 'reserved') ? 'reserved' : 'unknown';
|
|
352
|
-
const committed = capabilityState === 'consumed';
|
|
353
|
-
return {
|
|
354
|
-
version: 1,
|
|
355
|
-
operationId,
|
|
356
|
-
status: committed ? 'committed' : capabilityState === 'reserved' || phaseMap.size > 0 ? 'in-progress' : 'unknown',
|
|
357
|
-
capabilityState,
|
|
358
|
-
phases: Object.freeze([...phaseMap.values()])
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
function authorityRejected(request: LifecycleAuthorityRequestV1, code: string, message: string): LifecycleAuthorityResponseV1 {
|
|
363
|
-
return { version: 1, requestId: request.requestId, operationId: request.operationId, phase: request.phase, status: 'rejected', attestation: null, error: { code, message } };
|
|
364
|
-
}
|
|
365
43
|
|
|
366
44
|
export type TaskControlControllerBinding = Readonly<{
|
|
367
45
|
instanceDigest: string;
|
|
@@ -375,7 +53,6 @@ export type TaskControlExecutionContext =
|
|
|
375
53
|
repoRoot: string;
|
|
376
54
|
runtimeDir?: string;
|
|
377
55
|
controllerBinding?: TaskControlControllerBinding | null;
|
|
378
|
-
lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
|
|
379
56
|
}>
|
|
380
57
|
| Readonly<{
|
|
381
58
|
source: 'sandbox-executor';
|
|
@@ -388,7 +65,6 @@ export type TaskControlExecutionContext =
|
|
|
388
65
|
manifestPath: string;
|
|
389
66
|
requestId: string;
|
|
390
67
|
controllerBinding?: TaskControlControllerBinding | null;
|
|
391
|
-
lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
|
|
392
68
|
}>;
|
|
393
69
|
|
|
394
70
|
export type TaskControlOrchestrationIntent =
|
|
@@ -448,7 +124,6 @@ export function createDirectHostExecutionContext(params: Readonly<{
|
|
|
448
124
|
repoRoot: string;
|
|
449
125
|
runtimeDir?: string;
|
|
450
126
|
controllerBinding?: TaskControlControllerBinding | null;
|
|
451
|
-
lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
|
|
452
127
|
}>): TaskControlExecutionContext {
|
|
453
128
|
return {
|
|
454
129
|
source: 'direct-host',
|
|
@@ -456,10 +131,6 @@ export function createDirectHostExecutionContext(params: Readonly<{
|
|
|
456
131
|
repoRoot: absolute('repoRoot', params.repoRoot),
|
|
457
132
|
...(params.runtimeDir === undefined ? {} : { runtimeDir: absolute('runtimeDir', params.runtimeDir) }),
|
|
458
133
|
...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) }),
|
|
459
|
-
...(params.lifecycleRecoveryAttestation === undefined ? {} : {
|
|
460
|
-
lifecycleRecoveryAttestation: params.lifecycleRecoveryAttestation === null
|
|
461
|
-
? null : validateLifecycleRecoveryAttestation(params.lifecycleRecoveryAttestation)
|
|
462
|
-
})
|
|
463
134
|
};
|
|
464
135
|
}
|
|
465
136
|
|
|
@@ -472,7 +143,6 @@ export function createSandboxExecutorExecutionContext(params: Readonly<{
|
|
|
472
143
|
manifestPath: string;
|
|
473
144
|
requestId: string;
|
|
474
145
|
controllerBinding?: TaskControlControllerBinding | null;
|
|
475
|
-
lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
|
|
476
146
|
}>): TaskControlExecutionContext {
|
|
477
147
|
if (!/^TASK-\d{8}-\d{6}$/u.test(params.taskId) || !params.generation || !params.requestId) {
|
|
478
148
|
invalidContext('sandbox binding is incomplete');
|
|
@@ -492,10 +162,6 @@ export function createSandboxExecutorExecutionContext(params: Readonly<{
|
|
|
492
162
|
manifestPath: absolute('manifestPath', params.manifestPath),
|
|
493
163
|
requestId: params.requestId,
|
|
494
164
|
...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) }),
|
|
495
|
-
...(params.lifecycleRecoveryAttestation === undefined ? {} : {
|
|
496
|
-
lifecycleRecoveryAttestation: params.lifecycleRecoveryAttestation === null
|
|
497
|
-
? null : validateLifecycleRecoveryAttestation(params.lifecycleRecoveryAttestation)
|
|
498
|
-
})
|
|
499
165
|
};
|
|
500
166
|
}
|
|
501
167
|
|
|
@@ -505,7 +171,6 @@ export function assertTaskControlExecutionContext(context: TaskControlExecutionC
|
|
|
505
171
|
absolute('repoRoot', context.repoRoot);
|
|
506
172
|
if (context.runtimeDir !== undefined) absolute('runtimeDir', context.runtimeDir);
|
|
507
173
|
binding(context.controllerBinding);
|
|
508
|
-
if (context.lifecycleRecoveryAttestation) validateLifecycleRecoveryAttestation(context.lifecycleRecoveryAttestation);
|
|
509
174
|
return;
|
|
510
175
|
}
|
|
511
176
|
if (context.mode !== 'task-bound-sandbox' || !/^TASK-\d{8}-\d{6}$/u.test(context.taskId)
|
|
@@ -520,13 +185,20 @@ export function assertTaskControlExecutionContext(context: TaskControlExecutionC
|
|
|
520
185
|
invalidContext('runtimeDir is not bound to the manifest control root');
|
|
521
186
|
}
|
|
522
187
|
binding(context.controllerBinding);
|
|
523
|
-
if (context.lifecycleRecoveryAttestation) validateLifecycleRecoveryAttestation(context.lifecycleRecoveryAttestation);
|
|
524
188
|
}
|
|
525
189
|
|
|
526
|
-
function
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
190
|
+
function boundTaskRef(context: TaskControlExecutionContext, taskRef: string): string {
|
|
191
|
+
return context.source === 'sandbox-executor' ? context.taskId : taskRef;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function autoTaskOptions(
|
|
195
|
+
context: TaskControlExecutionContext,
|
|
196
|
+
options: OrchestrationOptions
|
|
197
|
+
): OrchestrationOptions {
|
|
198
|
+
if (context.source === 'sandbox-executor') return { ...options, taskId: context.taskId };
|
|
199
|
+
const resolved = resolveTaskContext(undefined, { repoRoot: context.repoRoot });
|
|
200
|
+
if (!resolved.ok) throw new Error(`${resolved.code}: ${resolved.message}`);
|
|
201
|
+
return { ...options, taskId: resolved.taskId };
|
|
530
202
|
}
|
|
531
203
|
|
|
532
204
|
function domainOptions(
|
|
@@ -553,7 +225,7 @@ async function applyLifecycleWithAuthority(
|
|
|
553
225
|
context: TaskControlExecutionContext,
|
|
554
226
|
request: TaskLifecycleControlRequest
|
|
555
227
|
): Promise<TaskLifecycleResult | LifecycleRecoveryResult> {
|
|
556
|
-
const execute = async (): Promise<TaskLifecycleResult | LifecycleRecoveryResult> => {
|
|
228
|
+
const execute = async (underTaskLock = false): Promise<TaskLifecycleResult | LifecycleRecoveryResult> => {
|
|
557
229
|
if (request.intent === 'recover-started') {
|
|
558
230
|
const adapter = isAgentClientId(request.agent)
|
|
559
231
|
? getAgentClientAdapter(request.agent).orchestrationAdapter
|
|
@@ -567,7 +239,9 @@ async function applyLifecycleWithAuthority(
|
|
|
567
239
|
}
|
|
568
240
|
return adapter.recoverStarted(request as LifecycleRecoveryRequest, { repoRoot: context.repoRoot });
|
|
569
241
|
}
|
|
570
|
-
return
|
|
242
|
+
return underTaskLock
|
|
243
|
+
? applyTaskLifecycleWithinTaskLock(request, { repoRoot: context.repoRoot }, taskId!)
|
|
244
|
+
: applyTaskLifecycle(request, { repoRoot: context.repoRoot });
|
|
571
245
|
};
|
|
572
246
|
const taskId = lifecycleTaskId(request, context.repoRoot);
|
|
573
247
|
if (!taskId) return await execute();
|
|
@@ -576,7 +250,7 @@ async function applyLifecycleWithAuthority(
|
|
|
576
250
|
context.repoRoot,
|
|
577
251
|
taskId,
|
|
578
252
|
`task-lifecycle.${request.intent}`,
|
|
579
|
-
execute
|
|
253
|
+
() => execute(true)
|
|
580
254
|
);
|
|
581
255
|
} catch (error) {
|
|
582
256
|
if (!(error instanceof TaskExecutionLockError)) throw error;
|
|
@@ -596,51 +270,40 @@ function orchestration(
|
|
|
596
270
|
context: TaskControlExecutionContext,
|
|
597
271
|
operation: Extract<TaskControlOperation, { family: 'task-orchestration' }>
|
|
598
272
|
): OrchestrationResult | Promise<OrchestrationResult> {
|
|
599
|
-
|
|
273
|
+
const taskRef = boundTaskRef(context, operation.taskRef);
|
|
600
274
|
const input = operation.input;
|
|
601
275
|
const options = domainOptions(context, operation.options);
|
|
602
276
|
switch (operation.intent) {
|
|
603
277
|
case 'begin-or-resume':
|
|
604
|
-
return beginOrResumeOrchestration(
|
|
278
|
+
return beginOrResumeOrchestration(taskRef, {
|
|
605
279
|
...options,
|
|
606
280
|
client: input.client as AgentClientId,
|
|
607
281
|
maxSteps: input.maxSteps as number | undefined,
|
|
608
282
|
modelPolicy: input.modelPolicy as OrchestrationOptions['modelPolicy']
|
|
609
283
|
});
|
|
610
|
-
case 'route': return routeOrchestration(
|
|
611
|
-
case 'status': return statusOrchestration(
|
|
612
|
-
case 'prepare': {
|
|
613
|
-
|
|
614
|
-
client: input.client as AgentClientId,
|
|
615
|
-
requestedModel: input.requestedModel as string | undefined,
|
|
616
|
-
requestedReasoningEffort: input.requestedReasoningEffort as string | undefined,
|
|
617
|
-
};
|
|
618
|
-
const prepareDelegation = getAgentClientAdapter(prepareInput.client)
|
|
619
|
-
.orchestrationAdapter?.prepareDelegation;
|
|
620
|
-
if (prepareDelegation) {
|
|
621
|
-
return prepareDelegation(operation.taskRef, prepareInput, {
|
|
622
|
-
repoRoot: context.repoRoot,
|
|
623
|
-
orchestrationOptions: options,
|
|
624
|
-
});
|
|
625
|
-
}
|
|
626
|
-
return prepareOrchestrationDelegation(operation.taskRef, prepareInput, options);
|
|
627
|
-
}
|
|
628
|
-
case 'dispatch': return dispatchOrchestrationDelegation(operation.taskRef, options);
|
|
284
|
+
case 'route': return routeOrchestration(taskRef, options);
|
|
285
|
+
case 'status': return statusOrchestration(taskRef, options);
|
|
286
|
+
case 'prepare': return prepareTaskOrchestration(context, { ...operation, taskRef }, options);
|
|
287
|
+
case 'dispatch': return dispatchOrchestrationDelegation(taskRef, options);
|
|
629
288
|
case 'await-activation':
|
|
630
|
-
return awaitOrchestrationDelegationActivation(
|
|
631
|
-
case 'recover-prepared': return recoverPreparedOrchestrationDelegation(
|
|
289
|
+
return awaitOrchestrationDelegationActivation(taskRef, input.event as never, options);
|
|
290
|
+
case 'recover-prepared': return recoverPreparedOrchestrationDelegation(taskRef, options);
|
|
632
291
|
case 'hook-start':
|
|
633
292
|
return input.auto === true
|
|
634
|
-
? activateMatchingOrchestrationDelegation(
|
|
635
|
-
|
|
293
|
+
? activateMatchingOrchestrationDelegation(
|
|
294
|
+
input.client as AgentClientId, input.event as never, autoTaskOptions(context, options)
|
|
295
|
+
)
|
|
296
|
+
: activateOrchestrationDelegation(taskRef, input.event as never, options);
|
|
636
297
|
case 'hook-stop':
|
|
637
298
|
return input.auto === true
|
|
638
|
-
? sealMatchingOrchestrationDelegation(
|
|
639
|
-
|
|
640
|
-
|
|
299
|
+
? sealMatchingOrchestrationDelegation(
|
|
300
|
+
input.client as AgentClientId, input.event as never, autoTaskOptions(context, options)
|
|
301
|
+
)
|
|
302
|
+
: sealOrchestrationDelegation(taskRef, input.event as never, undefined, options);
|
|
303
|
+
case 'advance': return advanceOrchestration(taskRef, options);
|
|
641
304
|
case 'pause':
|
|
642
305
|
return pauseOrchestration(
|
|
643
|
-
|
|
306
|
+
taskRef,
|
|
644
307
|
input.code as string,
|
|
645
308
|
input.message as string,
|
|
646
309
|
input.recoverable as boolean,
|
|
@@ -649,6 +312,26 @@ function orchestration(
|
|
|
649
312
|
}
|
|
650
313
|
}
|
|
651
314
|
|
|
315
|
+
function prepareTaskOrchestration(
|
|
316
|
+
context: TaskControlExecutionContext,
|
|
317
|
+
operation: Extract<TaskControlOperation, { family: 'task-orchestration' }>,
|
|
318
|
+
options: OrchestrationOptions
|
|
319
|
+
): OrchestrationResult | Promise<OrchestrationResult> {
|
|
320
|
+
const input = operation.input;
|
|
321
|
+
const prepareInput = {
|
|
322
|
+
client: input.client as AgentClientId,
|
|
323
|
+
requestedModel: input.requestedModel as string | undefined,
|
|
324
|
+
requestedReasoningEffort: input.requestedReasoningEffort as string | undefined,
|
|
325
|
+
};
|
|
326
|
+
const prepareDelegation = getAgentClientAdapter(prepareInput.client).orchestrationAdapter?.prepareDelegation;
|
|
327
|
+
return prepareDelegation
|
|
328
|
+
? prepareDelegation(operation.taskRef, prepareInput, {
|
|
329
|
+
repoRoot: context.repoRoot,
|
|
330
|
+
orchestrationOptions: options,
|
|
331
|
+
})
|
|
332
|
+
: prepareOrchestrationDelegation(operation.taskRef, prepareInput, options);
|
|
333
|
+
}
|
|
334
|
+
|
|
652
335
|
function orchestrationFailure(error: unknown): OrchestrationResult {
|
|
653
336
|
if (error instanceof OrchestrationStateError) {
|
|
654
337
|
return {
|
|
@@ -678,12 +361,16 @@ export function dispatchTaskControlOperation(
|
|
|
678
361
|
assertTaskControlExecutionContext(context);
|
|
679
362
|
assertTaskControlOperation(operation);
|
|
680
363
|
if (operation.family === 'task-lifecycle') {
|
|
681
|
-
|
|
682
|
-
|
|
364
|
+
return applyLifecycleWithAuthority(context, {
|
|
365
|
+
...operation.request,
|
|
366
|
+
taskRef: boundTaskRef(context, operation.request.taskRef)
|
|
367
|
+
});
|
|
683
368
|
}
|
|
684
369
|
if (operation.family === 'task-finalization') {
|
|
685
|
-
|
|
686
|
-
|
|
370
|
+
return applyTaskFinalization({
|
|
371
|
+
...operation.request,
|
|
372
|
+
taskRef: boundTaskRef(context, operation.request.taskRef)
|
|
373
|
+
}, {
|
|
687
374
|
repoRoot: context.repoRoot,
|
|
688
375
|
...(context.source === 'sandbox-executor' ? {
|
|
689
376
|
controlBinding: { generation: context.generation, requestId: context.requestId },
|
|
@@ -790,6 +477,7 @@ export function parseTaskControlOperation(
|
|
|
790
477
|
const taskRef = args[0]!;
|
|
791
478
|
const intent = args[1]!;
|
|
792
479
|
if (family === 'task-lifecycle') {
|
|
480
|
+
if (intent === 'complete') operationInvalid('complete must use task-finalization');
|
|
793
481
|
const values = parseValues(args, 2, LIFECYCLE_FLAGS);
|
|
794
482
|
const agent = normalizeAgentToken(value(values, '--agent') ?? '');
|
|
795
483
|
if (!agent) operationInvalid('lifecycle agent is invalid');
|
|
@@ -13,7 +13,7 @@ export function parseControlOutput(output: string | null): Record<string, unknow
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export type ControlRecoveryOperation = Readonly<{
|
|
16
|
-
family: 'task-lifecycle' | 'task-
|
|
16
|
+
family: 'task-lifecycle' | 'task-create' | 'agent-client';
|
|
17
17
|
intent: string;
|
|
18
18
|
class: string;
|
|
19
19
|
}>;
|
|
@@ -51,12 +51,11 @@ export type ControlRecoveryDecision = Readonly<{
|
|
|
51
51
|
reasonCode: string;
|
|
52
52
|
}>;
|
|
53
53
|
|
|
54
|
-
const LIFECYCLE_INTENTS = ['block', 'activate', 'cancel', '
|
|
54
|
+
const LIFECYCLE_INTENTS = ['block', 'activate', 'cancel', 'close-codescan', 'close-dependabot', 'restore', 'recover-started'] as const;
|
|
55
55
|
export const SANDBOX_CONTROL_RECOVERY_OPERATIONS: readonly ControlRecoveryOperation[] = Object.freeze([
|
|
56
56
|
...LIFECYCLE_INTENTS.map((intent) => ({ family: 'task-lifecycle' as const, intent, class: 'lifecycle-mutation' })),
|
|
57
|
-
{ family: 'task-finalization', intent: 'complete', class: 'finalization' },
|
|
58
57
|
{ family: 'task-create', intent: 'create', class: 'task-create' },
|
|
59
|
-
...(['
|
|
58
|
+
...(['controller.close', 'controller.verify'] as const).map((intent) => ({ family: 'agent-client' as const, intent, class: 'agent-client-operation' }))
|
|
60
59
|
]);
|
|
61
60
|
|
|
62
61
|
export function digestControlRecoveryIntent(family: string, intent: string): string {
|
package/lib/task/create.ts
CHANGED
|
@@ -11,8 +11,8 @@ import { buildUnboundFact, encodePrDeliveryFact } from './pr-delivery-fact.ts';
|
|
|
11
11
|
import { CANDIDATE_COLUMNS, CONSTRAINT_COLUMNS, parseTaskQualification } from './qualification-audit.ts';
|
|
12
12
|
import { enumerateAllTaskDirs } from './resolve-ref.ts';
|
|
13
13
|
import { parseTaskFrontmatter } from './frontmatter.ts';
|
|
14
|
+
import { AGENT_TOKEN_VALUES, type AgentToken } from '../agent-clients/tokens.ts';
|
|
14
15
|
|
|
15
|
-
const AGENTS = ['claude', 'codex', 'antigravity', 'opencode', 'cursor'] as const;
|
|
16
16
|
const TYPES = ['feature', 'bugfix', 'refactor', 'docs', 'chore'] as const;
|
|
17
17
|
const PRIORITIES = ['Urgent', 'High', 'Medium', 'Low'] as const;
|
|
18
18
|
const EFFORTS = ['High', 'Medium', 'Low'] as const;
|
|
@@ -21,7 +21,7 @@ const TASK_INPUT_KEYS = [
|
|
|
21
21
|
'acceptanceCriteria', 'openQuestions'
|
|
22
22
|
] as const;
|
|
23
23
|
|
|
24
|
-
type TaskAgent =
|
|
24
|
+
type TaskAgent = AgentToken;
|
|
25
25
|
type TaskType = typeof TYPES[number];
|
|
26
26
|
type TaskPriority = typeof PRIORITIES[number];
|
|
27
27
|
type TaskEffort = typeof EFFORTS[number];
|
|
@@ -122,7 +122,7 @@ function validateTaskCreateCandidate(value: unknown): TaskCreateCandidateV1 {
|
|
|
122
122
|
return {
|
|
123
123
|
version: 1,
|
|
124
124
|
idempotencyKey,
|
|
125
|
-
agent: enumValue(candidate.agent,
|
|
125
|
+
agent: enumValue(candidate.agent, AGENT_TOKEN_VALUES, 'agent'),
|
|
126
126
|
title,
|
|
127
127
|
type: enumValue(candidate.type, TYPES, 'type'),
|
|
128
128
|
branchSlug,
|