@bjoernrochel/sevro 0.1.0-rc.16 → 0.1.0-rc.17
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.
- package/docs/installing.md +2 -2
- package/docs/native-controls.md +47 -12
- package/package.json +1 -1
- package/src/hosts/codex-exec-controls.ts +195 -55
- package/src/hosts/native-controls.ts +24 -2
package/docs/installing.md
CHANGED
|
@@ -23,10 +23,10 @@ Local installation records the selected version in the project lockfile.
|
|
|
23
23
|
|
|
24
24
|
## Current release
|
|
25
25
|
|
|
26
|
-
The current release is `0.1.0-rc.
|
|
26
|
+
The current release is `0.1.0-rc.17`. Install this exact version:
|
|
27
27
|
|
|
28
28
|
```sh
|
|
29
|
-
bun add --exact @bjoernrochel/sevro@0.1.0-rc.
|
|
29
|
+
bun add --exact @bjoernrochel/sevro@0.1.0-rc.17
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
<!-- sevro-current-release:end -->
|
package/docs/native-controls.md
CHANGED
|
@@ -5,7 +5,8 @@ retain an observation with that ID. It exposes host facts for extension policy:
|
|
|
5
5
|
|
|
6
6
|
- `method: native_control_calls`;
|
|
7
7
|
- ordered `calls` containing only `ordinal`, `namespace`, and `name`;
|
|
8
|
-
- `acceptedAgentCount`, a verified Codex acceptance count
|
|
8
|
+
- `acceptedAgentCount`, a verified direct native Codex acceptance count when
|
|
9
|
+
overall coverage is complete, otherwise `null` (also `null` on Claude);
|
|
9
10
|
- `submittedExecCalls`, the Codex submitted-code count or `null` on Claude; and
|
|
10
11
|
- `truncated`, which is `false` for a complete record and may be unknown (`null`)
|
|
11
12
|
for an incomplete Codex record.
|
|
@@ -16,6 +17,18 @@ invocation attempt, not success. Extensions decide which calls are prohibited.
|
|
|
16
17
|
An unknown acceptance count cannot establish agent acceptance.
|
|
17
18
|
|
|
18
19
|
Codex projects labels and counts from its bound native-session observation.
|
|
20
|
+
It additionally retains `directAcceptedAgentCount`, the number of positively
|
|
21
|
+
correlated direct native spawn acceptances in that source, independently of exec
|
|
22
|
+
inspection. This is an observed count, not a total across unknown launch paths.
|
|
23
|
+
`coverage.nativeCalls` records native-session completeness;
|
|
24
|
+
`coverage.execInspection` records whether all submitted exec sources were
|
|
25
|
+
understood and matched to the direct submission inventory;
|
|
26
|
+
`coverage.acceptedAgents: direct_native_calls` names the scope of both acceptance
|
|
27
|
+
counts. Accepted launches through exec are not included in these direct counts.
|
|
28
|
+
When native capture is incomplete, even a zero observed count cannot establish
|
|
29
|
+
absence. Native labels remain usable as positive attempt evidence regardless
|
|
30
|
+
of an unrelated exec inspection gap. A rejected attempt is never an acceptance.
|
|
31
|
+
|
|
19
32
|
When submitted code is present, both observations also retain `execCalls`:
|
|
20
33
|
one receipt per native `exec` submission, with its native `ordinal`, inspection
|
|
21
34
|
`completeness`, and ordered `calls` containing `index`, `namespace`, and `name`.
|
|
@@ -27,25 +40,46 @@ programs without executing them. It accepts literal tool calls, constant result
|
|
|
27
40
|
bindings, template interpolation and `+` composition of inspected expressions,
|
|
28
41
|
direct `text`/`store` output, and literal `Promise.all`/`allSettled` batches.
|
|
29
42
|
Batch results may use flat constant array bindings or result-only
|
|
30
|
-
`forEach(text)` or `for ... of` output
|
|
43
|
+
`forEach(text)` or `for ... of` output, including blocks and conditional
|
|
44
|
+
result-field selection. `JSON.stringify`, `String`, object spread/shorthand,
|
|
45
|
+
and nested result fields are supported. Numeric output loops must use the
|
|
46
|
+
bounded shape `for (let i = 0; i < results.length; i++)`, indexing the known
|
|
47
|
+
batch with that loop variable. Output loops and conditional expressions cannot
|
|
48
|
+
invoke tools. Destructuring defaults, rest elements,
|
|
31
49
|
holes, nested bindings, duplicate names, and shadowed globals remain unsupported.
|
|
32
|
-
|
|
33
|
-
unique, later native completion for the same call ID with an intact successful
|
|
34
|
-
script envelope. Tool argument strings, comments and printed tool-looking text
|
|
50
|
+
Tool argument strings, comments and printed tool-looking text
|
|
35
51
|
do not become call labels. A rejected tool request returned by a completed
|
|
36
52
|
script still appears in its submitted-call inventory. This inventory proves
|
|
37
53
|
submission of those requests, not successful effects or acceptance; the existing
|
|
38
54
|
acceptance count describes independently correlated direct native spawns.
|
|
39
55
|
|
|
40
56
|
Dynamic lookup, aliases, shadowed globals, callbacks that invoke tools,
|
|
41
|
-
conditional
|
|
42
|
-
|
|
57
|
+
conditional tool execution, and nested executors keep source inspection partial.
|
|
58
|
+
Each receipt's `inspectionReason` is `null` on success, or `unsupported_source`,
|
|
59
|
+
`malformed_source`, `source_limit`, or `foreign_namespace`. Unsupported syntax
|
|
60
|
+
and expression/call-budget exhaustion use `unsupported_source`.
|
|
61
|
+
Source is bounded at
|
|
43
62
|
64 KiB, traversed expressions at 2,048, and calls at 128 per submission; tool
|
|
44
63
|
labels are bounded at 64 characters. Private source and output bytes are discarded.
|
|
45
64
|
The portable observation becomes partial if any submission is uninspected or
|
|
46
|
-
receipt counts/ordinals disagree with the native list.
|
|
47
|
-
`null
|
|
48
|
-
|
|
65
|
+
receipt counts/ordinals disagree with the native list. `acceptedAgentCount`
|
|
66
|
+
remains `null` in that case, while native coverage, direct acceptance facts and
|
|
67
|
+
the native `submittedExecCalls` count remain independently available.
|
|
68
|
+
`submittedExecCalls` and `truncated` depend on native capture, not exec output.
|
|
69
|
+
|
|
70
|
+
Receipt `completeness` describes source inspection, independently of execution
|
|
71
|
+
or displayed output. `execution` is `completed`, `failed`, or `incomplete`;
|
|
72
|
+
`output` is `complete`, `truncated`, or `unavailable`. Output evidence requires
|
|
73
|
+
a unique later output bound to the same native call ID, with recognized text
|
|
74
|
+
blocks. `complete` describes the displayed receipt, not the whole program's
|
|
75
|
+
execution. Execution status additionally requires a successful or failed script
|
|
76
|
+
header. Missing, ambiguous or
|
|
77
|
+
malformed output yields `incomplete`/`unavailable`; a yielded or unrecognized
|
|
78
|
+
header stays `incomplete`. Displayed truncation does not change source coverage.
|
|
79
|
+
A fully inspected source with no launch call proves that absence even if its
|
|
80
|
+
shell output was truncated or the exec cell failed. Source inspection cannot
|
|
81
|
+
prove execution or success. Missing fields in historical receipts are unknown;
|
|
82
|
+
historical artifacts retain their original results and meanings.
|
|
49
83
|
|
|
50
84
|
Claude records every tool-use label from the bounded stream, with namespace
|
|
51
85
|
`claude`, without filtering to Skill or Agent. It requires a completed successful
|
|
@@ -55,11 +89,12 @@ Tool-looking text in arguments or output does not become a native call.
|
|
|
55
89
|
|
|
56
90
|
Consumers must verify the source, completeness, shape, ordering, and counts
|
|
57
91
|
before using an empty call list to establish absence. Missing or partial
|
|
58
|
-
|
|
92
|
+
source coverage cannot establish a negative assertion. For Codex, consumers must
|
|
59
93
|
inspect every `execCalls` receipt in addition to the direct `calls` list,
|
|
60
94
|
validate receipt ordinals against the direct `exec` submissions, and require
|
|
61
95
|
all receipts to be complete with counts matching `submittedExecCalls`.
|
|
62
|
-
They
|
|
96
|
+
They can use positively observed native attempt labels before requiring complete
|
|
97
|
+
coverage for an absence pass. They must examine submitted labels for prohibited controls even when
|
|
63
98
|
`acceptedAgentCount` is zero. Older consumers that require
|
|
64
99
|
`submittedExecCalls === 0` remain conservative but cannot use this new evidence
|
|
65
100
|
until they adopt receipt validation.
|
package/package.json
CHANGED
|
@@ -21,6 +21,14 @@ export interface ExecCallReceipt {
|
|
|
21
21
|
ordinal: number;
|
|
22
22
|
completeness: "complete" | "partial";
|
|
23
23
|
calls: SubmittedCall[];
|
|
24
|
+
inspectionReason?:
|
|
25
|
+
| "unsupported_source"
|
|
26
|
+
| "malformed_source"
|
|
27
|
+
| "source_limit"
|
|
28
|
+
| "foreign_namespace"
|
|
29
|
+
| null;
|
|
30
|
+
execution?: "completed" | "failed" | "incomplete";
|
|
31
|
+
output?: "complete" | "truncated" | "unavailable";
|
|
24
32
|
}
|
|
25
33
|
|
|
26
34
|
function toolNamespace(name: string): SubmittedCall["namespace"] {
|
|
@@ -77,7 +85,15 @@ function member(node: ts.Expression, owner: string, names: string[]): boolean {
|
|
|
77
85
|
function localName(node: ts.BindingName): string | undefined {
|
|
78
86
|
const ts = typescript();
|
|
79
87
|
if (!ts.isIdentifier(node)) return undefined;
|
|
80
|
-
return [
|
|
88
|
+
return [
|
|
89
|
+
"tools",
|
|
90
|
+
"text",
|
|
91
|
+
"store",
|
|
92
|
+
"Promise",
|
|
93
|
+
"load",
|
|
94
|
+
"JSON",
|
|
95
|
+
"String",
|
|
96
|
+
].includes(node.text)
|
|
81
97
|
? undefined
|
|
82
98
|
: node.text;
|
|
83
99
|
}
|
|
@@ -85,6 +101,7 @@ class Submission {
|
|
|
85
101
|
readonly calls: SubmittedCall[] = [];
|
|
86
102
|
private readonly locals = new Set<string>();
|
|
87
103
|
private readonly collections = new Set<string>();
|
|
104
|
+
private readonly indices = new Set<string>();
|
|
88
105
|
private nodes = 0;
|
|
89
106
|
expression(node: ts.Expression): boolean {
|
|
90
107
|
const ts = typescript();
|
|
@@ -110,17 +127,42 @@ class Submission {
|
|
|
110
127
|
return this.localProperty(node);
|
|
111
128
|
case ts.isCallExpression(node):
|
|
112
129
|
return this.call(node);
|
|
130
|
+
case ts.isConditionalExpression(node):
|
|
131
|
+
return [node.condition, node.whenTrue, node.whenFalse].every((part) =>
|
|
132
|
+
this.outputExpression(part),
|
|
133
|
+
);
|
|
134
|
+
case ts.isElementAccessExpression(node):
|
|
135
|
+
return (
|
|
136
|
+
this.collection(node.expression) &&
|
|
137
|
+
typescript().isIdentifier(node.argumentExpression) &&
|
|
138
|
+
this.indices.has(node.argumentExpression.text)
|
|
139
|
+
);
|
|
113
140
|
default:
|
|
114
141
|
return false;
|
|
115
142
|
}
|
|
116
143
|
}
|
|
117
144
|
private property(node: ts.ObjectLiteralElementLike): boolean {
|
|
118
145
|
const ts = typescript();
|
|
146
|
+
if (ts.isSpreadAssignment(node))
|
|
147
|
+
return this.outputExpression(node.expression);
|
|
148
|
+
if (ts.isShorthandPropertyAssignment(node)) return this.shorthand(node);
|
|
119
149
|
if (!ts.isPropertyAssignment(node) || ts.isComputedPropertyName(node.name))
|
|
120
150
|
return false;
|
|
121
151
|
return this.expression(node.initializer);
|
|
122
152
|
}
|
|
153
|
+
private shorthand(node: ts.ShorthandPropertyAssignment): boolean {
|
|
154
|
+
return !node.objectAssignmentInitializer && this.expression(node.name);
|
|
155
|
+
}
|
|
123
156
|
private composition(node: ts.BinaryExpression): boolean {
|
|
157
|
+
if (
|
|
158
|
+
[
|
|
159
|
+
typescript().SyntaxKind.EqualsEqualsEqualsToken,
|
|
160
|
+
typescript().SyntaxKind.ExclamationEqualsEqualsToken,
|
|
161
|
+
].includes(node.operatorToken.kind)
|
|
162
|
+
)
|
|
163
|
+
return (
|
|
164
|
+
this.outputExpression(node.left) && this.outputExpression(node.right)
|
|
165
|
+
);
|
|
124
166
|
return (
|
|
125
167
|
node.operatorToken.kind === typescript().SyntaxKind.PlusToken &&
|
|
126
168
|
this.expression(node.left) &&
|
|
@@ -148,13 +190,12 @@ class Submission {
|
|
|
148
190
|
if (ts.isVariableStatement(node))
|
|
149
191
|
return this.declarations(node.declarationList);
|
|
150
192
|
if (ts.isForOfStatement(node)) return this.outputLoop(node);
|
|
193
|
+
if (ts.isForStatement(node)) return this.numericOutputLoop(node);
|
|
151
194
|
return ts.isExpressionStatement(node) && this.expression(node.expression);
|
|
152
195
|
}
|
|
153
196
|
private localProperty(node: ts.PropertyAccessExpression): boolean {
|
|
154
|
-
const ts = typescript();
|
|
155
197
|
return (
|
|
156
|
-
|
|
157
|
-
this.locals.has(node.expression.text) &&
|
|
198
|
+
this.expression(node.expression) &&
|
|
158
199
|
!["constructor", "__proto__", "prototype"].includes(node.name.text)
|
|
159
200
|
);
|
|
160
201
|
}
|
|
@@ -231,10 +272,43 @@ class Submission {
|
|
|
231
272
|
if (!this.collection(node.expression)) return false;
|
|
232
273
|
if (!ts.isVariableDeclarationList(node.initializer)) return false;
|
|
233
274
|
const name = loopVariable(node.initializer);
|
|
275
|
+
if (!name || this.locals.has(name)) return false;
|
|
276
|
+
this.locals.add(name);
|
|
277
|
+
const valid = this.outputBody(node.statement);
|
|
278
|
+
this.locals.delete(name);
|
|
279
|
+
return valid;
|
|
280
|
+
}
|
|
281
|
+
private outputExpression(node: ts.Expression): boolean {
|
|
282
|
+
const count = this.calls.length;
|
|
283
|
+
return this.expression(node) && this.calls.length === count;
|
|
284
|
+
}
|
|
285
|
+
private numericOutputLoop(node: ts.ForStatement): boolean {
|
|
286
|
+
const name = numericLoopVariable(node);
|
|
287
|
+
if (!name || this.locals.has(name)) return false;
|
|
288
|
+
const condition = node.condition as ts.BinaryExpression;
|
|
289
|
+
const bound = condition.right as ts.PropertyAccessExpression;
|
|
290
|
+
if (!this.collection(bound.expression)) return false;
|
|
291
|
+
this.locals.add(name);
|
|
292
|
+
this.indices.add(name);
|
|
293
|
+
const valid = this.outputBody(node.statement);
|
|
294
|
+
this.locals.delete(name);
|
|
295
|
+
this.indices.delete(name);
|
|
296
|
+
return valid;
|
|
297
|
+
}
|
|
298
|
+
private outputBody(node: ts.Statement): boolean {
|
|
299
|
+
const ts = typescript();
|
|
300
|
+
if (ts.isBlock(node))
|
|
301
|
+
return node.statements.every((statement) => this.outputBody(statement));
|
|
302
|
+
if (ts.isIfStatement(node)) return this.outputConditional(node);
|
|
234
303
|
return (
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
304
|
+
ts.isExpressionStatement(node) && this.outputExpression(node.expression)
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
private outputConditional(node: ts.IfStatement): boolean {
|
|
308
|
+
return (
|
|
309
|
+
this.outputExpression(node.expression) &&
|
|
310
|
+
this.outputBody(node.thenStatement) &&
|
|
311
|
+
(!node.elseStatement || this.outputBody(node.elseStatement))
|
|
238
312
|
);
|
|
239
313
|
}
|
|
240
314
|
private collection(node: ts.Expression): boolean {
|
|
@@ -244,9 +318,12 @@ class Submission {
|
|
|
244
318
|
}
|
|
245
319
|
function helperCall(node: ts.Expression): boolean {
|
|
246
320
|
const ts = typescript();
|
|
247
|
-
if (ts.isIdentifier(node) && ["text", "store"].includes(node.text))
|
|
321
|
+
if (ts.isIdentifier(node) && ["text", "store", "String"].includes(node.text))
|
|
248
322
|
return true;
|
|
249
|
-
return
|
|
323
|
+
return (
|
|
324
|
+
member(node, "Promise", ["all", "allSettled"]) ||
|
|
325
|
+
member(node, "JSON", ["stringify"])
|
|
326
|
+
);
|
|
250
327
|
}
|
|
251
328
|
function namedIdentifier(
|
|
252
329
|
node: ts.Expression | undefined,
|
|
@@ -271,22 +348,69 @@ function loopVariable(list: ts.VariableDeclarationList): string | undefined {
|
|
|
271
348
|
? localName(declaration.name)
|
|
272
349
|
: undefined;
|
|
273
350
|
}
|
|
274
|
-
function
|
|
351
|
+
function numericLoopVariable(node: ts.ForStatement): string | undefined {
|
|
275
352
|
const ts = typescript();
|
|
353
|
+
if (!node.initializer || !ts.isVariableDeclarationList(node.initializer))
|
|
354
|
+
return undefined;
|
|
355
|
+
const name = numericBinding(node.initializer);
|
|
356
|
+
return name && numericLoopBounds(node, name) ? name : undefined;
|
|
357
|
+
}
|
|
358
|
+
function numericBinding(list: ts.VariableDeclarationList): string | undefined {
|
|
276
359
|
if (
|
|
277
|
-
!
|
|
278
|
-
|
|
360
|
+
!(list.flags & typescript().NodeFlags.Let) ||
|
|
361
|
+
list.declarations.length !== 1
|
|
279
362
|
)
|
|
280
|
-
return
|
|
281
|
-
const
|
|
363
|
+
return undefined;
|
|
364
|
+
const declaration = list.declarations[0];
|
|
365
|
+
return declaration && zeroInitializer(declaration.initializer)
|
|
366
|
+
? localName(declaration.name)
|
|
367
|
+
: undefined;
|
|
368
|
+
}
|
|
369
|
+
function zeroInitializer(node: ts.Expression | undefined): boolean {
|
|
370
|
+
return (
|
|
371
|
+
node !== undefined &&
|
|
372
|
+
typescript().isNumericLiteral(node) &&
|
|
373
|
+
node.text === "0"
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
function numericLoopBounds(node: ts.ForStatement, name: string): boolean {
|
|
377
|
+
const ts = typescript();
|
|
378
|
+
const condition = node.condition;
|
|
379
|
+
if (!condition || !ts.isBinaryExpression(condition)) return false;
|
|
282
380
|
return (
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
namedIdentifier(call.arguments[0], name)
|
|
381
|
+
numericCondition(condition, name) &&
|
|
382
|
+
numericIncrement(node.incrementor, name)
|
|
286
383
|
);
|
|
287
384
|
}
|
|
288
|
-
function
|
|
289
|
-
|
|
385
|
+
function numericCondition(node: ts.BinaryExpression, name: string): boolean {
|
|
386
|
+
return (
|
|
387
|
+
node.operatorToken.kind === typescript().SyntaxKind.LessThanToken &&
|
|
388
|
+
namedIdentifier(node.left, name) &&
|
|
389
|
+
lengthProperty(node.right)
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
function lengthProperty(node: ts.Expression): boolean {
|
|
393
|
+
return (
|
|
394
|
+
typescript().isPropertyAccessExpression(node) && node.name.text === "length"
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
function numericIncrement(
|
|
398
|
+
node: ts.Expression | undefined,
|
|
399
|
+
name: string,
|
|
400
|
+
): boolean {
|
|
401
|
+
return (
|
|
402
|
+
node !== undefined &&
|
|
403
|
+
typescript().isPostfixUnaryExpression(node) &&
|
|
404
|
+
node.operator === typescript().SyntaxKind.PlusPlusToken &&
|
|
405
|
+
namedIdentifier(node.operand, name)
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
function inspectedSource(code: unknown): {
|
|
409
|
+
submission?: Submission;
|
|
410
|
+
reason: ExecCallReceipt["inspectionReason"];
|
|
411
|
+
} {
|
|
412
|
+
if (typeof code !== "string") return { reason: "unsupported_source" };
|
|
413
|
+
if (code.length > 65536) return { reason: "source_limit" };
|
|
290
414
|
const ts = typescript();
|
|
291
415
|
const source = ts.createSourceFile(
|
|
292
416
|
"submitted.js",
|
|
@@ -298,33 +422,26 @@ function inspectedSource(code: unknown): Submission | undefined {
|
|
|
298
422
|
const diagnostics = (
|
|
299
423
|
source as ts.SourceFile & { parseDiagnostics: readonly ts.Diagnostic[] }
|
|
300
424
|
).parseDiagnostics;
|
|
301
|
-
if (diagnostics.length !== 0) return
|
|
425
|
+
if (diagnostics.length !== 0) return { reason: "malformed_source" };
|
|
302
426
|
const submission = new Submission();
|
|
303
427
|
return source.statements.every((statement) => submission.statement(statement))
|
|
304
|
-
? submission
|
|
305
|
-
:
|
|
428
|
+
? { submission, reason: null }
|
|
429
|
+
: { reason: "unsupported_source" };
|
|
306
430
|
}
|
|
307
|
-
function
|
|
308
|
-
if (isUnknownArray(output)) return
|
|
309
|
-
if (typeof output !== "string") return
|
|
431
|
+
function outputBlocks(output: unknown): unknown[] {
|
|
432
|
+
if (isUnknownArray(output)) return output;
|
|
433
|
+
if (typeof output !== "string") return [];
|
|
310
434
|
try {
|
|
311
435
|
const blocks: unknown = JSON.parse(output);
|
|
312
|
-
|
|
313
|
-
return completedBlocks(blocks);
|
|
436
|
+
return isUnknownArray(blocks) ? blocks : [];
|
|
314
437
|
} catch {
|
|
315
|
-
return
|
|
438
|
+
return [];
|
|
316
439
|
}
|
|
317
440
|
}
|
|
318
|
-
function completedBlocks(blocks: unknown[]): boolean {
|
|
319
|
-
return blocks.every(completeTextBlock) && completedHeader(blocks[0]);
|
|
320
|
-
}
|
|
321
441
|
function completeTextBlock(block: unknown): boolean {
|
|
322
442
|
if (!isRecord(block) || !["text", "input_text"].includes(String(block.type)))
|
|
323
443
|
return false;
|
|
324
|
-
return
|
|
325
|
-
typeof block.text === "string" &&
|
|
326
|
-
!block.text.includes("Warning: truncated output")
|
|
327
|
-
);
|
|
444
|
+
return typeof block.text === "string";
|
|
328
445
|
}
|
|
329
446
|
function completedHeader(block: unknown): boolean {
|
|
330
447
|
return (
|
|
@@ -335,23 +452,35 @@ function completedHeader(block: unknown): boolean {
|
|
|
335
452
|
)
|
|
336
453
|
);
|
|
337
454
|
}
|
|
338
|
-
function
|
|
339
|
-
if (!nativeExecNamespace(entry.payload.namespace)) return false;
|
|
455
|
+
function submissionOutput(entry: Entry, entries: Entry[]): unknown[] {
|
|
340
456
|
const id = entry.payload.call_id;
|
|
341
|
-
if (typeof id !== "string") return
|
|
457
|
+
if (typeof id !== "string") return [];
|
|
342
458
|
const matches = entries.filter((other) => other.payload.call_id === id);
|
|
343
|
-
if (matches.length !== 2) return
|
|
459
|
+
if (matches.length !== 2) return [];
|
|
344
460
|
const result = matches.find(
|
|
345
461
|
(other) => other.payload.type === "custom_tool_call_output",
|
|
346
462
|
);
|
|
347
|
-
return
|
|
463
|
+
return result && result.ordinal > entry.ordinal
|
|
464
|
+
? outputBlocks(result.payload.output)
|
|
465
|
+
: [];
|
|
348
466
|
}
|
|
349
|
-
function
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
467
|
+
function outputEvidence(
|
|
468
|
+
blocks: unknown[],
|
|
469
|
+
): Pick<ExecCallReceipt, "execution" | "output"> {
|
|
470
|
+
if (!blocks.length || !blocks.every(completeTextBlock))
|
|
471
|
+
return { execution: "incomplete", output: "unavailable" };
|
|
472
|
+
const header = blocks[0] as { text: string };
|
|
473
|
+
const execution = executionStatus(header);
|
|
474
|
+
const truncated = blocks.some((block) =>
|
|
475
|
+
(block as { text: string }).text.includes("Warning: truncated output"),
|
|
354
476
|
);
|
|
477
|
+
return { execution, output: truncated ? "truncated" : "complete" };
|
|
478
|
+
}
|
|
479
|
+
function executionStatus(header: {
|
|
480
|
+
text: string;
|
|
481
|
+
}): ExecCallReceipt["execution"] {
|
|
482
|
+
if (completedHeader(header)) return "completed";
|
|
483
|
+
return /^Script failed\b/.test(header.text) ? "failed" : "incomplete";
|
|
355
484
|
}
|
|
356
485
|
function nativeExecNamespace(namespace: unknown): boolean {
|
|
357
486
|
return namespace === undefined || namespace === "functions";
|
|
@@ -363,14 +492,25 @@ export function codexExecCallReceipts(entries: Entry[]): ExecCallReceipt[] {
|
|
|
363
492
|
({ payload }) =>
|
|
364
493
|
payload.type === "custom_tool_call" && payload.name === "exec",
|
|
365
494
|
)
|
|
366
|
-
.map((entry) => {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
495
|
+
.map((entry) => ({
|
|
496
|
+
ordinal: entry.ordinal,
|
|
497
|
+
...inspectionEvidence(entry),
|
|
498
|
+
...outputEvidence(submissionOutput(entry, entries)),
|
|
499
|
+
}));
|
|
500
|
+
}
|
|
501
|
+
function inspectionEvidence(
|
|
502
|
+
entry: Entry,
|
|
503
|
+
): Pick<ExecCallReceipt, "completeness" | "calls" | "inspectionReason"> {
|
|
504
|
+
if (!nativeExecNamespace(entry.payload.namespace))
|
|
505
|
+
return {
|
|
506
|
+
completeness: "partial",
|
|
507
|
+
calls: [],
|
|
508
|
+
inspectionReason: "foreign_namespace",
|
|
509
|
+
};
|
|
510
|
+
const inspected = inspectedSource(entry.payload.input);
|
|
511
|
+
return {
|
|
512
|
+
completeness: inspected.submission ? "complete" : "partial",
|
|
513
|
+
calls: inspected.submission?.calls ?? [],
|
|
514
|
+
inspectionReason: inspected.reason,
|
|
515
|
+
};
|
|
376
516
|
}
|
|
@@ -14,6 +14,12 @@ export interface NativeControlObservation {
|
|
|
14
14
|
method: "native_control_calls";
|
|
15
15
|
calls: Call[];
|
|
16
16
|
acceptedAgentCount: number | null;
|
|
17
|
+
directAcceptedAgentCount?: number;
|
|
18
|
+
coverage?: {
|
|
19
|
+
nativeCalls: NativeControlObservation["completeness"];
|
|
20
|
+
execInspection: "complete" | "partial";
|
|
21
|
+
acceptedAgents: "direct_native_calls";
|
|
22
|
+
};
|
|
17
23
|
submittedExecCalls: number | null;
|
|
18
24
|
execCalls?: ExecCallReceipt[];
|
|
19
25
|
truncated: boolean | null;
|
|
@@ -47,12 +53,28 @@ export function codexNativeControls(
|
|
|
47
53
|
acceptedAgentCount: complete
|
|
48
54
|
? observation.data.acceptedSpawns.length
|
|
49
55
|
: null,
|
|
50
|
-
|
|
56
|
+
directAcceptedAgentCount: observation.data.acceptedSpawns.length,
|
|
57
|
+
coverage: {
|
|
58
|
+
nativeCalls: observation.completeness,
|
|
59
|
+
execInspection: inspectedExecCalls(observation)
|
|
60
|
+
? "complete"
|
|
61
|
+
: "partial",
|
|
62
|
+
acceptedAgents: "direct_native_calls",
|
|
63
|
+
},
|
|
64
|
+
...nativeCounts(observation),
|
|
51
65
|
...execEvidence(observation.data.execCalls),
|
|
52
|
-
truncated: complete ? false : null,
|
|
53
66
|
},
|
|
54
67
|
};
|
|
55
68
|
}
|
|
69
|
+
function nativeCounts(
|
|
70
|
+
observation: Awaited<ReturnType<typeof codexNativeCallObservation>>,
|
|
71
|
+
) {
|
|
72
|
+
const complete = observation.completeness === "complete";
|
|
73
|
+
return {
|
|
74
|
+
submittedExecCalls: complete ? observation.data.submittedExecCalls : null,
|
|
75
|
+
truncated: complete ? false : null,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
56
78
|
function controlCompleteness(
|
|
57
79
|
source: NativeControlObservation["completeness"],
|
|
58
80
|
complete: boolean,
|