@bjoernrochel/sevro 0.1.0-rc.2 → 0.1.0-rc.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +170 -0
- package/LICENSE +63 -136
- package/README.md +83 -66
- package/docs/README.md +44 -0
- package/docs/acknowledgements.md +14 -0
- package/docs/architecture.md +146 -0
- package/docs/assets/README.md +27 -0
- package/docs/assets/sevro-logo.png +0 -0
- package/docs/assets/sevro-symbol.svg +1 -0
- package/docs/assets/sevro-wordmark.svg +1 -0
- package/docs/builtin-graders-v1.md +41 -13
- package/docs/codex-host.md +184 -52
- package/docs/contracts.md +19 -0
- package/docs/creating-cases.md +139 -0
- package/docs/development-cli.md +190 -30
- package/docs/documentation-quality.md +61 -0
- package/docs/evaluation-workflow.md +110 -0
- package/docs/evidence/code-mode-controls-71.md +64 -0
- package/docs/evidence/code-mode-programs-85.md +64 -0
- package/docs/evidence/codex-state-73/README.md +204 -0
- package/docs/evidence/codex-state-73/darrow-migration.patch +182 -0
- package/docs/evidence/codex-state-73/results.json +661 -0
- package/docs/evidence/local-http-74.md +104 -0
- package/docs/evidence/runtime-startup-30/README.md +185 -0
- package/docs/evidence/runtime-startup-30/after.json +319 -0
- package/docs/evidence/runtime-startup-30/baseline-timeout.jsonl +2 -0
- package/docs/evidence/runtime-startup-30/before.json +403 -0
- package/docs/evidence/runtime-startup-30/consumer-smoke.json +63 -0
- package/docs/evidence/runtime-startup-30/full-policy-diagnostic.jsonl +3 -0
- package/docs/evidence/runtime-startup-30/minimal-policy-diagnostic.jsonl +3 -0
- package/docs/evidence/runtime-startup-30/plugin-template-assessment.json +20 -0
- package/docs/evidence/runtime-startup-30/seedless-timeout.jsonl +2 -0
- package/docs/evidence/skill-reads-70/results.json +823 -0
- package/docs/evidence.md +21 -0
- package/docs/extension-protocol-v1.md +50 -16
- package/docs/fixture-cleanup-validation.md +3 -0
- package/docs/getting-started.md +102 -0
- package/docs/guide-evaluation.md +89 -0
- package/docs/guide-verification.json +239 -0
- package/docs/identity-v1.md +35 -1
- package/docs/installing.md +88 -0
- package/docs/licenses/BUSL-1.1-0.1.0-rc.2.txt +159 -0
- package/docs/licensing.md +42 -0
- package/docs/native-controls.md +76 -5
- package/docs/native-hosts.md +228 -0
- package/docs/reading-results.md +141 -0
- package/docs/recipes.md +489 -0
- package/docs/releases.md +81 -40
- package/docs/report-v1.md +29 -1
- package/docs/results-v1.md +180 -0
- package/docs/run-settings-v1.md +147 -0
- package/docs/running-evaluations.md +31 -0
- package/docs/runtime-v1.md +614 -0
- package/docs/specs/repository-guide.md +58 -0
- package/docs/troubleshooting.md +30 -0
- package/docs/typescript-quality.md +234 -0
- package/docs/usability-study.md +114 -0
- package/examples/basic/host.ts +2 -2
- 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 +39 -6
- package/schemas/report-v1.schema.json +9 -0
- package/schemas/run-evidence-v1.schema.json +2 -1
- package/schemas/runtime-v1.schema.json +193 -0
- package/src/advisory.ts +10 -5
- package/src/candidate-artifacts.ts +208 -0
- package/src/candidate-codex-state.ts +185 -0
- package/src/case-validation.ts +105 -0
- package/src/cli/cancellation.ts +20 -0
- package/src/cli/command.ts +81 -0
- package/src/cli/completion.ts +15 -0
- package/src/cli/demo.ts +59 -0
- package/src/cli/evaluation.ts +55 -0
- package/src/cli/extension.ts +79 -0
- package/src/cli/help.ts +222 -0
- package/src/cli/hosts.ts +59 -0
- package/src/cli/init-settings.ts +30 -0
- package/src/cli/init.ts +111 -0
- package/src/cli/invocation.ts +627 -0
- package/src/cli/loading.ts +151 -0
- package/src/cli/preparation.ts +163 -0
- package/src/cli/presentation.ts +91 -0
- package/src/cli/progress.ts +6 -0
- package/src/cli/run-manifest.ts +133 -0
- package/src/cli/run-options.ts +101 -0
- package/src/cli/run-overrides.ts +83 -0
- package/src/cli/settings.ts +160 -0
- package/src/cli/summary.ts +43 -0
- package/src/cli.ts +13 -843
- package/src/contract-types.ts +94 -0
- package/src/engine.ts +19 -2373
- package/src/evaluation/advisory.ts +150 -0
- package/src/evaluation/check-plan.ts +510 -0
- package/src/evaluation/collection.ts +88 -0
- package/src/evaluation/context.ts +10 -0
- package/src/evaluation/grading-host.ts +39 -0
- package/src/evaluation/grading.ts +258 -0
- package/src/evaluation/invocation-plan.ts +548 -0
- package/src/evaluation/plan.ts +736 -0
- package/src/evaluation/progress.ts +19 -0
- package/src/evaluation/resources.ts +241 -0
- package/src/evaluation/run.ts +206 -0
- package/src/{trial-scheduler.ts → evaluation/scheduler.ts} +3 -1
- package/src/evaluation/semantic.ts +312 -0
- package/src/evaluation/trial.ts +866 -0
- package/src/evaluation/types.ts +207 -0
- package/src/evidence/host-results.ts +92 -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 +228 -148
- package/src/extension-session.ts +326 -195
- package/src/fixtures/advisory.ts +338 -0
- package/src/fixtures/bin.ts +57 -0
- package/src/{fixture-cleanup.ts → fixtures/cleanup.ts} +13 -7
- package/src/fixtures/create.ts +187 -0
- package/src/fixtures/generated.ts +300 -0
- package/src/fixtures/git-hooks.ts +24 -0
- package/src/fixtures/preparation.ts +270 -0
- package/src/{repository-fixture.ts → fixtures/repository.ts} +207 -85
- package/src/fixtures/setup.ts +224 -0
- package/src/fixtures/tools.ts +88 -0
- package/src/fixtures/workspace-state.ts +212 -0
- package/src/generated/cli-result.cjs +2 -0
- package/src/generated/cli-result.d.cts +3 -0
- package/src/generated/extension.cjs +2 -0
- package/src/generated/extension.d.cts +3 -0
- package/src/generated/report.cjs +2 -0
- package/src/generated/report.d.cts +3 -0
- package/src/generated/run-evidence.cjs +2 -0
- package/src/generated/run-evidence.d.cts +3 -0
- package/src/generated/runtime.cjs +2 -0
- package/src/generated/runtime.d.cts +3 -0
- package/src/graders/artifact.ts +56 -23
- package/src/graders/git-head.ts +79 -75
- package/src/graders/output.ts +222 -140
- package/src/graders/semantic.ts +102 -43
- package/src/graders/shell.ts +379 -207
- package/src/host-diagnostic-redaction.ts +52 -0
- package/src/host-diagnostics.ts +319 -0
- package/src/hosts/claude-continuation.ts +63 -18
- package/src/hosts/claude-credential.ts +43 -62
- package/src/hosts/claude-events.ts +86 -51
- package/src/hosts/claude-keychain.ts +59 -0
- package/src/hosts/claude-native-goal.ts +57 -33
- package/src/hosts/claude-process.ts +111 -0
- package/src/hosts/claude-repository-invocation.ts +208 -117
- package/src/hosts/claude-settings.ts +240 -21
- package/src/hosts/claude-skill-activations.ts +166 -0
- package/src/hosts/claude-state.ts +403 -0
- package/src/hosts/claude-tool-calls.ts +171 -106
- package/src/hosts/claude.ts +561 -451
- package/src/hosts/codex-app-server.ts +326 -178
- package/src/hosts/codex-configuration.ts +44 -18
- package/src/hosts/codex-events.ts +97 -60
- package/src/hosts/codex-exec-controls.ts +516 -0
- package/src/hosts/codex-invocation.ts +70 -0
- package/src/hosts/codex-marketplace.ts +283 -0
- package/src/hosts/codex-native-calls.ts +546 -500
- package/src/hosts/codex-process.ts +202 -0
- package/src/hosts/codex-profile.ts +257 -28
- package/src/hosts/codex-session-files.ts +106 -0
- package/src/hosts/codex-state.ts +492 -0
- package/src/hosts/codex.ts +783 -832
- package/src/hosts/isolation-common.ts +94 -0
- package/src/hosts/isolation-roots.ts +54 -27
- package/src/hosts/isolation.ts +10 -0
- package/src/hosts/linux-sandbox.ts +159 -0
- package/src/hosts/mac-sandbox.ts +187 -61
- package/src/hosts/native-controls.ts +152 -62
- package/src/hosts/process-lifecycle.ts +37 -0
- package/src/hosts/protected-worktrees.ts +51 -19
- package/src/hosts/skill-hook-catalog.ts +131 -0
- package/src/hosts/skill-hook-handler.ts +374 -0
- package/src/hosts/skill-hooks.ts +580 -0
- package/src/hosts/workspace-fingerprint.ts +55 -35
- package/src/identity.ts +102 -48
- package/src/instrumentation.ts +109 -48
- package/src/native-transcript-state.ts +131 -0
- package/src/native-transcript-view.ts +90 -0
- package/src/native-transcripts.ts +286 -0
- package/src/process-lifecycle.ts +150 -0
- package/src/provenance.ts +161 -81
- package/src/prune.ts +215 -0
- package/src/report.ts +172 -140
- package/src/resolved-case.ts +33 -0
- package/src/results-lock.ts +29 -0
- package/src/results.ts +133 -86
- package/src/run-owner.ts +154 -63
- package/src/runtime/config.ts +456 -0
- package/src/runtime/discovery-apple.ts +90 -0
- package/src/runtime/discovery-providers.ts +63 -0
- package/src/runtime/discovery.ts +70 -0
- package/src/runtime/hook-processes.ts +97 -0
- package/src/runtime/hooks.ts +476 -0
- package/src/runtime/paths.ts +313 -0
- package/src/runtime/query.ts +85 -0
- package/src/runtime/seeds.ts +134 -0
- package/src/runtime/shared-cache.ts +143 -0
- package/src/runtime/state.ts +143 -0
- package/src/schema-types.ts +198 -0
- package/src/schema.ts +15 -19
- package/src/transcript-retention.ts +146 -0
- package/src/value-guards.ts +20 -0
- package/src/advisory-fixture.ts +0 -282
- package/src/fixture-bin.ts +0 -87
- package/src/fixture-setup.ts +0 -163
- package/src/generated-fixture.ts +0 -220
- package/src/git-hooks.ts +0 -63
- package/src/hosts/claude-nested-skills.ts +0 -218
- package/src/hosts/codex-command-output.ts +0 -399
- package/src/hosts/codex-executor-command.ts +0 -195
- package/src/hosts/codex-skill-reads.ts +0 -391
- package/src/preparation.ts +0 -211
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { Dirent } from "node:fs";
|
|
3
|
-
import { lstat, readFile, readdir, stat } from "node:fs/promises";
|
|
4
|
-
import { join } from "node:path";
|
|
1
|
+
import { isRecord, isUnknownArray } from "../value-guards";
|
|
5
2
|
import {
|
|
6
|
-
|
|
7
|
-
type
|
|
8
|
-
} from "./codex-
|
|
3
|
+
codexExecCallReceipts,
|
|
4
|
+
type ExecCallReceipt,
|
|
5
|
+
} from "./codex-exec-controls";
|
|
6
|
+
import {
|
|
7
|
+
readNativeSession,
|
|
8
|
+
locateNativeSession as sessionPath,
|
|
9
|
+
} from "./codex-session-files";
|
|
9
10
|
|
|
10
|
-
const MAX_SESSION_BYTES = 8 * 1024 * 1024;
|
|
11
11
|
const MAX_SESSION_ENTRIES = 10_000;
|
|
12
|
-
const MAX_SESSION_FILES = 1024;
|
|
13
12
|
const MAX_RETAINED_CALLS = 128;
|
|
14
13
|
const MAX_CHILD_SESSIONS = 8;
|
|
15
|
-
const THREAD_ID = /^[A-Za-z0-9._-]{1,128}$/;
|
|
16
14
|
const GOAL_CONTROLS = new Set(["create_goal", "get_goal", "update_goal"]);
|
|
17
15
|
const COLLABORATION_CONTROLS = new Set([
|
|
18
16
|
"spawn_agent",
|
|
@@ -63,7 +61,6 @@ interface ChildSession {
|
|
|
63
61
|
threadId: string;
|
|
64
62
|
status: "available" | "unavailable" | "ambiguous" | "partial";
|
|
65
63
|
resultStatus?: "completed" | "unavailable";
|
|
66
|
-
readDiagnostics?: NativeReadDiagnostic;
|
|
67
64
|
nestedSpawns?: NestedSpawn[];
|
|
68
65
|
requestsTruncated?: boolean;
|
|
69
66
|
}
|
|
@@ -81,9 +78,7 @@ interface NestedSpawn {
|
|
|
81
78
|
readerResultStatus: "completed" | "unavailable";
|
|
82
79
|
}
|
|
83
80
|
|
|
84
|
-
interface
|
|
85
|
-
workspace: string;
|
|
86
|
-
installedPluginRoots: string[];
|
|
81
|
+
interface NativeCallContext {
|
|
87
82
|
followUpPrompt?: string;
|
|
88
83
|
}
|
|
89
84
|
|
|
@@ -95,18 +90,14 @@ interface NativeCallObservation {
|
|
|
95
90
|
calls: NativeCall[];
|
|
96
91
|
toolCalls: NativeToolCall[];
|
|
97
92
|
submittedExecCalls: number;
|
|
93
|
+
execCalls?: ExecCallReceipt[];
|
|
98
94
|
acceptedSpawns: AcceptedSpawn[];
|
|
99
95
|
feedbackCalls: NativeFeedbackCall[];
|
|
100
|
-
parentReadDiagnostics?: NativeReadDiagnostic;
|
|
101
96
|
childSessions: ChildSession[];
|
|
102
97
|
childrenTruncated: boolean;
|
|
103
98
|
};
|
|
104
99
|
}
|
|
105
100
|
|
|
106
|
-
function record(value: unknown): value is Record<string, unknown> {
|
|
107
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
101
|
function unavailable(
|
|
111
102
|
completeness: NativeCallObservation["completeness"],
|
|
112
103
|
): NativeCallObservation {
|
|
@@ -126,76 +117,31 @@ function unavailable(
|
|
|
126
117
|
};
|
|
127
118
|
}
|
|
128
119
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
const pending = [sessionsRoot];
|
|
145
|
-
const suffix = `-${threadId}.jsonl`;
|
|
146
|
-
const matches: string[] = [];
|
|
147
|
-
let visited = 0;
|
|
148
|
-
while (pending.length) {
|
|
149
|
-
const directory = pending.pop()!;
|
|
150
|
-
let entries: Dirent<string>[];
|
|
151
|
-
try {
|
|
152
|
-
entries = await readdir(directory, { withFileTypes: true });
|
|
153
|
-
} catch {
|
|
154
|
-
return { status: "partial" as const };
|
|
155
|
-
}
|
|
156
|
-
for (const entry of entries) {
|
|
157
|
-
visited++;
|
|
158
|
-
if (visited > MAX_SESSION_FILES || entry.isSymbolicLink())
|
|
159
|
-
return { status: "partial" as const };
|
|
160
|
-
const path = join(directory, entry.name);
|
|
161
|
-
if (entry.isDirectory()) pending.push(path);
|
|
162
|
-
else if (entry.isFile() && entry.name.endsWith(suffix))
|
|
163
|
-
matches.push(path);
|
|
164
|
-
if (matches.length > 1) return { status: "ambiguous" as const };
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
return matches.length === 1
|
|
168
|
-
? { status: "found" as const, path: matches[0]! }
|
|
169
|
-
: { status: "unavailable" as const };
|
|
120
|
+
function nativeControlNamespace(
|
|
121
|
+
payload: Record<string, unknown> & { name: string },
|
|
122
|
+
): NativeCall["namespace"] | null {
|
|
123
|
+
if (goalNamespace(payload.namespace) && GOAL_CONTROLS.has(payload.name))
|
|
124
|
+
return "functions";
|
|
125
|
+
if (
|
|
126
|
+
payload.namespace === "collaboration" &&
|
|
127
|
+
COLLABORATION_CONTROLS.has(payload.name)
|
|
128
|
+
)
|
|
129
|
+
return "collaboration";
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
function goalNamespace(value: unknown): boolean {
|
|
133
|
+
return value === "functions" || value === undefined;
|
|
170
134
|
}
|
|
171
|
-
|
|
172
135
|
function directCall(
|
|
173
136
|
ordinal: number,
|
|
174
137
|
payload: Record<string, unknown>,
|
|
175
138
|
): NativeCall | null {
|
|
176
139
|
if (payload.type !== "function_call" || typeof payload.name !== "string")
|
|
177
140
|
return null;
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
return {
|
|
183
|
-
ordinal,
|
|
184
|
-
namespace: "functions",
|
|
185
|
-
name: payload.name,
|
|
186
|
-
evidence: "invocation_attempt",
|
|
187
|
-
};
|
|
188
|
-
if (
|
|
189
|
-
payload.namespace === "collaboration" &&
|
|
190
|
-
COLLABORATION_CONTROLS.has(payload.name)
|
|
191
|
-
)
|
|
192
|
-
return {
|
|
193
|
-
ordinal,
|
|
194
|
-
namespace: "collaboration",
|
|
195
|
-
name: payload.name,
|
|
196
|
-
evidence: "invocation_attempt",
|
|
197
|
-
};
|
|
198
|
-
return null;
|
|
141
|
+
const namespace = nativeControlNamespace({ ...payload, name: payload.name });
|
|
142
|
+
return namespace
|
|
143
|
+
? { ordinal, namespace, name: payload.name, evidence: "invocation_attempt" }
|
|
144
|
+
: null;
|
|
199
145
|
}
|
|
200
146
|
|
|
201
147
|
const IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
|
|
@@ -208,144 +154,191 @@ function bounded(value: unknown, pattern: RegExp): string | undefined {
|
|
|
208
154
|
: undefined;
|
|
209
155
|
}
|
|
210
156
|
|
|
157
|
+
function toolNamespace(value: unknown): NativeToolCall["namespace"] {
|
|
158
|
+
return value === "functions" || value === "collaboration" || value === "clock"
|
|
159
|
+
? value
|
|
160
|
+
: "other";
|
|
161
|
+
}
|
|
162
|
+
function parsedObject(text: unknown): Record<string, unknown> | null {
|
|
163
|
+
if (typeof text !== "string") return null;
|
|
164
|
+
try {
|
|
165
|
+
const value: unknown = JSON.parse(text);
|
|
166
|
+
return isRecord(value) ? value : null;
|
|
167
|
+
} catch {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function feedbackTarget(
|
|
172
|
+
args: Record<string, unknown> | null,
|
|
173
|
+
): string | undefined {
|
|
174
|
+
if (!args) return undefined;
|
|
175
|
+
return (
|
|
176
|
+
bounded(args.target, AGENT_REF) ??
|
|
177
|
+
bounded(args.target, /^[a-z0-9][a-z0-9_]{0,63}$/)
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
function nativeToolTarget(
|
|
181
|
+
payload: Record<string, unknown>,
|
|
182
|
+
namespace: NativeToolCall["namespace"],
|
|
183
|
+
name: string,
|
|
184
|
+
): string | undefined {
|
|
185
|
+
if (
|
|
186
|
+
namespace !== "collaboration" ||
|
|
187
|
+
!["followup_task", "send_message", "interrupt_agent"].includes(name)
|
|
188
|
+
)
|
|
189
|
+
return undefined;
|
|
190
|
+
return feedbackTarget(parsedObject(payload.arguments));
|
|
191
|
+
}
|
|
192
|
+
function toolCallType(value: unknown): boolean {
|
|
193
|
+
return value === "function_call" || value === "custom_tool_call";
|
|
194
|
+
}
|
|
211
195
|
function nativeToolCall(
|
|
212
196
|
ordinal: number,
|
|
213
197
|
payload: Record<string, unknown>,
|
|
214
198
|
): NativeToolCall | null {
|
|
215
|
-
if (payload.type
|
|
216
|
-
|
|
217
|
-
const namespace =
|
|
218
|
-
payload.namespace === "functions" ||
|
|
219
|
-
payload.namespace === "collaboration" ||
|
|
220
|
-
payload.namespace === "clock"
|
|
221
|
-
? payload.namespace
|
|
222
|
-
: "other";
|
|
199
|
+
if (!toolCallType(payload.type)) return null;
|
|
200
|
+
const namespace = toolNamespace(payload.namespace);
|
|
223
201
|
const name =
|
|
224
202
|
bounded(payload.name, /^[A-Za-z_][A-Za-z0-9_]{0,63}$/) ?? "other";
|
|
225
|
-
|
|
226
|
-
if (
|
|
227
|
-
namespace === "collaboration" &&
|
|
228
|
-
["followup_task", "send_message", "interrupt_agent"].includes(name) &&
|
|
229
|
-
typeof payload.arguments === "string"
|
|
230
|
-
) {
|
|
231
|
-
try {
|
|
232
|
-
const args: unknown = JSON.parse(payload.arguments);
|
|
233
|
-
if (record(args))
|
|
234
|
-
target =
|
|
235
|
-
bounded(args.target, AGENT_REF) ??
|
|
236
|
-
bounded(args.target, /^[a-z0-9][a-z0-9_]{0,63}$/);
|
|
237
|
-
} catch {
|
|
238
|
-
// An unreadable target stays unknown without exposing arguments.
|
|
239
|
-
}
|
|
240
|
-
}
|
|
203
|
+
const target = nativeToolTarget(payload, namespace, name);
|
|
241
204
|
return { ordinal, namespace, name, ...(target ? { target } : {}) };
|
|
242
205
|
}
|
|
243
206
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
payload.namespace === "collaboration" &&
|
|
251
|
-
payload.name === "spawn_agent",
|
|
207
|
+
type NativeEntry = { ordinal: number; payload: Record<string, unknown> };
|
|
208
|
+
function spawnRequest(payload: Record<string, unknown>): boolean {
|
|
209
|
+
return (
|
|
210
|
+
payload.type === "function_call" &&
|
|
211
|
+
payload.namespace === "collaboration" &&
|
|
212
|
+
payload.name === "spawn_agent"
|
|
252
213
|
);
|
|
214
|
+
}
|
|
215
|
+
function acceptedSpawns(entries: NativeEntry[]): AcceptedSpawn[] {
|
|
216
|
+
const requests = entries.filter(({ payload }) => spawnRequest(payload));
|
|
253
217
|
const accepted: AcceptedSpawn[] = [];
|
|
254
218
|
for (const request of requests) {
|
|
255
|
-
const
|
|
256
|
-
if (
|
|
257
|
-
!callId ||
|
|
258
|
-
requests.filter(({ payload }) => payload.call_id === callId).length !==
|
|
259
|
-
1 ||
|
|
260
|
-
typeof request.payload.arguments !== "string"
|
|
261
|
-
)
|
|
262
|
-
continue;
|
|
263
|
-
let args: unknown;
|
|
264
|
-
try {
|
|
265
|
-
args = JSON.parse(request.payload.arguments);
|
|
266
|
-
} catch {
|
|
267
|
-
continue;
|
|
268
|
-
}
|
|
269
|
-
if (!record(args)) continue;
|
|
270
|
-
const starts = entries.filter(({ payload }) => {
|
|
271
|
-
const item = record(payload.item) ? payload.item : undefined;
|
|
272
|
-
return (
|
|
273
|
-
payload.type === "item_completed" &&
|
|
274
|
-
item?.type === "SubAgentActivity" &&
|
|
275
|
-
item.id === callId &&
|
|
276
|
-
item.kind === "started"
|
|
277
|
-
);
|
|
278
|
-
});
|
|
279
|
-
const outputs = entries.filter(
|
|
280
|
-
({ payload }) =>
|
|
281
|
-
payload.type === "function_call_output" && payload.call_id === callId,
|
|
282
|
-
);
|
|
283
|
-
if (starts.length !== 1 || outputs.length !== 1) continue;
|
|
284
|
-
const start = starts[0]!;
|
|
285
|
-
const result = outputs[0]!;
|
|
286
|
-
const item = start.payload.item as Record<string, unknown>;
|
|
287
|
-
const agentRef = bounded(item.agent_path, AGENT_REF);
|
|
288
|
-
const threadId = bounded(item.agent_thread_id, IDENTIFIER);
|
|
289
|
-
if (
|
|
290
|
-
!agentRef ||
|
|
291
|
-
!threadId ||
|
|
292
|
-
!(request.ordinal < start.ordinal && start.ordinal < result.ordinal) ||
|
|
293
|
-
typeof result.payload.output !== "string"
|
|
294
|
-
)
|
|
295
|
-
continue;
|
|
296
|
-
let output: unknown;
|
|
297
|
-
try {
|
|
298
|
-
output = JSON.parse(result.payload.output);
|
|
299
|
-
} catch {
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
302
|
-
if (!record(output) || output.task_name !== agentRef) continue;
|
|
303
|
-
accepted.push({
|
|
304
|
-
callId,
|
|
305
|
-
agentRef,
|
|
306
|
-
threadId,
|
|
307
|
-
requestedOrdinal: request.ordinal,
|
|
308
|
-
startedOrdinal: start.ordinal,
|
|
309
|
-
acceptedOrdinal: result.ordinal,
|
|
310
|
-
...(bounded(args.task_name, /^[a-z0-9][a-z0-9_]{0,63}$/)
|
|
311
|
-
? { taskName: args.task_name as string }
|
|
312
|
-
: {}),
|
|
313
|
-
...(bounded(args.model, IDENTIFIER)
|
|
314
|
-
? { model: args.model as string }
|
|
315
|
-
: {}),
|
|
316
|
-
...(bounded(
|
|
317
|
-
args.reasoning_effort,
|
|
318
|
-
/^(?:low|medium|high|xhigh|max|ultra)$/,
|
|
319
|
-
)
|
|
320
|
-
? { reasoningEffort: args.reasoning_effort as string }
|
|
321
|
-
: {}),
|
|
322
|
-
...(bounded(args.fork_turns, /^(?:none|all|[1-9][0-9]*)$/)
|
|
323
|
-
? { forkTurns: args.fork_turns as string }
|
|
324
|
-
: {}),
|
|
325
|
-
});
|
|
219
|
+
const spawn = acceptedSpawn(request, requests, entries);
|
|
220
|
+
if (spawn) accepted.push(spawn);
|
|
326
221
|
}
|
|
327
222
|
return accepted;
|
|
328
223
|
}
|
|
224
|
+
function spawnArguments(request: NativeEntry, requests: NativeEntry[]) {
|
|
225
|
+
const callId = bounded(request.payload.call_id, IDENTIFIER);
|
|
226
|
+
if (
|
|
227
|
+
!callId ||
|
|
228
|
+
requests.filter(({ payload }) => payload.call_id === callId).length !== 1
|
|
229
|
+
)
|
|
230
|
+
return null;
|
|
231
|
+
const args = parsedObject(request.payload.arguments);
|
|
232
|
+
return args ? { callId, args } : null;
|
|
233
|
+
}
|
|
234
|
+
function startedSpawn(
|
|
235
|
+
payload: Record<string, unknown>,
|
|
236
|
+
callId: string,
|
|
237
|
+
): boolean {
|
|
238
|
+
const item = isRecord(payload.item) ? payload.item : {};
|
|
239
|
+
return (
|
|
240
|
+
payload.type === "item_completed" &&
|
|
241
|
+
item.type === "SubAgentActivity" &&
|
|
242
|
+
item.id === callId &&
|
|
243
|
+
item.kind === "started"
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
function spawnBoundaries(entries: NativeEntry[], callId: string) {
|
|
247
|
+
const starts = entries.filter(({ payload }) => startedSpawn(payload, callId));
|
|
248
|
+
const outputs = entries.filter(
|
|
249
|
+
({ payload }) =>
|
|
250
|
+
payload.type === "function_call_output" && payload.call_id === callId,
|
|
251
|
+
);
|
|
252
|
+
const [start] = starts,
|
|
253
|
+
[result] = outputs;
|
|
254
|
+
if (
|
|
255
|
+
starts.length !== 1 ||
|
|
256
|
+
outputs.length !== 1 ||
|
|
257
|
+
start === undefined ||
|
|
258
|
+
result === undefined
|
|
259
|
+
)
|
|
260
|
+
return null;
|
|
261
|
+
return { start, result };
|
|
262
|
+
}
|
|
263
|
+
function spawnIdentity(
|
|
264
|
+
request: NativeEntry,
|
|
265
|
+
start: NativeEntry,
|
|
266
|
+
result: NativeEntry,
|
|
267
|
+
) {
|
|
268
|
+
const item = isRecord(start.payload.item) ? start.payload.item : {};
|
|
269
|
+
const agentRef = bounded(item.agent_path, AGENT_REF),
|
|
270
|
+
threadId = bounded(item.agent_thread_id, IDENTIFIER);
|
|
271
|
+
if (!agentRef || !threadId || !spawnOrdered(request, start, result))
|
|
272
|
+
return null;
|
|
273
|
+
return { agentRef, threadId };
|
|
274
|
+
}
|
|
275
|
+
function spawnOrdered(
|
|
276
|
+
request: NativeEntry,
|
|
277
|
+
start: NativeEntry,
|
|
278
|
+
result: NativeEntry,
|
|
279
|
+
): boolean {
|
|
280
|
+
return request.ordinal < start.ordinal && start.ordinal < result.ordinal;
|
|
281
|
+
}
|
|
282
|
+
function acceptedSpawn(
|
|
283
|
+
request: NativeEntry,
|
|
284
|
+
requests: NativeEntry[],
|
|
285
|
+
entries: NativeEntry[],
|
|
286
|
+
): AcceptedSpawn | null {
|
|
287
|
+
const declaration = spawnArguments(request, requests);
|
|
288
|
+
if (!declaration) return null;
|
|
289
|
+
const boundaries = spawnBoundaries(entries, declaration.callId);
|
|
290
|
+
if (!boundaries) return null;
|
|
291
|
+
return correlatedSpawn(request, declaration, boundaries);
|
|
292
|
+
}
|
|
293
|
+
function correlatedSpawn(
|
|
294
|
+
request: NativeEntry,
|
|
295
|
+
declaration: { callId: string; args: Record<string, unknown> },
|
|
296
|
+
boundaries: { start: NativeEntry; result: NativeEntry },
|
|
297
|
+
): AcceptedSpawn | null {
|
|
298
|
+
const { start, result } = boundaries;
|
|
299
|
+
const identity = spawnIdentity(request, start, result);
|
|
300
|
+
if (!identity) return null;
|
|
301
|
+
const output = parsedObject(result.payload.output);
|
|
302
|
+
if (!output || output.task_name !== identity.agentRef) return null;
|
|
303
|
+
return {
|
|
304
|
+
callId: declaration.callId,
|
|
305
|
+
...identity,
|
|
306
|
+
requestedOrdinal: request.ordinal,
|
|
307
|
+
startedOrdinal: start.ordinal,
|
|
308
|
+
acceptedOrdinal: result.ordinal,
|
|
309
|
+
...requestFields(declaration.args),
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
function requestFields(args: Record<string, unknown>) {
|
|
313
|
+
const taskName = bounded(args.task_name, /^[a-z0-9][a-z0-9_]{0,63}$/);
|
|
314
|
+
const model = bounded(args.model, IDENTIFIER);
|
|
315
|
+
const reasoningEffort = bounded(
|
|
316
|
+
args.reasoning_effort,
|
|
317
|
+
/^(?:low|medium|high|xhigh|max|ultra)$/,
|
|
318
|
+
);
|
|
319
|
+
const forkTurns = bounded(args.fork_turns, /^(?:none|all|[1-9][0-9]*)$/);
|
|
320
|
+
return {
|
|
321
|
+
...(taskName ? { taskName } : {}),
|
|
322
|
+
...(model ? { model } : {}),
|
|
323
|
+
...(reasoningEffort ? { reasoningEffort } : {}),
|
|
324
|
+
...(forkTurns ? { forkTurns } : {}),
|
|
325
|
+
};
|
|
326
|
+
}
|
|
329
327
|
|
|
328
|
+
function feedbackRepresentation(
|
|
329
|
+
message: unknown,
|
|
330
|
+
): NativeFeedbackCall["messageRepresentation"] {
|
|
331
|
+
if (typeof message !== "string") return "unavailable";
|
|
332
|
+
return /^gAAAAA[A-Za-z0-9_-]+={0,2}$/.test(message)
|
|
333
|
+
? "encrypted"
|
|
334
|
+
: "plaintext";
|
|
335
|
+
}
|
|
330
336
|
function feedbackMessageEvidence(
|
|
331
337
|
payload: Record<string, unknown>,
|
|
332
338
|
followUpPrompt?: string,
|
|
333
339
|
) {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
try {
|
|
337
|
-
const args: unknown = JSON.parse(payload.arguments);
|
|
338
|
-
if (record(args)) message = args.message;
|
|
339
|
-
} catch {
|
|
340
|
-
// An unreadable message remains unavailable.
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
const messageRepresentation =
|
|
344
|
-
typeof message !== "string"
|
|
345
|
-
? ("unavailable" as const)
|
|
346
|
-
: /^gAAAAA[A-Za-z0-9_-]+={0,2}$/.test(message)
|
|
347
|
-
? ("encrypted" as const)
|
|
348
|
-
: ("plaintext" as const);
|
|
340
|
+
const message = parsedObject(payload.arguments)?.message;
|
|
341
|
+
const messageRepresentation = feedbackRepresentation(message);
|
|
349
342
|
return {
|
|
350
343
|
messageRepresentation,
|
|
351
344
|
messageMatchesFollowUpPrompt:
|
|
@@ -355,190 +348,278 @@ function feedbackMessageEvidence(
|
|
|
355
348
|
};
|
|
356
349
|
}
|
|
357
350
|
|
|
351
|
+
function feedbackPayload(
|
|
352
|
+
payload: Record<string, unknown>,
|
|
353
|
+
): payload is Record<string, unknown> & { name: NativeFeedbackCall["tool"] } {
|
|
354
|
+
return (
|
|
355
|
+
payload.type === "function_call" &&
|
|
356
|
+
payload.namespace === "collaboration" &&
|
|
357
|
+
typeof payload.name === "string" &&
|
|
358
|
+
["followup_task", "send_message", "interrupt_agent"].includes(payload.name)
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
function feedbackResponses(
|
|
362
|
+
entries: NativeEntry[],
|
|
363
|
+
callId: string | undefined,
|
|
364
|
+
ordinal: number,
|
|
365
|
+
) {
|
|
366
|
+
return callId
|
|
367
|
+
? entries.filter(
|
|
368
|
+
(entry) =>
|
|
369
|
+
entry.ordinal > ordinal &&
|
|
370
|
+
entry.payload.type === "function_call_output" &&
|
|
371
|
+
entry.payload.call_id === callId,
|
|
372
|
+
)
|
|
373
|
+
: [];
|
|
374
|
+
}
|
|
375
|
+
function responseObserved(outputs: NativeEntry[]): boolean {
|
|
376
|
+
return outputs.length === 1 && typeof outputs[0]?.payload.output === "string";
|
|
377
|
+
}
|
|
358
378
|
function nativeFeedbackCalls(
|
|
359
|
-
entries:
|
|
379
|
+
entries: NativeEntry[],
|
|
360
380
|
followUpPrompt?: string,
|
|
361
381
|
): NativeFeedbackCall[] {
|
|
362
|
-
return entries.flatMap((
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
outputs.length === 1 &&
|
|
387
|
-
typeof outputs[0]?.payload.output === "string",
|
|
388
|
-
...feedbackMessageEvidence(payload, followUpPrompt),
|
|
389
|
-
},
|
|
390
|
-
];
|
|
391
|
-
});
|
|
382
|
+
return entries.flatMap((entry) =>
|
|
383
|
+
feedbackCall(entry, entries, followUpPrompt),
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
function feedbackCall(
|
|
387
|
+
{ ordinal, payload }: NativeEntry,
|
|
388
|
+
entries: NativeEntry[],
|
|
389
|
+
followUpPrompt: string | undefined,
|
|
390
|
+
): NativeFeedbackCall[] {
|
|
391
|
+
if (!feedbackPayload(payload)) return [];
|
|
392
|
+
const outputs = feedbackResponses(
|
|
393
|
+
entries,
|
|
394
|
+
bounded(payload.call_id, IDENTIFIER),
|
|
395
|
+
ordinal,
|
|
396
|
+
);
|
|
397
|
+
return [
|
|
398
|
+
{
|
|
399
|
+
ordinal,
|
|
400
|
+
tool: payload.name,
|
|
401
|
+
target: nativeToolCall(ordinal, payload)?.target ?? null,
|
|
402
|
+
responseObserved: responseObserved(outputs),
|
|
403
|
+
...feedbackMessageEvidence(payload, followUpPrompt),
|
|
404
|
+
},
|
|
405
|
+
];
|
|
392
406
|
}
|
|
393
407
|
|
|
408
|
+
interface ParseState {
|
|
409
|
+
calls: NativeCall[];
|
|
410
|
+
toolCalls: NativeToolCall[];
|
|
411
|
+
entries: NativeEntry[];
|
|
412
|
+
submittedExecCalls: number;
|
|
413
|
+
lastOrdinal: number;
|
|
414
|
+
malformed: boolean;
|
|
415
|
+
entryCount: number;
|
|
416
|
+
}
|
|
394
417
|
function parseSession(
|
|
395
418
|
text: string,
|
|
396
419
|
followUpPrompt?: string,
|
|
397
|
-
): {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
let malformed = false;
|
|
408
|
-
let entryCount = 0;
|
|
420
|
+
): { observation: NativeCallObservation; entries: NativeEntry[] } {
|
|
421
|
+
const state: ParseState = {
|
|
422
|
+
calls: [],
|
|
423
|
+
toolCalls: [],
|
|
424
|
+
entries: [],
|
|
425
|
+
submittedExecCalls: 0,
|
|
426
|
+
lastOrdinal: -1,
|
|
427
|
+
malformed: false,
|
|
428
|
+
entryCount: 0,
|
|
429
|
+
};
|
|
409
430
|
for (const line of text.split("\n")) {
|
|
410
431
|
if (!line.trim()) continue;
|
|
411
|
-
|
|
412
|
-
if (entryCount > MAX_SESSION_ENTRIES) {
|
|
413
|
-
malformed = true;
|
|
414
|
-
break;
|
|
415
|
-
}
|
|
416
|
-
let entry: unknown;
|
|
417
|
-
try {
|
|
418
|
-
entry = JSON.parse(line);
|
|
419
|
-
} catch {
|
|
420
|
-
malformed = true;
|
|
421
|
-
continue;
|
|
422
|
-
}
|
|
423
|
-
if (
|
|
424
|
-
!record(entry) ||
|
|
425
|
-
!Number.isSafeInteger(entry.ordinal) ||
|
|
426
|
-
(entry.ordinal as number) <= lastOrdinal ||
|
|
427
|
-
!record(entry.payload)
|
|
428
|
-
) {
|
|
429
|
-
malformed = true;
|
|
430
|
-
continue;
|
|
431
|
-
}
|
|
432
|
-
lastOrdinal = entry.ordinal as number;
|
|
433
|
-
entries.push({ ordinal: lastOrdinal, payload: entry.payload });
|
|
434
|
-
if (
|
|
435
|
-
entry.payload.type === "custom_tool_call" &&
|
|
436
|
-
entry.payload.name === "exec"
|
|
437
|
-
)
|
|
438
|
-
submittedExecCalls++;
|
|
439
|
-
const call = directCall(lastOrdinal, entry.payload);
|
|
440
|
-
if (call) calls.push(call);
|
|
441
|
-
const toolCall = nativeToolCall(lastOrdinal, entry.payload);
|
|
442
|
-
if (toolCall) toolCalls.push(toolCall);
|
|
443
|
-
if (
|
|
444
|
-
calls.length > MAX_RETAINED_CALLS ||
|
|
445
|
-
toolCalls.length > MAX_RETAINED_CALLS
|
|
446
|
-
) {
|
|
447
|
-
calls.length = MAX_RETAINED_CALLS;
|
|
448
|
-
toolCalls.length = MAX_RETAINED_CALLS;
|
|
449
|
-
malformed = true;
|
|
450
|
-
break;
|
|
451
|
-
}
|
|
432
|
+
if (!consumeSessionLine(line, state)) break;
|
|
452
433
|
}
|
|
453
434
|
return {
|
|
454
|
-
entries,
|
|
455
|
-
observation:
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
435
|
+
entries: state.entries,
|
|
436
|
+
observation: sessionObservation(state, followUpPrompt),
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
function consumeSessionLine(line: string, state: ParseState): boolean {
|
|
440
|
+
state.entryCount++;
|
|
441
|
+
if (state.entryCount > MAX_SESSION_ENTRIES) {
|
|
442
|
+
state.malformed = true;
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
const entry = parsedSessionEntry(line, state);
|
|
446
|
+
if (!entry) return true;
|
|
447
|
+
state.lastOrdinal = entry.ordinal;
|
|
448
|
+
state.entries.push(entry);
|
|
449
|
+
retainSessionCalls(entry, state);
|
|
450
|
+
return !truncateSessionCalls(state);
|
|
451
|
+
}
|
|
452
|
+
function sessionOrdinal(
|
|
453
|
+
entry: Record<string, unknown>,
|
|
454
|
+
lastOrdinal: number,
|
|
455
|
+
): entry is Record<string, unknown> & { ordinal: number } {
|
|
456
|
+
return (
|
|
457
|
+
typeof entry.ordinal === "number" &&
|
|
458
|
+
Number.isSafeInteger(entry.ordinal) &&
|
|
459
|
+
entry.ordinal > lastOrdinal
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
function parsedSessionEntry(
|
|
463
|
+
line: string,
|
|
464
|
+
state: ParseState,
|
|
465
|
+
): NativeEntry | null {
|
|
466
|
+
let entry: unknown;
|
|
467
|
+
try {
|
|
468
|
+
entry = JSON.parse(line);
|
|
469
|
+
} catch {
|
|
470
|
+
state.malformed = true;
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
if (
|
|
474
|
+
!isRecord(entry) ||
|
|
475
|
+
!sessionOrdinal(entry, state.lastOrdinal) ||
|
|
476
|
+
!isRecord(entry.payload)
|
|
477
|
+
) {
|
|
478
|
+
state.malformed = true;
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
return { ordinal: entry.ordinal, payload: entry.payload };
|
|
482
|
+
}
|
|
483
|
+
function retainSessionCalls(entry: NativeEntry, state: ParseState): void {
|
|
484
|
+
if (
|
|
485
|
+
entry.payload.type === "custom_tool_call" &&
|
|
486
|
+
entry.payload.name === "exec"
|
|
487
|
+
)
|
|
488
|
+
state.submittedExecCalls++;
|
|
489
|
+
const call = directCall(entry.ordinal, entry.payload);
|
|
490
|
+
if (call) state.calls.push(call);
|
|
491
|
+
const tool = nativeToolCall(entry.ordinal, entry.payload);
|
|
492
|
+
if (tool) state.toolCalls.push(tool);
|
|
493
|
+
}
|
|
494
|
+
function truncateSessionCalls(state: ParseState): boolean {
|
|
495
|
+
if (
|
|
496
|
+
state.calls.length <= MAX_RETAINED_CALLS &&
|
|
497
|
+
state.toolCalls.length <= MAX_RETAINED_CALLS
|
|
498
|
+
)
|
|
499
|
+
return false;
|
|
500
|
+
state.calls.length = MAX_RETAINED_CALLS;
|
|
501
|
+
state.toolCalls.length = MAX_RETAINED_CALLS;
|
|
502
|
+
state.malformed = true;
|
|
503
|
+
return true;
|
|
504
|
+
}
|
|
505
|
+
function sessionReceipts(
|
|
506
|
+
state: ParseState,
|
|
507
|
+
followUpPrompt: string | undefined,
|
|
508
|
+
) {
|
|
509
|
+
if (state.malformed || state.entries.length === 0)
|
|
510
|
+
return { acceptedSpawns: [], feedbackCalls: [] };
|
|
511
|
+
return {
|
|
512
|
+
acceptedSpawns: acceptedSpawns(state.entries),
|
|
513
|
+
feedbackCalls: nativeFeedbackCalls(state.entries, followUpPrompt),
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
function sessionObservation(
|
|
517
|
+
state: ParseState,
|
|
518
|
+
followUpPrompt: string | undefined,
|
|
519
|
+
): NativeCallObservation {
|
|
520
|
+
return {
|
|
521
|
+
id: "sevro.codex.native-calls",
|
|
522
|
+
completeness:
|
|
523
|
+
state.malformed || state.entryCount === 0 ? "partial" : "complete",
|
|
524
|
+
data: {
|
|
525
|
+
method: "native_session",
|
|
526
|
+
calls: state.calls,
|
|
527
|
+
toolCalls: state.toolCalls,
|
|
528
|
+
submittedExecCalls: state.submittedExecCalls,
|
|
529
|
+
...(state.submittedExecCalls > 0
|
|
530
|
+
? { execCalls: codexExecCallReceipts(state.entries) }
|
|
531
|
+
: {}),
|
|
532
|
+
...sessionReceipts(state, followUpPrompt),
|
|
533
|
+
childSessions: [],
|
|
534
|
+
childrenTruncated: false,
|
|
472
535
|
},
|
|
473
536
|
};
|
|
474
537
|
}
|
|
475
538
|
|
|
476
539
|
/** Bind a private final answer to the same turn's later native completion. */
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
):
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
});
|
|
540
|
+
|
|
541
|
+
function finalAgentMessage(payload: Record<string, unknown>): boolean {
|
|
542
|
+
const item = isRecord(payload.item) ? payload.item : {};
|
|
543
|
+
return (
|
|
544
|
+
payload.type === "item_completed" &&
|
|
545
|
+
item.type === "AgentMessage" &&
|
|
546
|
+
item.phase === "final_answer"
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
function childResultCompleted(entries: NativeEntry[]): boolean {
|
|
550
|
+
const finals = entries.filter(({ payload }) => finalAgentMessage(payload));
|
|
488
551
|
const completions = entries.filter(
|
|
489
552
|
({ payload }) => payload.type === "task_complete",
|
|
490
553
|
);
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
554
|
+
const boundaries = childResultBoundaries(finals, completions, entries);
|
|
555
|
+
return (
|
|
556
|
+
boundaries !== null &&
|
|
557
|
+
childFinalMatched(boundaries.final, boundaries.complete)
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
function childResultBoundaries(
|
|
561
|
+
finals: NativeEntry[],
|
|
562
|
+
completions: NativeEntry[],
|
|
563
|
+
entries: NativeEntry[],
|
|
564
|
+
) {
|
|
565
|
+
if (!uniqueChildCompletion(finals, completions, entries)) return null;
|
|
566
|
+
const [final] = finals,
|
|
567
|
+
[complete] = completions;
|
|
568
|
+
return final && complete ? { final, complete } : null;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function uniqueChildCompletion(
|
|
572
|
+
finals: NativeEntry[],
|
|
573
|
+
completions: NativeEntry[],
|
|
574
|
+
entries: NativeEntry[],
|
|
575
|
+
): boolean {
|
|
576
|
+
return (
|
|
577
|
+
finals.length === 1 &&
|
|
578
|
+
completions.length === 1 &&
|
|
579
|
+
!entries.some(
|
|
495
580
|
({ payload }) =>
|
|
496
581
|
payload.type === "turn_aborted" || payload.type === "turn_failed",
|
|
497
582
|
)
|
|
498
|
-
)
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
const item = final.payload.item
|
|
503
|
-
const
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
function childFinalMatched(final: NativeEntry, complete: NativeEntry): boolean {
|
|
586
|
+
if (!childCompletionOrdered(final, complete)) return false;
|
|
587
|
+
const item = isRecord(final.payload.item) ? final.payload.item : {};
|
|
588
|
+
const message = childMessage(item.content);
|
|
589
|
+
return (
|
|
590
|
+
message !== null &&
|
|
591
|
+
message.trim().length > 0 &&
|
|
592
|
+
complete.payload.last_agent_message === message
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
function childCompletionOrdered(
|
|
596
|
+
final: NativeEntry,
|
|
597
|
+
complete: NativeEntry,
|
|
598
|
+
): boolean {
|
|
599
|
+
return (
|
|
600
|
+
!!bounded(final.payload.turn_id, IDENTIFIER) &&
|
|
601
|
+
complete.payload.turn_id === final.payload.turn_id &&
|
|
602
|
+
final.ordinal < complete.ordinal
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
function childTextBlock(part: unknown): part is { type: "Text"; text: string } {
|
|
606
|
+
return (
|
|
607
|
+
isRecord(part) && part.type === "Text" && typeof part.text === "string"
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
function childMessage(content: unknown): string | null {
|
|
504
611
|
if (
|
|
505
|
-
!
|
|
506
|
-
complete.payload.turn_id !== final.payload.turn_id ||
|
|
507
|
-
final.ordinal >= complete.ordinal ||
|
|
508
|
-
!Array.isArray(content) ||
|
|
612
|
+
!isUnknownArray(content) ||
|
|
509
613
|
content.length === 0 ||
|
|
510
|
-
!content.every(
|
|
511
|
-
(part) =>
|
|
512
|
-
record(part) && part.type === "Text" && typeof part.text === "string",
|
|
513
|
-
)
|
|
614
|
+
!content.every(childTextBlock)
|
|
514
615
|
)
|
|
515
|
-
return
|
|
516
|
-
|
|
517
|
-
return (
|
|
518
|
-
message.trim().length > 0 && complete.payload.last_agent_message === message
|
|
519
|
-
);
|
|
616
|
+
return null;
|
|
617
|
+
return content.map((part) => part.text).join("");
|
|
520
618
|
}
|
|
521
619
|
|
|
522
620
|
function nestedRequestFields(payload: Record<string, unknown>) {
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
args = JSON.parse(payload.arguments as string);
|
|
526
|
-
} catch {
|
|
527
|
-
return {};
|
|
528
|
-
}
|
|
529
|
-
if (!record(args)) return {};
|
|
530
|
-
return {
|
|
531
|
-
...(bounded(args.task_name, /^[a-z0-9][a-z0-9_]{0,63}$/)
|
|
532
|
-
? { taskName: args.task_name as string }
|
|
533
|
-
: {}),
|
|
534
|
-
...(bounded(args.model, IDENTIFIER) ? { model: args.model as string } : {}),
|
|
535
|
-
...(bounded(args.reasoning_effort, /^(?:low|medium|high|xhigh|max|ultra)$/)
|
|
536
|
-
? { reasoningEffort: args.reasoning_effort as string }
|
|
537
|
-
: {}),
|
|
538
|
-
...(bounded(args.fork_turns, /^(?:none|all|[1-9][0-9]*)$/)
|
|
539
|
-
? { forkTurns: args.fork_turns as string }
|
|
540
|
-
: {}),
|
|
541
|
-
};
|
|
621
|
+
const args = parsedObject(payload.arguments);
|
|
622
|
+
return args ? requestFields(args) : {};
|
|
542
623
|
}
|
|
543
624
|
|
|
544
625
|
async function nestedSpawnReceipts(
|
|
@@ -555,26 +636,11 @@ async function nestedSpawnReceipts(
|
|
|
555
636
|
);
|
|
556
637
|
const accepted = acceptedSpawns(entries);
|
|
557
638
|
const nestedSpawns: NestedSpawn[] = await Promise.all(
|
|
558
|
-
requests
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
? receipt.threadId === rootThread
|
|
564
|
-
? { threadId: receipt.threadId, status: "partial" }
|
|
565
|
-
: await childSessionStatus(home, parentThread, receipt.threadId)
|
|
566
|
-
: null;
|
|
567
|
-
return {
|
|
568
|
-
requestedOrdinal: ordinal,
|
|
569
|
-
status: receipt ? ("accepted" as const) : ("unaccepted" as const),
|
|
570
|
-
...nestedRequestFields(payload),
|
|
571
|
-
...(receipt
|
|
572
|
-
? { agentRef: receipt.agentRef, threadId: receipt.threadId }
|
|
573
|
-
: {}),
|
|
574
|
-
sessionStatus: session?.status ?? "unavailable",
|
|
575
|
-
readerResultStatus: session?.resultStatus ?? "unavailable",
|
|
576
|
-
};
|
|
577
|
-
}),
|
|
639
|
+
requests
|
|
640
|
+
.slice(0, MAX_CHILD_SESSIONS)
|
|
641
|
+
.map((request) =>
|
|
642
|
+
nestedSpawnReceipt(home, rootThread, parentThread, accepted, request),
|
|
643
|
+
),
|
|
578
644
|
);
|
|
579
645
|
return {
|
|
580
646
|
nestedSpawns,
|
|
@@ -582,51 +648,88 @@ async function nestedSpawnReceipts(
|
|
|
582
648
|
};
|
|
583
649
|
}
|
|
584
650
|
|
|
651
|
+
async function nestedSpawnReceipt(
|
|
652
|
+
home: string,
|
|
653
|
+
rootThread: string,
|
|
654
|
+
parentThread: string,
|
|
655
|
+
accepted: AcceptedSpawn[],
|
|
656
|
+
{ ordinal, payload }: NativeEntry,
|
|
657
|
+
): Promise<NestedSpawn> {
|
|
658
|
+
const receipt = accepted.find((spawn) => spawn.requestedOrdinal === ordinal);
|
|
659
|
+
const session = await nestedReaderSession(
|
|
660
|
+
home,
|
|
661
|
+
rootThread,
|
|
662
|
+
parentThread,
|
|
663
|
+
receipt,
|
|
664
|
+
);
|
|
665
|
+
return {
|
|
666
|
+
requestedOrdinal: ordinal,
|
|
667
|
+
status: receipt ? "accepted" : "unaccepted",
|
|
668
|
+
...nestedRequestFields(payload),
|
|
669
|
+
...(receipt
|
|
670
|
+
? { agentRef: receipt.agentRef, threadId: receipt.threadId }
|
|
671
|
+
: {}),
|
|
672
|
+
...nestedReaderStatus(session),
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
function nestedReaderStatus(session: ChildSession | null) {
|
|
676
|
+
return {
|
|
677
|
+
sessionStatus: session?.status ?? "unavailable",
|
|
678
|
+
readerResultStatus: session?.resultStatus ?? "unavailable",
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
async function nestedReaderSession(
|
|
682
|
+
home: string,
|
|
683
|
+
rootThread: string,
|
|
684
|
+
parentThread: string,
|
|
685
|
+
receipt: AcceptedSpawn | undefined,
|
|
686
|
+
): Promise<ChildSession | null> {
|
|
687
|
+
if (!receipt) return null;
|
|
688
|
+
if (receipt.threadId === rootThread)
|
|
689
|
+
return { threadId: receipt.threadId, status: "partial" };
|
|
690
|
+
return childSessionStatus(home, parentThread, receipt.threadId);
|
|
691
|
+
}
|
|
692
|
+
|
|
585
693
|
async function childSessionStatus(
|
|
586
694
|
home: string,
|
|
587
695
|
parentThread: string,
|
|
588
696
|
threadId: string,
|
|
589
|
-
skillContext?: NativeSkillContext,
|
|
590
697
|
rootThread?: string,
|
|
591
698
|
): Promise<ChildSession> {
|
|
592
699
|
if (threadId === parentThread) return { threadId, status: "partial" };
|
|
593
700
|
const located = await sessionPath(home, threadId);
|
|
594
701
|
if (located.status !== "found") return { threadId, status: located.status };
|
|
595
702
|
try {
|
|
596
|
-
const
|
|
597
|
-
if (
|
|
598
|
-
|
|
599
|
-
if (bytes.byteLength > MAX_SESSION_BYTES)
|
|
600
|
-
return { threadId, status: "partial" };
|
|
601
|
-
const parsed = parseSession(
|
|
602
|
-
new TextDecoder("utf-8", { fatal: true }).decode(bytes),
|
|
603
|
-
);
|
|
604
|
-
if (parsed.observation.completeness !== "complete")
|
|
605
|
-
return { threadId, status: "partial" };
|
|
606
|
-
const readDiagnostics = skillContext
|
|
607
|
-
? await codexNativeReadDiagnostic(
|
|
608
|
-
parsed.entries,
|
|
609
|
-
skillContext.workspace,
|
|
610
|
-
skillContext.installedPluginRoots,
|
|
611
|
-
)
|
|
612
|
-
: undefined;
|
|
613
|
-
const nested = rootThread
|
|
614
|
-
? await nestedSpawnReceipts(home, rootThread, threadId, parsed.entries)
|
|
615
|
-
: undefined;
|
|
616
|
-
return {
|
|
617
|
-
threadId,
|
|
618
|
-
status: "available",
|
|
619
|
-
resultStatus: childResultCompleted(parsed.entries)
|
|
620
|
-
? "completed"
|
|
621
|
-
: "unavailable",
|
|
622
|
-
...(readDiagnostics ? { readDiagnostics } : {}),
|
|
623
|
-
...nested,
|
|
624
|
-
};
|
|
703
|
+
const text = await readNativeSession(located.path);
|
|
704
|
+
if (text === null) return { threadId, status: "partial" };
|
|
705
|
+
return await availableChildSession(home, threadId, text, rootThread);
|
|
625
706
|
} catch {
|
|
626
707
|
return { threadId, status: "partial" };
|
|
627
708
|
}
|
|
628
709
|
}
|
|
629
710
|
|
|
711
|
+
async function availableChildSession(
|
|
712
|
+
home: string,
|
|
713
|
+
threadId: string,
|
|
714
|
+
text: string,
|
|
715
|
+
rootThread: string | undefined,
|
|
716
|
+
): Promise<ChildSession> {
|
|
717
|
+
const parsed = parseSession(text);
|
|
718
|
+
if (parsed.observation.completeness !== "complete")
|
|
719
|
+
return { threadId, status: "partial" };
|
|
720
|
+
const nested = rootThread
|
|
721
|
+
? await nestedSpawnReceipts(home, rootThread, threadId, parsed.entries)
|
|
722
|
+
: undefined;
|
|
723
|
+
return {
|
|
724
|
+
threadId,
|
|
725
|
+
status: "available",
|
|
726
|
+
resultStatus: childResultCompleted(parsed.entries)
|
|
727
|
+
? "completed"
|
|
728
|
+
: "unavailable",
|
|
729
|
+
...nested,
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
|
|
630
733
|
/** Last validated native event before a follow-up prompt enters this thread. */
|
|
631
734
|
export async function codexNativeSessionLastOrdinal(
|
|
632
735
|
home: string,
|
|
@@ -635,24 +738,24 @@ export async function codexNativeSessionLastOrdinal(
|
|
|
635
738
|
const located = await sessionPath(home, threadId);
|
|
636
739
|
if (located.status !== "found") return null;
|
|
637
740
|
try {
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
if (bytes.byteLength > MAX_SESSION_BYTES) return null;
|
|
641
|
-
const parsed = parseSession(
|
|
642
|
-
new TextDecoder("utf-8", { fatal: true }).decode(bytes),
|
|
643
|
-
);
|
|
644
|
-
if (parsed.observation.completeness !== "complete") return null;
|
|
645
|
-
return parsed.entries.at(-1)?.ordinal ?? null;
|
|
741
|
+
const text = await readNativeSession(located.path);
|
|
742
|
+
return text === null ? null : lastSessionOrdinal(text);
|
|
646
743
|
} catch {
|
|
647
744
|
return null;
|
|
648
745
|
}
|
|
649
746
|
}
|
|
650
747
|
|
|
748
|
+
function lastSessionOrdinal(text: string): number | null {
|
|
749
|
+
const parsed = parseSession(text);
|
|
750
|
+
if (parsed.observation.completeness !== "complete") return null;
|
|
751
|
+
return parsed.entries.at(-1)?.ordinal ?? null;
|
|
752
|
+
}
|
|
753
|
+
|
|
651
754
|
/** Bind one private native session to the completed public thread. */
|
|
652
755
|
export async function codexNativeCallObservation(
|
|
653
756
|
home: string,
|
|
654
757
|
threadId: string,
|
|
655
|
-
|
|
758
|
+
context?: NativeCallContext,
|
|
656
759
|
): Promise<NativeCallObservation> {
|
|
657
760
|
const located = await sessionPath(home, threadId);
|
|
658
761
|
if (located.status !== "found")
|
|
@@ -660,98 +763,41 @@ export async function codexNativeCallObservation(
|
|
|
660
763
|
located.status === "ambiguous" ? "partial" : located.status,
|
|
661
764
|
);
|
|
662
765
|
try {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
new TextDecoder("utf-8", { fatal: true }).decode(bytes),
|
|
669
|
-
skillContext?.followUpPrompt,
|
|
670
|
-
);
|
|
671
|
-
if (observation.completeness !== "complete") return observation;
|
|
672
|
-
const parentReadDiagnostics = skillContext
|
|
673
|
-
? await codexNativeReadDiagnostic(
|
|
674
|
-
entries,
|
|
675
|
-
skillContext.workspace,
|
|
676
|
-
skillContext.installedPluginRoots,
|
|
677
|
-
)
|
|
678
|
-
: undefined;
|
|
679
|
-
const children = [
|
|
680
|
-
...new Set(
|
|
681
|
-
observation.data.acceptedSpawns.map((spawn) => spawn.threadId),
|
|
682
|
-
),
|
|
683
|
-
];
|
|
684
|
-
const childSessions = await Promise.all(
|
|
685
|
-
children
|
|
686
|
-
.slice(0, MAX_CHILD_SESSIONS)
|
|
687
|
-
.map((child) =>
|
|
688
|
-
childSessionStatus(home, threadId, child, skillContext, threadId),
|
|
689
|
-
),
|
|
766
|
+
return await nativeSessionObservation(
|
|
767
|
+
home,
|
|
768
|
+
threadId,
|
|
769
|
+
located.path,
|
|
770
|
+
context,
|
|
690
771
|
);
|
|
691
|
-
return {
|
|
692
|
-
...observation,
|
|
693
|
-
data: {
|
|
694
|
-
...observation.data,
|
|
695
|
-
...(parentReadDiagnostics ? { parentReadDiagnostics } : {}),
|
|
696
|
-
childSessions,
|
|
697
|
-
childrenTruncated: children.length > MAX_CHILD_SESSIONS,
|
|
698
|
-
},
|
|
699
|
-
};
|
|
700
772
|
} catch {
|
|
701
773
|
return unavailable("partial");
|
|
702
774
|
}
|
|
703
775
|
}
|
|
704
776
|
|
|
705
|
-
|
|
706
|
-
export async function codexNativeSkillReadRecovery(
|
|
777
|
+
async function nativeSessionObservation(
|
|
707
778
|
home: string,
|
|
708
779
|
threadId: string,
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
if (
|
|
733
|
-
payload.type !== "item_completed" ||
|
|
734
|
-
item?.type !== "CommandExecution" ||
|
|
735
|
-
typeof item.id !== "string" ||
|
|
736
|
-
counts.get(item.id) !== 1
|
|
737
|
-
)
|
|
738
|
-
continue;
|
|
739
|
-
const recoveredOutput = output.get(ordinal)?.output;
|
|
740
|
-
if (
|
|
741
|
-
recoveredOutput === undefined ||
|
|
742
|
-
!Array.isArray(item.command) ||
|
|
743
|
-
item.command.length !== 3 ||
|
|
744
|
-
!["-c", "-lc"].includes(String(item.command[1])) ||
|
|
745
|
-
typeof item.command[2] !== "string"
|
|
746
|
-
)
|
|
747
|
-
continue;
|
|
748
|
-
recovered.set(item.id, {
|
|
749
|
-
command: item.command[2],
|
|
750
|
-
output: recoveredOutput,
|
|
751
|
-
});
|
|
752
|
-
}
|
|
753
|
-
return recovered;
|
|
754
|
-
} catch {
|
|
755
|
-
return new Map();
|
|
756
|
-
}
|
|
780
|
+
path: string,
|
|
781
|
+
context: NativeCallContext | undefined,
|
|
782
|
+
): Promise<NativeCallObservation> {
|
|
783
|
+
const text = await readNativeSession(path);
|
|
784
|
+
if (text === null) return unavailable("partial");
|
|
785
|
+
const { observation } = parseSession(text, context?.followUpPrompt);
|
|
786
|
+
if (observation.completeness !== "complete") return observation;
|
|
787
|
+
const children = [
|
|
788
|
+
...new Set(observation.data.acceptedSpawns.map((spawn) => spawn.threadId)),
|
|
789
|
+
];
|
|
790
|
+
const childSessions = await Promise.all(
|
|
791
|
+
children
|
|
792
|
+
.slice(0, MAX_CHILD_SESSIONS)
|
|
793
|
+
.map((child) => childSessionStatus(home, threadId, child, threadId)),
|
|
794
|
+
);
|
|
795
|
+
return {
|
|
796
|
+
...observation,
|
|
797
|
+
data: {
|
|
798
|
+
...observation.data,
|
|
799
|
+
childSessions,
|
|
800
|
+
childrenTruncated: children.length > MAX_CHILD_SESSIONS,
|
|
801
|
+
},
|
|
802
|
+
};
|
|
757
803
|
}
|