@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
@@ -0,0 +1,158 @@
1
+ import { SandboxControlClientError, requestSandboxAgentOperation } from '../../../sandbox/control/client.ts';
2
+ import type { SandboxControlResponse } from '../../../sandbox/control/protocol.ts';
3
+ import type { ProcessIdentity } from '../../../server/process-state.ts';
4
+ import type { CodexControllerLeaseProofV1, CodexControllerOpened } from './controller-registration.ts';
5
+
6
+ type ControllerClosed = Readonly<{ version: 1; status: 'closed'; changed: boolean; lease: null; error: null }>;
7
+ type ControllerVerified = Readonly<{
8
+ version: 1; status: 'verified'; changed: false; lease: null;
9
+ binding: Readonly<{ taskId: string; controlGeneration: string; controllerInstanceDigest: string }>;
10
+ error: null;
11
+ }>;
12
+ type ControllerResult = CodexControllerOpened | ControllerClosed | ControllerVerified;
13
+
14
+ function fail(code: string, message: string, accepted = true): never {
15
+ throw new SandboxControlClientError({ code, message: `${code}: ${message}`, retryable: false }, accepted);
16
+ }
17
+
18
+ function exactKeys(value: Record<string, unknown>, keys: readonly string[]): boolean {
19
+ return Object.keys(value).sort().join(',') === [...keys].sort().join(',');
20
+ }
21
+
22
+ function validProcess(value: unknown): value is ProcessIdentity {
23
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
24
+ const process = value as Record<string, unknown>;
25
+ return exactKeys(process, ['pid', 'startTime'])
26
+ && Number.isSafeInteger(process.pid) && (process.pid as number) > 0
27
+ && Number.isSafeInteger(process.startTime) && (process.startTime as number) >= 0;
28
+ }
29
+
30
+ function validBuild(value: unknown): boolean {
31
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
32
+ const build = value as Record<string, unknown>;
33
+ return exactKeys(build, ['internalExecutableBuildHash', 'lifecycleContractHash', 'packageVersion', 'protocolVersion'])
34
+ && build.protocolVersion === 3
35
+ && typeof build.packageVersion === 'string' && build.packageVersion.length > 0
36
+ && typeof build.internalExecutableBuildHash === 'string' && /^[a-f0-9]{64}$/u.test(build.internalExecutableBuildHash)
37
+ && typeof build.lifecycleContractHash === 'string' && /^[a-f0-9]{64}$/u.test(build.lifecycleContractHash);
38
+ }
39
+
40
+ function parseCodexControllerResult(response: SandboxControlResponse): ControllerResult {
41
+ if (response.phase !== 'completed' || response.error !== null || response.stderr !== '') {
42
+ fail('SANDBOX_CONTROL_RESULT_INVALID', 'controller result outer response is invalid');
43
+ }
44
+ let value: unknown;
45
+ try {
46
+ if (!response.stdout.endsWith('\n') || response.stdout.slice(0, -1).includes('\n')) throw new Error('not canonical');
47
+ value = JSON.parse(response.stdout);
48
+ } catch {
49
+ fail('SANDBOX_CONTROL_RESULT_INVALID', 'controller result payload is invalid');
50
+ }
51
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
52
+ fail('SANDBOX_CONTROL_RESULT_INVALID', 'controller result payload is invalid');
53
+ }
54
+ const result = value as Record<string, unknown>;
55
+ const keys = result.status === 'verified'
56
+ ? ['binding', 'changed', 'error', 'lease', 'status', 'version']
57
+ : ['changed', 'error', 'lease', 'status', 'version'];
58
+ if (!exactKeys(result, keys) || result.version !== 1
59
+ || !['opened', 'closed', 'failed', 'verified'].includes(result.status as string)) {
60
+ fail('SANDBOX_CONTROL_RESULT_INVALID', 'controller result schema is invalid');
61
+ }
62
+ if (result.status === 'failed') {
63
+ const error = result.error as Record<string, unknown> | null;
64
+ if (response.exitCode !== 1 || result.changed !== false || result.lease !== null || !error
65
+ || !exactKeys(error, ['code', 'message', 'retryable'])
66
+ || typeof error.code !== 'string' || !/^[A-Z][A-Z0-9_]+$/u.test(error.code)
67
+ || typeof error.message !== 'string' || error.retryable !== false) {
68
+ fail('SANDBOX_CONTROL_RESULT_INVALID', 'controller failure result is invalid');
69
+ }
70
+ fail(error.code, error.message);
71
+ }
72
+ if (response.exitCode !== 0 || result.error !== null) {
73
+ fail('SANDBOX_CONTROL_RESULT_INVALID', 'controller success result is invalid');
74
+ }
75
+ if (result.status === 'closed') {
76
+ if (typeof result.changed !== 'boolean' || result.lease !== null) fail('SANDBOX_CONTROL_RESULT_INVALID', 'controller close result is invalid');
77
+ return result as unknown as ControllerClosed;
78
+ }
79
+ if (result.status === 'verified') {
80
+ const binding = result.binding as Record<string, unknown> | null;
81
+ if (result.changed !== false || result.lease !== null || !binding
82
+ || !exactKeys(binding, ['controllerInstanceDigest', 'controlGeneration', 'taskId'])
83
+ || typeof binding.taskId !== 'string' || binding.taskId.length === 0
84
+ || typeof binding.controlGeneration !== 'string' || binding.controlGeneration.length === 0
85
+ || typeof binding.controllerInstanceDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(binding.controllerInstanceDigest)) {
86
+ fail('SANDBOX_CONTROL_RESULT_INVALID', 'controller verify result is invalid');
87
+ }
88
+ return result as unknown as ControllerVerified;
89
+ }
90
+ const lease = result.lease as Record<string, unknown> | null;
91
+ if (result.changed !== true || !lease
92
+ || !exactKeys(lease, ['buildIdentity', 'controlGeneration', 'controllerInstanceDigest', 'controllerProcess', 'expiresAt', 'issuedAt', 'leaseId', 'leaseSecret', 'taskId', 'version'])
93
+ || lease.version !== 1 || typeof lease.leaseId !== 'string' || !/^[a-f0-9]{64}$/u.test(lease.leaseId)
94
+ || typeof lease.leaseSecret !== 'string' || !/^[a-f0-9]{64}$/u.test(lease.leaseSecret)
95
+ || typeof lease.taskId !== 'string' || lease.taskId.length === 0
96
+ || typeof lease.controlGeneration !== 'string' || lease.controlGeneration.length === 0
97
+ || typeof lease.controllerInstanceDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(lease.controllerInstanceDigest)
98
+ || !validProcess(lease.controllerProcess) || !validBuild(lease.buildIdentity)
99
+ || !Number.isSafeInteger(lease.issuedAt) || !Number.isSafeInteger(lease.expiresAt)
100
+ || (lease.expiresAt as number) <= (lease.issuedAt as number)) {
101
+ fail('SANDBOX_CONTROL_RESULT_INVALID', 'controller open result is invalid');
102
+ }
103
+ return result as unknown as CodexControllerOpened;
104
+ }
105
+
106
+ function requestController(params: Readonly<{
107
+ command: 'open' | 'close' | 'verify';
108
+ controllerProcess: ProcessIdentity;
109
+ controllerProof: CodexControllerLeaseProofV1 | null;
110
+ channelDir?: string;
111
+ statusDir?: string;
112
+ token?: string;
113
+ generation?: string;
114
+ timeoutMs?: number;
115
+ }>): ControllerResult {
116
+ const result = parseCodexControllerResult(requestSandboxAgentOperation({
117
+ agentClient: 'codex',
118
+ operation: `controller.${params.command}`,
119
+ payload: params.command === 'open'
120
+ ? { controllerProcess: params.controllerProcess }
121
+ : { proof: params.controllerProof },
122
+ ...params
123
+ }));
124
+ if (result.status === 'opened'
125
+ && (result.lease.controlGeneration !== (params.generation ?? process.env.AGENT_INFRA_CONTROL_GENERATION)
126
+ || result.lease.controllerProcess.pid !== params.controllerProcess.pid
127
+ || result.lease.controllerProcess.startTime !== params.controllerProcess.startTime)) {
128
+ fail('SANDBOX_CONTROL_RESULT_INVALID', 'controller result does not match the request');
129
+ }
130
+ return result;
131
+ }
132
+
133
+ function requestCodexControllerOpen(params: Omit<Parameters<typeof requestController>[0], 'command' | 'controllerProof'>): CodexControllerOpened {
134
+ const result = requestController({ ...params, command: 'open', controllerProof: null });
135
+ if (result.status !== 'opened') fail('SANDBOX_CONTROL_RESULT_INVALID', 'controller open returned the wrong result');
136
+ return result;
137
+ }
138
+
139
+ function requestCodexControllerClose(params: Omit<Parameters<typeof requestController>[0], 'command'>): ControllerClosed {
140
+ const result = requestController({ ...params, command: 'close' });
141
+ if (result.status !== 'closed') fail('SANDBOX_CONTROL_RESULT_INVALID', 'controller close returned the wrong result');
142
+ return result;
143
+ }
144
+
145
+ function requestCodexControllerVerify(params: Readonly<{
146
+ controllerProof: CodexControllerLeaseProofV1;
147
+ channelDir?: string;
148
+ statusDir?: string;
149
+ token?: string;
150
+ generation?: string;
151
+ timeoutMs?: number;
152
+ }>): ControllerVerified {
153
+ const result = requestController({ ...params, controllerProcess: params.controllerProof.controllerProcess, command: 'verify' });
154
+ if (result.status !== 'verified') fail('SANDBOX_CONTROL_RESULT_INVALID', 'controller verify returned the wrong result');
155
+ return result;
156
+ }
157
+
158
+ export { parseCodexControllerResult, requestCodexControllerClose, requestCodexControllerOpen, requestCodexControllerVerify };
@@ -3,7 +3,7 @@ import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
 
