@bjoernrochel/sevro 0.1.0-rc.7 → 1.0.0

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 (230) hide show
  1. package/CONTRIBUTING.md +22 -6
  2. package/README.md +5 -1
  3. package/docs/README.md +19 -15
  4. package/docs/architecture.md +138 -17
  5. package/docs/assets/README.md +5 -4
  6. package/docs/builtin-graders-v1.md +17 -3
  7. package/docs/codex-host.md +155 -49
  8. package/docs/contracts.md +2 -1
  9. package/docs/creating-cases.md +9 -2
  10. package/docs/development-cli.md +187 -19
  11. package/docs/documentation-quality.md +12 -2
  12. package/docs/evaluation-workflow.md +110 -0
  13. package/docs/extension-protocol-v1.md +26 -3
  14. package/docs/getting-started.md +23 -2
  15. package/docs/guide-evaluation.md +33 -1
  16. package/docs/identity-v1.md +25 -2
  17. package/docs/installing.md +11 -11
  18. package/docs/native-controls.md +76 -5
  19. package/docs/native-hosts.md +196 -7
  20. package/docs/reading-results.md +102 -0
  21. package/docs/recipes.md +489 -0
  22. package/docs/releases.md +31 -12
  23. package/docs/report-v1.md +29 -1
  24. package/docs/results-v1.md +141 -4
  25. package/docs/run-settings-v1.md +147 -0
  26. package/docs/runtime-v1.md +307 -18
  27. package/docs/troubleshooting.md +12 -12
  28. package/docs/typescript-quality.md +89 -26
  29. package/examples/recipes/candidate.ts +26 -0
  30. package/examples/recipes/cases/coding-change.json +21 -0
  31. package/examples/recipes/cases/host-tools.json +21 -0
  32. package/examples/recipes/cases/mise.json +21 -0
  33. package/examples/recipes/cases/node.json +21 -0
  34. package/examples/recipes/cases/python-uv.json +21 -0
  35. package/examples/recipes/cases/semantic.json +17 -0
  36. package/examples/recipes/coding-change/check.ts +23 -0
  37. package/examples/recipes/extension-artifacts.ts +65 -0
  38. package/examples/recipes/extension-methods.ts +174 -0
  39. package/examples/recipes/extension.ts +37 -0
  40. package/examples/recipes/repository-skill/.agents/skills/startup-guide/SKILL.md +7 -0
  41. package/examples/recipes/repository-skill/.claude/skills/startup-guide/SKILL.md +7 -0
  42. package/examples/recipes/repository-skill/README.md +3 -0
  43. package/examples/recipes/run-inputs.ts +188 -0
  44. package/examples/recipes/run-options.ts +131 -0
  45. package/examples/recipes/run.ts +63 -0
  46. package/examples/recipes/runtime/empty.json +1 -0
  47. package/examples/recipes/runtime/goals.json +1 -0
  48. package/examples/recipes/runtime/mise.json +6 -0
  49. package/examples/recipes/runtime/path.json +1 -0
  50. package/examples/recipes/runtime/plugins.json +4 -0
  51. package/examples/recipes/runtime/python-uv.json +10 -0
  52. package/examples/recipes/semantic-judge.ts +29 -0
  53. package/examples/templates/coding/adapter.ts +17 -0
  54. package/examples/templates/coding/case.json +15 -0
  55. package/examples/templates/repository-skill/.agents/skills/startup-guide/SKILL.md +6 -0
  56. package/examples/templates/repository-skill/extension-data.ts +64 -0
  57. package/examples/templates/repository-skill/extension.ts +31 -0
  58. package/examples/templates/response/adapter.ts +12 -0
  59. package/examples/templates/response/case.json +13 -0
  60. package/package.json +11 -5
  61. package/schemas/report-v1.schema.json +9 -0
  62. package/schemas/run-evidence-v1.schema.json +2 -1
  63. package/schemas/runtime-v1.schema.json +63 -7
  64. package/src/advisory.ts +2 -2
  65. package/src/candidate-artifacts.ts +206 -0
  66. package/src/candidate-codex-state.ts +185 -0
  67. package/src/case-validation.ts +73 -50
  68. package/src/cli/cancellation.ts +20 -0
  69. package/src/cli/command.ts +87 -0
  70. package/src/cli/completion.ts +15 -0
  71. package/src/cli/demo.ts +60 -0
  72. package/src/cli/evaluation.ts +55 -0
  73. package/src/cli/extension.ts +79 -0
  74. package/src/cli/help.ts +198 -0
  75. package/src/cli/hosts.ts +55 -0
  76. package/src/cli/init-settings.ts +30 -0
  77. package/src/cli/init.ts +111 -0
  78. package/src/{cli-invocation.ts → cli/invocation.ts} +214 -218
  79. package/src/cli/loading.ts +151 -0
  80. package/src/cli/preparation.ts +154 -0
  81. package/src/cli/presentation.ts +93 -0
  82. package/src/cli/progress.ts +6 -0
  83. package/src/cli/run-manifest.ts +128 -0
  84. package/src/cli/run-options.ts +403 -0
  85. package/src/cli/run-overrides.ts +69 -0
  86. package/src/cli/settings.ts +154 -0
  87. package/src/cli/summary.ts +43 -0
  88. package/src/cli.ts +13 -591
  89. package/src/contract-types.ts +94 -0
  90. package/src/engine.ts +13 -5
  91. package/src/evaluation/advisory.ts +150 -0
  92. package/src/{evaluation-check-plan.ts → evaluation/check-plan.ts} +103 -67
  93. package/src/evaluation/collection.ts +88 -0
  94. package/src/evaluation/context.ts +10 -0
  95. package/src/{evaluation-grading-host.ts → evaluation/grading-host.ts} +5 -5
  96. package/src/evaluation/grading.ts +235 -0
  97. package/src/{evaluation-invocation-plan.ts → evaluation/invocation-plan.ts} +147 -58
  98. package/src/{evaluation-plan.ts → evaluation/plan.ts} +208 -141
  99. package/src/evaluation/progress.ts +19 -0
  100. package/src/evaluation/resources.ts +241 -0
  101. package/src/evaluation/run.ts +206 -0
  102. package/src/{evaluation-semantic.ts → evaluation/semantic.ts} +133 -115
  103. package/src/evaluation/trial.ts +800 -0
  104. package/src/evaluation/types.ts +207 -0
  105. package/src/evidence/host-results.ts +85 -0
  106. package/src/evidence/retained-artifacts.ts +18 -0
  107. package/src/evidence/trial-writer.ts +203 -0
  108. package/src/evidence/usage.ts +17 -0
  109. package/src/extension-client.ts +34 -96
  110. package/src/extension-session.ts +24 -48
  111. package/src/{advisory-fixture.ts → fixtures/advisory.ts} +15 -76
  112. package/src/{fixture-bin.ts → fixtures/bin.ts} +1 -1
  113. package/src/{fixture-cleanup.ts → fixtures/cleanup.ts} +13 -7
  114. package/src/fixtures/create.ts +183 -0
  115. package/src/{generated-fixture.ts → fixtures/generated.ts} +10 -53
  116. package/src/{git-hooks.ts → fixtures/git-hooks.ts} +1 -1
  117. package/src/{preparation.ts → fixtures/preparation.ts} +6 -16
  118. package/src/{repository-fixture.ts → fixtures/repository.ts} +30 -64
  119. package/src/{fixture-setup.ts → fixtures/setup.ts} +33 -51
  120. package/src/{fixture-tools.ts → fixtures/tools.ts} +1 -1
  121. package/src/fixtures/workspace-state.ts +212 -0
  122. package/src/generated/cli-result.d.cts +2 -1
  123. package/src/generated/extension.d.cts +2 -1
  124. package/src/generated/report.cjs +1 -1
  125. package/src/generated/report.d.cts +2 -1
  126. package/src/generated/run-evidence.cjs +1 -1
  127. package/src/generated/run-evidence.d.cts +2 -1
  128. package/src/generated/runtime.cjs +1 -1
  129. package/src/generated/runtime.d.cts +2 -1
  130. package/src/git.ts +93 -0
  131. package/src/graders/git-head.ts +29 -84
  132. package/src/graders/shell.ts +57 -68
  133. package/src/host-diagnostic-redaction.ts +52 -0
  134. package/src/host-diagnostics.ts +331 -0
  135. package/src/hosts/claude-continuation.ts +57 -40
  136. package/src/hosts/claude-credential.ts +160 -63
  137. package/src/hosts/claude-events.ts +34 -18
  138. package/src/hosts/claude-keychain.ts +78 -21
  139. package/src/hosts/claude-login-recovery.ts +85 -0
  140. package/src/hosts/claude-login-refresh.ts +178 -0
  141. package/src/hosts/claude-login-store.ts +162 -0
  142. package/src/hosts/claude-login.ts +107 -0
  143. package/src/hosts/claude-native-goal.ts +15 -12
  144. package/src/hosts/claude-process.ts +10 -50
  145. package/src/hosts/claude-refresh-lock.ts +81 -0
  146. package/src/hosts/claude-repository-invocation.ts +9 -34
  147. package/src/hosts/claude-settings.ts +91 -14
  148. package/src/hosts/claude-skill-activations.ts +169 -0
  149. package/src/hosts/claude-state.ts +46 -16
  150. package/src/hosts/claude-tool-calls.ts +13 -10
  151. package/src/hosts/claude.ts +122 -73
  152. package/src/hosts/codex-app-server.ts +78 -52
  153. package/src/hosts/codex-configuration.ts +5 -10
  154. package/src/hosts/codex-exec-controls.ts +516 -0
  155. package/src/hosts/codex-marketplace.ts +12 -10
  156. package/src/hosts/codex-native-calls.ts +15 -122
  157. package/src/hosts/codex-process.ts +10 -54
  158. package/src/hosts/codex-profile.ts +50 -21
  159. package/src/hosts/codex-session-files.ts +1 -9
  160. package/src/hosts/codex-state.ts +141 -19
  161. package/src/hosts/codex.ts +221 -179
  162. package/src/hosts/isolation-common.ts +15 -11
  163. package/src/hosts/isolation-roots.ts +13 -20
  164. package/src/hosts/linux-sandbox.ts +27 -3
  165. package/src/hosts/mac-sandbox.ts +111 -18
  166. package/src/hosts/native-controls.ts +72 -22
  167. package/src/hosts/native-request.ts +55 -0
  168. package/src/hosts/process-lifecycle.ts +221 -45
  169. package/src/hosts/protected-worktrees.ts +1 -1
  170. package/src/hosts/skill-hook-catalog.ts +131 -0
  171. package/src/hosts/skill-hook-handler.ts +374 -0
  172. package/src/hosts/skill-hooks.ts +577 -0
  173. package/src/instrumentation.ts +7 -8
  174. package/src/native-transcript-state.ts +22 -3
  175. package/src/native-transcript-view.ts +52 -13
  176. package/src/native-transcripts.ts +34 -15
  177. package/src/paths.ts +22 -0
  178. package/src/process-lifecycle.ts +150 -0
  179. package/src/provenance.ts +27 -7
  180. package/src/prune.ts +300 -0
  181. package/src/report.ts +63 -36
  182. package/src/resolved-case.ts +1 -1
  183. package/src/results-lock.ts +42 -0
  184. package/src/results.ts +14 -18
  185. package/src/run-owner.ts +4 -4
  186. package/src/{runtime-config.ts → runtime/config.ts} +182 -44
  187. package/src/{runtime-discovery-apple.ts → runtime/discovery-apple.ts} +3 -3
  188. package/src/{runtime-discovery-providers.ts → runtime/discovery-providers.ts} +3 -9
  189. package/src/{runtime-discovery.ts → runtime/discovery.ts} +4 -4
  190. package/src/{runtime-hook-processes.ts → runtime/hook-processes.ts} +1 -1
  191. package/src/{runtime-hooks.ts → runtime/hooks.ts} +33 -22
  192. package/src/{runtime-paths.ts → runtime/paths.ts} +6 -18
  193. package/src/{runtime-query.ts → runtime/query.ts} +5 -5
  194. package/src/{runtime-seeds.ts → runtime/seeds.ts} +3 -3
  195. package/src/runtime/shared-cache.ts +130 -0
  196. package/src/{runtime-state.ts → runtime/state.ts} +28 -11
  197. package/src/schema-types.ts +73 -161
  198. package/src/shell.ts +4 -0
  199. package/src/transcript-retention.ts +146 -0
  200. package/src/value-guards.ts +22 -0
  201. package/docs/assets/sevro-logo.png +0 -0
  202. package/docs/assets/sevro-symbol.svg +0 -1
  203. package/docs/assets/sevro-wordmark.svg +0 -1
  204. package/docs/cache-ownership-validation.md +0 -49
  205. package/docs/claude-continuation-validation.md +0 -47
  206. package/docs/concurrency-validation.md +0 -86
  207. package/docs/configuration-root-validation.md +0 -70
  208. package/docs/engine-slice.md +0 -114
  209. package/docs/evidence/runtime-startup-30/README.md +0 -185
  210. package/docs/evidence/runtime-startup-30/after.json +0 -319
  211. package/docs/evidence/runtime-startup-30/baseline-timeout.jsonl +0 -2
  212. package/docs/evidence/runtime-startup-30/before.json +0 -403
  213. package/docs/evidence/runtime-startup-30/consumer-smoke.json +0 -63
  214. package/docs/evidence/runtime-startup-30/full-policy-diagnostic.jsonl +0 -3
  215. package/docs/evidence/runtime-startup-30/minimal-policy-diagnostic.jsonl +0 -3
  216. package/docs/evidence/runtime-startup-30/plugin-template-assessment.json +0 -20
  217. package/docs/evidence/runtime-startup-30/seedless-timeout.jsonl +0 -2
  218. package/docs/evidence.md +0 -17
  219. package/docs/fixture-cleanup-validation.md +0 -70
  220. package/docs/guide-verification.json +0 -239
  221. package/src/evaluation-context.ts +0 -257
  222. package/src/evaluation-fixture.ts +0 -313
  223. package/src/evaluation-run.ts +0 -198
  224. package/src/evaluation-trial.ts +0 -946
  225. package/src/evaluation-types.ts +0 -149
  226. package/src/hosts/claude-nested-skills.ts +0 -272
  227. package/src/hosts/codex-command-output.ts +0 -510
  228. package/src/hosts/codex-executor-command.ts +0 -291
  229. package/src/hosts/codex-skill-reads.ts +0 -609
  230. /package/src/{trial-scheduler.ts → evaluation/scheduler.ts} +0 -0
