@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.
- package/CONTRIBUTING.md +22 -6
- package/README.md +5 -1
- package/docs/README.md +19 -15
- package/docs/architecture.md +138 -17
- package/docs/assets/README.md +5 -4
- package/docs/builtin-graders-v1.md +17 -3
- package/docs/codex-host.md +155 -49
- package/docs/contracts.md +2 -1
- package/docs/creating-cases.md +9 -2
- package/docs/development-cli.md +187 -19
- package/docs/documentation-quality.md +12 -2
- package/docs/evaluation-workflow.md +110 -0
- package/docs/extension-protocol-v1.md +26 -3
- package/docs/getting-started.md +23 -2
- package/docs/guide-evaluation.md +33 -1
- package/docs/identity-v1.md +25 -2
- package/docs/installing.md +11 -11
- package/docs/native-controls.md +76 -5
- package/docs/native-hosts.md +196 -7
- package/docs/reading-results.md +102 -0
- package/docs/recipes.md +489 -0
- package/docs/releases.md +31 -12
- package/docs/report-v1.md +29 -1
- package/docs/results-v1.md +141 -4
- package/docs/run-settings-v1.md +147 -0
- package/docs/runtime-v1.md +307 -18
- package/docs/troubleshooting.md +12 -12
- package/docs/typescript-quality.md +89 -26
- package/examples/recipes/candidate.ts +26 -0
- package/examples/recipes/cases/coding-change.json +21 -0
- package/examples/recipes/cases/host-tools.json +21 -0
- package/examples/recipes/cases/mise.json +21 -0
- package/examples/recipes/cases/node.json +21 -0
- package/examples/recipes/cases/python-uv.json +21 -0
- package/examples/recipes/cases/semantic.json +17 -0
- package/examples/recipes/coding-change/check.ts +23 -0
- package/examples/recipes/extension-artifacts.ts +65 -0
- package/examples/recipes/extension-methods.ts +174 -0
- package/examples/recipes/extension.ts +37 -0
- package/examples/recipes/repository-skill/.agents/skills/startup-guide/SKILL.md +7 -0
- package/examples/recipes/repository-skill/.claude/skills/startup-guide/SKILL.md +7 -0
- package/examples/recipes/repository-skill/README.md +3 -0
- package/examples/recipes/run-inputs.ts +188 -0
- package/examples/recipes/run-options.ts +131 -0
- package/examples/recipes/run.ts +63 -0
- package/examples/recipes/runtime/empty.json +1 -0
- package/examples/recipes/runtime/goals.json +1 -0
- package/examples/recipes/runtime/mise.json +6 -0
- package/examples/recipes/runtime/path.json +1 -0
- package/examples/recipes/runtime/plugins.json +4 -0
- package/examples/recipes/runtime/python-uv.json +10 -0
- package/examples/recipes/semantic-judge.ts +29 -0
- package/examples/templates/coding/adapter.ts +17 -0
- package/examples/templates/coding/case.json +15 -0
- package/examples/templates/repository-skill/.agents/skills/startup-guide/SKILL.md +6 -0
- package/examples/templates/repository-skill/extension-data.ts +64 -0
- package/examples/templates/repository-skill/extension.ts +31 -0
- package/examples/templates/response/adapter.ts +12 -0
- package/examples/templates/response/case.json +13 -0
- package/package.json +11 -5
- package/schemas/report-v1.schema.json +9 -0
- package/schemas/run-evidence-v1.schema.json +2 -1
- package/schemas/runtime-v1.schema.json +63 -7
- package/src/advisory.ts +2 -2
- package/src/candidate-artifacts.ts +206 -0
- package/src/candidate-codex-state.ts +185 -0
- package/src/case-validation.ts +73 -50
- package/src/cli/cancellation.ts +20 -0
- package/src/cli/command.ts +87 -0
- package/src/cli/completion.ts +15 -0
- package/src/cli/demo.ts +60 -0
- package/src/cli/evaluation.ts +55 -0
- package/src/cli/extension.ts +79 -0
- package/src/cli/help.ts +198 -0
- package/src/cli/hosts.ts +55 -0
- package/src/cli/init-settings.ts +30 -0
- package/src/cli/init.ts +111 -0
- package/src/{cli-invocation.ts → cli/invocation.ts} +214 -218
- package/src/cli/loading.ts +151 -0
- package/src/cli/preparation.ts +154 -0
- package/src/cli/presentation.ts +93 -0
- package/src/cli/progress.ts +6 -0
- package/src/cli/run-manifest.ts +128 -0
- package/src/cli/run-options.ts +403 -0
- package/src/cli/run-overrides.ts +69 -0
- package/src/cli/settings.ts +154 -0
- package/src/cli/summary.ts +43 -0
- package/src/cli.ts +13 -591
- package/src/contract-types.ts +94 -0
- package/src/engine.ts +13 -5
- package/src/evaluation/advisory.ts +150 -0
- package/src/{evaluation-check-plan.ts → evaluation/check-plan.ts} +103 -67
- package/src/evaluation/collection.ts +88 -0
- package/src/evaluation/context.ts +10 -0
- package/src/{evaluation-grading-host.ts → evaluation/grading-host.ts} +5 -5
- package/src/evaluation/grading.ts +235 -0
- package/src/{evaluation-invocation-plan.ts → evaluation/invocation-plan.ts} +147 -58
- package/src/{evaluation-plan.ts → evaluation/plan.ts} +208 -141
- package/src/evaluation/progress.ts +19 -0
- package/src/evaluation/resources.ts +241 -0
- package/src/evaluation/run.ts +206 -0
- package/src/{evaluation-semantic.ts → evaluation/semantic.ts} +133 -115
- package/src/evaluation/trial.ts +800 -0
- package/src/evaluation/types.ts +207 -0
- package/src/evidence/host-results.ts +85 -0
- package/src/evidence/retained-artifacts.ts +18 -0
- package/src/evidence/trial-writer.ts +203 -0
- package/src/evidence/usage.ts +17 -0
- package/src/extension-client.ts +34 -96
- package/src/extension-session.ts +24 -48
- package/src/{advisory-fixture.ts → fixtures/advisory.ts} +15 -76
- package/src/{fixture-bin.ts → fixtures/bin.ts} +1 -1
- package/src/{fixture-cleanup.ts → fixtures/cleanup.ts} +13 -7
- package/src/fixtures/create.ts +183 -0
- package/src/{generated-fixture.ts → fixtures/generated.ts} +10 -53
- package/src/{git-hooks.ts → fixtures/git-hooks.ts} +1 -1
- package/src/{preparation.ts → fixtures/preparation.ts} +6 -16
- package/src/{repository-fixture.ts → fixtures/repository.ts} +30 -64
- package/src/{fixture-setup.ts → fixtures/setup.ts} +33 -51
- package/src/{fixture-tools.ts → fixtures/tools.ts} +1 -1
- package/src/fixtures/workspace-state.ts +212 -0
- package/src/generated/cli-result.d.cts +2 -1
- package/src/generated/extension.d.cts +2 -1
- package/src/generated/report.cjs +1 -1
- package/src/generated/report.d.cts +2 -1
- package/src/generated/run-evidence.cjs +1 -1
- package/src/generated/run-evidence.d.cts +2 -1
- package/src/generated/runtime.cjs +1 -1
- package/src/generated/runtime.d.cts +2 -1
- package/src/git.ts +93 -0
- package/src/graders/git-head.ts +29 -84
- package/src/graders/shell.ts +57 -68
- package/src/host-diagnostic-redaction.ts +52 -0
- package/src/host-diagnostics.ts +331 -0
- package/src/hosts/claude-continuation.ts +57 -40
- package/src/hosts/claude-credential.ts +160 -63
- package/src/hosts/claude-events.ts +34 -18
- package/src/hosts/claude-keychain.ts +78 -21
- package/src/hosts/claude-login-recovery.ts +85 -0
- package/src/hosts/claude-login-refresh.ts +178 -0
- package/src/hosts/claude-login-store.ts +162 -0
- package/src/hosts/claude-login.ts +107 -0
- package/src/hosts/claude-native-goal.ts +15 -12
- package/src/hosts/claude-process.ts +10 -50
- package/src/hosts/claude-refresh-lock.ts +81 -0
- package/src/hosts/claude-repository-invocation.ts +9 -34
- package/src/hosts/claude-settings.ts +91 -14
- package/src/hosts/claude-skill-activations.ts +169 -0
- package/src/hosts/claude-state.ts +46 -16
- package/src/hosts/claude-tool-calls.ts +13 -10
- package/src/hosts/claude.ts +122 -73
- package/src/hosts/codex-app-server.ts +78 -52
- package/src/hosts/codex-configuration.ts +5 -10
- package/src/hosts/codex-exec-controls.ts +516 -0
- package/src/hosts/codex-marketplace.ts +12 -10
- package/src/hosts/codex-native-calls.ts +15 -122
- package/src/hosts/codex-process.ts +10 -54
- package/src/hosts/codex-profile.ts +50 -21
- package/src/hosts/codex-session-files.ts +1 -9
- package/src/hosts/codex-state.ts +141 -19
- package/src/hosts/codex.ts +221 -179
- package/src/hosts/isolation-common.ts +15 -11
- package/src/hosts/isolation-roots.ts +13 -20
- package/src/hosts/linux-sandbox.ts +27 -3
- package/src/hosts/mac-sandbox.ts +111 -18
- package/src/hosts/native-controls.ts +72 -22
- package/src/hosts/native-request.ts +55 -0
- package/src/hosts/process-lifecycle.ts +221 -45
- package/src/hosts/protected-worktrees.ts +1 -1
- package/src/hosts/skill-hook-catalog.ts +131 -0
- package/src/hosts/skill-hook-handler.ts +374 -0
- package/src/hosts/skill-hooks.ts +577 -0
- package/src/instrumentation.ts +7 -8
- package/src/native-transcript-state.ts +22 -3
- package/src/native-transcript-view.ts +52 -13
- package/src/native-transcripts.ts +34 -15
- package/src/paths.ts +22 -0
- package/src/process-lifecycle.ts +150 -0
- package/src/provenance.ts +27 -7
- package/src/prune.ts +300 -0
- package/src/report.ts +63 -36
- package/src/resolved-case.ts +1 -1
- package/src/results-lock.ts +42 -0
- package/src/results.ts +14 -18
- package/src/run-owner.ts +4 -4
- package/src/{runtime-config.ts → runtime/config.ts} +182 -44
- package/src/{runtime-discovery-apple.ts → runtime/discovery-apple.ts} +3 -3
- package/src/{runtime-discovery-providers.ts → runtime/discovery-providers.ts} +3 -9
- package/src/{runtime-discovery.ts → runtime/discovery.ts} +4 -4
- package/src/{runtime-hook-processes.ts → runtime/hook-processes.ts} +1 -1
- package/src/{runtime-hooks.ts → runtime/hooks.ts} +33 -22
- package/src/{runtime-paths.ts → runtime/paths.ts} +6 -18
- package/src/{runtime-query.ts → runtime/query.ts} +5 -5
- package/src/{runtime-seeds.ts → runtime/seeds.ts} +3 -3
- package/src/runtime/shared-cache.ts +130 -0
- package/src/{runtime-state.ts → runtime/state.ts} +28 -11
- package/src/schema-types.ts +73 -161
- package/src/shell.ts +4 -0
- package/src/transcript-retention.ts +146 -0
- package/src/value-guards.ts +22 -0
- package/docs/assets/sevro-logo.png +0 -0
- package/docs/assets/sevro-symbol.svg +0 -1
- package/docs/assets/sevro-wordmark.svg +0 -1
- package/docs/cache-ownership-validation.md +0 -49
- package/docs/claude-continuation-validation.md +0 -47
- package/docs/concurrency-validation.md +0 -86
- package/docs/configuration-root-validation.md +0 -70
- package/docs/engine-slice.md +0 -114
- package/docs/evidence/runtime-startup-30/README.md +0 -185
- package/docs/evidence/runtime-startup-30/after.json +0 -319
- package/docs/evidence/runtime-startup-30/baseline-timeout.jsonl +0 -2
- package/docs/evidence/runtime-startup-30/before.json +0 -403
- package/docs/evidence/runtime-startup-30/consumer-smoke.json +0 -63
- package/docs/evidence/runtime-startup-30/full-policy-diagnostic.jsonl +0 -3
- package/docs/evidence/runtime-startup-30/minimal-policy-diagnostic.jsonl +0 -3
- package/docs/evidence/runtime-startup-30/plugin-template-assessment.json +0 -20
- package/docs/evidence/runtime-startup-30/seedless-timeout.jsonl +0 -2
- package/docs/evidence.md +0 -17
- package/docs/fixture-cleanup-validation.md +0 -70
- package/docs/guide-verification.json +0 -239
- package/src/evaluation-context.ts +0 -257
- package/src/evaluation-fixture.ts +0 -313
- package/src/evaluation-run.ts +0 -198
- package/src/evaluation-trial.ts +0 -946
- package/src/evaluation-types.ts +0 -149
- package/src/hosts/claude-nested-skills.ts +0 -272
- package/src/hosts/codex-command-output.ts +0 -510
- package/src/hosts/codex-executor-command.ts +0 -291
- package/src/hosts/codex-skill-reads.ts +0 -609
- /package/src/{trial-scheduler.ts → evaluation/scheduler.ts} +0 -0
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import type ts from "typescript";
|
|
3
|
+
import { isRecord, isUnknownArray } from "../value-guards";
|
|
4
|
+
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
let parser: typeof ts | undefined;
|
|
7
|
+
function typescript(): typeof ts {
|
|
8
|
+
return (parser ??= require("typescript") as typeof ts);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface Entry {
|
|
12
|
+
ordinal: number;
|
|
13
|
+
payload: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
interface SubmittedCall {
|
|
16
|
+
index: number;
|
|
17
|
+
namespace: "functions" | "collaboration" | "other";
|
|
18
|
+
name: string;
|
|
19
|
+
}
|
|
20
|
+
export interface ExecCallReceipt {
|
|
21
|
+
ordinal: number;
|
|
22
|
+
completeness: "complete" | "partial";
|
|
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";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function toolNamespace(name: string): SubmittedCall["namespace"] {
|
|
35
|
+
if (
|
|
36
|
+
[
|
|
37
|
+
"spawn_agent",
|
|
38
|
+
"list_agents",
|
|
39
|
+
"wait_agent",
|
|
40
|
+
"followup_task",
|
|
41
|
+
"send_message",
|
|
42
|
+
"interrupt_agent",
|
|
43
|
+
].includes(name)
|
|
44
|
+
)
|
|
45
|
+
return "collaboration";
|
|
46
|
+
if (
|
|
47
|
+
[
|
|
48
|
+
"create_goal",
|
|
49
|
+
"get_goal",
|
|
50
|
+
"update_goal",
|
|
51
|
+
"exec_command",
|
|
52
|
+
"write_stdin",
|
|
53
|
+
].includes(name)
|
|
54
|
+
)
|
|
55
|
+
return "functions";
|
|
56
|
+
return "other";
|
|
57
|
+
}
|
|
58
|
+
function toolName(node: ts.Expression): string | undefined {
|
|
59
|
+
const ts = typescript();
|
|
60
|
+
if (!ts.isPropertyAccessExpression(node)) return undefined;
|
|
61
|
+
if (!ts.isIdentifier(node.expression) || node.expression.text !== "tools")
|
|
62
|
+
return undefined;
|
|
63
|
+
return /^[A-Za-z_][A-Za-z0-9_]{0,63}$/.test(node.name.text)
|
|
64
|
+
? node.name.text
|
|
65
|
+
: undefined;
|
|
66
|
+
}
|
|
67
|
+
function literal(node: ts.Expression): boolean {
|
|
68
|
+
const ts = typescript();
|
|
69
|
+
if (ts.isStringLiteralLike(node) || ts.isNumericLiteral(node)) return true;
|
|
70
|
+
return [
|
|
71
|
+
ts.SyntaxKind.TrueKeyword,
|
|
72
|
+
ts.SyntaxKind.FalseKeyword,
|
|
73
|
+
ts.SyntaxKind.NullKeyword,
|
|
74
|
+
].includes(node.kind);
|
|
75
|
+
}
|
|
76
|
+
function member(node: ts.Expression, owner: string, names: string[]): boolean {
|
|
77
|
+
const ts = typescript();
|
|
78
|
+
return (
|
|
79
|
+
ts.isPropertyAccessExpression(node) &&
|
|
80
|
+
ts.isIdentifier(node.expression) &&
|
|
81
|
+
node.expression.text === owner &&
|
|
82
|
+
names.includes(node.name.text)
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
function localName(node: ts.BindingName): string | undefined {
|
|
86
|
+
const ts = typescript();
|
|
87
|
+
if (!ts.isIdentifier(node)) return undefined;
|
|
88
|
+
return [
|
|
89
|
+
"tools",
|
|
90
|
+
"text",
|
|
91
|
+
"store",
|
|
92
|
+
"Promise",
|
|
93
|
+
"load",
|
|
94
|
+
"JSON",
|
|
95
|
+
"String",
|
|
96
|
+
].includes(node.text)
|
|
97
|
+
? undefined
|
|
98
|
+
: node.text;
|
|
99
|
+
}
|
|
100
|
+
class Submission {
|
|
101
|
+
readonly calls: SubmittedCall[] = [];
|
|
102
|
+
private readonly locals = new Set<string>();
|
|
103
|
+
private readonly collections = new Set<string>();
|
|
104
|
+
private readonly indices = new Set<string>();
|
|
105
|
+
private nodes = 0;
|
|
106
|
+
expression(node: ts.Expression): boolean {
|
|
107
|
+
const ts = typescript();
|
|
108
|
+
if (++this.nodes > 2048) return false;
|
|
109
|
+
switch (true) {
|
|
110
|
+
case literal(node):
|
|
111
|
+
return true;
|
|
112
|
+
case ts.isTemplateExpression(node):
|
|
113
|
+
return node.templateSpans.every((span) =>
|
|
114
|
+
this.expression(span.expression),
|
|
115
|
+
);
|
|
116
|
+
case ts.isBinaryExpression(node):
|
|
117
|
+
return this.composition(node);
|
|
118
|
+
case ts.isAwaitExpression(node):
|
|
119
|
+
return this.expression(node.expression);
|
|
120
|
+
case ts.isObjectLiteralExpression(node):
|
|
121
|
+
return node.properties.every((property) => this.property(property));
|
|
122
|
+
case ts.isArrayLiteralExpression(node):
|
|
123
|
+
return node.elements.every((element) => this.expression(element));
|
|
124
|
+
case ts.isIdentifier(node):
|
|
125
|
+
return this.locals.has(node.text);
|
|
126
|
+
case ts.isPropertyAccessExpression(node):
|
|
127
|
+
return this.localProperty(node);
|
|
128
|
+
case ts.isCallExpression(node):
|
|
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
|
+
);
|
|
140
|
+
default:
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
private property(node: ts.ObjectLiteralElementLike): boolean {
|
|
145
|
+
const ts = typescript();
|
|
146
|
+
if (ts.isSpreadAssignment(node))
|
|
147
|
+
return this.outputExpression(node.expression);
|
|
148
|
+
if (ts.isShorthandPropertyAssignment(node)) return this.shorthand(node);
|
|
149
|
+
if (!ts.isPropertyAssignment(node) || ts.isComputedPropertyName(node.name))
|
|
150
|
+
return false;
|
|
151
|
+
return this.expression(node.initializer);
|
|
152
|
+
}
|
|
153
|
+
private shorthand(node: ts.ShorthandPropertyAssignment): boolean {
|
|
154
|
+
return !node.objectAssignmentInitializer && this.expression(node.name);
|
|
155
|
+
}
|
|
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
|
+
);
|
|
166
|
+
return (
|
|
167
|
+
node.operatorToken.kind === typescript().SyntaxKind.PlusToken &&
|
|
168
|
+
this.expression(node.left) &&
|
|
169
|
+
this.expression(node.right)
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
private call(node: ts.CallExpression): boolean {
|
|
173
|
+
if (this.outputEach(node)) return true;
|
|
174
|
+
if (!node.arguments.every((argument) => this.expression(argument)))
|
|
175
|
+
return false;
|
|
176
|
+
return helperCall(node.expression) || this.retainToolCall(node.expression);
|
|
177
|
+
}
|
|
178
|
+
private retainToolCall(expression: ts.Expression): boolean {
|
|
179
|
+
const name = toolName(expression);
|
|
180
|
+
if (!name || this.calls.length >= 128) return false;
|
|
181
|
+
this.calls.push({
|
|
182
|
+
index: this.calls.length,
|
|
183
|
+
namespace: toolNamespace(name),
|
|
184
|
+
name,
|
|
185
|
+
});
|
|
186
|
+
return name !== "exec";
|
|
187
|
+
}
|
|
188
|
+
statement(node: ts.Statement): boolean {
|
|
189
|
+
const ts = typescript();
|
|
190
|
+
if (ts.isVariableStatement(node))
|
|
191
|
+
return this.declarations(node.declarationList);
|
|
192
|
+
if (ts.isForOfStatement(node)) return this.outputLoop(node);
|
|
193
|
+
if (ts.isForStatement(node)) return this.numericOutputLoop(node);
|
|
194
|
+
return ts.isExpressionStatement(node) && this.expression(node.expression);
|
|
195
|
+
}
|
|
196
|
+
private localProperty(node: ts.PropertyAccessExpression): boolean {
|
|
197
|
+
return (
|
|
198
|
+
this.expression(node.expression) &&
|
|
199
|
+
!["constructor", "__proto__", "prototype"].includes(node.name.text)
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
private declarations(list: ts.VariableDeclarationList): boolean {
|
|
203
|
+
const ts = typescript();
|
|
204
|
+
if (!(list.flags & ts.NodeFlags.Const)) return false;
|
|
205
|
+
return list.declarations.every((declaration) =>
|
|
206
|
+
this.declaration(declaration),
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
private declaration(node: ts.VariableDeclaration): boolean {
|
|
210
|
+
if (!node.initializer) return false;
|
|
211
|
+
const names = this.bindingNames(node.name, node.initializer);
|
|
212
|
+
if (!names) return false;
|
|
213
|
+
if (!this.expression(node.initializer)) return false;
|
|
214
|
+
names.forEach((name) => this.locals.add(name));
|
|
215
|
+
this.retainCollection(node.name, node.initializer, names);
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
private retainCollection(
|
|
219
|
+
name: ts.BindingName,
|
|
220
|
+
initializer: ts.Expression,
|
|
221
|
+
names: string[],
|
|
222
|
+
): void {
|
|
223
|
+
if (localName(name) && collectionExpression(initializer))
|
|
224
|
+
names.forEach((name) => this.collections.add(name));
|
|
225
|
+
}
|
|
226
|
+
private bindingNames(
|
|
227
|
+
node: ts.BindingName,
|
|
228
|
+
initializer: ts.Expression,
|
|
229
|
+
): string[] | undefined {
|
|
230
|
+
const ts = typescript();
|
|
231
|
+
if (ts.isIdentifier(node)) {
|
|
232
|
+
const name = this.declaredName(node);
|
|
233
|
+
return name ? [name] : undefined;
|
|
234
|
+
}
|
|
235
|
+
if (!ts.isArrayBindingPattern(node) || !collectionExpression(initializer))
|
|
236
|
+
return undefined;
|
|
237
|
+
return this.batchNames(node);
|
|
238
|
+
}
|
|
239
|
+
private batchNames(node: ts.ArrayBindingPattern): string[] | undefined {
|
|
240
|
+
const names = node.elements.map((element) => this.batchBinding(element));
|
|
241
|
+
if (!names.every((name): name is string => name !== undefined))
|
|
242
|
+
return undefined;
|
|
243
|
+
return new Set(names).size === names.length ? names : undefined;
|
|
244
|
+
}
|
|
245
|
+
private batchBinding(node: ts.ArrayBindingElement): string | undefined {
|
|
246
|
+
const ts = typescript();
|
|
247
|
+
if (!ts.isBindingElement(node)) return undefined;
|
|
248
|
+
if (node.dotDotDotToken || node.initializer || node.propertyName)
|
|
249
|
+
return undefined;
|
|
250
|
+
return this.declaredName(node.name);
|
|
251
|
+
}
|
|
252
|
+
private declaredName(node: ts.BindingName): string | undefined {
|
|
253
|
+
const name = localName(node);
|
|
254
|
+
return name && !this.locals.has(name) ? name : undefined;
|
|
255
|
+
}
|
|
256
|
+
private outputEach(node: ts.CallExpression): boolean {
|
|
257
|
+
const ts = typescript();
|
|
258
|
+
const [callback] = node.arguments;
|
|
259
|
+
if (
|
|
260
|
+
node.arguments.length !== 1 ||
|
|
261
|
+
!callback ||
|
|
262
|
+
!ts.isIdentifier(callback) ||
|
|
263
|
+
callback.text !== "text"
|
|
264
|
+
)
|
|
265
|
+
return false;
|
|
266
|
+
return [...this.collections].some((name) =>
|
|
267
|
+
member(node.expression, name, ["forEach"]),
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
private outputLoop(node: ts.ForOfStatement): boolean {
|
|
271
|
+
const ts = typescript();
|
|
272
|
+
if (!this.collection(node.expression)) return false;
|
|
273
|
+
if (!ts.isVariableDeclarationList(node.initializer)) return false;
|
|
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);
|
|
303
|
+
return (
|
|
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))
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
private collection(node: ts.Expression): boolean {
|
|
315
|
+
const ts = typescript();
|
|
316
|
+
return ts.isIdentifier(node) && this.collections.has(node.text);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function helperCall(node: ts.Expression): boolean {
|
|
320
|
+
const ts = typescript();
|
|
321
|
+
if (ts.isIdentifier(node) && ["text", "store", "String"].includes(node.text))
|
|
322
|
+
return true;
|
|
323
|
+
return (
|
|
324
|
+
member(node, "Promise", ["all", "allSettled"]) ||
|
|
325
|
+
member(node, "JSON", ["stringify"])
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
function namedIdentifier(
|
|
329
|
+
node: ts.Expression | undefined,
|
|
330
|
+
name: string,
|
|
331
|
+
): boolean {
|
|
332
|
+
return (
|
|
333
|
+
node !== undefined && typescript().isIdentifier(node) && node.text === name
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
function collectionExpression(node: ts.Expression): boolean {
|
|
337
|
+
const ts = typescript();
|
|
338
|
+
if (!ts.isAwaitExpression(node) || !ts.isCallExpression(node.expression))
|
|
339
|
+
return false;
|
|
340
|
+
return member(node.expression.expression, "Promise", ["all", "allSettled"]);
|
|
341
|
+
}
|
|
342
|
+
function loopVariable(list: ts.VariableDeclarationList): string | undefined {
|
|
343
|
+
const ts = typescript();
|
|
344
|
+
if (!(list.flags & ts.NodeFlags.Const) || list.declarations.length !== 1)
|
|
345
|
+
return undefined;
|
|
346
|
+
const declaration = list.declarations[0];
|
|
347
|
+
return declaration && !declaration.initializer
|
|
348
|
+
? localName(declaration.name)
|
|
349
|
+
: undefined;
|
|
350
|
+
}
|
|
351
|
+
function numericLoopVariable(node: ts.ForStatement): string | undefined {
|
|
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 {
|
|
359
|
+
if (
|
|
360
|
+
!(list.flags & typescript().NodeFlags.Let) ||
|
|
361
|
+
list.declarations.length !== 1
|
|
362
|
+
)
|
|
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;
|
|
380
|
+
return (
|
|
381
|
+
numericCondition(condition, name) &&
|
|
382
|
+
numericIncrement(node.incrementor, name)
|
|
383
|
+
);
|
|
384
|
+
}
|
|
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" };
|
|
414
|
+
const ts = typescript();
|
|
415
|
+
const source = ts.createSourceFile(
|
|
416
|
+
"submitted.js",
|
|
417
|
+
code,
|
|
418
|
+
ts.ScriptTarget.Latest,
|
|
419
|
+
false,
|
|
420
|
+
ts.ScriptKind.JS,
|
|
421
|
+
);
|
|
422
|
+
const diagnostics = (
|
|
423
|
+
source as ts.SourceFile & { parseDiagnostics: readonly ts.Diagnostic[] }
|
|
424
|
+
).parseDiagnostics;
|
|
425
|
+
if (diagnostics.length !== 0) return { reason: "malformed_source" };
|
|
426
|
+
const submission = new Submission();
|
|
427
|
+
return source.statements.every((statement) => submission.statement(statement))
|
|
428
|
+
? { submission, reason: null }
|
|
429
|
+
: { reason: "unsupported_source" };
|
|
430
|
+
}
|
|
431
|
+
function outputBlocks(output: unknown): unknown[] {
|
|
432
|
+
if (isUnknownArray(output)) return output;
|
|
433
|
+
if (typeof output !== "string") return [];
|
|
434
|
+
try {
|
|
435
|
+
const blocks: unknown = JSON.parse(output);
|
|
436
|
+
return isUnknownArray(blocks) ? blocks : [];
|
|
437
|
+
} catch {
|
|
438
|
+
return [];
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
function completeTextBlock(block: unknown): boolean {
|
|
442
|
+
if (!isRecord(block) || !["text", "input_text"].includes(String(block.type)))
|
|
443
|
+
return false;
|
|
444
|
+
return typeof block.text === "string";
|
|
445
|
+
}
|
|
446
|
+
function completedHeader(block: unknown): boolean {
|
|
447
|
+
return (
|
|
448
|
+
isRecord(block) &&
|
|
449
|
+
typeof block.text === "string" &&
|
|
450
|
+
/^Script completed\nWall time [0-9]+(?:\.[0-9]+)? seconds\nOutput:\n$/.test(
|
|
451
|
+
block.text,
|
|
452
|
+
)
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
function submissionOutput(entry: Entry, entries: Entry[]): unknown[] {
|
|
456
|
+
const id = entry.payload.call_id;
|
|
457
|
+
if (typeof id !== "string") return [];
|
|
458
|
+
const matches = entries.filter((other) => other.payload.call_id === id);
|
|
459
|
+
if (matches.length !== 2) return [];
|
|
460
|
+
const result = matches.find(
|
|
461
|
+
(other) => other.payload.type === "custom_tool_call_output",
|
|
462
|
+
);
|
|
463
|
+
return result && result.ordinal > entry.ordinal
|
|
464
|
+
? outputBlocks(result.payload.output)
|
|
465
|
+
: [];
|
|
466
|
+
}
|
|
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"),
|
|
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";
|
|
484
|
+
}
|
|
485
|
+
function nativeExecNamespace(namespace: unknown): boolean {
|
|
486
|
+
return namespace === undefined || namespace === "functions";
|
|
487
|
+
}
|
|
488
|
+
/** Static inspection only: submitted code is never executed by the observer. */
|
|
489
|
+
export function codexExecCallReceipts(entries: Entry[]): ExecCallReceipt[] {
|
|
490
|
+
return entries
|
|
491
|
+
.filter(
|
|
492
|
+
({ payload }) =>
|
|
493
|
+
payload.type === "custom_tool_call" && payload.name === "exec",
|
|
494
|
+
)
|
|
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
|
+
};
|
|
516
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { Dirent } from "node:fs";
|
|
2
2
|
import { readFile, readdir, realpath, stat } from "node:fs/promises";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { join, relative, sep } from "node:path";
|
|
4
|
+
import { insideRoot as inside } from "../paths";
|
|
5
|
+
import { fixtureParts } from "../fixtures/preparation";
|
|
5
6
|
import type { HostAdapter } from "../engine";
|
|
6
7
|
import { runCodexProcess } from "./codex-process";
|
|
7
8
|
import { isRecord, isUnknownArray } from "../value-guards";
|
|
9
|
+
import type { HostDiagnostics } from "../host-diagnostics";
|
|
8
10
|
type Request = Parameters<HostAdapter["run"]>[0];
|
|
9
11
|
type Declaration = NonNullable<Request["codexMarketplace"]>;
|
|
10
12
|
interface InstallContext {
|
|
@@ -12,15 +14,9 @@ interface InstallContext {
|
|
|
12
14
|
env: Record<string, string>;
|
|
13
15
|
pluginCacheRoot: string;
|
|
14
16
|
request: Request;
|
|
17
|
+
diagnostics: HostDiagnostics;
|
|
15
18
|
}
|
|
16
19
|
|
|
17
|
-
function inside(root: string, path: string): boolean {
|
|
18
|
-
const child = relative(root, path);
|
|
19
|
-
return (
|
|
20
|
-
child === "" ||
|
|
21
|
-
(child !== ".." && !child.startsWith(`..${sep}`) && !isAbsolute(child))
|
|
22
|
-
);
|
|
23
|
-
}
|
|
24
20
|
function validPluginNames(declaration: Declaration): boolean {
|
|
25
21
|
const names = declaration.pluginNames;
|
|
26
22
|
return (
|
|
@@ -174,20 +170,24 @@ function verifyPackageEntry(
|
|
|
174
170
|
}
|
|
175
171
|
|
|
176
172
|
async function pluginCommand(context: InstallContext, args: string[]) {
|
|
177
|
-
|
|
173
|
+
const result = await runCodexProcess({
|
|
174
|
+
diagnostics: context.diagnostics,
|
|
178
175
|
argv: [context.binary, "plugin", ...args, "--json"],
|
|
179
176
|
cwd: context.request.workspace,
|
|
180
177
|
env: context.env,
|
|
181
178
|
timeoutMs: 30_000,
|
|
182
179
|
signal: context.request.signal,
|
|
183
180
|
});
|
|
181
|
+
return result;
|
|
184
182
|
}
|
|
185
183
|
export async function installCodexPlugins(
|
|
186
184
|
context: InstallContext,
|
|
187
185
|
): Promise<string[]> {
|
|
188
186
|
const declaration = context.request.codexMarketplace;
|
|
189
187
|
if (!declaration) return [];
|
|
188
|
+
context.diagnostics.stage = "marketplace-validation";
|
|
190
189
|
const root = await marketplaceRoot(context.request.workspace, declaration);
|
|
190
|
+
context.diagnostics.stage = "marketplace-install";
|
|
191
191
|
const added = await pluginCommand(context, ["marketplace", "add", root]);
|
|
192
192
|
if (added.code !== 0)
|
|
193
193
|
throw new Error("Codex local marketplace installation failed");
|
|
@@ -201,12 +201,14 @@ async function installPlugin(
|
|
|
201
201
|
declaration: Declaration,
|
|
202
202
|
pluginName: string,
|
|
203
203
|
): Promise<string> {
|
|
204
|
+
context.diagnostics.stage = "plugin-install";
|
|
204
205
|
const installed = await pluginCommand(context, [
|
|
205
206
|
"add",
|
|
206
207
|
`${pluginName}@${declaration.marketplaceName}`,
|
|
207
208
|
]);
|
|
208
209
|
if (installed.code !== 0)
|
|
209
210
|
throw new Error("Codex local plugin installation failed");
|
|
211
|
+
context.diagnostics.stage = "plugin-receipt";
|
|
210
212
|
const path = await installedPluginPath(
|
|
211
213
|
installed.out,
|
|
212
214
|
declaration.marketplaceName,
|