5
5
  import { getProcessIdentityState, type ProcessIdentity } from '../../../server/process-state.ts';
6
- import type { CodexControllerLeaseProofV1, CodexControllerLeaseV1 } from '../../../sandbox/control/controller-registration.ts';
6
+ import type { CodexControllerLeaseProofV1, CodexControllerLeaseV1 } from './controller-registration.ts';
7
7
  import {
8
8
  computeLifecycleBuildIdentity,
9
9
  verifyLifecycleBuildIdentity,
@@ -0,0 +1,151 @@
1
+ import type { ProcessIdentity } from '../../../server/process-state.ts';
2
+ import {
3
+ closeCodexControllerRegistration,
4
+ CodexControllerRegistrationError,
5
+ openCodexControllerRegistration,
6
+ readCodexControllerRegistration,
7
+ resolveCodexControllerBinding,
8
+ type CodexControllerLeaseProofV1
9
+ } from './controller-registration.ts';
10
+ import { computeLifecycleBuildIdentity } from './build-identity.ts';
11
+
12
+ function validProcess(value: unknown): value is ProcessIdentity {
13
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
14
+ const process = value as Record<string, unknown>;
15
+ return Object.keys(process).sort().join(',') === 'pid,startTime'
16
+ && Number.isSafeInteger(process.pid) && (process.pid as number) > 0
17
+ && Number.isSafeInteger(process.startTime) && (process.startTime as number) >= 0;
18
+ }
19
+
20
+ function validProof(value: unknown): value is CodexControllerLeaseProofV1 {
21
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
22
+ const proof = value as Record<string, unknown>;
23
+ return Object.keys(proof).sort().join(',') === 'controllerProcess,leaseId,leaseSecret,version'
24
+ && proof.version === 1
25
+ && typeof proof.leaseId === 'string' && /^[a-f0-9]{64}$/u.test(proof.leaseId)
26
+ && typeof proof.leaseSecret === 'string' && /^[a-f0-9]{64}$/u.test(proof.leaseSecret)
27
+ && validProcess(proof.controllerProcess);
28
+ }
29
+
30
+ function validate(params: Readonly<{
31
+ operation: string;
32
+ payload: Record<string, unknown>;
33
+ manifest: { mode: string; taskId: string | null };
34
+ }>): void {
35
+ const { operation, payload, manifest } = params;
36
+ const expectedKeys = operation === 'controller.open' ? ['controllerProcess'] : ['proof'];
37
+ if (!['controller.open', 'controller.close', 'controller.verify'].includes(operation)
38
+ || Object.keys(payload).sort().join(',') !== expectedKeys.sort().join(',')
39
+ || (operation === 'controller.open'
40
+ ? !validProcess(payload.controllerProcess)
41
+ : !validProof(payload.proof))) {
42
+ throw new Error('SANDBOX_CONTROL_REQUEST_INVALID: controller operation payload is invalid');
43
+ }
44
+ if (manifest.mode !== 'task-bound' || !manifest.taskId) {
45
+ throw new Error('SANDBOX_CONTROL_BRANCH_ONLY: controller operations require a task-bound sandbox');
46
+ }
47
+ }
48
+
49
+ async function execute(params: Readonly<{
50
+ operation: string;
51
+ payload: Record<string, unknown>;
52
+ manifest: Parameters<typeof openCodexControllerRegistration>[0]['manifest'];
53
+ manifestPath: string;
54
+ }>): Promise<Readonly<{ exitCode: number; stdout: string; stderr: string }>> {
55
+ const { operation, payload, manifest, manifestPath } = params;
56
+ try {
57
+ const result = operation === 'controller.open'
58
+ ? openCodexControllerRegistration({
59
+ manifest,
60
+ manifestPath,
61
+ controllerProcess: payload.controllerProcess as ProcessIdentity,
62
+ buildIdentity: computeLifecycleBuildIdentity(manifest.repoRoot)
63
+ })
64
+ : operation === 'controller.close'
65
+ ? closeCodexControllerRegistration({
66
+ manifest,
67
+ manifestPath,
68
+ proof: payload.proof as CodexControllerLeaseProofV1
69
+ })
70
+ : (() => {
71
+ const binding = resolveCodexControllerBinding({
72
+ manifest,
73
+ manifestPath,
74
+ proof: payload.proof as CodexControllerLeaseProofV1,
75
+ buildIdentity: computeLifecycleBuildIdentity(manifest.repoRoot)
76
+ });
77
+ return {
78
+ version: 1,
79
+ status: 'verified',
80
+ changed: false,
81
+ lease: null,
82
+ binding: {
83
+ taskId: manifest.taskId,
84
+ controlGeneration: binding.controlGeneration,
85
+ controllerInstanceDigest: binding.instanceDigest
86
+ },
87
+ error: null
88
+ };
89
+ })();
90
+ return { exitCode: 0, stdout: `${JSON.stringify(result)}\n`, stderr: '' };
91
+ } catch (error) {
92
+ const message = error instanceof Error ? error.message : String(error);
93
+ const code = error instanceof CodexControllerRegistrationError
94
+ ? error.code
95
+ : /^([A-Z][A-Z0-9_]+)/u.exec(message)?.[1] ?? 'CODEX_SANDBOX_CONTROLLER_FAILED';
96
+ return {
97
+ exitCode: 1,
98
+ stdout: `${JSON.stringify({
99
+ version: 1,
100
+ status: 'failed',
101
+ changed: false,
102
+ lease: null,
103
+ error: { code, message, retryable: false }
104
+ })}\n`,
105
+ stderr: ''
106
+ };
107
+ }
108
+ }
109
+
110
+ function recover(params: Readonly<{
111
+ operation: string;
112
+ manifest: Parameters<typeof openCodexControllerRegistration>[0]['manifest'];
113
+ manifestPath: string;
114
+ stdout: string | null;
115
+ }>): Readonly<Record<string, unknown>> {
116
+ if (params.stdout === null) return { consistent: false };
117
+ let output: Record<string, unknown>;
118
+ try {
119
+ const parsed: unknown = JSON.parse(params.stdout);
120
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return { consistent: false };
121
+ output = parsed as Record<string, unknown>;
122
+ } catch {
123
+ return { consistent: false };
124
+ }
125
+ if (output.error !== null || typeof output.status !== 'string') return { consistent: false };
126
+ try {
127
+ const registration = readCodexControllerRegistration(params.manifestPath);
128
+ if (params.operation === 'controller.open') {
129
+ const lease = output.lease as Record<string, unknown> | null;
130
+ return { consistent: output.status === 'opened' && output.changed === true
131
+ && lease?.controlGeneration === registration.controlGeneration
132
+ && lease?.controllerInstanceDigest === registration.controllerInstanceDigest
133
+ && registration.controlGeneration === params.manifest.generation };
134
+ }
135
+ if (params.operation === 'controller.verify') {
136
+ const binding = output.binding as Record<string, unknown> | null;
137
+ return { consistent: output.status === 'verified' && output.changed === false
138
+ && binding?.controlGeneration === registration.controlGeneration
139
+ && binding?.controllerInstanceDigest === registration.controllerInstanceDigest };
140
+ }
141
+ return { consistent: false };
142
+ } catch (error) {
143
+ return { consistent: params.operation === 'controller.close'
144
+ && (error as { code?: string }).code === 'CODEX_SANDBOX_CONTROLLER_REGISTRATION_MISSING'
145
+ && output.status === 'closed' && typeof output.changed === 'boolean' };
146
+ }
147
+ }
148
+
149
+ const codexControllerOperation = Object.freeze({ validate, execute, recover });
150
+
151
+ export { codexControllerOperation };
@@ -2,10 +2,10 @@ import crypto from 'node:crypto';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
 
5
- import type { LifecycleBuildIdentity } from '../../agent-clients/adapters/codex-lifecycle/build-identity.ts';
6
- import { parseLinuxProcessStat, type ProcessIdentity, type ProcessIdentityState } from '../../server/process-state.ts';
7
- import { commandForEngine, runProbe } from '../shell.ts';
8
- import type { SandboxControlManifest } from './protocol.ts';
5
+ import type { LifecycleBuildIdentity } from './build-identity.ts';
6
+ import { parseLinuxProcessStat, type ProcessIdentity, type ProcessIdentityState } from '../../../server/process-state.ts';
7
+ import { commandForEngine, runProbe } from '../../../sandbox/shell.ts';
8
+ import type { SandboxControlManifest } from '../../../sandbox/control/protocol.ts';
9
9
 
10
10
  const CONTROLLER_TTL_MS = 4 * 60 * 60 * 1_000;
11
11
  const HEX_256 = /^[a-f0-9]{64}$/u;
@@ -1,3 +1,5 @@
1
+ import type { CodexLifecycleTaskBinding } from './binding.ts';
2
+
1
3
  type CodexEvidenceSource =
2
4
  | 'codex-hook'
3
5
  | 'codex-app-server-thread'
@@ -15,6 +17,7 @@ type CodexLifecycleEvent =
15
17
  requestedModel?: string;
16
18
  requestedReasoningEffort?: string;
17
19
  hookDefinitionHash: string;
20
+ taskBinding?: CodexLifecycleTaskBinding;
18
21
  }>
