@bjoernrochel/sevro 0.1.0-rc.2 → 0.1.0-rc.21

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 (243) hide show
  1. package/CONTRIBUTING.md +170 -0
  2. package/LICENSE +63 -136
  3. package/README.md +83 -66
  4. package/docs/README.md +44 -0
  5. package/docs/acknowledgements.md +14 -0
  6. package/docs/architecture.md +146 -0
  7. package/docs/assets/README.md +27 -0
  8. package/docs/assets/sevro-logo.png +0 -0
  9. package/docs/assets/sevro-symbol.svg +1 -0
  10. package/docs/assets/sevro-wordmark.svg +1 -0
  11. package/docs/builtin-graders-v1.md +41 -13
  12. package/docs/codex-host.md +184 -52
  13. package/docs/contracts.md +19 -0
  14. package/docs/creating-cases.md +139 -0
  15. package/docs/development-cli.md +190 -30
  16. package/docs/documentation-quality.md +61 -0
  17. package/docs/evaluation-workflow.md +110 -0
  18. package/docs/evidence/code-mode-controls-71.md +64 -0
  19. package/docs/evidence/code-mode-programs-85.md +64 -0
  20. package/docs/evidence/codex-state-73/README.md +204 -0
  21. package/docs/evidence/codex-state-73/darrow-migration.patch +182 -0
  22. package/docs/evidence/codex-state-73/results.json +661 -0
  23. package/docs/evidence/local-http-74.md +104 -0
  24. package/docs/evidence/runtime-startup-30/README.md +185 -0
  25. package/docs/evidence/runtime-startup-30/after.json +319 -0
  26. package/docs/evidence/runtime-startup-30/baseline-timeout.jsonl +2 -0
  27. package/docs/evidence/runtime-startup-30/before.json +403 -0
  28. package/docs/evidence/runtime-startup-30/consumer-smoke.json +63 -0
  29. package/docs/evidence/runtime-startup-30/full-policy-diagnostic.jsonl +3 -0
  30. package/docs/evidence/runtime-startup-30/minimal-policy-diagnostic.jsonl +3 -0
  31. package/docs/evidence/runtime-startup-30/plugin-template-assessment.json +20 -0
  32. package/docs/evidence/runtime-startup-30/seedless-timeout.jsonl +2 -0
  33. package/docs/evidence/skill-reads-70/results.json +823 -0
  34. package/docs/evidence.md +21 -0
  35. package/docs/extension-protocol-v1.md +50 -16
  36. package/docs/fixture-cleanup-validation.md +3 -0
  37. package/docs/getting-started.md +102 -0
  38. package/docs/guide-evaluation.md +89 -0
  39. package/docs/guide-verification.json +239 -0
  40. package/docs/identity-v1.md +35 -1
  41. package/docs/installing.md +88 -0
  42. package/docs/licenses/BUSL-1.1-0.1.0-rc.2.txt +159 -0
  43. package/docs/licensing.md +42 -0
  44. package/docs/native-controls.md +76 -5
  45. package/docs/native-hosts.md +228 -0
  46. package/docs/reading-results.md +141 -0
  47. package/docs/recipes.md +489 -0
  48. package/docs/releases.md +81 -40
  49. package/docs/report-v1.md +29 -1
  50. package/docs/results-v1.md +180 -0
  51. package/docs/run-settings-v1.md +147 -0
  52. package/docs/running-evaluations.md +31 -0
  53. package/docs/runtime-v1.md +614 -0
  54. package/docs/specs/repository-guide.md +58 -0
  55. package/docs/troubleshooting.md +30 -0
  56. package/docs/typescript-quality.md +234 -0
  57. package/docs/usability-study.md +114 -0
  58. package/examples/basic/host.ts +2 -2
  59. package/examples/recipes/candidate.ts +26 -0
  60. package/examples/recipes/cases/coding-change.json +21 -0
  61. package/examples/recipes/cases/host-tools.json +21 -0
  62. package/examples/recipes/cases/mise.json +21 -0
  63. package/examples/recipes/cases/node.json +21 -0
  64. package/examples/recipes/cases/python-uv.json +21 -0
  65. package/examples/recipes/cases/semantic.json +17 -0
  66. package/examples/recipes/coding-change/check.ts +23 -0
  67. package/examples/recipes/extension-artifacts.ts +65 -0
  68. package/examples/recipes/extension-methods.ts +174 -0
  69. package/examples/recipes/extension.ts +37 -0
  70. package/examples/recipes/repository-skill/.agents/skills/startup-guide/SKILL.md +7 -0
  71. package/examples/recipes/repository-skill/.claude/skills/startup-guide/SKILL.md +7 -0
  72. package/examples/recipes/repository-skill/README.md +3 -0
  73. package/examples/recipes/run-inputs.ts +188 -0
  74. package/examples/recipes/run-options.ts +131 -0
  75. package/examples/recipes/run.ts +63 -0
  76. package/examples/recipes/runtime/empty.json +1 -0
  77. package/examples/recipes/runtime/goals.json +1 -0
  78. package/examples/recipes/runtime/mise.json +6 -0
  79. package/examples/recipes/runtime/path.json +1 -0
  80. package/examples/recipes/runtime/plugins.json +4 -0
  81. package/examples/recipes/runtime/python-uv.json +10 -0
  82. package/examples/recipes/semantic-judge.ts +29 -0
  83. package/examples/templates/coding/adapter.ts +17 -0
  84. package/examples/templates/coding/case.json +15 -0
  85. package/examples/templates/repository-skill/.agents/skills/startup-guide/SKILL.md +6 -0
  86. package/examples/templates/repository-skill/extension-data.ts +64 -0
  87. package/examples/templates/repository-skill/extension.ts +31 -0
  88. package/examples/templates/response/adapter.ts +12 -0
  89. package/examples/templates/response/case.json +13 -0
  90. package/package.json +39 -6
  91. package/schemas/report-v1.schema.json +9 -0
  92. package/schemas/run-evidence-v1.schema.json +2 -1
  93. package/schemas/runtime-v1.schema.json +193 -0
  94. package/src/advisory.ts +10 -5
  95. package/src/candidate-artifacts.ts +208 -0
  96. package/src/candidate-codex-state.ts +185 -0
  97. package/src/case-validation.ts +105 -0
  98. package/src/cli/cancellation.ts +20 -0
  99. package/src/cli/command.ts +81 -0
  100. package/src/cli/completion.ts +15 -0
  101. package/src/cli/demo.ts +59 -0
  102. package/src/cli/evaluation.ts +55 -0
  103. package/src/cli/extension.ts +79 -0
  104. package/src/cli/help.ts +222 -0
  105. package/src/cli/hosts.ts +59 -0
  106. package/src/cli/init-settings.ts +30 -0
  107. package/src/cli/init.ts +111 -0
  108. package/src/cli/invocation.ts +627 -0
  109. package/src/cli/loading.ts +151 -0
  110. package/src/cli/preparation.ts +163 -0
  111. package/src/cli/presentation.ts +91 -0
  112. package/src/cli/progress.ts +6 -0
  113. package/src/cli/run-manifest.ts +133 -0
  114. package/src/cli/run-options.ts +101 -0
  115. package/src/cli/run-overrides.ts +83 -0
  116. package/src/cli/settings.ts +160 -0
  117. package/src/cli/summary.ts +43 -0
  118. package/src/cli.ts +13 -843
  119. package/src/contract-types.ts +94 -0
  120. package/src/engine.ts +19 -2373
  121. package/src/evaluation/advisory.ts +150 -0
  122. package/src/evaluation/check-plan.ts +510 -0
  123. package/src/evaluation/collection.ts +88 -0
  124. package/src/evaluation/context.ts +10 -0
  125. package/src/evaluation/grading-host.ts +39 -0
  126. package/src/evaluation/grading.ts +258 -0
  127. package/src/evaluation/invocation-plan.ts +548 -0
  128. package/src/evaluation/plan.ts +736 -0
  129. package/src/evaluation/progress.ts +19 -0
  130. package/src/evaluation/resources.ts +241 -0
  131. package/src/evaluation/run.ts +206 -0
  132. package/src/{trial-scheduler.ts → evaluation/scheduler.ts} +3 -1
  133. package/src/evaluation/semantic.ts +312 -0
  134. package/src/evaluation/trial.ts +866 -0
  135. package/src/evaluation/types.ts +207 -0
  136. package/src/evidence/host-results.ts +92 -0
  137. package/src/evidence/retained-artifacts.ts +18 -0
  138. package/src/evidence/trial-writer.ts +203 -0
  139. package/src/evidence/usage.ts +17 -0
  140. package/src/extension-client.ts +228 -148
  141. package/src/extension-session.ts +326 -195
  142. package/src/fixtures/advisory.ts +338 -0
  143. package/src/fixtures/bin.ts +57 -0
  144. package/src/{fixture-cleanup.ts → fixtures/cleanup.ts} +13 -7
  145. package/src/fixtures/create.ts +187 -0
  146. package/src/fixtures/generated.ts +300 -0
  147. package/src/fixtures/git-hooks.ts +24 -0
  148. package/src/fixtures/preparation.ts +270 -0
  149. package/src/{repository-fixture.ts → fixtures/repository.ts} +207 -85
  150. package/src/fixtures/setup.ts +224 -0
  151. package/src/fixtures/tools.ts +88 -0
  152. package/src/fixtures/workspace-state.ts +212 -0
  153. package/src/generated/cli-result.cjs +2 -0
  154. package/src/generated/cli-result.d.cts +3 -0
  155. package/src/generated/extension.cjs +2 -0
  156. package/src/generated/extension.d.cts +3 -0
  157. package/src/generated/report.cjs +2 -0
  158. package/src/generated/report.d.cts +3 -0
  159. package/src/generated/run-evidence.cjs +2 -0
  160. package/src/generated/run-evidence.d.cts +3 -0
  161. package/src/generated/runtime.cjs +2 -0
  162. package/src/generated/runtime.d.cts +3 -0
  163. package/src/graders/artifact.ts +56 -23
  164. package/src/graders/git-head.ts +79 -75
  165. package/src/graders/output.ts +222 -140
  166. package/src/graders/semantic.ts +102 -43
  167. package/src/graders/shell.ts +379 -207
  168. package/src/host-diagnostic-redaction.ts +52 -0
  169. package/src/host-diagnostics.ts +319 -0
  170. package/src/hosts/claude-continuation.ts +63 -18
  171. package/src/hosts/claude-credential.ts +43 -62
  172. package/src/hosts/claude-events.ts +86 -51
  173. package/src/hosts/claude-keychain.ts +59 -0
  174. package/src/hosts/claude-native-goal.ts +57 -33
  175. package/src/hosts/claude-process.ts +111 -0
  176. package/src/hosts/claude-repository-invocation.ts +208 -117
  177. package/src/hosts/claude-settings.ts +240 -21
  178. package/src/hosts/claude-skill-activations.ts +166 -0
  179. package/src/hosts/claude-state.ts +403 -0
  180. package/src/hosts/claude-tool-calls.ts +171 -106
  181. package/src/hosts/claude.ts +561 -451
  182. package/src/hosts/codex-app-server.ts +326 -178
  183. package/src/hosts/codex-configuration.ts +44 -18
  184. package/src/hosts/codex-events.ts +97 -60
  185. package/src/hosts/codex-exec-controls.ts +516 -0
  186. package/src/hosts/codex-invocation.ts +70 -0
  187. package/src/hosts/codex-marketplace.ts +283 -0
  188. package/src/hosts/codex-native-calls.ts +546 -500
  189. package/src/hosts/codex-process.ts +202 -0
  190. package/src/hosts/codex-profile.ts +257 -28
  191. package/src/hosts/codex-session-files.ts +106 -0
  192. package/src/hosts/codex-state.ts +492 -0
  193. package/src/hosts/codex.ts +783 -832
  194. package/src/hosts/isolation-common.ts +94 -0
  195. package/src/hosts/isolation-roots.ts +54 -27
  196. package/src/hosts/isolation.ts +10 -0
  197. package/src/hosts/linux-sandbox.ts +159 -0
  198. package/src/hosts/mac-sandbox.ts +187 -61
  199. package/src/hosts/native-controls.ts +152 -62
  200. package/src/hosts/process-lifecycle.ts +37 -0
  201. package/src/hosts/protected-worktrees.ts +51 -19
  202. package/src/hosts/skill-hook-catalog.ts +131 -0
  203. package/src/hosts/skill-hook-handler.ts +374 -0
  204. package/src/hosts/skill-hooks.ts +580 -0
  205. package/src/hosts/workspace-fingerprint.ts +55 -35
  206. package/src/identity.ts +102 -48
  207. package/src/instrumentation.ts +109 -48
  208. package/src/native-transcript-state.ts +131 -0
  209. package/src/native-transcript-view.ts +90 -0
  210. package/src/native-transcripts.ts +286 -0
  211. package/src/process-lifecycle.ts +150 -0
  212. package/src/provenance.ts +161 -81
  213. package/src/prune.ts +215 -0
  214. package/src/report.ts +172 -140
  215. package/src/resolved-case.ts +33 -0
  216. package/src/results-lock.ts +29 -0
  217. package/src/results.ts +133 -86
  218. package/src/run-owner.ts +154 -63
  219. package/src/runtime/config.ts +456 -0
  220. package/src/runtime/discovery-apple.ts +90 -0
  221. package/src/runtime/discovery-providers.ts +63 -0
  222. package/src/runtime/discovery.ts +70 -0
  223. package/src/runtime/hook-processes.ts +97 -0
  224. package/src/runtime/hooks.ts +476 -0
  225. package/src/runtime/paths.ts +313 -0
  226. package/src/runtime/query.ts +85 -0
  227. package/src/runtime/seeds.ts +134 -0
  228. package/src/runtime/shared-cache.ts +143 -0
  229. package/src/runtime/state.ts +143 -0
  230. package/src/schema-types.ts +198 -0
  231. package/src/schema.ts +15 -19
  232. package/src/transcript-retention.ts +146 -0
  233. package/src/value-guards.ts +20 -0
  234. package/src/advisory-fixture.ts +0 -282
  235. package/src/fixture-bin.ts +0 -87
  236. package/src/fixture-setup.ts +0 -163
  237. package/src/generated-fixture.ts +0 -220
  238. package/src/git-hooks.ts +0 -63
  239. package/src/hosts/claude-nested-skills.ts +0 -218
  240. package/src/hosts/codex-command-output.ts +0 -399
  241. package/src/hosts/codex-executor-command.ts +0 -195
  242. package/src/hosts/codex-skill-reads.ts +0 -391
  243. package/src/preparation.ts +0 -211