@@ -1,24 +1,22 @@
1
1
  import { existsSync } from "node:fs";
2
- import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
2
+ import { candidateCodexStateRetained } from "../candidate-codex-state";
3
+ import { lstat, mkdtemp, rm, writeFile } from "node:fs/promises";
3
4
  import { tmpdir } from "node:os";
4
5
  import { isAbsolute, join } from "node:path";
5
6
  import type { HostAdapter, HostResult } from "../engine";
6
- import {
7
- allocateNativeState,
8
- requireNativeTranscriptView,
9
- } from "../native-transcript-state";
7
+ import { allocateNativeState } from "../native-transcript-state";
10
8
  import {
11
9
  failedNativeTranscripts,
12
10
  retainEnabledNativeTranscripts,
13
11
  } from "../native-transcripts";
14
- import { runtimeNativeGoalEnabled } from "../runtime-config";
12
+ import { runtimeNativeGoalEnabled } from "../runtime/config";
15
13
  import {
16
14
  releaseRuntimeHooks,
17
15
  runtimeHookObservation,
18
16
  type HookMounts,
19
- } from "../runtime-hooks";
20
- import { runtimeObservations } from "../runtime-state";
21
- import { isOptionalNonblankString } from "../value-guards";
17
+ } from "../runtime/hooks";
18
+ import { runtimeObservations } from "../runtime/state";
19
+ import { validateNativeHostRequest, validHostTimeout } from "./native-request";
22
20
  import { runCodexAppServer } from "./codex-app-server";
