@fitlab-ai/agent-infra 0.9.14 → 0.9.15

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 (250) hide show
  1. package/README.md +10 -0
  2. package/README.zh-CN.md +10 -0
  3. package/bin/internal-cli.sh +45 -0
  4. package/dist/bin/internal-cli.js +93 -30
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -2
  7. package/dist/lib/defaults.json +103 -103
  8. package/dist/lib/fs/durable-write.js +37 -0
  9. package/dist/lib/fs/file-lock.js +111 -0
  10. package/dist/lib/host-control/audit.js +19 -0
  11. package/dist/lib/host-control/client.js +142 -0
  12. package/dist/lib/host-control/command.js +49 -0
  13. package/dist/lib/host-control/path.js +160 -0
  14. package/dist/lib/host-control/secure-fs.js +140 -0
  15. package/dist/lib/host-control/server.js +159 -0
  16. package/dist/lib/host-control/service.js +112 -0
  17. package/dist/lib/init.js +11 -2
  18. package/dist/lib/internal/cli-route-inventory.js +3 -1
  19. package/dist/lib/internal/codex-lifecycle.js +6 -6
  20. package/dist/lib/internal/demo-promotion.js +322 -0
  21. package/dist/lib/internal/demo-transcript.js +454 -0
  22. package/dist/lib/internal/host-control.js +28 -0
  23. package/dist/lib/internal/manual-validation.js +322 -0
  24. package/dist/lib/internal/platform-pr.js +18 -4
  25. package/dist/lib/internal/release-workflow.js +63 -84
  26. package/dist/lib/internal/sandbox-control.js +149 -2
  27. package/dist/lib/internal/task-artifact.js +15 -262
  28. package/dist/lib/internal/task-create.js +6 -5
  29. package/dist/lib/internal/task-event.js +37 -37
  30. package/dist/lib/internal/task-operation-registry.js +101 -3
  31. package/dist/lib/internal/task-review.js +8 -53
  32. package/dist/lib/internal/task-validate.js +30 -10
  33. package/dist/lib/owned-path.js +33 -0
  34. package/dist/lib/platform/check-status.js +11 -0
  35. package/dist/lib/platform/comment-safety.js +4 -1
  36. package/dist/lib/platform/completion-backfill.js +1 -1
  37. package/dist/lib/platform/github-data.js +241 -0
  38. package/dist/lib/platform/github-provider.js +15 -20
  39. package/dist/lib/platform/issue-comments.js +2 -2
  40. package/dist/lib/platform/issues.js +1 -179
  41. package/dist/lib/platform/pr-checks.js +12 -65
  42. package/dist/lib/platform/pr-summary.js +55 -3
  43. package/dist/lib/platform/pull-requests.js +2 -105
  44. package/dist/lib/platform/snapshots.js +2 -0
  45. package/dist/lib/platform/verification-sync.js +127 -142
  46. package/dist/lib/release/workflow.js +1 -1
  47. package/dist/lib/sandbox/commands/create.js +7 -2
  48. package/dist/lib/sandbox/commands/rm.js +3 -1514
  49. package/dist/lib/sandbox/control/audit.js +203 -0
  50. package/dist/lib/sandbox/control/client.js +93 -54
  51. package/dist/lib/sandbox/control/executor.js +129 -65
  52. package/dist/lib/sandbox/control/finalization-response.js +27 -0
  53. package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
  54. package/dist/lib/sandbox/control/lifecycle.js +36 -2
  55. package/dist/lib/sandbox/control/native-file-lock.js +6 -98
  56. package/dist/lib/sandbox/control/protocol.js +55 -4
  57. package/dist/lib/sandbox/control/server.js +491 -120
  58. package/dist/lib/sandbox/control/state.js +58 -0
  59. package/dist/lib/sandbox/control/task-workflow.js +74 -0
  60. package/dist/lib/sandbox/control/workflow-executor.js +99 -0
  61. package/dist/lib/sandbox/index.js +2 -2
  62. package/dist/lib/sandbox/recovery.js +28 -4
  63. package/dist/lib/sandbox/removal.js +1717 -0
  64. package/dist/lib/sandbox/task-cleanup.js +217 -0
  65. package/dist/lib/sandbox/workspace-view.js +13 -14
  66. package/dist/lib/task/activity-intent.js +2 -1
  67. package/dist/lib/task/activity-log.js +30 -24
  68. package/dist/lib/task/artifact-command.js +101 -0
  69. package/dist/lib/task/artifact-lifecycle.js +30 -28
  70. package/dist/lib/task/artifact-name.js +36 -0
  71. package/dist/lib/task/artifact-operations.js +23 -69
  72. package/dist/lib/task/artifact-receipts.js +3 -15
  73. package/dist/lib/task/artifact-repair-intent.js +87 -16
  74. package/dist/lib/task/artifact-schema.js +6 -14
  75. package/dist/lib/task/capabilities.js +12 -19
  76. package/dist/lib/task/codex-orchestration.js +71 -25
  77. package/dist/lib/task/commit-identity.js +28 -0
  78. package/dist/lib/task/commit-operation.js +2 -16
  79. package/dist/lib/task/control-authority.js +371 -5
  80. package/dist/lib/task/control-recovery.js +128 -0
  81. package/dist/lib/task/create.js +62 -9
  82. package/dist/lib/task/decision-details.js +2 -46
  83. package/dist/lib/task/events.js +120 -9
  84. package/dist/lib/task/finalization-state.js +9 -0
  85. package/dist/lib/task/human-override.js +3 -6
  86. package/dist/lib/task/implementation-inputs.js +3 -3
  87. package/dist/lib/task/intermediate-cleanup.js +383 -0
  88. package/dist/lib/task/invalidation.js +18 -65
  89. package/dist/lib/task/ledger-intents.js +7 -22
  90. package/dist/lib/task/ledger.js +1 -3
  91. package/dist/lib/task/lifecycle.js +19 -1
  92. package/dist/lib/task/local-artifact-finalization.js +132 -157
  93. package/dist/lib/task/manual-validation-completion.js +26 -0
  94. package/dist/lib/task/manual-validation-evidence.js +130 -0
  95. package/dist/lib/task/manual-validation-receipt.js +101 -0
  96. package/dist/lib/task/manual-validation-shared.js +40 -0
  97. package/dist/lib/task/manual-validation-transaction.js +243 -0
  98. package/dist/lib/task/markdown.js +47 -0
  99. package/dist/lib/task/orchestration.js +14 -17
  100. package/dist/lib/task/qualification-audit.js +20 -34
  101. package/dist/lib/task/review-artifacts.js +2 -18
  102. package/dist/lib/task/review-command.js +25 -0
  103. package/dist/lib/task/review-finalization.js +125 -193
  104. package/dist/lib/task/review-fingerprint.js +2 -2
  105. package/dist/lib/task/sections.js +34 -56
  106. package/dist/lib/task/verification-engine.js +116 -323
  107. package/dist/lib/task/verification.js +1 -1
  108. package/dist/lib/task/workflow-command.js +17 -0
  109. package/dist/lib/task/workflow-warning-intents.js +0 -16
  110. package/dist/lib/task/workflow-warnings.js +28 -2
  111. package/dist/lib/update.js +2 -0
  112. package/dist/lifecycle-build-manifest.json +3 -3
  113. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
  114. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -2
  115. package/lib/fs/durable-write.ts +29 -0
  116. package/lib/fs/file-lock.ts +113 -0
  117. package/lib/host-control/audit.ts +22 -0
  118. package/lib/host-control/client.ts +172 -0
  119. package/lib/host-control/command.ts +60 -0
  120. package/lib/host-control/path.ts +167 -0
  121. package/lib/host-control/secure-fs.ts +135 -0
  122. package/lib/host-control/server.ts +180 -0
  123. package/lib/host-control/service.ts +133 -0
  124. package/lib/init.ts +15 -2
  125. package/lib/internal/cli-route-inventory.ts +3 -1
  126. package/lib/internal/codex-lifecycle.ts +6 -6
  127. package/lib/internal/demo-promotion.ts +354 -0
  128. package/lib/internal/demo-transcript.ts +419 -0
  129. package/lib/internal/host-control.ts +28 -0
  130. package/lib/internal/manual-validation.ts +306 -0
  131. package/lib/internal/platform-pr.ts +15 -4
  132. package/lib/internal/release-workflow.ts +74 -82
  133. package/lib/internal/sandbox-control.ts +162 -1
  134. package/lib/internal/task-artifact.ts +13 -216
  135. package/lib/internal/task-create.ts +5 -5
  136. package/lib/internal/task-event.ts +20 -11
  137. package/lib/internal/task-operation-registry.ts +111 -3
  138. package/lib/internal/task-review.ts +6 -51
  139. package/lib/internal/task-validate.ts +26 -10
  140. package/lib/owned-path.ts +26 -0
  141. package/lib/platform/check-status.ts +9 -0
  142. package/lib/platform/comment-safety.ts +5 -0
  143. package/lib/platform/completion-backfill.ts +1 -1
  144. package/lib/platform/github-data.ts +322 -0
  145. package/lib/platform/github-provider.ts +15 -20
  146. package/lib/platform/issue-comments.ts +3 -4
  147. package/lib/platform/issues.ts +4 -245
  148. package/lib/platform/pr-checks.ts +13 -77
  149. package/lib/platform/pr-summary.ts +61 -5
  150. package/lib/platform/pull-requests.ts +3 -170
  151. package/lib/platform/snapshots.ts +53 -0
  152. package/lib/platform/verification-sync.ts +145 -155
  153. package/lib/release/workflow.ts +3 -3
  154. package/lib/sandbox/commands/create.ts +7 -6
  155. package/lib/sandbox/commands/rm.ts +3 -1909
  156. package/lib/sandbox/control/audit.ts +253 -0
  157. package/lib/sandbox/control/client.ts +109 -66
  158. package/lib/sandbox/control/executor.ts +134 -74
  159. package/lib/sandbox/control/finalization-response.ts +29 -0
  160. package/lib/sandbox/control/identity-sentinel.ts +137 -0
  161. package/lib/sandbox/control/lifecycle.ts +45 -3
  162. package/lib/sandbox/control/native-file-lock.ts +6 -107
  163. package/lib/sandbox/control/protocol.ts +75 -6
  164. package/lib/sandbox/control/server.ts +538 -123
  165. package/lib/sandbox/control/state.ts +78 -0
  166. package/lib/sandbox/control/task-workflow.ts +95 -0
  167. package/lib/sandbox/control/workflow-executor.ts +102 -0
  168. package/lib/sandbox/index.ts +2 -2
  169. package/lib/sandbox/recovery.ts +28 -5
  170. package/lib/sandbox/removal.ts +2144 -0
  171. package/lib/sandbox/task-cleanup.ts +209 -0
  172. package/lib/sandbox/workspace-view.ts +17 -15
  173. package/lib/task/activity-intent.ts +2 -2
  174. package/lib/task/activity-log.ts +27 -19
  175. package/lib/task/artifact-command.ts +104 -0
  176. package/lib/task/artifact-lifecycle.ts +31 -26
  177. package/lib/task/artifact-name.ts +55 -0
  178. package/lib/task/artifact-operations.ts +22 -68
  179. package/lib/task/artifact-receipts.ts +3 -14
  180. package/lib/task/artifact-repair-intent.ts +99 -18
  181. package/lib/task/artifact-schema.ts +5 -34
  182. package/lib/task/capabilities.ts +13 -22
  183. package/lib/task/codex-orchestration.ts +85 -24
  184. package/lib/task/commit-identity.ts +31 -0
  185. package/lib/task/commit-operation.ts +2 -16
  186. package/lib/task/control-authority.ts +502 -5
  187. package/lib/task/control-recovery.ts +174 -0
  188. package/lib/task/create.ts +63 -8
  189. package/lib/task/decision-details.ts +3 -71
  190. package/lib/task/events.ts +131 -10
  191. package/lib/task/finalization-state.ts +11 -0
  192. package/lib/task/human-override.ts +3 -8
  193. package/lib/task/implementation-inputs.ts +3 -3
  194. package/lib/task/intermediate-cleanup.ts +455 -0
  195. package/lib/task/invalidation.ts +18 -62
  196. package/lib/task/ledger-intents.ts +7 -24
  197. package/lib/task/ledger.ts +1 -3
  198. package/lib/task/lifecycle.ts +17 -1
  199. package/lib/task/local-artifact-finalization.ts +148 -182
  200. package/lib/task/manual-validation-completion.ts +40 -0
  201. package/lib/task/manual-validation-evidence.ts +209 -0
  202. package/lib/task/manual-validation-receipt.ts +152 -0
  203. package/lib/task/manual-validation-shared.ts +50 -0
  204. package/lib/task/manual-validation-transaction.ts +304 -0
  205. package/lib/task/markdown.ts +70 -0
  206. package/lib/task/orchestration.ts +19 -19
  207. package/lib/task/qualification-audit.ts +22 -33
  208. package/lib/task/review-artifacts.ts +1 -20
  209. package/lib/task/review-command.ts +23 -0
  210. package/lib/task/review-finalization.ts +131 -259
  211. package/lib/task/review-fingerprint.ts +2 -2
  212. package/lib/task/sections.ts +33 -54
  213. package/lib/task/verification-engine.ts +110 -366
  214. package/lib/task/verification-types.ts +17 -0
  215. package/lib/task/verification.ts +1 -1
  216. package/lib/task/workflow-command.ts +18 -0
  217. package/lib/task/workflow-warning-intents.ts +0 -13
  218. package/lib/task/workflow-warnings.ts +21 -2
  219. package/lib/update.ts +6 -0
  220. package/package.json +6 -6
  221. package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
  222. package/templates/.agents/rules/testing-discipline.en.md +2 -0
  223. package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
  224. package/templates/.agents/scripts/package.json +3 -0
  225. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +32 -20
  226. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +32 -20
  227. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
  228. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
  229. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
  230. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
  231. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
  232. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
  233. package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
  234. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
  235. package/templates/.agents/skills/release/SKILL.en.md +7 -3
  236. package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
  237. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
  238. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
  239. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
  240. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
  241. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
  242. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
  243. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  244. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  245. package/templates/.agents/skills/test/SKILL.en.md +17 -6
  246. package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
  247. package/templates/.claude/commands/run-manual-validation.en.md +1 -1
  248. package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
  249. package/dist/lib/platform/adapters.js +0 -56
  250. package/lib/platform/adapters.ts +0 -197
