@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
@@ -7,44 +7,6 @@ import { normalizeAgentToken } from '../agent-clients/tokens.ts';
7
7
  type DelegationRole = 'executor' | 'reviewer';
8
8
  type DelegationStage = 'analysis' | 'review-analysis' | 'plan' | 'review-plan' | 'code' | 'review-code' | 'commit';
9
9
  type DelegationStatus = 'prepared' | 'activated' | 'stage-completed' | 'sealed' | 'consumed' | 'aborted' | 'expired';
10
- type DelegationHostEvidence = Readonly<{
11
- kind: 'codex-lifecycle-v1' | 'codex-lifecycle-v2';
12
- hookDefinitionHash: string;
13
- startRevision: number;
14
- stopRevision: number | null;
15
- consumer: string | null;
16
- consumedAt: string | null;
17
- protocolVersion?: number;
18
- packageVersion?: string;
19
- internalExecutableBuildHash?: string;
20
- lifecycleContractHash?: string;
21
- hookSource?: 'project' | 'managed' | 'isolated-user';
22
- hookSourcePathDigest?: string;
23
- hookSourceHash?: string;
24
- capabilitySessionId?: string;
25
- capabilityTurnId?: string;
26
- capabilityToolUseId?: string;
27
- spawnToolUseId?: string;
28
- spawnObservedAt?: string;
29
- controllerInstanceDigest?: string | null;
30
- controlGeneration?: string | null;
31
- }>;
32
- type DelegationLifecycleProvenance = Readonly<{
33
- protocolVersion: number;
34
- packageVersion: string;
35
- internalExecutableBuildHash: string;
36
- lifecycleContractHash: string;
37
- hookDefinitionHash: string;
38
- hookSource: 'project' | 'managed' | 'isolated-user';
39
- hookSourcePathDigest: string;
40
- hookSourceHash: string;
41
- capabilitySessionId: string;
42
- capabilityTurnId: string;
43
- capabilityToolUseId: string;
44
- controllerInstanceDigest: string | null;
45
- controlGeneration: string | null;
46
- }>;
47
-
48
10
  type DelegationReceipt = Readonly<{
49
11
  id: string;
50
12
  taskId: string;
@@ -66,8 +28,7 @@ type DelegationReceipt = Readonly<{
66
28
  agent: string | null;
67
29
  status: DelegationStatus;
68
30
  workspaceSnapshotScope?: 'task';
69
- lifecycleProvenance?: DelegationLifecycleProvenance | null;
70
- hostEvidence?: DelegationHostEvidence | null;
31
+ adapterEvidence?: Readonly<Record<string, unknown>>;
71
32
  beforeFingerprint: string;
72
33
  afterFingerprint: string | null;
73
34
  changedPaths: readonly string[];
@@ -116,76 +77,35 @@ function nullableSafeInteger(value: unknown): value is number | null {
116
77
  return value === null || (Number.isSafeInteger(value) && (value as number) >= 0);
117
78
  }
118
79
 
119
- const LIFECYCLE_PROVENANCE_KEYS = [
120
- 'protocolVersion', 'packageVersion', 'internalExecutableBuildHash', 'lifecycleContractHash',
121
- 'hookDefinitionHash', 'hookSource', 'hookSourcePathDigest', 'hookSourceHash',
122
- 'capabilitySessionId', 'capabilityTurnId', 'capabilityToolUseId',
123
- 'controllerInstanceDigest', 'controlGeneration'
124
- ] as const;
125
-
126
- function isDelegationLifecycleProvenance(value: unknown): value is DelegationLifecycleProvenance {
127
- if (!hasExactKeys(value, LIFECYCLE_PROVENANCE_KEYS)) return false;
128
- return Number.isSafeInteger(value.protocolVersion)
129
- && (value.protocolVersion as number) > 0
130
- && exactText(value.packageVersion)
131
- && exactText(value.internalExecutableBuildHash)
132
- && exactText(value.lifecycleContractHash)
133
- && exactText(value.hookDefinitionHash)
134
- && ['project', 'managed', 'isolated-user'].includes(value.hookSource as string)
135
- && exactText(value.hookSourcePathDigest)
136
- && exactText(value.hookSourceHash)
137
- && exactText(value.capabilitySessionId)
138
- && exactText(value.capabilityTurnId)
139
- && exactText(value.capabilityToolUseId)
140
- && nullableText(value.controllerInstanceDigest)
141
- && nullableText(value.controlGeneration);
142
- }
143
-
144
- const HOST_EVIDENCE_REQUIRED_KEYS = [
145
- 'kind', 'hookDefinitionHash', 'startRevision', 'stopRevision', 'consumer', 'consumedAt'
146
- ] as const;
147
- const HOST_EVIDENCE_OPTIONAL_KEYS = [
148
- 'protocolVersion', 'packageVersion', 'internalExecutableBuildHash', 'lifecycleContractHash',
149
- 'hookSource', 'hookSourcePathDigest', 'hookSourceHash', 'capabilitySessionId',
150
- 'capabilityTurnId', 'capabilityToolUseId', 'spawnToolUseId', 'spawnObservedAt',
151
- 'controllerInstanceDigest', 'controlGeneration'
152
- ] as const;
153
-
154
- function isDelegationHostEvidence(value: unknown): value is DelegationHostEvidence {
155
- if (!hasExactKeys(value, HOST_EVIDENCE_REQUIRED_KEYS, HOST_EVIDENCE_OPTIONAL_KEYS)) return false;
156
- if (!['codex-lifecycle-v1', 'codex-lifecycle-v2'].includes(value.kind as string)) return false;
157
- if (!exactText(value.hookDefinitionHash)
158
- || !Number.isSafeInteger(value.startRevision) || (value.startRevision as number) < 1
159
- || !nullableSafeInteger(value.stopRevision)
160
- || !nullableText(value.consumer)
161
- || !nullableText(value.consumedAt)) return false;
162
- if (value.kind === 'codex-lifecycle-v1') return true;
163
- return Number.isSafeInteger(value.protocolVersion)
164
- && (value.protocolVersion as number) > 0
165
- && exactText(value.packageVersion)
166
- && exactText(value.internalExecutableBuildHash)
167
- && exactText(value.lifecycleContractHash)
168
- && ['project', 'managed', 'isolated-user'].includes(value.hookSource as string)
169
- && exactText(value.hookSourcePathDigest)
170
- && exactText(value.hookSourceHash)
171
- && exactText(value.capabilitySessionId)
172
- && exactText(value.capabilityTurnId)
173
- && exactText(value.spawnToolUseId)
174
- && exactText(value.spawnObservedAt)
175
- && nullableText(value.controllerInstanceDigest)
176
- && nullableText(value.controlGeneration);
177
- }
178
-
179
80
  const RECEIPT_KEYS = [
180
81
  'id', 'taskId', 'runId', 'role', 'stage', 'round', 'artifact', 'client',
181
82
  'requestedModel', 'requestedReasoningEffort', 'actualModel', 'actualReasoningEffort',
182
83
  'modelFallbackReason', 'reasoningEffortFallbackReason', 'parentId', 'childId',
183
- 'spawnMode', 'agent', 'status', 'workspaceSnapshotScope', 'lifecycleProvenance',
184
- 'hostEvidence', 'beforeFingerprint', 'afterFingerprint', 'changedPaths', 'createdAt',
84
+ 'spawnMode', 'agent', 'status', 'workspaceSnapshotScope',
85
+ 'beforeFingerprint', 'afterFingerprint', 'changedPaths', 'createdAt',
185
86
  'preparedMonotonicMs', 'spawnDispatchMonotonicMs', 'activationDeadlineMonotonicMs',
186
87
  'spawnDispatchedAt', 'activationDeadlineAt', 'startEvidenceMonotonicMs',
187
88
  'activatedMonotonicMs', 'activatedAt', 'sealedAt', 'consumedAt'
188
89
  ] as const;
90
+ const RECEIPT_OPTIONAL_KEYS = ['adapterEvidence'] as const;
91
+
92
+ function isJsonRecord(value: unknown): value is Readonly<Record<string, unknown>> {
93
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
94
+ try {
95
+ return JSON.parse(JSON.stringify(value)) !== null;
96
+ } catch {
97
+ return false;
98
+ }
99
+ }
100
+
101
+ function withClientEvidence(receipt: DelegationReceipt, evidence: unknown): Readonly<Record<string, unknown>> | null {
102
+ if (evidence === undefined) return receipt.adapterEvidence ?? Object.freeze({});
103
+ if (!isJsonRecord(evidence)) return null;
104
+ return Object.freeze({
105
+ ...(receipt.adapterEvidence ?? {}),
106
+ [receipt.client]: JSON.parse(JSON.stringify(evidence)) as unknown
107
+ });
108
+ }
189
109
 
190
110
  function hasStatusBoundEvidence(receipt: DelegationReceipt): boolean {
191
111
  const dispatchFields = [
@@ -209,7 +129,6 @@ function hasStatusBoundEvidence(receipt: DelegationReceipt): boolean {
209
129
  && receipt.modelFallbackReason === null
210
130
  && receipt.reasoningEffortFallbackReason === null
211
131
  && receipt.agent === null
212
- && receipt.hostEvidence === null
213
132
  && receipt.startEvidenceMonotonicMs === null
214
133
  && receipt.activatedMonotonicMs === null
215
134
  && receipt.activatedAt === null
@@ -238,18 +157,12 @@ function hasStatusBoundEvidence(receipt: DelegationReceipt): boolean {
238
157
  }
239
158
 
240
159
  if (receipt.status === 'aborted') {
241
- const host = receipt.hostEvidence;
242
160
  return receipt.agent === null
243
161
  && receipt.afterFingerprint === null
244
162
  && receipt.changedPaths.length === 0
245
163
  && receipt.sealedAt === null
246
164
  && receipt.consumedAt === null
247
- && host !== null
248
- && host !== undefined
249
- && Number.isSafeInteger(host.stopRevision)
250
- && (host.stopRevision as number) > host.startRevision
251
- && host.consumer === receipt.id
252
- && exactText(host.consumedAt);
165
+ && receipt.childId !== null;
253
166
  }
254
167
 
255
168
  if (
@@ -271,7 +184,7 @@ function hasStatusBoundEvidence(receipt: DelegationReceipt): boolean {
271
184
  }
272
185
 
273
186
  function isDelegationReceipt(value: unknown): value is DelegationReceipt {
274
- if (!hasExactKeys(value, RECEIPT_KEYS)) return false;
187
+ if (!hasExactKeys(value, RECEIPT_KEYS, RECEIPT_OPTIONAL_KEYS)) return false;
275
188
  const structurallyValid = exactText(value.id)
276
189
  && exactText(value.taskId)
277
190
  && exactText(value.runId)
@@ -293,8 +206,7 @@ function isDelegationReceipt(value: unknown): value is DelegationReceipt {
293
206
  && nullableText(value.agent)
294
207
  && ['prepared', 'activated', 'stage-completed', 'sealed', 'consumed', 'aborted', 'expired'].includes(value.status as string)
295
208
  && value.workspaceSnapshotScope === 'task'
296
- && (value.lifecycleProvenance === null || isDelegationLifecycleProvenance(value.lifecycleProvenance))
297
- && (value.hostEvidence === null || isDelegationHostEvidence(value.hostEvidence))
209
+ && (value.adapterEvidence === undefined || isJsonRecord(value.adapterEvidence))
298
210
  && exactText(value.beforeFingerprint)
299
211
  && nullableText(value.afterFingerprint)
300
212
  && Array.isArray(value.changedPaths)
@@ -314,13 +226,6 @@ function isDelegationReceipt(value: unknown): value is DelegationReceipt {
314
226
  if (!structurallyValid) return false;
315
227
  const receipt = value as unknown as DelegationReceipt;
316
228
  if (!hasStatusBoundEvidence(receipt)) return false;
317
- const host = receipt.hostEvidence;
318
- if (receipt.client === 'codex' && receipt.activatedAt !== null) {
319
- if (host?.kind !== 'codex-lifecycle-v2' || host.capabilitySessionId !== receipt.parentId) return false;
320
- if (['sealed', 'consumed'].includes(receipt.status)) {
321
- return Number.isSafeInteger(host.stopRevision) && host.stopRevision! > host.startRevision;
322
- }
323
- }
324
229
  return true;
325
230
  }
326
231
 
@@ -342,13 +247,42 @@ function foldBlankToNull(value: string | undefined): string | null {
342
247
  return value?.trim() ? value : null;
343
248
  }
344
249
 
250
+ function validateSealRequest(
251
+ receipt: DelegationReceipt,
252
+ event: Parameters<typeof sealDelegation>[1]
253
+ ): ReceiptFailure | null {
254
+ if (receipt.status !== 'stage-completed') {
255
+ return fail('DELEGATION_STATE_INVALID', `delegation ${receipt.id} is ${receipt.status}, expected stage-completed`);
256
+ }
257
+ if (event.childId !== receipt.childId || event.exitCode !== 0) {
258
+ return fail('DELEGATION_STOP_INVALID', 'native stop identity or exit status is invalid');
259
+ }
260
+ const reviewerPathFailure = validateReviewerSealPaths(receipt, event.changedPaths);
261
+ return reviewerPathFailure;
262
+ }
263
+
264
+ function validateReviewerSealPaths(receipt: DelegationReceipt, changedPaths: readonly string[]): ReceiptFailure | null {
265
+ if (receipt.role !== 'reviewer') return null;
266
+ const taskRoot = `.agents/workspace/active/${receipt.taskId}/`;
267
+ const allowed = new Set([
268
+ `${taskRoot}${receipt.artifact}`,
269
+ `${taskRoot}task.md`,
270
+ `${taskRoot}.runtime/orchestration.json`
271
+ ]);
272
+ const disallowed = changedPaths.find((entry) => !allowed.has(entry));
273
+ return disallowed
274
+ ? fail('DELEGATION_REVIEWER_WRITE_FORBIDDEN', `reviewer changed forbidden path '${disallowed}'`)
275
+ : null;
276
+ }
277
+
345
278
  function prepareDelegation(
346
- input: Omit<DelegationReceipt, 'id' | 'requestedModel' | 'requestedReasoningEffort' | 'actualModel' | 'actualReasoningEffort' | 'modelFallbackReason' | 'reasoningEffortFallbackReason' | 'parentId' | 'childId' | 'spawnMode' | 'agent' | 'status' | 'hostEvidence' | 'afterFingerprint' | 'changedPaths' | 'createdAt' | 'preparedMonotonicMs' | 'spawnDispatchMonotonicMs' | 'activationDeadlineMonotonicMs' | 'spawnDispatchedAt' | 'activationDeadlineAt' | 'startEvidenceMonotonicMs' | 'activatedMonotonicMs' | 'activatedAt' | 'sealedAt' | 'consumedAt'> & Readonly<{ requestedModel: string; requestedReasoningEffort: string }>,
279
+ input: Omit<DelegationReceipt, 'id' | 'requestedModel' | 'requestedReasoningEffort' | 'actualModel' | 'actualReasoningEffort' | 'modelFallbackReason' | 'reasoningEffortFallbackReason' | 'parentId' | 'childId' | 'spawnMode' | 'agent' | 'status' | 'afterFingerprint' | 'changedPaths' | 'createdAt' | 'preparedMonotonicMs' | 'spawnDispatchMonotonicMs' | 'activationDeadlineMonotonicMs' | 'spawnDispatchedAt' | 'activationDeadlineAt' | 'startEvidenceMonotonicMs' | 'activatedMonotonicMs' | 'activatedAt' | 'sealedAt' | 'consumedAt'> & Readonly<{ requestedModel: string; requestedReasoningEffort: string }>,
347
280
  options: { id?: () => string; now?: () => string; monotonicNow?: () => number } = {}
348
281
  ): DelegationReceipt {
349
282
  const monotonic = (options.monotonicNow ?? (() => Number(process.hrtime.bigint() / 1_000_000n)))();
350
283
  return Object.freeze({
351
284
  ...input,
285
+ adapterEvidence: Object.freeze({ ...(input.adapterEvidence ?? {}) }),
352
286
  id: (options.id ?? randomUUID)(),
353
287
  actualModel: null,
354
288
  actualReasoningEffort: null,
@@ -359,7 +293,6 @@ function prepareDelegation(
359
293
  spawnMode: null,
360
294
  agent: null,
361
295
  status: 'prepared' as const,
362
- hostEvidence: null,
363
296
  afterFingerprint: null,
364
297
  changedPaths: Object.freeze([]),
365
298
  createdAt: (options.now ?? (() => new Date().toISOString()))(),
@@ -409,27 +342,18 @@ function activateDelegation(
409
342
  actualReasoningEffort?: string;
410
343
  modelFallbackReason?: string;
411
344
  reasoningEffortFallbackReason?: string;
412
- hostEvidence?: Readonly<{
413
- kind: 'codex-lifecycle-v1' | 'codex-lifecycle-v2';
414
- hookDefinitionHash: string;
415
- startRevision: number;
416
- protocolVersion?: number;
417
- packageVersion?: string;
418
- internalExecutableBuildHash?: string;
419
- lifecycleContractHash?: string;
420
- hookSource?: 'project' | 'managed' | 'isolated-user';
421
- hookSourcePathDigest?: string;
422
- hookSourceHash?: string;
423
- capabilitySessionId?: string;
424
- capabilityTurnId?: string;
425
- capabilityToolUseId?: string;
426
- spawnToolUseId?: string;
427
- spawnObservedAt?: string;
428
- controllerInstanceDigest?: string | null;
429
- controlGeneration?: string | null;
430
- }>;
345
+ clientEvidence?: unknown;
431
346
  }>,
432
- options: { now?: () => string; monotonicNow?: () => number } = {}
347
+ options: {
348
+ now?: () => string;
349
+ monotonicNow?: () => number;
350
+ evidencePolicy?: Readonly<{
351
+ spawnModeRequired?: boolean;
352
+ actualModelRequired?: boolean;
353
+ actualReasoningEffortRequired?: boolean;
354
+ fallbackReasonRequired?: boolean;
355
+ }>;
356
+ } = {}
433
357
  ): ReceiptResult {
434
358
  const managedRole = managedDelegationRole(event.nativeAgent);
435
359
  if (!managedRole) return fail('DELEGATION_IGNORED', `subagent '${event.nativeAgent}' is not lifecycle-managed`);
@@ -453,34 +377,29 @@ function activateDelegation(
453
377
  if (!event.parentId || (receipt.parentId !== null && event.parentId !== receipt.parentId) || !event.childId || event.childId === event.parentId) {
454
378
  return fail('DELEGATION_IDENTITY_INVALID', 'native parent/child identity does not match the prepared delegation');
455
379
  }
456
- // spawnMode 的必需性按 client 判断:claude-code 宿主结构上不提供该字段,跳过;其余 client 维持硬校验
457
- if (receipt.client !== 'claude-code' && event.spawnMode !== 'fresh') {
380
+ const evidencePolicy = options.evidencePolicy ?? {};
381
+ if (evidencePolicy.spawnModeRequired !== false && event.spawnMode !== 'fresh') {
458
382
  return fail('DELEGATION_FORK_FORBIDDEN', `spawn mode '${event.spawnMode}' is not fresh`);
459
383
  }
460
- // model/effort 维度:claude-code 如实记录、不做门禁;其余 client 维持现状硬校验(HDR-2)
461
384
  const actualModel = foldBlankToNull(event.actualModel);
462
385
  const actualReasoningEffort = foldBlankToNull(event.actualReasoningEffort);
463
- if (receipt.client !== 'claude-code') {
386
+ if (evidencePolicy.actualModelRequired !== false) {
464
387
  if (!actualModel || actualModel.trim() !== actualModel) {
465
388
  return fail('DELEGATION_MODEL_IDENTITY_MISSING', 'native start event must provide a non-empty actual model identity');
466
389
  }
467
390
  }
468
391
  if (actualModel !== null && actualModel !== receipt.requestedModel) {
469
- if (receipt.client !== 'claude-code' && (!event.modelFallbackReason || event.modelFallbackReason.trim() === '')) {
392
+ if (evidencePolicy.fallbackReasonRequired !== false && (!event.modelFallbackReason || event.modelFallbackReason.trim() === '')) {
470
393
  return fail('DELEGATION_MODEL_FALLBACK_UNRECORDED', 'actual model differs from requested model without a fallback reason');
471
394
  }
472
395
  }
473
- if (receipt.client !== 'claude-code') {
396
+ if (evidencePolicy.actualReasoningEffortRequired !== false) {
474
397
  if (!actualReasoningEffort || actualReasoningEffort.trim() !== actualReasoningEffort) {
475
398
  return fail('DELEGATION_REASONING_EFFORT_MISSING', 'native start event must provide a non-empty actual reasoning effort');
476
399
  }
477
400
  }
478
- if (
479
- receipt.client === 'codex'
480
- && event.hostEvidence?.kind !== 'codex-lifecycle-v2'
481
- ) return fail('DELEGATION_HOST_EVIDENCE_REQUIRED', 'Codex activation requires trusted lifecycle-v2 host evidence');
482
401
  if (actualReasoningEffort !== null && actualReasoningEffort !== receipt.requestedReasoningEffort) {
483
- if (receipt.client !== 'claude-code' && (!event.reasoningEffortFallbackReason || event.reasoningEffortFallbackReason.trim() === '')) {
402
+ if (evidencePolicy.fallbackReasonRequired !== false && (!event.reasoningEffortFallbackReason || event.reasoningEffortFallbackReason.trim() === '')) {
484
403
  return fail('DELEGATION_REASONING_EFFORT_FALLBACK_UNRECORDED', 'actual reasoning effort differs from requested effort without a fallback reason');
485
404
  }
486
405
  }
@@ -495,19 +414,8 @@ function activateDelegation(
495
414
  ) {
496
415
  return fail('DELEGATION_REASONING_EFFORT_FALLBACK_INVALID', 'reasoning-effort fallback reason is only valid when actual effort differs');
497
416
  }
498
- if (
499
- event.hostEvidence
500
- && (
501
- receipt.client !== 'codex'
502
- || !event.hostEvidence.hookDefinitionHash.trim()
503
- || !Number.isSafeInteger(event.hostEvidence.startRevision)
504
- || event.hostEvidence.startRevision < 1
505
- )
506
- ) return fail('DELEGATION_HOST_EVIDENCE_INVALID', 'Codex start evidence reference is invalid');
507
- if (event.hostEvidence?.kind === 'codex-lifecycle-v2'
508
- && event.hostEvidence.capabilitySessionId !== event.parentId) {
509
- return fail('DELEGATION_HOST_EVIDENCE_INVALID', 'Codex start evidence does not match the observed parent');
510
- }
417
+ const adapterEvidence = withClientEvidence(receipt, event.clientEvidence);
418
+ if (!adapterEvidence) return fail('DELEGATION_ADAPTER_EVIDENCE_INVALID', 'adapter evidence must be a JSON object');
511
419
  return { ok: true, receipt: Object.freeze({
512
420
  ...receipt,
513
421
  status: 'activated',
@@ -518,12 +426,7 @@ function activateDelegation(
518
426
  actualReasoningEffort,
519
427
  modelFallbackReason: event.modelFallbackReason ?? null,
520
428
  reasoningEffortFallbackReason: event.reasoningEffortFallbackReason ?? null,
521
- hostEvidence: event.hostEvidence ? Object.freeze({
522
- ...event.hostEvidence,
523
- stopRevision: null,
524
- consumer: null,
525
- consumedAt: null
526
- }) : receipt.hostEvidence ?? null,
429
+ adapterEvidence,
527
430
  startEvidenceMonotonicMs: monotonic,
528
431
  activatedMonotonicMs: (options.monotonicNow ?? (() => Number(process.hrtime.bigint() / 1_000_000n)))(),
529
432
  activatedAt: new Date(wallNow).toISOString()
@@ -542,31 +445,14 @@ function abortPreparedDelegation(receipt: DelegationReceipt): ReceiptResult {
542
445
 
543
446
  function abortActivatedDelegation(
544
447
  receipt: DelegationReceipt,
545
- event: Readonly<{ childId: string; stopRevision: number; consumer: string; consumedAt: string }>
448
+ event: Readonly<{ childId: string; clientEvidence?: unknown }>
546
449
  ): ReceiptResult {
547
450
  if (receipt.status !== 'activated') {
548
451
  return fail('DELEGATION_STATE_INVALID', `delegation ${receipt.id} is ${receipt.status}, expected activated`);
549
452
  }
550
- if (
551
- receipt.client !== 'codex'
552
- || !receipt.lifecycleProvenance
553
- || receipt.hostEvidence?.kind !== 'codex-lifecycle-v2'
554
- || typeof receipt.lifecycleProvenance.controllerInstanceDigest !== 'string'
555
- || typeof receipt.lifecycleProvenance.controlGeneration !== 'string'
556
- || typeof receipt.hostEvidence.controllerInstanceDigest !== 'string'
557
- || typeof receipt.hostEvidence.controlGeneration !== 'string'
558
- ) {
559
- return fail('DELEGATION_RECOVERY_EVIDENCE_INVALID', 'activated delegation lacks the current Codex controller binding');
560
- }
561
- if (
562
- event.childId !== receipt.childId
563
- || !Number.isSafeInteger(event.stopRevision)
564
- || event.stopRevision <= receipt.hostEvidence.startRevision
565
- || event.consumer !== receipt.id
566
- || !exactText(event.consumedAt)
567
- ) {
568
- return fail('DELEGATION_RECOVERY_EVIDENCE_INVALID', 'recovery stop evidence does not match the activated delegation');
569
- }
453
+ if (event.childId !== receipt.childId) return fail('DELEGATION_RECOVERY_EVIDENCE_INVALID', 'recovery child does not match the activated delegation');
454
+ const adapterEvidence = withClientEvidence(receipt, event.clientEvidence);
455
+ if (!adapterEvidence) return fail('DELEGATION_ADAPTER_EVIDENCE_INVALID', 'adapter evidence must be a JSON object');
570
456
  return {
571
457
  ok: true,
572
458
  receipt: Object.freeze({
@@ -577,12 +463,7 @@ function abortActivatedDelegation(
577
463
  changedPaths: Object.freeze([]),
578
464
  sealedAt: null,
579
465
  consumedAt: null,
580
- hostEvidence: Object.freeze({
581
- ...receipt.hostEvidence,
582
- stopRevision: event.stopRevision,
583
- consumer: event.consumer,
584
- consumedAt: event.consumedAt
585
- })
466
+ adapterEvidence
586
467
  })
587
468
  };
588
469
  }
@@ -612,34 +493,14 @@ function sealDelegation(
612
493
  actualReasoningEffort?: string;
613
494
  modelFallbackReason?: string;
614
495
  reasoningEffortFallbackReason?: string;
615
- hostEvidence?: Readonly<{ stopRevision: number; consumer: string; consumedAt: string }>;
496
+ clientEvidence?: unknown;
616
497
  }>,
617
- options: { now?: () => string; requireHostEvidence?: boolean } = {}
498
+ options: { now?: () => string } = {}
618
499
  ): ReceiptResult {
619
- if (receipt.status !== 'stage-completed') return fail('DELEGATION_STATE_INVALID', `delegation ${receipt.id} is ${receipt.status}, expected stage-completed`);
620
- if (receipt.client === 'codex' && options.requireHostEvidence !== false && !event.hostEvidence) {
621
- return fail('DELEGATION_HOST_EVIDENCE_REQUIRED', 'Codex sealing requires consumed lifecycle-v2 host evidence');
622
- }
623
- if (event.childId !== receipt.childId || event.exitCode !== 0) return fail('DELEGATION_STOP_INVALID', 'native stop identity or exit status is invalid');
624
- if (receipt.role === 'reviewer') {
625
- const taskRoot = `.agents/workspace/active/${receipt.taskId}/`;
626
- const allowed = new Set([
627
- `${taskRoot}${receipt.artifact}`,
628
- `${taskRoot}task.md`,
629
- `${taskRoot}orchestration.json`
630
- ]);
631
- const disallowed = event.changedPaths.find((entry) => !allowed.has(entry));
632
- if (disallowed) return fail('DELEGATION_REVIEWER_WRITE_FORBIDDEN', `reviewer changed forbidden path '${disallowed}'`);
633
- }
634
- if (event.hostEvidence) {
635
- if (
636
- !receipt.hostEvidence
637
- || !Number.isSafeInteger(event.hostEvidence.stopRevision)
638
- || event.hostEvidence.stopRevision <= receipt.hostEvidence.startRevision
639
- || event.hostEvidence.consumer !== receipt.id
640
- || !event.hostEvidence.consumedAt.trim()
641
- ) return fail('DELEGATION_HOST_EVIDENCE_INVALID', 'Codex stop evidence reference is invalid');
642
- }
500
+ const validationFailure = validateSealRequest(receipt, event);
501
+ if (validationFailure) return validationFailure;
502
+ const adapterEvidence = withClientEvidence(receipt, event.clientEvidence);
503
+ if (!adapterEvidence) return fail('DELEGATION_ADAPTER_EVIDENCE_INVALID', 'adapter evidence must be a JSON object');
643
504
  return { ok: true, receipt: Object.freeze({
644
505
  ...receipt,
645
506
  status: 'sealed',
@@ -649,10 +510,7 @@ function sealDelegation(
649
510
  actualReasoningEffort: receipt.actualReasoningEffort ?? foldBlankToNull(event.actualReasoningEffort),
650
511
  modelFallbackReason: receipt.modelFallbackReason ?? event.modelFallbackReason ?? null,
651
512
  reasoningEffortFallbackReason: receipt.reasoningEffortFallbackReason ?? event.reasoningEffortFallbackReason ?? null,
652
- hostEvidence: event.hostEvidence && receipt.hostEvidence ? Object.freeze({
653
- ...receipt.hostEvidence,
654
- ...event.hostEvidence
655
- }) : receipt.hostEvidence ?? null,
513
+ adapterEvidence,
656
514
  sealedAt: (options.now ?? (() => new Date().toISOString()))()
657
515
  }) };
658
516
  }
@@ -681,8 +539,6 @@ export {
681
539
  sealDelegation
682
540
  };
683
541
  export type {
684
- DelegationHostEvidence,
685
- DelegationLifecycleProvenance,
686
542
  DelegationReceipt,
687
543
  DelegationRole,
688
544
  DelegationStage,