@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.
Files changed (154) hide show
  1. package/dist/bin/internal-cli.js +2 -2
  2. package/dist/lib/agent-clients/adapter.js +7 -1
  3. package/dist/lib/agent-clients/adapters/claude-code.js +5 -1
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +78 -63
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/binding.js +114 -0
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/cli.js +447 -0
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-client.js +130 -0
  8. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-operation.js +131 -0
  9. package/dist/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.js +2 -2
  10. package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +48 -1
  11. package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +124 -62
  12. package/dist/lib/{internal/codex-sandbox-controller.js → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.js} +3 -3
  13. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -5
  14. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +211 -213
  15. package/dist/lib/agent-clients/adapters/codex-orchestration.js +468 -0
  16. package/dist/lib/agent-clients/adapters/codex.js +40 -1
  17. package/dist/lib/agent-clients/adapters/index.js +2 -10
  18. package/dist/lib/agent-clients/tokens.js +3 -2
  19. package/dist/lib/internal/agent-client.js +132 -72
  20. package/dist/lib/internal/cli-route-inventory.js +2 -2
  21. package/dist/lib/internal/sandbox-control.js +1 -81
  22. package/dist/lib/internal/task-finalization.js +26 -19
  23. package/dist/lib/internal/task-lifecycle.js +1 -1
  24. package/dist/lib/internal/task-operation-registry.js +1 -1
  25. package/dist/lib/internal/task-validate.js +1 -1
  26. package/dist/lib/platform/github-client.js +33 -3
  27. package/dist/lib/platform/issues.js +18 -6
  28. package/dist/lib/runtime/agent-runtime.js +6 -29
  29. package/dist/lib/sandbox/commands/control-status.js +1 -0
  30. package/dist/lib/sandbox/commands/create.js +7 -2
  31. package/dist/lib/sandbox/commands/ls.js +4 -5
  32. package/dist/lib/sandbox/commands/rm.js +1 -1
  33. package/dist/lib/sandbox/control/agent-operation.js +34 -0
  34. package/dist/lib/sandbox/control/client.js +14 -201
  35. package/dist/lib/sandbox/control/executor.js +12 -125
  36. package/dist/lib/sandbox/control/lifecycle.js +66 -4
  37. package/dist/lib/sandbox/control/protocol.js +15 -54
  38. package/dist/lib/sandbox/control/server.js +216 -281
  39. package/dist/lib/sandbox/control/task-view.js +0 -13
  40. package/dist/lib/sandbox/mounts.js +9 -1
  41. package/dist/lib/sandbox/recovery.js +31 -10
  42. package/dist/lib/sandbox/removal.js +28 -5
  43. package/dist/lib/sandbox/workspace-identity.js +18 -6
  44. package/dist/lib/sandbox/workspace-view.js +56 -19
  45. package/dist/lib/task/commands/ls.js +3 -3
  46. package/dist/lib/task/control-authority.js +56 -264
  47. package/dist/lib/task/control-recovery.js +2 -3
  48. package/dist/lib/task/create.js +2 -2
  49. package/dist/lib/task/delegation-receipts.js +74 -161
  50. package/dist/lib/task/events.js +88 -44
  51. package/dist/lib/task/finalization.js +35 -6
  52. package/dist/lib/task/lifecycle.js +274 -115
  53. package/dist/lib/task/orchestration.js +201 -194
  54. package/dist/lib/task/runtime-paths.js +10 -0
  55. package/dist/lib/task/short-id.js +37 -27
  56. package/dist/lib/task/verification-engine.js +32 -26
  57. package/dist/lib/task/workspace-snapshot.js +1 -1
  58. package/dist/lifecycle-build-manifest.json +3 -3
  59. package/lib/agent-clients/adapter.ts +39 -0
  60. package/lib/agent-clients/adapters/claude-code.ts +5 -1
  61. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +105 -80
  62. package/lib/agent-clients/adapters/codex-lifecycle/binding.ts +147 -0
  63. package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +510 -0
  64. package/lib/agent-clients/adapters/codex-lifecycle/controller-client.ts +158 -0
  65. package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +1 -1
  66. package/lib/agent-clients/adapters/codex-lifecycle/controller-operation.ts +151 -0
  67. package/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.ts +4 -4
  68. package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +43 -0
  69. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +8 -5
  70. package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +190 -65
  71. package/lib/{internal/codex-sandbox-controller.ts → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.ts} +2 -2
  72. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -5
  73. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +270 -224
  74. package/lib/agent-clients/adapters/codex-orchestration.ts +632 -0
  75. package/lib/agent-clients/adapters/codex.ts +44 -1
  76. package/lib/agent-clients/adapters/index.ts +7 -20
  77. package/lib/agent-clients/tokens.ts +4 -3
  78. package/lib/internal/agent-client.ts +142 -73
  79. package/lib/internal/cli-route-inventory.ts +2 -2
  80. package/lib/internal/sandbox-control.ts +0 -97
  81. package/lib/internal/task-finalization.ts +30 -18
  82. package/lib/internal/task-lifecycle.ts +1 -1
  83. package/lib/internal/task-operation-registry.ts +1 -1
  84. package/lib/internal/task-validate.ts +1 -1
  85. package/lib/platform/github-client.ts +34 -3
  86. package/lib/platform/issues.ts +18 -6
  87. package/lib/runtime/agent-runtime.ts +6 -41
  88. package/lib/sandbox/commands/control-status.ts +1 -0
  89. package/lib/sandbox/commands/create.ts +7 -1
  90. package/lib/sandbox/commands/ls.ts +4 -5
  91. package/lib/sandbox/commands/rm.ts +1 -1
  92. package/lib/sandbox/control/agent-operation.ts +49 -0
  93. package/lib/sandbox/control/client.ts +20 -254
  94. package/lib/sandbox/control/executor.ts +13 -136
  95. package/lib/sandbox/control/lifecycle.ts +67 -4
  96. package/lib/sandbox/control/protocol.ts +22 -66
  97. package/lib/sandbox/control/server.ts +21 -95
  98. package/lib/sandbox/control/task-view.ts +0 -19
  99. package/lib/sandbox/mounts.ts +10 -1
  100. package/lib/sandbox/recovery.ts +38 -17
  101. package/lib/sandbox/removal.ts +33 -7
  102. package/lib/sandbox/workspace-identity.ts +16 -6
  103. package/lib/sandbox/workspace-view.ts +50 -18
  104. package/lib/task/commands/ls.ts +3 -3
  105. package/lib/task/control-authority.ts +66 -378
  106. package/lib/task/control-recovery.ts +3 -4
  107. package/lib/task/create.ts +3 -3
  108. package/lib/task/delegation-receipts.ts +87 -231
  109. package/lib/task/events.ts +100 -42
  110. package/lib/task/finalization.ts +37 -7
  111. package/lib/task/lifecycle.ts +257 -109
  112. package/lib/task/orchestration.ts +260 -237
  113. package/lib/task/runtime-paths.ts +12 -0
  114. package/lib/task/short-id.ts +33 -40
  115. package/lib/task/verification-engine.ts +30 -25
  116. package/lib/task/workspace-snapshot.ts +1 -1
  117. package/package.json +1 -1
  118. package/templates/.agents/hooks/lifecycle-delegation.js +1 -25
  119. package/templates/.agents/rules/README.en.md +2 -1
  120. package/templates/.agents/rules/README.zh-CN.md +2 -1
  121. package/templates/.agents/rules/next-step-output.en.md +6 -19
  122. package/templates/.agents/rules/next-step-output.zh-CN.md +6 -19
  123. package/templates/.agents/rules/strategy-pattern.en.md +25 -0
  124. package/templates/.agents/rules/strategy-pattern.zh-CN.md +25 -0
  125. package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -1
  126. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -1
  127. package/templates/.agents/skills/check-task/SKILL.en.md +1 -1
  128. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +1 -1
  129. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  130. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
  131. package/templates/.agents/skills/code-task/reference/code-rules.en.md +4 -0
  132. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +4 -0
  133. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +0 -1
  134. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +0 -1
  135. package/templates/.agents/skills/complete-task/SKILL.en.md +5 -29
  136. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +5 -26
  137. package/templates/.agents/skills/plan-task/SKILL.en.md +7 -4
  138. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +7 -4
  139. package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
  140. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
  141. package/templates/.agents/skills/review-code/SKILL.en.md +1 -1
  142. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +1 -1
  143. package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
  144. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
  145. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
  146. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
  147. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  148. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  149. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +0 -546
  150. package/dist/lib/internal/codex-lifecycle.js +0 -385
  151. package/dist/lib/task/codex-orchestration.js +0 -206
  152. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +0 -776
  153. package/lib/internal/codex-lifecycle.ts +0 -390
  154. package/lib/task/codex-orchestration.ts +0 -276