@@ -0,0 +1,180 @@
1
+ import fs from 'node:fs';
2
+ import net from 'node:net';
3
+ import { createHash } from 'node:crypto';
4
+
5
+ import {
6
+ HOST_CONTROL_DIRECTORY_MODE,
7
+ HOST_CONTROL_SOCKET_MODE,
8
+ ensureHostControlWorkerToken,
9
+ inspectHostControlEndpoint,
10
+ prepareHostControlDirectory,
11
+ removeHostControlWorkerToken,
12
+ resolveHostControlEndpoint
13
+ } from './path.ts';
14
+ import {
15
+ HOST_CONTROL_MAX_REQUEST_BYTES,
16
+ validateHostControlRequest,
17
+ type HostControlRequest,
18
+ type HostControlResponse
19
+ } from './client.ts';
20
+ import { acquireFileLock } from '../fs/file-lock.ts';
21
+ import { appendHostControlAudit } from './audit.ts';
22
+
23
+ export type HostControlDispatch = (request: HostControlRequest) => Promise<{ stdout: string; stderr: string; exitCode: number }>;
24
+
25
+ export type HostControlAudit = Readonly<{
26
+ requestId: string;
27
+ taskId: string | null;
28
+ operation: string;
29
+ scope: string;
30
+ phase: 'accepted' | 'completed' | 'rejected';
31
+ outcome: string;
32
+ authorityKind: 'host-broker';
33
+ transportKind: 'host-fixed-transport';
34
+ identityDigest: string;
35
+ }>;
36
+
37
+ export type HostControlServerOptions = Readonly<{
38
+ endpoint?: string;
39
+ dispatch: HostControlDispatch;
40
+ audit?: (entry: HostControlAudit) => void;
41
+ }>;
42
+
43
+ function auditFor(request: HostControlRequest, phase: HostControlAudit['phase'], outcome: string): HostControlAudit {
44
+ return {
45
+ requestId: request.id,
46
+ taskId: request.taskId,
47
+ operation: request.operation,
48
+ scope: request.scope,
49
+ phase,
50
+ outcome,
51
+ authorityKind: 'host-broker',
52
+ transportKind: 'host-fixed-transport',
53
+ identityDigest: createHash('sha256').update(`${request.taskId ?? '-'}\0${request.generation ?? '-'}`, 'utf8').digest('hex')
54
+ };
55
+ }
56
+
57
+ function failedResponse(id: string, status: 'rejected' | 'unknown', code: string, message: string): HostControlResponse {
58
+ const error = { code, message };
59
+ const stdout = `${JSON.stringify({ status: 'failed', changed: status === 'rejected' ? false : null, error })}\n`;
60
+ return { version: 1, id, status, exitCode: 1, stdout, stderr: '', error };
61
+ }
62
+
63
+ async function handleConnection(socket: net.Socket, options: HostControlServerOptions): Promise<void> {
64
+ await new Promise<void>((resolve) => {
65
+ let input = '';
66
+ let completed = false;
67
+ let processing = false;
68
+ socket.setEncoding('utf8');
69
+ // A disconnected caller does not cancel an already dispatched domain operation.
70
+ socket.on('error', () => socket.destroy());
71
+ socket.on('close', () => { if (!processing) resolve(); });
72
+ socket.on('end', () => { if (!processing) socket.end(); });
73
+ const finish = (value: HostControlResponse): void => {
74
+ if (completed || socket.destroyed) return;
75
+ completed = true;
76
+ socket.end(`${JSON.stringify(value)}\n`);
77
+ };
78
+ socket.on('data', async (chunk: string) => {
79
+ if (completed || processing) return;
80
+ input += chunk;
81
+ if (Buffer.byteLength(input, 'utf8') > HOST_CONTROL_MAX_REQUEST_BYTES) {
82
+ finish(failedResponse('invalid', 'rejected', 'HOST_CONTROL_REQUEST_TOO_LARGE', 'request exceeds the control limit'));
83
+ return;
84
+ }
85
+ const newline = input.indexOf('\n');
86
+ if (newline < 0) return;
87
+ const raw = input.slice(0, newline);
88
+ let request: HostControlRequest;
89
+ try { request = validateHostControlRequest(JSON.parse(raw)); }
90
+ catch (error) {
91
+ finish(failedResponse('invalid', 'rejected', 'HOST_CONTROL_REQUEST_INVALID', error instanceof Error ? error.message : String(error)));
92
+ return;
93
+ }
94
+ processing = true;
95
+ let started = false;
96
+ try {
97
+ options.audit?.(auditFor(request, 'accepted', 'in-progress'));
98
+ started = true;
99
+ const result = await options.dispatch(request);
100
+ options.audit?.(auditFor(request, 'completed', result.exitCode === 0 ? 'success' : 'failure'));
101
+ finish({ version: 1, id: request.id, status: 'completed', ...result, error: null });
102
+ } catch (error) {
103
+ const message = error instanceof Error ? error.message : String(error);
104
+ const code = /^([A-Z][A-Z0-9_]+)/u.exec(message)?.[1] ?? 'HOST_CONTROL_DISPATCH_FAILED';
105
+ const status = started ? 'unknown' : 'rejected';
106
+ try { options.audit?.(auditFor(request, started ? 'completed' : 'rejected', status)); }
107
+ catch { /* Return the uncertainty even when the audit sink is unavailable. */ }
108
+ finish(failedResponse(request.id, status, code, message));
109
+ } finally {
110
+ resolve();
111
+ }
112
+ });
113
+ });
114
+ }
115
+
116
+ export type RunningHostControlServer = Readonly<{
117
+ endpoint: string;
118
+ server: net.Server;
119
+ close: () => Promise<void>;
120
+ }>;
121
+
122
+ export async function startHostControlServer(options: HostControlServerOptions): Promise<RunningHostControlServer> {
123
+ const endpoint = options.endpoint ?? resolveHostControlEndpoint();
124
+ const audit = options.audit ?? ((entry: HostControlAudit) => appendHostControlAudit(endpoint, entry));
125
+ prepareHostControlDirectory(endpoint);
126
+ const lock = acquireFileLock(`${endpoint}.lock`);
127
+ let server: net.Server | undefined;
128
+ try {
129
+ ensureHostControlWorkerToken(endpoint);
130
+ try {
131
+ const existing = fs.lstatSync(endpoint);
132
+ if (existing.isSymbolicLink() || !existing.isSocket()) throw new Error('HOST_CONTROL_ENDPOINT_INVALID');
133
+ fs.unlinkSync(endpoint);
134
+ } catch (error) {
135
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
136
+ }
137
+ const connections = new Set<Promise<void>>();
138
+ server = net.createServer({ allowHalfOpen: true }, (socket) => {
139
+ const connection = handleConnection(socket, { ...options, audit });
140
+ connections.add(connection);
141
+ void connection.finally(() => connections.delete(connection));
142
+ });
143
+ const listeningServer = server;
144
+ await new Promise<void>((resolve, reject) => {
145
+ listeningServer.once('error', reject);
146
+ listeningServer.listen(endpoint, () => { listeningServer.off('error', reject); resolve(); });
147
+ });
148
+ fs.chmodSync(endpoint, HOST_CONTROL_SOCKET_MODE);
149
+ const inspection = inspectHostControlEndpoint(endpoint, { uid: process.getuid?.() });
150
+ if (!inspection.ok) {
151
+ await new Promise<void>((resolve) => listeningServer.close(() => resolve()));
152
+ removeHostControlWorkerToken(endpoint);
153
+ throw new Error(inspection.code ?? 'HOST_CONTROL_ENDPOINT_INVALID');
154
+ }
155
+ let closing: Promise<void> | undefined;
156
+ return {
157
+ endpoint,
158
+ server,
159
+ close: () => closing ??= (async () => {
160
+ try {
161
+ await new Promise<void>((resolve) => listeningServer.close(() => resolve()));
162
+ await Promise.all(connections);
163
+ await fs.promises.rm(endpoint, { force: true });
164
+ removeHostControlWorkerToken(endpoint);
165
+ } finally { lock.release(); }
166
+ })()
167
+ };
168
+ } catch (error) {
169
+ if (server?.listening) await new Promise<void>((resolve) => server!.close(() => resolve()));
170
+ lock.release();
171
+ throw error;
172
+ }
173
+ }
174
+
175
+ export async function serveHostControl(options: HostControlServerOptions, signal?: AbortSignal): Promise<void> {
176
+ const running = await startHostControlServer(options);
177
+ if (!signal) return;
178
+ if (signal.aborted) { await running.close(); return; }
179
+ await new Promise<void>((resolve) => signal.addEventListener('abort', () => { void running.close().then(resolve); }, { once: true }));
180
+ }
@@ -0,0 +1,133 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ import {
7
+ inspectHostControlEndpoint,
8
+ resolveHostControlEndpoint,
9
+ type HostControlPlatform
10
+ } from './path.ts';
11
+ import { serveHostControl, startHostControlServer, type HostControlDispatch, type RunningHostControlServer } from './server.ts';
12
+
13
+ export type HostControlServiceOptions = Readonly<{
14
+ platform?: HostControlPlatform;
15
+ uid?: number;
16
+ username?: string;
17
+ endpoint?: string;
18
+ dispatch: HostControlDispatch;
19
+ audit?: Parameters<typeof startHostControlServer>[0]['audit'];
20
+ }>;
21
+
22
+ export type HostControlServicePlatform = 'linux' | 'darwin' | 'unsupported';
23
+
24
+ function servicePlatform(): HostControlServicePlatform {
25
+ if (process.platform === 'linux' || process.platform === 'darwin') return process.platform;
26
+ return 'unsupported';
27
+ }
28
+
29
+ function serviceEntryPoint(): string {
30
+ const compiled = fileURLToPath(new URL('../../bin/internal-cli.js', import.meta.url));
31
+ if (fs.existsSync(compiled)) return fs.realpathSync.native(compiled);
32
+ const candidate = process.argv[1];
33
+ if (candidate && fs.existsSync(candidate)) return fs.realpathSync.native(candidate);
34
+ return fs.realpathSync.native(fileURLToPath(new URL('../../bin/internal-cli.ts', import.meta.url)));
35
+ }
36
+
37
+ function xml(value: string): string {
38
+ return value.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;');
39
+ }
40
+
41
+ function systemdArgument(value: string): string {
42
+ const escaped = value.replace(/[\\"]/g, '\\$&')
43
+ .replace(/[\x00-\x1f\x7f]/g, (character) => `\\x${character.charCodeAt(0).toString(16).padStart(2, '0')}`)
44
+ .replaceAll('%', '%%');
45
+ return `"${escaped}"`;
46
+ }
47
+
48
+ function servicePaths(platform: HostControlServicePlatform = servicePlatform()): Readonly<{ unit?: string; plist?: string }> {
49
+ if (platform === 'linux') return { unit: path.join(os.homedir(), '.config', 'systemd', 'user', 'agent-infra-host-control.service') };
50
+ if (platform === 'darwin') return { plist: path.join(os.homedir(), 'Library', 'LaunchAgents', 'com.fitlab-ai.agent-infra.host-control.plist') };
51
+ throw new Error('HOST_CONTROL_UNSUPPORTED_PLATFORM');
52
+ }
53
+
54
+ export function installHostControlService(platform: HostControlServicePlatform = servicePlatform()): string {
55
+ const paths = servicePaths(platform);
56
+ const entry = serviceEntryPoint();
57
+ if (paths.unit) {
58
+ // mkdir only applies mode to directories it creates, and this one usually
59
+ // already exists, so restate the intended mode explicitly.
60
+ fs.mkdirSync(path.dirname(paths.unit), { recursive: true, mode: 0o700 });
61
+ fs.chmodSync(path.dirname(paths.unit), 0o700);
62
+ fs.writeFileSync(paths.unit, [
63
+ '[Unit]',
64
+ 'Description=agent-infra host-control service',
65
+ '',
66
+ '[Service]',
67
+ // The ':' executable prefix disables environment expansion for literal paths.
68
+ `ExecStart=${systemdArgument(`:${process.execPath}`)} ${systemdArgument(entry)} host-control serve`,
69
+ 'Restart=on-failure',
70
+ 'NoNewPrivileges=true',
71
+ // Workers receive host paths, including working directories and inputs under /tmp.
72
+ 'PrivateTmp=false',
73
+ '',
74
+ '[Install]',
75
+ 'WantedBy=default.target',
76
+ ''
77
+ ].join('\n'), { mode: 0o600 });
78
+ fs.chmodSync(paths.unit, 0o600);
79
+ return paths.unit;
80
+ }
81
+ const plist = paths.plist!;
82
+ fs.mkdirSync(path.dirname(plist), { recursive: true, mode: 0o700 });
83
+ fs.chmodSync(path.dirname(plist), 0o700);
84
+ const argumentsXml = [process.execPath, entry, 'host-control', 'serve']
85
+ .map((argument) => ` <string>${xml(argument)}</string>`)
86
+ .join('\n');
87
+ fs.writeFileSync(plist, [
88
+ '<?xml version="1.0" encoding="UTF-8"?>',
89
+ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
90
+ '<plist version="1.0">',
91
+ '<dict>',
92
+ ' <key>Label</key>',
93
+ ' <string>com.fitlab-ai.agent-infra.host-control</string>',
94
+ ' <key>ProgramArguments</key>',
95
+ ' <array>',
96
+ argumentsXml,
97
+ ' </array>',
98
+ ' <key>RunAtLoad</key>',
99
+ ' <true/>',
100
+ ' <key>KeepAlive</key>',
101
+ ' <true/>',
102
+ '</dict>',
103
+ '</plist>',
104
+ ''
105
+ ].join('\n'), { mode: 0o600 });
106
+ fs.chmodSync(plist, 0o600);
107
+ return plist;
108
+ }
109
+
110
+ export function uninstallHostControlService(platform: HostControlServicePlatform = servicePlatform()): string {
111
+ const paths = servicePaths(platform);
112
+ const target = paths.unit ?? paths.plist!;
113
+ fs.rmSync(target, { force: true });
114
+ return target;
115
+ }
116
+
117
+ export function hostControlServiceEndpoint(options: Pick<HostControlServiceOptions, 'platform' | 'uid' | 'username' | 'endpoint'> = {}): string {
118
+ return options.endpoint ?? resolveHostControlEndpoint(options);
119
+ }
120
+
121
+ export async function startHostControlService(options: HostControlServiceOptions): Promise<RunningHostControlServer> {
122
+ return startHostControlServer({ endpoint: hostControlServiceEndpoint(options), dispatch: options.dispatch, ...(options.audit ? { audit: options.audit } : {}) });
123
+ }
124
+
125
+ export async function serveHostControlService(options: HostControlServiceOptions, signal?: AbortSignal): Promise<void> {
126
+ await serveHostControl({ endpoint: hostControlServiceEndpoint(options), dispatch: options.dispatch, ...(options.audit ? { audit: options.audit } : {}) }, signal);
127
+ }
128
+
129
+ export function hostControlServiceStatus(options: Pick<HostControlServiceOptions, 'platform' | 'uid' | 'username' | 'endpoint'> = {}): Readonly<{ endpoint: string; available: boolean; code: string | null }> {
130
+ const endpoint = hostControlServiceEndpoint(options);
131
+ const result = inspectHostControlEndpoint(endpoint, { uid: options.uid ?? process.getuid?.() });
132
+ return { endpoint, available: result.ok, code: result.code };
133
+ }
package/lib/init.ts CHANGED
@@ -61,6 +61,7 @@ const PLATFORM_DEFAULT_ENGINES = Object.freeze({
61
61
  darwin: 'colima',
62
62
  win32: 'wsl2'
63
63
  });
64
+ const DEMO_PLATFORM_ENV = 'AGENT_INFRA_DEMO_PLATFORM';
64
65
 
65
66
  function detectProjectName(): string {
66
67
  try {
@@ -88,6 +89,12 @@ function detectOrgName(): string {
88
89
  return '';
89
90
  }
90
91
 
92
+ function resolveInitPlatform(): NodeJS.Platform {
93
+ const override = process.env[DEMO_PLATFORM_ENV];
94
+ if (override === 'linux' || override === 'darwin' || override === 'win32') return override;
95
+ return platform();
96
+ }
97
+
91
98
  const VALID_NAME_RE = /^[a-zA-Z0-9_.@-]+$/;
92
99
 
93
100
  function parseLocalSources(input: string): SourceEntry[] {
@@ -160,7 +167,7 @@ async function cmdInit(): Promise<void> {
160
167
  return;
161
168
  }
162
169
 
163
- const currentPlatform = platform();
170
+ const currentPlatform = resolveInitPlatform();
164
171
  const defaultEngine = PLATFORM_DEFAULT_ENGINES[currentPlatform as keyof typeof PLATFORM_DEFAULT_ENGINES];
165
172
  const engineChoices = enginesForPlatform(currentPlatform).sort((left, right) => {
166
173
  if (left === defaultEngine) return -1;
@@ -288,6 +295,12 @@ async function cmdInit(): Promise<void> {
288
295
  replacements
289
296
  );
290
297
  ok('Installed .agents/scripts/lib/agent-infra-package.js');
298
+ renderFile(
299
+ path.join(templateDir, '.agents', 'scripts', 'package.json'),
300
+ path.join('.agents', 'scripts', 'package.json'),
301
+ replacements
302
+ );
303
+ ok('Installed .agents/scripts/package.json');
291
304
 
292
305
  const reconcileResult = applyAgentClientReconciliation(workflowPlan);
293
306
  for (const target of reconcileResult.applied) ok(`Installed ${target}`);
@@ -317,4 +330,4 @@ async function cmdInit(): Promise<void> {
317
330
  }
318
331
  }
319
332
 
320
- export { cmdInit };
333
+ export { cmdInit, resolveInitPlatform };
@@ -36,6 +36,7 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
36
36
  'task-create': ['input'],
37
37
  'task-qualification': ['proposal', 'confirm', 'supersede', 'revoke'],
38
38
  'sandbox-control': ['serve', 'execute', 'recover', 'client'],
39
+ 'host-control': ['serve', 'status', 'install', 'uninstall'],
39
40
  'agent-client': ['next-steps', 'model-selection'],
40
41
  'codex-lifecycle': ['preflight', 'capability-arm', 'hook-event', 'resolve-start', 'resolve-stop', 'consume'],
41
42
  'codex-sandbox-controller': ['verify-context', 'run'],
@@ -67,7 +68,8 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
67
68
  'task-short-id': ['list', 'list-verify', 'alloc', 'release', 'resolve'],
68
69
  'task-snapshot': ['snapshot'],
69
70
  'task-verify': ['event'],
70
- 'task-validate': ['snapshot', 'inplace']
71
+ 'task-validate': ['snapshot', 'inplace'],
72
+ 'manual-validation': ['transaction']
71
73
  } as const);
72
74
 
73
75
  export const INTERNAL_CLI_ROUTE_SELECTORS = INTERNAL_HANDLER_ROUTE_SELECTORS;
@@ -203,7 +203,7 @@ async function codexLifecycle(args: string[] = []): Promise<void> {
203
203
  output({
204
204
  status: 'armed',
205
205
  changed: true,
206
- capabilityToken: armed.token,
206
+ capabilityRef: armed.capabilityRef,
207
207
  marker: armed.marker,
208
208
  expiresAt: armed.expiresAt,
209
209
  buildIdentity: armed.buildIdentity,
@@ -223,12 +223,12 @@ async function codexLifecycle(args: string[] = []): Promise<void> {
223
223
  }
224
224
  const payload = await readStdin();
225
225
  if (phase === 'post-tool') {
226
- const capabilityToken = payloadText(payload, 'capabilityToken');
227
- if (capabilityToken) {
226
+ const capabilityRef = payloadText(payload, 'capabilityRef');
227
+ if (capabilityRef) {
228
228
  const capabilityStore = createCodexCapabilityStore();
229
- const armed = capabilityStore.inspect(capabilityToken);
230
- const capability = createCodexCapabilityStore().attest({
231
- token: capabilityToken,
229
+ const armed = capabilityStore.inspectReference(capabilityRef);
230
+ const capability = capabilityStore.attestByReference({
231
+ capabilityRef,
232
232
  sessionId: payloadText(payload, 'sessionId'),
233
233
  turnId: payloadText(payload, 'turnId'),
234
234
  toolUseId: payloadText(payload, 'toolUseId'),