@@ -0,0 +1,492 @@
1
+ import { runtimeLocalHttpEnabled, runtimeSeedSources } from "../runtime/config";
2
+ import {
3
+ candidateArtifactEnvironment,
4
+ candidateArtifactReadRoots,
5
+ } from "../candidate-artifacts";
6
+ import {
7
+ codexStateEnabled,
8
+ retainCandidateCodexState,
9
+ } from "../candidate-codex-state";
10
+ import { randomUUID } from "node:crypto";
11
+ import { existsSync } from "node:fs";
12
+ import { isMissingFile } from "../fixtures/tools";
13
+ import {
14
+ cp,
15
+ lstat,
16
+ mkdir,
17
+ readFile,
18
+ realpath,
19
+ rm,
20
+ stat,
21
+ symlink,
22
+ writeFile,
23
+ } from "node:fs/promises";
24
+ import { delimiter, dirname, join, sep } from "node:path";
25
+ import type { HostAdapter } from "../engine";
26
+ import {
27
+ candidateTranscriptEnvironment,
28
+ candidateTranscriptRoots,
29
+ } from "../native-transcripts";
30
+ import { prepareHookMounts } from "../runtime/hooks";
31
+ import {
32
+ requireRuntimeReadRoots,
33
+ runtimeExecutableProtection,
34
+ } from "../runtime/paths";
35
+ import { prepareRuntimeState } from "../runtime/state";
36
+ import {
37
+ sharedCacheDenyRoots,
38
+ sharedCacheRoots,
39
+ } from "../runtime/shared-cache";
40
+ import { codexPermissionProfile } from "./codex-profile";
41
+ import { evaluationProtectedRoots } from "./isolation-roots";
42
+ type Request = Parameters<HostAdapter["run"]>[0];
43
+ type CodexState = Awaited<ReturnType<typeof prepareCodexState>>;
44
+ const MAX_AUTH_BYTES = 1024 * 1024;
45
+
46
+ export interface CodexHostOptions {
47
+ binary: string;
48
+ authFile: string;
49
+ model: string;
50
+ effort: string;
51
+ projectRoot: string;
52
+ resultsRoot: string;
53
+ additionalProtectedRoots: string[];
54
+ agentConcurrencyLimit?: number | null;
55
+ timeoutMs?: number;
56
+ entrypoint?: "exec" | "app-server";
57
+ retainHostDiagnostics?: boolean;
58
+ /** Integration-test seam; production uses the same CLI for execution and preflight. */
59
+ sandboxBinary?: string;
60
+ }
61
+
62
+ async function copyAuth(source: string, target: string): Promise<void> {
63
+ let bytes: Buffer;
64
+ try {
65
+ if ((await stat(source)).size > MAX_AUTH_BYTES)
66
+ throw new Error("Codex auth file exceeds the size limit");
67
+ bytes = await readFile(source);
68
+ } catch (error) {
69
+ throw new Error("Codex auth file is unreadable or oversized", {
70
+ cause: error,
71
+ });
72
+ }
73
+ if (bytes.byteLength > MAX_AUTH_BYTES)
74
+ throw new Error("Codex auth file exceeds the size limit");
75
+ await writeFile(target, bytes, { flag: "wx", mode: 0o600 });
76
+ }
77
+
78
+ function shellQuote(value: string): string {
79
+ return `'${value.replaceAll("'", `'"'"'`)}'`;
80
+ }
81
+
82
+ export async function codexHookMounts(
83
+ request: Request,
84
+ state: CodexState,
85
+ installedPluginRoots: string[],
86
+ ) {
87
+ const hooks = await prepareHookMounts({
88
+ workspace: request.workspace,
89
+ stateRoot: state.stateRoot,
90
+ privateRoot: state.codexHome,
91
+ pluginRoots: installedPluginRoots,
92
+ protectedRoots: state.protectedRoots,
93
+ policy: request.runtimePolicy ?? {
94
+ format: "sevro.runtime.v1",
95
+ environment: {},
96
+ readOnlyRoots: [],
97
+ },
98
+ });
99
+ for (const [index, root] of installedPluginRoots.entries()) {
100
+ const curated = hooks.directories[index];
101
+ if (!curated) throw new Error("Codex hook projection is incomplete");
102
+ await rm(root, { recursive: true });
103
+ await cp(curated, root, { recursive: true });
104
+ }
105
+ return hooks;
106
+ }
107
+
108
+ async function privateCodexDirectories(stateRoot: string) {
109
+ const codexHome = join(stateRoot, "codex-home"),
110
+ parentHome = join(stateRoot, "home"),
111
+ parentTemp = join(stateRoot, "tmp"),
112
+ commandHome = join(stateRoot, "command-home"),
113
+ commandTemp = join(stateRoot, "command-tmp");
114
+ await Promise.all(
115
+ [codexHome, parentHome, parentTemp, commandHome, commandTemp].map((path) =>
116
+ mkdir(path, { mode: 0o700 }),
117
+ ),
118
+ );
119
+ if (process.platform === "linux")
120
+ await mkdir(join(codexHome, "tmp", "arg0"), {
121
+ recursive: true,
122
+ mode: 0o700,
123
+ });
124
+ return {
125
+ codexHome,
126
+ parentHome,
127
+ parentTemp,
128
+ commandHome,
129
+ commandTemp,
130
+ pluginCacheRoot: join(codexHome, "plugins", "cache"),
131
+ };
132
+ }
133
+
134
+ function requireUnprotectedExecutables(
135
+ readRoots: string[],
136
+ protectedRoots: string[],
137
+ ): void {
138
+ if (
139
+ readRoots.some((readRoot) =>
140
+ protectedRoots.some(
141
+ (root) => readRoot === root || readRoot.startsWith(`${root}${sep}`),
142
+ ),
143
+ )
144
+ )
145
+ throw new Error("Codex executable resides inside a protected root");
146
+ }
147
+
148
+ function concurrencyConfiguration(limit: number | null): string {
149
+ return limit === null
150
+ ? ""
151
+ : `\n[agents]\nmax_concurrent_threads_per_session = ${limit}\n`;
152
+ }
153
+
154
+ async function writeCodexProfile(
155
+ paths: Awaited<ReturnType<typeof privateCodexDirectories>>,
156
+ profileId: string,
157
+ request: Request,
158
+ readRoots: string[],
159
+ protectedRoots: string[],
160
+ limit: number | null,
161
+ commandEnvironment = request.runtimePolicy?.environment,
162
+ runtimeWriteRoot: string | undefined,
163
+ writableCacheRoots: string[],
164
+ ): Promise<void> {
165
+ const profile = codexPermissionProfile({
166
+ localHttp: runtimeLocalHttpEnabled(
167
+ request.runtimePolicy,
168
+ request.runtimeRole ?? "candidate",
169
+ ),
170
+ commandEnvironment: codexProfileEnvironment(
171
+ request,
172
+ paths,
173
+ commandEnvironment,
174
+ ),
175
+ runtimeWriteRoot,
176
+ runtimeReadRoots: request.runtimePolicy?.readOnlyRoots,
177
+ nativeReadRoots: codexNativeReadRoots(request, paths.codexHome),
178
+ nativeWriteRoots: [
179
+ ...codexStateWriteRoots(request, paths),
180
+ ...writableCacheRoots,
181
+ ],
182
+ id: profileId,
183
+ workspace: request.workspace,
184
+ commandHome: paths.commandHome,
185
+ commandTemp: paths.commandTemp,
186
+ executableReadRoots: readRoots,
187
+ ...codexPluginReadRoot(request, paths.pluginCacheRoot),
188
+ protectedRoots,
189
+ });
190
+ await writeFile(
191
+ join(paths.codexHome, "config.toml"),
192
+ profile +
193
+ concurrencyConfiguration(limit) +
194
+ fixtureTrustConfiguration(request),
195
+ { flag: "wx", mode: 0o600 },
196
+ );
197
+ }
198
+
199
+ function fixtureTrustConfiguration(request: Request): string {
200
+ return `\n[projects.${JSON.stringify(request.workspace)}]\ntrust_level = "untrusted"\n`;
201
+ }
202
+
203
+ function codexProfileEnvironment(
204
+ request: Request,
205
+ paths: Awaited<ReturnType<typeof privateCodexDirectories>>,
206
+ environment: Record<string, string> | undefined,
207
+ ) {
208
+ return {
209
+ ...codexCommandEnvironment(request, environment),
210
+ ...codexTranscriptEnvironment(request, paths.codexHome),
211
+ ...candidateTranscriptEnvironment(request.candidateTranscriptRoot),
212
+ ...candidateArtifactEnvironment(request.candidateArtifactRoots),
213
+ ...(request.runtimePolicy?.codexState === true
214
+ ? {
215
+ SEVRO_CODEX_AGENT_SKILLS: join(paths.parentHome, ".agents", "skills"),
216
+ }
217
+ : {}),
218
+ };
219
+ }
220
+
221
+ function codexTranscriptEnvironment(
222
+ request: Request,
223
+ home: string,
224
+ ): Record<string, string> {
225
+ return {
226
+ ...(request.runtimePolicy?.codexState === true ? { CODEX_HOME: home } : {}),
227
+ ...(request.runtimePolicy?.nativeTranscripts === true
228
+ ? {
229
+ CODEX_HOME: home,
230
+ SEVRO_NATIVE_TRANSCRIPT_ROOT: join(home, "sessions"),
231
+ }
232
+ : {}),
233
+ };
234
+ }
235
+
236
+ function codexNativeReadRoots(request: Request, home: string) {
237
+ return [
238
+ ...linuxArg0ReadRoots(home),
239
+ ...(request.runtimePolicy?.codexState === true
240
+ ? [join(home, "config.toml"), join(home, "skills", ".system")]
241
+ : []),
242
+ ...codexTranscriptRoots(request, home),
243
+ ...candidateTranscriptRoots(request.candidateTranscriptRoot),
244
+ ...candidateArtifactReadRoots(request.candidateArtifactRoots),
245
+ ];
246
+ }
247
+
248
+ function codexTranscriptRoots(request: Request, home: string) {
249
+ return request.runtimePolicy?.nativeTranscripts === true
250
+ ? [join(home, "sessions")]
251
+ : [];
252
+ }
253
+
254
+ function linuxArg0ReadRoots(codexHome: string): string[] {
255
+ return process.platform === "linux" ? [join(codexHome, "tmp", "arg0")] : [];
256
+ }
257
+
258
+ function codexPluginReadRoot(request: Request, root: string) {
259
+ return request.codexMarketplace ? { pluginReadRoot: root } : {};
260
+ }
261
+
262
+ function codexHelperBinary(options: CodexHostOptions, sandboxBinary: string) {
263
+ const sibling = join(dirname(options.binary), "codex-linux-sandbox");
264
+ return (
265
+ options.sandboxBinary ?? (existsSync(sibling) ? sibling : sandboxBinary)
266
+ );
267
+ }
268
+
269
+ function codexCommandEnvironment(
270
+ request: Request,
271
+ environment: Record<string, string> | undefined,
272
+ ): Record<string, string> | undefined {
273
+ if (!environment) return undefined;
274
+ const path =
275
+ environment.PATH ??
276
+ "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
277
+ return {
278
+ ...environment,
279
+ PATH: [request.fixtureBinDir, path].filter(Boolean).join(delimiter),
280
+ };
281
+ }
282
+
283
+ async function fixtureShellRoot(
284
+ request: Request,
285
+ parentHome: string,
286
+ ): Promise<string> {
287
+ const root = join(request.workspace, ".git", "sevro-shell");
288
+ if (request.fixtureBinDir) {
289
+ await mkdir(root, { mode: 0o700 });
290
+ await writeFile(
291
+ process.platform === "linux"
292
+ ? join(parentHome, ".bash_profile")
293
+ : join(root, ".zprofile"),
294
+ `export PATH=${shellQuote(request.fixtureBinDir)}:"$PATH"\n`,
295
+ { flag: "wx", mode: 0o600 },
296
+ );
297
+ }
298
+ return root;
299
+ }
300
+
301
+ function codexPath(fixtureBin: string | undefined): string {
302
+ const path = process.env.PATH ?? "/usr/bin:/bin";
303
+ return fixtureBin ? `${fixtureBin}${delimiter}${path}` : path;
304
+ }
305
+
306
+ function codexEnvironment(
307
+ paths: Awaited<ReturnType<typeof privateCodexDirectories>>,
308
+ request: Request,
309
+ shellRoot: string,
310
+ ): Record<string, string> {
311
+ return {
312
+ ...request.runtimePolicy?.environment,
313
+ PATH: declaredCodexPath(request),
314
+ LANG: process.env.LANG ?? "C",
315
+ HOME: paths.parentHome,
316
+ TMPDIR: paths.parentTemp,
317
+ CODEX_HOME: paths.codexHome,
318
+ NO_COLOR: "1",
319
+ ...(request.fixtureBinDir && process.platform === "darwin"
320
+ ? { ZDOTDIR: shellRoot }
321
+ : {}),
322
+ };
323
+ }
324
+
325
+ function declaredCodexPath(request: Request): string {
326
+ return (
327
+ request.runtimePolicy?.environment.PATH ?? codexPath(request.fixtureBinDir)
328
+ );
329
+ }
330
+
331
+ function codexSeedSources(request: Request): string[] {
332
+ return runtimeSeedSources(request.runtimePolicy);
333
+ }
334
+
335
+ async function codexRuntime(
336
+ request: Request,
337
+ paths: Awaited<ReturnType<typeof privateCodexDirectories>>,
338
+ stateRoot: string,
339
+ ) {
340
+ if (!request.runtimePolicy) return undefined;
341
+ const runtime = await prepareRuntimeState(
342
+ request.workspace,
343
+ request.runtimePolicy,
344
+ request.runtimeRole ?? "candidate",
345
+ request.candidateRuntimeRoot ?? join(stateRoot, "runtime"),
346
+ );
347
+ paths.commandHome = runtime.home;
348
+ paths.commandTemp = runtime.temp;
349
+ return runtime;
350
+ }
351
+
352
+ function applyCodexRuntime(
353
+ runtime: Awaited<ReturnType<typeof codexRuntime>>,
354
+ env: Record<string, string>,
355
+ paths: Awaited<ReturnType<typeof privateCodexDirectories>>,
356
+ ): void {
357
+ if (runtime)
358
+ Object.assign(env, runtime.environment, {
359
+ HOME: paths.parentHome,
360
+ TMPDIR: paths.parentTemp,
361
+ });
362
+ }
363
+
364
+ function codexRuntimeProfile(
365
+ runtime: Awaited<ReturnType<typeof codexRuntime>>,
366
+ ) {
367
+ return {
368
+ environment: runtime?.environment,
369
+ root: runtime?.root,
370
+ writableCacheRoots: runtime?.writableCacheRoots ?? [],
371
+ };
372
+ }
373
+
374
+ export async function prepareCodexState(
375
+ stateRoot: string,
376
+ options: CodexHostOptions,
377
+ limit: number | null,
378
+ request: Request,
379
+ helperRoot?: string,
380
+ ) {
381
+ await requireIsolatedGitMetadata(request.workspace);
382
+ const paths = await privateCodexDirectories(stateRoot);
383
+ const runtime = await codexRuntime(request, paths, stateRoot);
384
+ await copyAuth(options.authFile, join(paths.codexHome, "auth.json"));
385
+ const protectedRoots = await evaluationProtectedRoots({
386
+ workspace: request.workspace,
387
+ projectRoot: options.projectRoot,
388
+ resultsRoot: options.resultsRoot,
389
+ additionalRoots: [
390
+ ...options.additionalProtectedRoots,
391
+ ...(process.platform === "linux" ? [] : [stateRoot]),
392
+ ...codexSeedSources(request),
393
+ ],
394
+ });
395
+ const sandboxBinary = options.sandboxBinary ?? options.binary;
396
+ const helperBinary = codexHelperBinary(options, sandboxBinary);
397
+ if (helperRoot)
398
+ await symlink(helperBinary, join(helperRoot, "codex-linux-sandbox"));
399
+ const readRoots = await Promise.all(
400
+ [options.binary, sandboxBinary, helperBinary].flatMap((binary) => [
401
+ realpath(dirname(binary)),
402
+ realpath(binary).then(dirname),
403
+ ]),
404
+ );
405
+ if (helperRoot) readRoots.push(helperRoot);
406
+ requireCodexRuntimeReadRoots(request, protectedRoots);
407
+ await sharedCacheRoots(
408
+ request.runtimePolicy,
409
+ request.runtimeRole,
410
+ protectedRoots,
411
+ );
412
+ protectedRoots.push(
413
+ ...sharedCacheDenyRoots(request.runtimePolicy, request.runtimeRole),
414
+ );
415
+ requireUnprotectedExecutables(
416
+ readRoots,
417
+ runtimeExecutableProtection(
418
+ readRoots,
419
+ protectedRoots,
420
+ Boolean(request.runtimePolicy),
421
+ ),
422
+ );
423
+ const profileId = `sevro_${randomUUID().replaceAll("-", "")}`;
424
+ const runtimeProfile = codexRuntimeProfile(runtime);
425
+ await writeCodexProfile(
426
+ paths,
427
+ profileId,
428
+ request,
429
+ readRoots,
430
+ protectedRoots,
431
+ limit,
432
+ runtimeProfile.environment,
433
+ runtimeProfile.root,
434
+ runtimeProfile.writableCacheRoots,
435
+ );
436
+ const shellRoot = await fixtureShellRoot(request, paths.parentHome);
437
+ const env = codexEnvironment(paths, request, shellRoot);
438
+ applyCodexRuntime(runtime, env, paths);
439
+ await prepareCodexStateEvidence(request, stateRoot, paths);
440
+ return { ...paths, stateRoot, sandboxBinary, profileId, env, protectedRoots };
441
+ }
442
+
443
+ function codexStateWriteRoots(
444
+ request: Request,
445
+ paths: Awaited<ReturnType<typeof privateCodexDirectories>>,
446
+ ): string[] {
447
+ return codexStateEnabled(request.runtimePolicy)
448
+ ? [
449
+ join(paths.codexHome, "skills"),
450
+ join(paths.parentHome, ".agents", "skills"),
451
+ join(paths.commandHome, ".agents", "skills"),
452
+ join(paths.commandHome, ".codex", "skills"),
453
+ ]
454
+ : [];
455
+ }
456
+
457
+ async function prepareCodexStateEvidence(
458
+ request: Request,
459
+ stateRoot: string,
460
+ paths: Awaited<ReturnType<typeof privateCodexDirectories>>,
461
+ ): Promise<void> {
462
+ if (!codexStateEnabled(request.runtimePolicy)) return;
463
+ const skillRoots = codexStateWriteRoots(request, paths);
464
+ await Promise.all(
465
+ skillRoots.map((root) => mkdir(root, { recursive: true, mode: 0o700 })),
466
+ );
467
+ await mkdir(join(paths.codexHome, "skills", ".system"), { mode: 0o700 });
468
+ await retainCandidateCodexState(
469
+ request.candidateRuntimeRoot,
470
+ stateRoot,
471
+ paths,
472
+ );
473
+ }
474
+
475
+ async function requireIsolatedGitMetadata(workspace: string): Promise<void> {
476
+ try {
477
+ if (!(await lstat(join(workspace, ".git"))).isDirectory())
478
+ throw new Error("Codex Git metadata must be an isolated directory");
479
+ } catch (cause) {
480
+ if (!isMissingFile(cause)) throw cause;
481
+ }
482
+ }
483
+
484
+ function requireCodexRuntimeReadRoots(
485
+ request: Request,
486
+ protectedRoots: string[],
487
+ ): void {
488
+ requireRuntimeReadRoots(
489
+ request.runtimePolicy?.readOnlyRoots ?? [],
490
+ protectedRoots,
491
+ );
492
+ }