@@ -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
- // Short ids live only in the registry and only for active tasks; archived
79
- // (blocked/completed) tasks have released their short id and render '-'.
80
- const shortIdByTaskId = state === 'active' ? loadShortIdByTaskId(repoRoot) : new Map<string, string>();
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 operationTaskId(context: TaskControlExecutionContext, taskRef: string): void {
527
- if (context.source === 'sandbox-executor' && taskRef !== context.taskId) {
528
- throw new Error('TASK_CONTROL_CONTEXT_TASK_MISMATCH: operation task does not match the sandbox manifest');
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 applyTaskLifecycle(request, { repoRoot: context.repoRoot });
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
- operationTaskId(context, operation.taskRef);
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(operation.taskRef, {
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(operation.taskRef, options);
611
- case 'status': return statusOrchestration(operation.taskRef, options);
612
- case 'prepare': {
613
- const prepareInput = {
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(operation.taskRef, input.event as never, options);
631
- case 'recover-prepared': return recoverPreparedOrchestrationDelegation(operation.taskRef, options);
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(input.client as AgentClientId, input.event as never, options)
635
- : activateOrchestrationDelegation(operation.taskRef, input.event as never, options);
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(input.client as AgentClientId, input.event as never, options)
639
- : sealOrchestrationDelegation(operation.taskRef, input.event as never, options);
640
- case 'advance': return advanceOrchestration(operation.taskRef, options);
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
- operation.taskRef,
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
- operationTaskId(context, operation.request.taskRef);
682
- return applyLifecycleWithAuthority(context, operation.request);
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
- operationTaskId(context, operation.request.taskRef);
686
- return applyTaskFinalization(operation.request, {
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-finalization' | 'task-create' | 'codex-controller';
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', 'complete', 'close-codescan', 'close-dependabot', 'restore', 'recover-started'] as const;
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
- ...(['open', 'close', 'verify'] as const).map((intent) => ({ family: 'codex-controller' as const, intent, class: 'codex-controller' }))
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 {
@@ -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 = typeof AGENTS[number];
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, AGENTS, 'agent'),
125
+ agent: enumValue(candidate.agent, AGENT_TOKEN_VALUES, 'agent'),
126
126
  title,
127
127
  type: enumValue(candidate.type, TYPES, 'type'),
128
128
  branchSlug,