@bjoernrochel/sevro 0.1.0-rc.8 → 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 (228) 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 +9 -2
  7. package/docs/codex-host.md +155 -57
  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 +122 -4
  25. package/docs/run-settings-v1.md +147 -0
  26. package/docs/runtime-v1.md +255 -22
  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 +43 -7
  64. package/src/advisory.ts +2 -2
  65. package/src/candidate-artifacts.ts +42 -11
  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 +9 -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} +77 -87
  103. package/src/evaluation/trial.ts +800 -0
  104. package/src/{evaluation-types.ts → evaluation/types.ts} +79 -29
  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} +12 -72
  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/{workspace-state.ts → fixtures/workspace-state.ts} +5 -5
  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 +50 -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 +46 -9
  148. package/src/hosts/claude-skill-activations.ts +169 -0
  149. package/src/hosts/claude-state.ts +28 -15
  150. package/src/hosts/claude-tool-calls.ts +13 -10
  151. package/src/hosts/claude.ts +121 -81
  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 -183
  158. package/src/hosts/codex-profile.ts +36 -21
  159. package/src/hosts/codex-session-files.ts +1 -9
  160. package/src/hosts/codex-state.ts +134 -18
  161. package/src/hosts/codex.ts +210 -183
  162. package/src/hosts/isolation-common.ts +8 -11
  163. package/src/hosts/isolation-roots.ts +2 -2
  164. package/src/hosts/linux-sandbox.ts +15 -2
  165. package/src/hosts/mac-sandbox.ts +87 -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 +216 -48
  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 +26 -11
  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/{runtime-config.ts → runtime/config.ts} +160 -46
  186. package/src/{runtime-discovery-apple.ts → runtime/discovery-apple.ts} +3 -3
  187. package/src/{runtime-discovery-providers.ts → runtime/discovery-providers.ts} +3 -9
  188. package/src/{runtime-discovery.ts → runtime/discovery.ts} +4 -4
  189. package/src/{runtime-hook-processes.ts → runtime/hook-processes.ts} +1 -1
  190. package/src/{runtime-hooks.ts → runtime/hooks.ts} +32 -22
  191. package/src/{runtime-paths.ts → runtime/paths.ts} +5 -19
  192. package/src/{runtime-query.ts → runtime/query.ts} +5 -5
  193. package/src/{runtime-seeds.ts → runtime/seeds.ts} +3 -3
  194. package/src/runtime/shared-cache.ts +130 -0
  195. package/src/{runtime-state.ts → runtime/state.ts} +28 -11
  196. package/src/schema-types.ts +73 -161
  197. package/src/shell.ts +4 -0
  198. package/src/transcript-retention.ts +146 -0
  199. package/src/value-guards.ts +22 -0
  200. package/docs/assets/sevro-logo.png +0 -0
  201. package/docs/assets/sevro-symbol.svg +0 -1
  202. package/docs/assets/sevro-wordmark.svg +0 -1
  203. package/docs/cache-ownership-validation.md +0 -49
  204. package/docs/claude-continuation-validation.md +0 -47
  205. package/docs/concurrency-validation.md +0 -86
  206. package/docs/configuration-root-validation.md +0 -70
  207. package/docs/engine-slice.md +0 -114
  208. package/docs/evidence/runtime-startup-30/README.md +0 -185
  209. package/docs/evidence/runtime-startup-30/after.json +0 -319
  210. package/docs/evidence/runtime-startup-30/baseline-timeout.jsonl +0 -2
  211. package/docs/evidence/runtime-startup-30/before.json +0 -403
  212. package/docs/evidence/runtime-startup-30/consumer-smoke.json +0 -63
  213. package/docs/evidence/runtime-startup-30/full-policy-diagnostic.jsonl +0 -3
  214. package/docs/evidence/runtime-startup-30/minimal-policy-diagnostic.jsonl +0 -3
  215. package/docs/evidence/runtime-startup-30/plugin-template-assessment.json +0 -20
  216. package/docs/evidence/runtime-startup-30/seedless-timeout.jsonl +0 -2
  217. package/docs/evidence.md +0 -17
  218. package/docs/fixture-cleanup-validation.md +0 -73
  219. package/docs/guide-verification.json +0 -239
  220. package/src/evaluation-context.ts +0 -259
  221. package/src/evaluation-fixture.ts +0 -302
  222. package/src/evaluation-run.ts +0 -200
  223. package/src/evaluation-trial.ts +0 -1016
  224. package/src/hosts/claude-nested-skills.ts +0 -272
  225. package/src/hosts/codex-command-output.ts +0 -510
  226. package/src/hosts/codex-executor-command.ts +0 -291
  227. package/src/hosts/codex-skill-reads.ts +0 -783
  228. /package/src/{trial-scheduler.ts → evaluation/scheduler.ts} +0 -0