19
22
  | Readonly<{
20
23
  type: 'hook-child';
@@ -98,6 +101,44 @@ type CodexLifecycleStatus =
98
101
 
99
102
  type CodexEvidenceError = Readonly<{ code: string; message: string }>;
100
103
 
104
+ function isCodexLifecycleActivationEvidence(value: unknown, parentId: string): boolean {
105
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
106
+ const state = value as Record<string, unknown>;
107
+ const evidence = state.activationEvidence;
108
+ if (!evidence || typeof evidence !== 'object' || Array.isArray(evidence)) return false;
109
+ const activation = evidence as Record<string, unknown>;
110
+ return activation.kind === 'codex-lifecycle-v2'
111
+ && typeof activation.hookDefinitionHash === 'string'
112
+ && activation.hookDefinitionHash.trim() !== ''
113
+ && Number.isSafeInteger(activation.startRevision)
114
+ && (activation.startRevision as number) >= 1
115
+ && activation.capabilitySessionId === parentId;
116
+ }
117
+
118
+ function isCodexDelegationReceiptEvidence(value: unknown): boolean {
119
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
120
+ const receipt = value as Record<string, unknown>;
121
+ if (receipt.client !== 'codex' || typeof receipt.id !== 'string') return false;
122
+ const status = receipt.status;
123
+ if (!['activated', 'stage-completed', 'sealed', 'consumed', 'aborted'].includes(status as string)) return true;
124
+ const adapterEvidence = receipt.adapterEvidence;
125
+ if (!adapterEvidence || typeof adapterEvidence !== 'object' || Array.isArray(adapterEvidence)) return false;
126
+ const codex = (adapterEvidence as Record<string, unknown>).codex;
127
+ if (!codex || typeof codex !== 'object' || Array.isArray(codex)) return false;
128
+ const state = codex as Record<string, unknown>;
129
+ if (!isCodexLifecycleActivationEvidence(state, String(receipt.parentId ?? ''))) return false;
130
+ if (!['sealed', 'consumed', 'aborted'].includes(status as string)) return true;
131
+ const records = state.records;
132
+ if (!records || typeof records !== 'object' || Array.isArray(records)) return false;
133
+ return Object.values(records as Record<string, unknown>).some((record) => {
134
+ if (!record || typeof record !== 'object' || Array.isArray(record)) return false;
135
+ const evidence = record as Record<string, unknown>;
136
+ return evidence.consumer === receipt.id
137
+ && typeof evidence.consumedAt === 'string'
138
+ && evidence.consumedAt.trim() !== '';
139
+ });
140
+ }
141
+
101
142
  type CodexLifecycleState = Readonly<{
102
143
  schemaVersion: 1;
103
144
  cliVersion: string;
@@ -329,6 +370,8 @@ function expireCodexLifecycleState(state: CodexLifecycleState): CodexLifecycleSt
329
370
  export {
330
371
  createCodexLifecycleState,
331
372
  expireCodexLifecycleState,
373
+ isCodexLifecycleActivationEvidence,
374
+ isCodexDelegationReceiptEvidence,
332
375
  reduceCodexLifecycleEvent
333
376
  };
334
377
  export type {
@@ -1,25 +1,28 @@
1
1
  {
2
2
  "executableFiles": [
3
3
  "bin/internal-cli.ts",
4
- "lib/internal/codex-lifecycle.ts",
5
- "lib/internal/codex-sandbox-controller.ts",
4
+ "lib/agent-clients/adapters/codex-lifecycle/cli.ts",
5
+ "lib/agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.ts",
6
6
  "lib/internal/sandbox-control.ts",
7
7
  "lib/internal/task-orchestration.ts",
8
- "lib/task/codex-orchestration.ts",
8
+ "lib/agent-clients/adapters/codex-orchestration.ts",
9
9
  "lib/task/delegation-receipts.ts",
10
10
  "lib/task/orchestration.ts",
11
11
  "lib/agent-clients/adapters/codex-lifecycle/build-identity.ts",
12
- "lib/agent-clients/adapters/codex-lifecycle/capability-store.ts",
13
12
  "lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts",
13
+ "lib/agent-clients/adapters/codex-lifecycle/controller-client.ts",
14
+ "lib/agent-clients/adapters/codex-lifecycle/controller-operation.ts",
14
15
  "lib/agent-clients/adapters/codex-lifecycle/controller-context.ts",
15
16
  "lib/agent-clients/adapters/codex-lifecycle/app-server.ts",
17
+ "lib/agent-clients/adapters/codex-lifecycle/binding.ts",
16
18
  "lib/agent-clients/adapters/codex-lifecycle/evidence.ts",
17
19
  "lib/agent-clients/adapters/codex-lifecycle/store.ts",
18
20
  "lib/agent-clients/adapters/codex-lifecycle/manifest-files.json",
21
+ "lib/agent-clients/adapters/codex-lifecycle/controller-registration.ts",
19
22
  "lib/sandbox/control/protocol.ts",
23
+ "lib/sandbox/control/agent-operation.ts",
20
24
  "lib/sandbox/control/client.ts",
21
25
  "lib/sandbox/control/executor.ts",
22
- "lib/sandbox/control/controller-registration.ts",
23
26
  "lib/server/process-state.ts",
24
27
  "lib/sandbox/shell.ts"
25
28
  ],