23
21
  import { summarizeCodexEvents, type CodexEventSummary } from "./codex-events";
24
22
  import { verifyCodexInvocation } from "./codex-invocation";
@@ -26,10 +24,8 @@ import { installCodexPlugins } from "./codex-marketplace";
26
24
  import {
27
25
  codexNativeCallObservation,
28
26
  codexNativeSessionLastOrdinal,
29
- codexNativeSkillReadRecovery,
30
27
  } from "./codex-native-calls";
31
- import { runCodexProcess } from "./codex-process";
32
- import { codexSkillReadObservation } from "./codex-skill-reads";
28
+ import { runCodexProcess, CodexProcessFailure } from "./codex-process";
33
29
  import {
34
30
  codexHookMounts,
35
31
  prepareCodexState,
@@ -37,13 +33,29 @@ import {
37
33
  } from "./codex-state";
38
34
  import { codexNativeControls } from "./native-controls";
39
35
  import { workspaceFingerprint } from "./workspace-fingerprint";
36
+ import { codexSkillCatalogRoots } from "./skill-hook-catalog";
37
+ import {
38
+ prepareSkillTracking,
39
+ skillTrackingObservation,
40
+ skillActivationClaims,
41
+ skillExposureClaims,
42
+ type SkillTracking,
43
+ } from "./skill-hooks";
44
+ import {
45
+ cleanupNativeState,
46
+ createHostDiagnostics,
47
+ diagnoseHostRequest,
48
+ diagnosticConfiguration,
49
+ type HostDiagnostics,
50
+ } from "../host-diagnostics";
40
51
  export type { CodexHostOptions } from "./codex-state";
41
- const MAX_EVENT_BYTES = 8 * 1024 * 1024;
42
52
  const DEFAULT_TIMEOUT_MS = 10 * 60_000;
43
- const MAX_TIMEOUT_MS = 30 * 60_000;
44
53
 
45
54
  type Request = Parameters<HostAdapter["run"]>[0];
46
- type ProcessResult = Awaited<ReturnType<typeof runCodexProcess>>;
55
+ type ProcessResult = Pick<
56
+ Awaited<ReturnType<typeof runCodexProcess>>,
57
+ "code" | "out"
58
+ >;
47
59
  type AppServerResult = Awaited<ReturnType<typeof runCodexAppServer>>;
48
60
  type Observation = {
49
61
  id: string;
@@ -56,8 +68,9 @@ interface ExecutionContext {
56
68
  request: Request;
57
69
  state: CodexState;
58
70
  timeoutMs: number;
59
- installedPluginRoots: string[];
60
- hooks?: HookMounts;
71
+ hooks: HookMounts;
72
+ skillTracking: SkillTracking;
73
+ diagnostics: HostDiagnostics;
61
74
  }
62
75
  interface TurnResult {
63
76
  execution: ProcessResult;
@@ -66,7 +79,6 @@ interface TurnResult {
66
79
  followUpSummary: CodexEventSummary | null;
67
80
  appServer: AppServerResult | null;
68
81
  continuation: Observation | undefined;
69
- observedEvents: string;
70
82
  }
71
83
 
72
84
  function validCodexPaths(options: CodexHostOptions): boolean {
@@ -89,13 +101,7 @@ function validEntrypoint(entrypoint: CodexHostOptions["entrypoint"]): boolean {
89
101
  entrypoint === undefined || ["exec", "app-server"].includes(entrypoint)
90
102
  );
91
103
  }
92
- function validCodexTimeout(timeoutMs: number): boolean {
93
- return (
94
- Number.isSafeInteger(timeoutMs) &&
95
- timeoutMs >= 1 &&
96
- timeoutMs <= MAX_TIMEOUT_MS
97
- );
98
- }
104
+
99
105
  function validCodexIdentity(
100
106
  options: CodexHostOptions,
101
107
  limit: number | null,
@@ -106,7 +112,7 @@ function validCodexIdentity(
106
112
  !!options.effort &&
107
113
  validEntrypoint(options.entrypoint) &&
108
114
  validConcurrencyLimit(limit) &&
109
- validCodexTimeout(timeoutMs)
115
+ validHostTimeout(timeoutMs)
110
116
  );
111
117
  }
112
118
  function codexCapabilities(
@@ -117,21 +123,24 @@ function codexCapabilities(
117
123
  "sevro.host.runtime",
118
124
  "sevro.host.native-transcripts",
119
125
  "sevro.host.hooks",
126
+ "sevro.host.skill-activations",
127
+ "sevro.host.skill-activation-claims",
128
+ "sevro.host.skill-exposure-claims",
120
129
  ...(entrypoint === "app-server" ? ["sevro.host.native-goal"] : []),
121
130
  "sevro.codex.plugin-marketplace",
122
131
  "sevro.codex.explicit-invocation",
123
132
  "sevro.codex.repository-invocation",
124
133
  "sevro.codex.native-calls",
125
134
  "sevro.host.native-controls",
126
- "sevro.codex.initial-skill-reads",
127
- "sevro.codex.follow-up-skill-reads",
128
135
  ];
129
136
  }
130
137
  function codexConfiguration(
131
138
  limit: number | null,
132
139
  entrypoint: CodexHostOptions["entrypoint"],
140
+ retainDiagnostics?: boolean,
133
141
  ) {
134
142
  return {
143
+ ...diagnosticConfiguration(retainDiagnostics),
135
144
  "sevro.codex.agent-concurrency-limit": limit,
136
145
  ...(entrypoint === "app-server"
137
146
  ? { "sevro.codex.entrypoint": "app-server" }
@@ -153,30 +162,17 @@ export function createCodexHost(options: CodexHostOptions): HostAdapter {
153
162
  hostCapabilities: codexCapabilities(options.entrypoint),
154
163
  model: options.model,
155
164
  effort: options.effort,
156
- configuration: codexConfiguration(limit, options.entrypoint),
165
+ configuration: codexConfiguration(
166
+ limit,
167
+ options.entrypoint,
168
+ options.retainHostDiagnostics,
169
+ ),
157
170
  run(request) {
158
171
  return runCodexRequest(options, timeoutMs, limit, request);
159
172
  },
160
173
  };
161
174
  }
162
175
 
163
- function requireCodexFollowUp(request: Request): void {
164
- if (!isOptionalNonblankString(request.followUpPrompt))
165
- throw new Error("Codex follow-up prompt must be nonempty");
166
- }
167
- function requireCodexFixtureBin(request: Request): void {
168
- if (
169
- request.fixtureBinDir !== undefined &&
170
- request.fixtureBinDir !== join(request.workspace, ".git", "fixture-bin")
171
- )
172
- throw new Error("fixture binary path is outside the workspace");
173
- }
174
- function requirePassiveCodexRequest(request: Request): void {
175
- if (request.instrumentation?.length)
176
- throw new Error("Codex instrumentation is unavailable");
177
- if (request.condition !== "passive")
178
- throw new Error("Codex enforcement instrumentation is unavailable");
179
- }
180
176
  function allocateCodexRequestState(request: Request): Promise<string> {
181
177
  return allocateNativeState(
182
178
  "codex-",
@@ -190,14 +186,27 @@ async function runCodexRequest(
190
186
  limit: number | null,
191
187
  request: Request,
192
188
  ): Promise<HostResult> {
193
- requireCodexFollowUp(request);
194
- await requireNativeTranscriptView(request.candidateTranscriptRoot);
195
- requireCodexFixtureBin(request);
196
- requirePassiveCodexRequest(request);
197
- requireCodexGoalRoute(request, options);
198
- await verifyCodexInvocation(request);
199
- if (existsSync(join(request.workspace, ".codex")))
200
- throw new Error("fixture Codex configuration is unsupported");
189
+ const diagnostics = createHostDiagnostics(
190
+ "codex",
191
+ options.retainHostDiagnostics,
192
+ request,
193
+ );
194
+ return diagnoseHostRequest(diagnostics, async () => {
195
+ diagnostics.stage = "request-validation";
196
+ await diagnostics.readCredentials(options.authFile);
197
+ return executeCodexRequest(options, timeoutMs, limit, request, diagnostics);
198
+ });
199
+ }
200
+
201
+ async function executeCodexRequest(
202
+ options: CodexHostOptions,
203
+ timeoutMs: number,
204
+ limit: number | null,
205
+ request: Request,
206
+ diagnostics: HostDiagnostics,
207
+ ): Promise<HostResult> {
208
+ await validateCodexRequest(options, request);
209
+ diagnostics.stage = "state-allocation";
201
210
  const stateRoot = await allocateCodexRequestState(request);
202
211
  const helperRoot =
203
212
  process.platform === "linux"
@@ -205,6 +214,7 @@ async function runCodexRequest(
205
214
  : undefined;
206
215
  let activeHooks: HookMounts | undefined;
207
216
  try {
217
+ diagnostics.stage = "state-preparation";
208
218
  const state = await prepareCodexState(
209
219
  stateRoot,
210
220
  options,
@@ -217,29 +227,29 @@ async function runCodexRequest(
217
227
  env: state.env,
218
228
  pluginCacheRoot: state.pluginCacheRoot,
219
229
  request,
230
+ diagnostics,
220
231
  });
232
+ diagnostics.stage = "hooks-preparation";
221
233
  const hooks = await codexHookMounts(request, state, installedPluginRoots);
222
234
  activeHooks = hooks;
223
- await codexPreflight(options, request, state);
235
+ const skillTracking = await prepareCodexSkillTracking(
236
+ state,
237
+ request,
238
+ installedPluginRoots,
239
+ );
240
+ await codexPreflight(options, request, state, diagnostics);
224
241
  const context: ExecutionContext = {
225
242
  options,
226
243
  request,
227
244
  state,
228
245
  timeoutMs,
229
- installedPluginRoots,
230
246
  hooks,
247
+ skillTracking,
248
+ diagnostics,
231
249
  };
232
250
  const turns = await new CodexTurnExecution(context).run();
233
- const result = await codexHostResult(context, turns);
234
- await retainEnabledNativeTranscripts(
235
- result,
236
- request.runtimePolicy,
237
- join(state.codexHome, "sessions"),
238
- "codex",
239
- request.runtimeRole,
240
- turns.summary.threadId,
241
- );
242
- return result;
251
+ if (turns.summary.complete) diagnostics.stage = "result-processing";
252
+ return await retainCodexResult(context, turns);
243
253
  } catch (cause) {
244
254
  return await failedNativeTranscripts(
245
255
  cause,
@@ -247,14 +257,92 @@ async function runCodexRequest(
247
257
  join(stateRoot, "codex-home", "sessions"),
248
258
  "codex",
249
259
  request.runtimeRole,
260
+ diagnostics,
250
261
  );
251
262
  } finally {
252
- await releaseRuntimeHooks(activeHooks);
253
- await rm(stateRoot, { recursive: true, force: true });
254
- if (helperRoot) await rm(helperRoot, { recursive: true, force: true });
263
+ await cleanupNativeState(diagnostics, async () => {
264
+ await releaseRuntimeHooks(activeHooks);
265
+ if (!candidateCodexStateRetained(request.candidateRuntimeRoot, stateRoot))
266
+ await rm(stateRoot, { recursive: true, force: true });
267
+ if (helperRoot) await rm(helperRoot, { recursive: true, force: true });
268
+ });
255
269
  }
256
270
  }
257
271
 
272
+ async function retainCodexResult(context: ExecutionContext, turns: TurnResult) {
273
+ const result = await codexHostResult(context, turns);
274
+ await context.diagnostics.retainFailure(
275
+ result,
276
+ "Codex host execution did not complete",
277
+ );
278
+ await retainEnabledNativeTranscripts(
279
+ result,
280
+ context.request.runtimePolicy,
281
+ join(context.state.codexHome, "sessions"),
282
+ "codex",
283
+ context.request.runtimeRole,
284
+ turns.summary.threadId,
285
+ );
286
+ return result;
287
+ }
288
+
289
+ async function prepareCodexSkillTracking(
290
+ state: CodexState,
291
+ request: Request,
292
+ pluginRoots: string[],
293
+ ): Promise<SkillTracking> {
294
+ const roots = await codexSkillCatalogRoots(request.workspace, pluginRoots, [
295
+ { directory: join(state.codexHome, "skills"), boundary: state.codexHome },
296
+ {
297
+ directory: join(state.parentHome, ".agents/skills"),
298
+ boundary: state.parentHome,
299
+ },
300
+ {
301
+ directory: join(state.commandHome, ".agents/skills"),
302
+ boundary: state.commandHome,
303
+ },
304
+ {
305
+ directory: join(state.commandHome, ".codex/skills"),
306
+ boundary: state.commandHome,
307
+ },
308
+ ]);
309
+ const tracking = await prepareSkillTracking({
310
+ privateRoot: state.codexHome,
311
+ host: "codex",
312
+ roots,
313
+ });
314
+ await writeFile(
315
+ join(state.codexHome, "hooks.json"),
316
+ JSON.stringify({ hooks: tracking.hooks }),
317
+ {
318
+ flag: "wx",
319
+ mode: 0o600,
320
+ },
321
+ );
322
+ return tracking;
323
+ }
324
+
325
+ async function validateCodexRequest(
326
+ options: CodexHostOptions,
327
+ request: Request,
328
+ ): Promise<void> {
329
+ await validateNativeHostRequest(request, "Codex");
330
+ requireCodexGoalRoute(request, options);
331
+ await verifyCodexInvocation(request);
332
+ await requireCodexFixtureConfiguration(request);
333
+ }
334
+
335
+ async function requireCodexFixtureConfiguration(
336
+ request: Request,
337
+ ): Promise<void> {
338
+ const path = join(request.workspace, ".codex");
339
+ if (!existsSync(path)) return;
340
+ if (request.runtimePolicy?.codexState !== true)
341
+ throw new Error("fixture Codex configuration is unsupported");
342
+ if (!(await lstat(path)).isDirectory())
343
+ throw new Error("fixture Codex configuration must be a physical directory");
344
+ }
345
+
258
346
  function requireCodexGoalRoute(
259
347
  request: Request,
260
348
  options: CodexHostOptions,
@@ -284,12 +372,15 @@ async function sandboxPreflight(
284
372
  state: CodexState,
285
373
  request: Request,
286
374
  command: string[],
375
+ diagnostics: HostDiagnostics,
287
376
  ) {
288
377
  return runCodexProcess({
378
+ diagnostics,
289
379
  argv: sandboxArgs(state, request, command),
290
380
  cwd: request.workspace,
291
381
  env: state.env,
292
382
  timeoutMs: 60_000,
383
+ phase: "isolation-preflight",
293
384
  signal: request.signal,
294
385
  });
295
386
  }
@@ -297,22 +388,37 @@ async function codexPreflight(
297
388
  options: CodexHostOptions,
298
389
  request: Request,
299
390
  state: CodexState,
391
+ diagnostics: HostDiagnostics,
300
392
  ): Promise<void> {
393
+ diagnostics.stage = "isolation-preflight";
301
394
  const probe = join(state.commandTemp, "isolation-probe");
302
395
  await writeFile(probe, "probe\n", { flag: "wx", mode: 0o600 });
303
- const checked = await sandboxPreflight(state, request, [
304
- "/bin/sh",
305
- "-c",
306
- '/bin/cat "$1" >/dev/null && ! /bin/ls "$2" >/dev/null 2>&1 && ! /bin/cat "$3" >/dev/null 2>&1 && ! /bin/cat "$4" >/dev/null 2>&1 || exit 70; "$5" --version >/dev/null 2>&1 || exit 71',
307
- "sevro-probe",
308
- probe,
309
- options.projectRoot,
310
- join(state.codexHome, "auth.json"),
311
- join(state.codexHome, "config.toml"),
312
- state.sandboxBinary,
313
- ]);
314
- if (checked.code === 71) throw new Error("Codex executable preflight failed");
315
- if (checked.code !== 0) throw new Error("Codex isolation preflight failed");
396
+ const checked = await sandboxPreflight(
397
+ state,
398
+ request,
399
+ [
400
+ "/bin/sh",
401
+ "-c",
402
+ '/bin/cat "$1" >/dev/null && ! /bin/ls "$2" >/dev/null 2>&1 && ! /bin/cat "$3" >/dev/null 2>&1 && ! /bin/cat "$4" >/dev/null 2>&1 || exit 70; "$5" --version >/dev/null 2>&1 || exit 71',
403
+ "sevro-probe",
404
+ probe,
405
+ options.projectRoot,
406
+ join(state.codexHome, "auth.json"),
407
+ // The opt-in config read is tested separately; credentials always stay denied.
408
+ join(
409
+ state.codexHome,
410
+ request.runtimePolicy?.codexState === true
411
+ ? "auth.json"
412
+ : "config.toml",
413
+ ),
414
+ state.sandboxBinary,
415
+ ],
416
+ diagnostics,
417
+ );
418
+ if (checked.code === 71)
419
+ throw new CodexProcessFailure("executable-preflight", checked);
420
+ if (checked.code !== 0)
421
+ throw new CodexProcessFailure("isolation-preflight", checked);
316
422
  await rm(probe);
317
423
  }
318
424
 
@@ -327,7 +433,7 @@ function routeArgs(context: ExecutionContext): string[] {
327
433
  "-c",
328
434
  'approval_policy="never"',
329
435
  "-c",
330
- `features.hooks=${Boolean(context.hooks)}`,
436
+ "features.hooks=true",
331
437
  ];
332
438
  }
333
439
  function initialTurnArgs(context: ExecutionContext): string[] {
@@ -342,7 +448,7 @@ function initialTurnArgs(context: ExecutionContext): string[] {
342
448
  ? []
343
449
  : ["--ephemeral"]),
344
450
  "--ignore-rules",
345
- ...(context.hooks ? ["--dangerously-bypass-hook-trust"] : []),
451
+ "--dangerously-bypass-hook-trust",
346
452
  "-C",
347
453
  context.request.workspace,
348
454
  ...routeArgs(context),
@@ -361,7 +467,7 @@ function followUpTurnArgs(
361
467
  "--strict-config",
362
468
  "--skip-git-repo-check",
363
469
  "--ignore-rules",
364
- ...(context.hooks ? ["--dangerously-bypass-hook-trust"] : []),
470
+ "--dangerously-bypass-hook-trust",
365
471
  ...routeArgs(context),
366
472
  threadId,
367
473
  "-",
@@ -372,7 +478,9 @@ async function codexProcessTurn(
372
478
  argv: string[],
373
479
  input: string,
374
480
  ): Promise<ProcessResult> {
375
- return runCodexProcess({
481
+ context.diagnostics.stage = "exec";
482
+ const result = await runCodexProcess({
483
+ diagnostics: context.diagnostics,
376
484
  argv,
377
485
  cwd: context.request.workspace,
378
486
  env: context.state.env,
@@ -380,6 +488,7 @@ async function codexProcessTurn(
380
488
  timeoutMs: context.timeoutMs,
381
489
  signal: context.request.signal,
382
490
  });
491
+ return result;
383
492
  }
384
493
  async function initialFingerprint(request: Request): Promise<string | null> {
385
494
  return request.followUpPrompt
@@ -427,43 +536,26 @@ function requireFollowUpCompletion(
427
536
  "Codex follow-up turn did not complete in the original thread",
428
537
  );
429
538
  }
430
- async function appServerSummary(
431
- result: AppServerResult,
432
- workspace: string,
433
- ): Promise<CodexEventSummary> {
539
+ function appServerSummary(result: AppServerResult): CodexEventSummary {
434
540
  return {
435
541
  threadId: result.evidence.threadId ?? "",
436
542
  complete: result.code === 0,
437
- finalMessage:
438
- result.code === 0
439
- ? await readFile(join(workspace, ".git", "last-message.md"), "utf8")
440
- : null,
543
+ finalMessage: result.finalMessage,
544
+ // App-server reports cumulative thread totals, not per-turn usage. Keep
545
+ // usage unknown rather than count continuations or feedback more than once.
441
546
  inputTokens: null,
442
547
  outputTokens: null,
443
548
  usageComplete: false,
444
549
  };
445
550
  }
446
- async function initialSummary(
551
+ function initialSummary(
447
552
  appServer: AppServerResult | null,
448
553
  execution: ProcessResult,
449
- request: Request,
450
- ): Promise<CodexEventSummary> {
554
+ ): CodexEventSummary {
451
555
  return appServer
452
- ? appServerSummary(appServer, request.workspace)
556
+ ? appServerSummary(appServer)
453
557
  : summarizeCodexEvents(execution.out, execution.code);
454
558
  }
455
- function combinedEvents(
456
- execution: ProcessResult,
457
- followUp: ProcessResult | null,
458
- ): string {
459
- const out = followUp
460
- ? `${execution.out.trimEnd()}\n${followUp.out.trimStart()}`
461
- : execution.out;
462
- if (Buffer.byteLength(out, "utf8") > MAX_EVENT_BYTES)
463
- throw new Error("Codex combined event stream exceeds the size limit");
464
- return out;
465
- }
466
-
467
559
  class CodexTurnExecution {
468
560
  private appServerContinuation?: Observation;
469
561
  constructor(private context: ExecutionContext) {}
@@ -477,11 +569,7 @@ class CodexTurnExecution {
477
569
  initialTurnArgs(this.context),
478
570
  this.context.request.prompt,
479
571
  ));
480
- const summary = await initialSummary(
481
- appServer,
482
- execution,
483
- this.context.request,
484
- );
572
+ const summary = initialSummary(appServer, execution);
485
573
  requireInitialCompletion(summary, appServer);
486
574
  const follow = await this.maybeFollowUp(summary, appServer, fingerprint);
487
575
  return {
@@ -489,7 +577,6 @@ class CodexTurnExecution {
489
577
  summary,
490
578
  appServer,
491
579
  ...follow,
492
- observedEvents: combinedEvents(execution, follow.followUp),
493
580
  };
494
581
  }
495
582
 
@@ -498,7 +585,9 @@ class CodexTurnExecution {
498
585
  ): Promise<AppServerResult | null> {
499
586
  const { options, request, state, timeoutMs } = this.context;
500
587
  if (options.entrypoint !== "app-server") return null;
588
+ this.context.diagnostics.stage = "app-server";
501
589
  return runCodexAppServer({
590
+ diagnostics: this.context.diagnostics,
502
591
  argv: [
503
592
  options.binary,
504
593
  "app-server",
@@ -507,11 +596,11 @@ class CodexTurnExecution {
507
596
  "-c",
508
597
  `default_permissions=${JSON.stringify(state.profileId)}`,
509
598
  "-c",
510
- `features.hooks=${Boolean(this.context.hooks)}`,
599
+ "features.hooks=true",
511
600
  ],
512
601
  env: state.env,
513
602
  permissionProfile: state.profileId,
514
- vettedHooks: Boolean(this.context.hooks),
603
+ vettedHooks: true,
515
604
  request: {
516
605
  repoDir: request.workspace,
517
606
  prompt: request.prompt,
@@ -568,62 +657,20 @@ class CodexTurnExecution {
568
657
  }
569
658
  }
570
659
 
571
- type SkillReads = Awaited<ReturnType<typeof codexSkillReadObservation>>;
572
- async function phaseSkillReads(
573
- context: ExecutionContext,
574
- turns: TurnResult,
575
- recovered: Awaited<ReturnType<typeof codexNativeSkillReadRecovery>>,
576
- ) {
577
- if (!turns.followUp) return { initial: null, followUp: null };
578
- const initial = {
579
- ...(await codexSkillReadObservation(
580
- turns.execution.out,
581
- context.request.workspace,
582
- context.installedPluginRoots,
583
- recovered,
584
- )),
585
- id: "sevro.codex.initial-skill-reads",
586
- };
587
- const followUp = {
588
- ...(await codexSkillReadObservation(
589
- turns.followUp.out,
590
- context.request.workspace,
591
- context.installedPluginRoots,
592
- recovered,
593
- )),
594
- id: "sevro.codex.follow-up-skill-reads",
595
- };
596
- return { initial, followUp };
597
- }
598
660
  function explicitInvocationReceipt(
599
661
  selected: Request["explicitSkillInvocation"],
600
- reads: SkillReads,
662
+ completed: boolean,
601
663
  ) {
602
664
  if (!selected) return null;
603
665
  return {
604
666
  id: "sevro.codex.explicit-invocation",
605
- completeness: reads.completeness,
667
+ completeness: completed ? ("complete" as const) : ("partial" as const),
606
668
  data: {
607
669
  method: "explicit_invocation",
608
670
  primarySkill: selected.skillName,
609
- observedSkills: [
610
- selected.skillName,
611
- ...reads.data.observedSkills.filter(
612
- (skill) => skill !== selected.skillName,
613
- ),
614
- ],
615
671
  },
616
672
  };
617
673
  }
618
- function nativeSkillContext(context: ExecutionContext) {
619
- return {
620
- workspace: context.request.workspace,
621
- installedPluginRoots: context.installedPluginRoots,
622
- ...(context.request.followUpPrompt
623
- ? { followUpPrompt: context.request.followUpPrompt }
624
- : {}),
625
- };
626
- }
627
674
  function appServerObservation(result: AppServerResult | null): Observation[] {
628
675
  return result
629
676
  ? [
@@ -636,45 +683,40 @@ function appServerObservation(result: AppServerResult | null): Observation[] {
636
683
  : [];
637
684
  }
638
685
  async function codexObservations(context: ExecutionContext, turns: TurnResult) {
639
- const recovered = await codexNativeSkillReadRecovery(
640
- context.state.codexHome,
686
+ const tracking = await skillTrackingObservation(
687
+ context.skillTracking,
641
688
  turns.summary.threadId,
689
+ turns.summary.complete,
642
690
  );
643
- const reads = await codexSkillReadObservation(
644
- turns.observedEvents,
645
- context.request.workspace,
646
- context.installedPluginRoots,
647
- recovered,
648
- );
649
- const phases = await phaseSkillReads(context, turns, recovered);
650
691
  const native = await codexNativeCallObservation(
651
692
  context.state.codexHome,
652
693
  turns.summary.threadId,
653
- nativeSkillContext(context),
694
+ { followUpPrompt: context.request.followUpPrompt },
654
695
  );
655
696
  const explicit = explicitInvocationReceipt(
656
697
  context.request.explicitSkillInvocation,
657
- reads,
698
+ turns.summary.complete,
658
699
  );
659
700
  return [
660
- ...(await codexHookObservations(context.hooks)),
701
+ ...(await codexHookObservations(context)),
661
702
  ...runtimeObservations(
662
703
  context.request.runtimePolicy,
663
704
  context.request.runtimeRole,
664
705
  ),
665
706
  ...appServerObservation(turns.appServer),
666
- reads,
667
- ...(phases.initial ? [phases.initial] : []),
668
- ...(phases.followUp ? [phases.followUp] : []),
669
707
  native,
670
708
  codexNativeControls(native),
671
709
  ...(turns.continuation ? [turns.continuation] : []),
672
710
  ...(explicit ? [explicit] : []),
711
+ tracking,
712
+ ...skillActivationClaims(tracking),
713
+ ...skillExposureClaims(tracking),
673
714
  ];
674
715
  }
675
716
 
676
- async function codexHookObservations(hooks: HookMounts | undefined) {
677
- return hooks ? [await runtimeHookObservation(hooks)] : [];
717
+ async function codexHookObservations(context: ExecutionContext) {
718
+ if (!context.request.runtimePolicy) return [];
719
+ return [await runtimeHookObservation(context.hooks)];
678
720
  }
679
721
  function codexArtifacts(turns: TurnResult) {
680
722
  return [