@@ -1,510 +0,0 @@
1
- import { isAbsolute, normalize } from "node:path";
2
- import { fileURLToPath } from "node:url";
3
- import { isRecord, isUnknownArray } from "../value-guards";
4
- import {
5
- literalExecutorCommand,
6
- type LiteralExecutorCommand,
7
- } from "./codex-executor-command";
8
-
9
- interface NativeEntry {
10
- ordinal: number;
11
- payload: Record<string, unknown>;
12
- }
13
-
14
- interface YieldedChunk {
15
- process: string;
16
- id: string;
17
- output: string;
18
- }
19
-
20
- export interface RecoveredCommandOutput {
21
- output: string;
22
- chunks: number;
23
- completedCall?: boolean;
24
- literalCommandCall?: boolean;
25
- }
26
-
27
- function processId(value: unknown): string | undefined {
28
- if (typeof value !== "string" && typeof value !== "number") return undefined;
29
- return /^(?:[1-9][0-9]*)$/.test(String(value)) ? String(value) : undefined;
30
- }
31
-
32
- function validChunkId(value: unknown): value is string {
33
- return typeof value === "string" && /^[\w-]{1,64}$/.test(value);
34
- }
35
- function validWallTime(value: unknown): boolean {
36
- return typeof value === "number" && Number.isFinite(value) && value >= 0;
37
- }
38
- function yieldedEnvelope(value: Record<string, unknown>) {
39
- const process = processId(value.session_id);
40
- if (!process || value.exit_code != null || typeof value.output !== "string")
41
- return undefined;
42
- return { process, output: value.output };
43
- }
44
- function yieldedChunk(
45
- value: Record<string, unknown>,
46
- ): YieldedChunk | undefined {
47
- const envelope = yieldedEnvelope(value);
48
- if (!envelope) return undefined;
49
- if (!validChunkId(value.chunk_id) || !validWallTime(value.wall_time_seconds))
50
- return undefined;
51
- return { ...envelope, id: value.chunk_id };
52
- }
53
- // Decode only host result/content envelopes, never arbitrary object properties,
54
- // prose containing JSON, or JSON printed inside a command's output.
55
-
56
- function resultChunks(value: unknown, depth = 0): YieldedChunk[] {
57
- if (depth > 6) return [];
58
- if (typeof value === "string") return decodedChunks(value, depth);
59
- return containerChunks(value, depth);
60
- }
61
- function decodedChunks(value: string, depth: number): YieldedChunk[] {
62
- try {
63
- return resultChunks(JSON.parse(value), depth + 1);
64
- } catch {
65
- return [];
66
- }
67
- }
68
- function textEnvelope(value: unknown): boolean {
69
- return value === "input_text" || value === "text";
70
- }
71
- function containerChunks(value: unknown, depth: number): YieldedChunk[] {
72
- if (Array.isArray(value))
73
- return value.flatMap((part: unknown) => resultChunks(part, depth + 1));
74
- if (!isRecord(value)) return [];
75
- if (textEnvelope(value.type)) return resultChunks(value.text, depth + 1);
76
- const chunk = yieldedChunk(value);
77
- return chunk ? [chunk] : [];
78
- }
79
-
80
- function execCall(payload: Record<string, unknown>): boolean {
81
- if (!executorCallType(payload.type)) return false;
82
- const namespace = payload.namespace;
83
- if (namespace !== undefined && namespace !== "functions") return false;
84
- const names = [
85
- "exec",
86
- "exec_command",
87
- "write_stdin",
88
- "functions.exec",
89
- "functions.exec_command",
90
- "functions.write_stdin",
91
- ];
92
- return typeof payload.name === "string" && names.includes(payload.name);
93
- }
94
-
95
- function chunksAtEntry(
96
- entry: NativeEntry,
97
- entries: NativeEntry[],
98
- ): YieldedChunk[] {
99
- const { payload, ordinal } = entry;
100
- if (!executorOutputType(payload.type)) return [];
101
- if (typeof payload.call_id !== "string") return [];
102
- const calls = entries.filter(
103
- (other) =>
104
- execCall(other.payload) && other.payload.call_id === payload.call_id,
105
- );
106
- if (!soleEarlierCall(calls, ordinal)) return [];
107
- return resultChunks(payload.output);
108
- }
109
-
110
- function commandProcess(entry: NativeEntry): string | undefined {
111
- const { payload } = entry;
112
- if (payload.type !== "item_completed" || !isRecord(payload.item))
113
- return undefined;
114
- if (
115
- payload.item.type !== "CommandExecution" ||
116
- payload.item.source !== "unified_exec_startup"
117
- )
118
- return undefined;
119
- return processId(payload.item.process_id);
120
- }
121
-
122
- function executorEnvelope(value: unknown): unknown {
123
- if (typeof value !== "string") return value;
124
- try {
125
- const decoded: unknown = JSON.parse(value);
126
- return decoded;
127
- } catch {
128
- return undefined;
129
- }
130
- }
131
- function executorTextBlock(block: unknown): string | undefined {
132
- if (
133
- !isRecord(block) ||
134
- !["input_text", "text"].includes(String(block.type)) ||
135
- typeof block.text !== "string"
136
- )
137
- return undefined;
138
- return block.text;
139
- }
140
- function executorTextBlocks(value: unknown): string[] | undefined {
141
- const envelope = executorEnvelope(value);
142
- if (!Array.isArray(envelope) || envelope.length < 2) return undefined;
143
- const text: string[] = [];
144
- for (const block of envelope) {
145
- const line = executorTextBlock(block);
146
- if (line === undefined) return undefined;
147
- text.push(line);
148
- }
149
- return text;
150
- }
151
-
152
- function structuredExecResult(text: string): boolean {
153
- try {
154
- const value: unknown = JSON.parse(text);
155
- return isRecord(value) && ("output" in value || "session_id" in value);
156
- } catch {
157
- return false;
158
- }
159
- }
160
-
161
- function completedText(value: unknown): string | undefined {
162
- const text = executorTextBlocks(value);
163
- if (!text) return undefined;
164
- if (
165
- !/^Script completed\nWall time [0-9]+(?:\.[0-9]+)? seconds\nOutput:\n$/.test(
166
- text.slice(0, 1).join(""),
167
- )
168
- )
169
- return undefined;
170
- const content = text.slice(1);
171
- // Structured exec chunks keep their process-identity rules above. Do not
172
- // reinterpret a late yielded chunk as a plain completed-call result.
173
- return content.some(structuredExecResult) ? undefined : content.join("\n");
174
- }
175
-
176
- function uniqueExecutorCall(
177
- result: NativeEntry,
178
- results: NativeEntry[],
179
- entries: NativeEntry[],
180
- ): NativeEntry | undefined {
181
- const id = result.payload.call_id;
182
- if (
183
- typeof id !== "string" ||
184
- results.filter((other) => other.payload.call_id === id).length !== 1
185
- )
186
- return undefined;
187
- const calls = entries.filter(
188
- (entry) => execCall(entry.payload) && entry.payload.call_id === id,
189
- );
190
- return soleEarlierCall(calls, result.ordinal);
191
- }
192
-
193
- function uniqueCommandIdentity(
194
- command: NativeEntry,
195
- entries: NativeEntry[],
196
- ): boolean {
197
- const process = commandProcess(command);
198
- if (
199
- !process ||
200
- entries.filter((entry) => commandProcess(entry) === process).length !== 1
201
- )
202
- return false;
203
- const item = command.payload.item as Record<string, unknown>;
204
- return (
205
- typeof item.id === "string" &&
206
- entries.filter(
207
- ({ payload }) =>
208
- payload.type === "item_completed" &&
209
- isRecord(payload.item) &&
210
- payload.item.id === item.id,
211
- ).length === 1
212
- );
213
- }
214
-
215
- function soleCompletedCommand(
216
- call: NativeEntry,
217
- result: NativeEntry,
218
- entries: NativeEntry[],
219
- ): NativeEntry | undefined {
220
- const between = entries.filter(
221
- (entry) => entry.ordinal > call.ordinal && entry.ordinal < result.ordinal,
222
- );
223
- if (
224
- between.some(({ payload }) =>
225
- ["custom_tool_call", "function_call"].includes(String(payload.type)),
226
- )
227
- )
228
- return undefined;
229
- const commands = between.filter(
230
- ({ payload }) =>
231
- payload.type === "item_completed" &&
232
- isRecord(payload.item) &&
233
- payload.item.type === "CommandExecution",
234
- );
235
- return soleUniqueCommand(commands, entries);
236
- }
237
-
238
- function completedCallOutputs(entries: NativeEntry[]): Map<number, string> {
239
- const outputs = new Map<number, string>();
240
- const results = entries.filter(({ payload }) =>
241
- executorOutputType(payload.type),
242
- );
243
- for (const result of results) {
244
- const output = completedResultOutput(result, results, entries);
245
- if (output) outputs.set(output.ordinal, output.output);
246
- }
247
- return outputs;
248
- }
249
- function completedResultOutput(
250
- result: NativeEntry,
251
- results: NativeEntry[],
252
- entries: NativeEntry[],
253
- ) {
254
- const call = uniqueExecutorCall(result, results, entries);
255
- if (!call) return undefined;
256
- const command = soleCompletedCommand(call, result, entries);
257
- if (!command) return undefined;
258
- const item = command.payload.item as Record<string, unknown>;
259
- const literal = literalCallCommand(call.payload);
260
- if (!literal || !literalCommandMatches(item, literal)) return undefined;
261
- return unaggregatedCompletedOutput(command, item, result);
262
- }
263
- function unaggregatedCompletedOutput(
264
- command: NativeEntry,
265
- item: Record<string, unknown>,
266
- result: NativeEntry,
267
- ) {
268
- const output = completedText(result.payload.output);
269
- if (output === undefined) return undefined;
270
- if (
271
- typeof item.aggregated_output === "string" &&
272
- item.aggregated_output.includes(output)
273
- )
274
- return undefined;
275
- return { ordinal: command.ordinal, output };
276
- }
277
-
278
- function literalArgv(argv: unknown, command: LiteralExecutorCommand): boolean {
279
- if (!isUnknownArray(argv) || argv.length !== 3) return false;
280
- const [shell, flag, script]: unknown[] = argv;
281
- return (
282
- shellFlag(flag) &&
283
- script === command.command &&
284
- requestedShell(shell, command.shell)
285
- );
286
- }
287
- function shellFlag(flag: unknown): boolean {
288
- return typeof flag === "string" && ["-c", "-lc"].includes(flag);
289
- }
290
- function requestedShell(
291
- shell: unknown,
292
- requested: string | undefined,
293
- ): boolean {
294
- return requested === undefined || shell === requested;
295
- }
296
- function literalCommandMatches(
297
- item: Record<string, unknown>,
298
- command: LiteralExecutorCommand,
299
- ): boolean {
300
- if (!literalArgv(item.command, command)) return false;
301
- if (command.cwd === undefined) return true;
302
- return matchingCwd(item.cwd, command.cwd);
303
- }
304
-
305
- function normalizedCwd(observed: string): string | undefined {
306
- try {
307
- const cwd = observed.startsWith("file:")
308
- ? fileURLToPath(observed)
309
- : observed;
310
- return isAbsolute(cwd) ? normalize(cwd) : undefined;
311
- } catch {
312
- return undefined;
313
- }
314
- }
315
- function matchingCwd(observed: unknown, requested: string): boolean {
316
- if (!isAbsolute(requested) || typeof observed !== "string") return false;
317
- return normalizedCwd(observed) === normalize(requested);
318
- }
319
-
320
- function literalCallOutputs(entries: NativeEntry[]): Map<number, string> {
321
- const outputs = new Map<number, string>();
322
- const results = entries.filter(({ payload }) =>
323
- executorOutputType(payload.type),
324
- );
325
- for (const result of results) {
326
- const output = literalResultOutput(result, results, entries);
327
- if (output) outputs.set(output.ordinal, output.output);
328
- }
329
- return outputs;
330
- }
331
- function literalResultOutput(
332
- result: NativeEntry,
333
- results: NativeEntry[],
334
- entries: NativeEntry[],
335
- ) {
336
- const output = completedText(result.payload.output);
337
- if (output === undefined) return undefined;
338
- const call = uniqueExecutorCall(result, results, entries);
339
- if (!call) return undefined;
340
- const command = literalCallCommand(call.payload);
341
- if (!command) return undefined;
342
- const completion = literalCompletion(result, command, entries);
343
- return completion ? { ordinal: completion.ordinal, output } : undefined;
344
- }
345
- function literalCompletion(
346
- result: NativeEntry,
347
- command: LiteralExecutorCommand,
348
- entries: NativeEntry[],
349
- ) {
350
- const matches = entries.filter(
351
- (entry) =>
352
- commandProcess(entry) &&
353
- literalCommandMatches(
354
- entry.payload.item as Record<string, unknown>,
355
- command,
356
- ),
357
- );
358
- const completion = uniqueLaterCommand(matches, result, entries);
359
- if (!completion || !uniqueLiteralInvocation(command, entries))
360
- return undefined;
361
- return completion;
362
- }
363
- function uniqueLaterCommand(
364
- matches: NativeEntry[],
365
- result: NativeEntry,
366
- entries: NativeEntry[],
367
- ) {
368
- const [completion] = matches;
369
- if (matches.length !== 1 || completion === undefined) return undefined;
370
- if (
371
- completion.ordinal <= result.ordinal ||
372
- !uniqueCommandIdentity(completion, entries)
373
- )
374
- return undefined;
375
- return completion;
376
- }
377
- function uniqueLiteralInvocation(
378
- command: LiteralExecutorCommand,
379
- entries: NativeEntry[],
380
- ): boolean {
381
- return (
382
- entries.filter(
383
- (entry) =>
384
- execCall(entry.payload) &&
385
- literalCallCommand(entry.payload)?.command === command.command,
386
- ).length === 1
387
- );
388
- }
389
-
390
- function literalCallCommand(
391
- payload: Record<string, unknown>,
392
- ): LiteralExecutorCommand | undefined {
393
- if (
394
- payload.type !== "custom_tool_call" ||
395
- !["exec", "functions.exec"].includes(String(payload.name))
396
- )
397
- return undefined;
398
- return literalExecutorCommand(payload.input);
399
- }
400
-
401
- function mergeLiteralOutputs(
402
- outputs: Map<number, RecoveredCommandOutput>,
403
- entries: NativeEntry[],
404
- ): void {
405
- for (const [ordinal, output] of literalCallOutputs(entries)) {
406
- const earlier = outputs.get(ordinal);
407
- outputs.set(ordinal, mergedLiteralOutput(output, earlier));
408
- }
409
- }
410
-
411
- /** Actor-local tool output bound to one native command completion. */
412
- export function nativeCommandOutputs(
413
- entries: NativeEntry[],
414
- ): Map<number, RecoveredCommandOutput> {
415
- const completions = entries.flatMap((entry) => {
416
- const process = commandProcess(entry);
417
- return process ? [{ process, entry }] : [];
418
- });
419
- const chunks = entries.flatMap((entry) =>
420
- chunksAtEntry(entry, entries).map((chunk) => ({
421
- ...chunk,
422
- ordinal: entry.ordinal,
423
- })),
424
- );
425
- const outputs = new Map<number, RecoveredCommandOutput>();
426
- mergeYieldedOutputs(outputs, completions, chunks);
427
- for (const [ordinal, output] of completedCallOutputs(entries)) {
428
- const earlier = outputs.get(ordinal);
429
- outputs.set(ordinal, mergedCompletedOutput(output, earlier));
430
- }
431
- mergeLiteralOutputs(outputs, entries);
432
- return outputs;
433
- }
434
-
435
- function executorCallType(value: unknown): boolean {
436
- return value === "custom_tool_call" || value === "function_call";
437
- }
438
- function executorOutputType(value: unknown): boolean {
439
- return (
440
- value === "custom_tool_call_output" || value === "function_call_output"
441
- );
442
- }
443
- function soleEarlierCall(
444
- calls: NativeEntry[],
445
- ordinal: number,
446
- ): NativeEntry | undefined {
447
- const [call] = calls;
448
- if (calls.length !== 1 || call === undefined || call.ordinal >= ordinal)
449
- return undefined;
450
- return call;
451
- }
452
- function soleUniqueCommand(
453
- commands: NativeEntry[],
454
- entries: NativeEntry[],
455
- ): NativeEntry | undefined {
456
- const [command] = commands;
457
- if (
458
- commands.length !== 1 ||
459
- command === undefined ||
460
- !uniqueCommandIdentity(command, entries)
461
- )
462
- return undefined;
463
- return command;
464
- }
465
- function mergedLiteralOutput(
466
- output: string,
467
- earlier: RecoveredCommandOutput | undefined,
468
- ): RecoveredCommandOutput {
469
- return {
470
- ...earlier,
471
- output: output + (earlier?.output ?? ""),
472
- chunks: earlier?.chunks ?? 0,
473
- literalCommandCall: true,
474
- };
475
- }
476
- function mergedCompletedOutput(
477
- output: string,
478
- earlier: RecoveredCommandOutput | undefined,
479
- ): RecoveredCommandOutput {
480
- return {
481
- output: (earlier?.output ?? "") + output,
482
- chunks: earlier?.chunks ?? 0,
483
- completedCall: true,
484
- };
485
- }
486
- function mergeYieldedOutputs(
487
- outputs: Map<number, RecoveredCommandOutput>,
488
- completions: { process: string; entry: NativeEntry }[],
489
- chunks: (YieldedChunk & { ordinal: number })[],
490
- ): void {
491
- for (const { process, entry } of completions) {
492
- // A reused or duplicated process identifier cannot bind a result uniquely.
493
- if (completions.filter((other) => other.process === process).length !== 1)
494
- continue;
495
- const earlier = chunks
496
- .filter(
497
- (chunk) => chunk.process === process && chunk.ordinal < entry.ordinal,
498
- )
499
- .sort((a, b) => a.ordinal - b.ordinal);
500
- if (
501
- !earlier.length ||
502
- new Set(earlier.map((chunk) => chunk.id)).size !== earlier.length
503
- )
504
- continue;
505
- outputs.set(entry.ordinal, {
506
- output: earlier.map((chunk) => chunk.output).join(""),
507
- chunks: earlier.length,
508
- });
509
- }
510
- }