deepline 0.2.51 → 0.2.53
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/dist/bundling-sources/sdk/src/client.ts +1 -1
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/cli/index.js +39 -2
- package/dist/cli/index.mjs +39 -2
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/viewer/viewer.css +57 -2
- package/dist/viewer/viewer.js +548 -46
- package/package.json +1 -1
|
@@ -819,7 +819,7 @@ export type MonitorsNamespace = {
|
|
|
819
819
|
definition: MonitorDefinition,
|
|
820
820
|
options?: { dryRun?: boolean },
|
|
821
821
|
) => Promise<MonitorDeployResult>;
|
|
822
|
-
/** List deployed monitors (active by default). `includeConsumers` requires limit
|
|
822
|
+
/** List deployed monitors (active by default). `includeConsumers` requires a limit of 20 or fewer. */
|
|
823
823
|
list: (options?: MonitorsListOptions) => Promise<MonitorsListResult>;
|
|
824
824
|
/** Fetch one deployed monitor by public key with bounded current listener health. */
|
|
825
825
|
get: (key: string) => Promise<MonitorDetail>;
|
|
@@ -160,7 +160,7 @@ export const SDK_RELEASE = {
|
|
|
160
160
|
// 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
|
|
161
161
|
// exposed storage-dependent synchronous access. This deliberate minor
|
|
162
162
|
// release keeps lazy paging semantics independent of row residency.
|
|
163
|
-
version: '0.2.
|
|
163
|
+
version: '0.2.53',
|
|
164
164
|
contracts: {
|
|
165
165
|
api: {
|
|
166
166
|
name: 'sdk-http-api',
|
package/dist/cli/index.js
CHANGED
|
@@ -1044,7 +1044,7 @@ var SDK_RELEASE = {
|
|
|
1044
1044
|
// 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
|
|
1045
1045
|
// exposed storage-dependent synchronous access. This deliberate minor
|
|
1046
1046
|
// release keeps lazy paging semantics independent of row residency.
|
|
1047
|
-
version: "0.2.
|
|
1047
|
+
version: "0.2.53",
|
|
1048
1048
|
contracts: {
|
|
1049
1049
|
api: {
|
|
1050
1050
|
name: "sdk-http-api",
|
|
@@ -30191,9 +30191,46 @@ function selectiveCompactToolResults(raw) {
|
|
|
30191
30191
|
return Buffer.from(lines.length > 0 ? `${lines.join("\n")}
|
|
30192
30192
|
` : "", "utf8");
|
|
30193
30193
|
}
|
|
30194
|
+
function compactCodexEvents(raw) {
|
|
30195
|
+
const rawLines = normalizedJsonLines(raw);
|
|
30196
|
+
const parsedEvents = rawLines.map(parseJsonLine);
|
|
30197
|
+
const isCodex = parsedEvents.some(
|
|
30198
|
+
(event) => event && typeof event === "object" && event.type === "session_meta"
|
|
30199
|
+
);
|
|
30200
|
+
if (!isCodex) return raw;
|
|
30201
|
+
let lastTokenCountIndex = -1;
|
|
30202
|
+
parsedEvents.forEach((event, index) => {
|
|
30203
|
+
if (!event || typeof event !== "object") return;
|
|
30204
|
+
const payload = event.payload;
|
|
30205
|
+
if (payload && typeof payload === "object" && payload.type === "token_count") {
|
|
30206
|
+
lastTokenCountIndex = index;
|
|
30207
|
+
}
|
|
30208
|
+
});
|
|
30209
|
+
const lines = [];
|
|
30210
|
+
parsedEvents.forEach((event, index) => {
|
|
30211
|
+
if (!event || typeof event !== "object") {
|
|
30212
|
+
lines.push(rawLines[index] ?? "");
|
|
30213
|
+
return;
|
|
30214
|
+
}
|
|
30215
|
+
const record = event;
|
|
30216
|
+
if (record.type === "world_state") return;
|
|
30217
|
+
const payload = record.payload;
|
|
30218
|
+
const payloadType = payload && typeof payload === "object" ? String(payload.type ?? "") : "";
|
|
30219
|
+
if (payloadType === "token_count" && index !== lastTokenCountIndex) return;
|
|
30220
|
+
if (payloadType === "custom_tool_call_output" || payloadType === "function_call_output" || payloadType === "tool_search_output") {
|
|
30221
|
+
const record_payload = payload;
|
|
30222
|
+
record_payload.output = compactEventValue(record_payload.output);
|
|
30223
|
+
lines.push(JSON.stringify(record));
|
|
30224
|
+
return;
|
|
30225
|
+
}
|
|
30226
|
+
lines.push(rawLines[index] ?? "");
|
|
30227
|
+
});
|
|
30228
|
+
return Buffer.from(lines.length > 0 ? `${lines.join("\n")}
|
|
30229
|
+
` : "", "utf8");
|
|
30230
|
+
}
|
|
30194
30231
|
function prepareSessionBuffer(raw) {
|
|
30195
30232
|
return selectiveCompactToolResults(
|
|
30196
|
-
dedupConsecutiveEvents(stripNoiseEvents(raw))
|
|
30233
|
+
dedupConsecutiveEvents(compactCodexEvents(stripNoiseEvents(raw)))
|
|
30197
30234
|
);
|
|
30198
30235
|
}
|
|
30199
30236
|
function buildSessionUploadContent(raw) {
|
package/dist/cli/index.mjs
CHANGED
|
@@ -1030,7 +1030,7 @@ var SDK_RELEASE = {
|
|
|
1030
1030
|
// 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
|
|
1031
1031
|
// exposed storage-dependent synchronous access. This deliberate minor
|
|
1032
1032
|
// release keeps lazy paging semantics independent of row residency.
|
|
1033
|
-
version: "0.2.
|
|
1033
|
+
version: "0.2.53",
|
|
1034
1034
|
contracts: {
|
|
1035
1035
|
api: {
|
|
1036
1036
|
name: "sdk-http-api",
|
|
@@ -30244,9 +30244,46 @@ function selectiveCompactToolResults(raw) {
|
|
|
30244
30244
|
return Buffer.from(lines.length > 0 ? `${lines.join("\n")}
|
|
30245
30245
|
` : "", "utf8");
|
|
30246
30246
|
}
|
|
30247
|
+
function compactCodexEvents(raw) {
|
|
30248
|
+
const rawLines = normalizedJsonLines(raw);
|
|
30249
|
+
const parsedEvents = rawLines.map(parseJsonLine);
|
|
30250
|
+
const isCodex = parsedEvents.some(
|
|
30251
|
+
(event) => event && typeof event === "object" && event.type === "session_meta"
|
|
30252
|
+
);
|
|
30253
|
+
if (!isCodex) return raw;
|
|
30254
|
+
let lastTokenCountIndex = -1;
|
|
30255
|
+
parsedEvents.forEach((event, index) => {
|
|
30256
|
+
if (!event || typeof event !== "object") return;
|
|
30257
|
+
const payload = event.payload;
|
|
30258
|
+
if (payload && typeof payload === "object" && payload.type === "token_count") {
|
|
30259
|
+
lastTokenCountIndex = index;
|
|
30260
|
+
}
|
|
30261
|
+
});
|
|
30262
|
+
const lines = [];
|
|
30263
|
+
parsedEvents.forEach((event, index) => {
|
|
30264
|
+
if (!event || typeof event !== "object") {
|
|
30265
|
+
lines.push(rawLines[index] ?? "");
|
|
30266
|
+
return;
|
|
30267
|
+
}
|
|
30268
|
+
const record = event;
|
|
30269
|
+
if (record.type === "world_state") return;
|
|
30270
|
+
const payload = record.payload;
|
|
30271
|
+
const payloadType = payload && typeof payload === "object" ? String(payload.type ?? "") : "";
|
|
30272
|
+
if (payloadType === "token_count" && index !== lastTokenCountIndex) return;
|
|
30273
|
+
if (payloadType === "custom_tool_call_output" || payloadType === "function_call_output" || payloadType === "tool_search_output") {
|
|
30274
|
+
const record_payload = payload;
|
|
30275
|
+
record_payload.output = compactEventValue(record_payload.output);
|
|
30276
|
+
lines.push(JSON.stringify(record));
|
|
30277
|
+
return;
|
|
30278
|
+
}
|
|
30279
|
+
lines.push(rawLines[index] ?? "");
|
|
30280
|
+
});
|
|
30281
|
+
return Buffer.from(lines.length > 0 ? `${lines.join("\n")}
|
|
30282
|
+
` : "", "utf8");
|
|
30283
|
+
}
|
|
30247
30284
|
function prepareSessionBuffer(raw) {
|
|
30248
30285
|
return selectiveCompactToolResults(
|
|
30249
|
-
dedupConsecutiveEvents(stripNoiseEvents(raw))
|
|
30286
|
+
dedupConsecutiveEvents(compactCodexEvents(stripNoiseEvents(raw)))
|
|
30250
30287
|
);
|
|
30251
30288
|
}
|
|
30252
30289
|
function buildSessionUploadContent(raw) {
|
package/dist/index.d.mts
CHANGED
|
@@ -2201,7 +2201,7 @@ type MonitorsNamespace = {
|
|
|
2201
2201
|
deploy: (definition: MonitorDefinition, options?: {
|
|
2202
2202
|
dryRun?: boolean;
|
|
2203
2203
|
}) => Promise<MonitorDeployResult>;
|
|
2204
|
-
/** List deployed monitors (active by default). `includeConsumers` requires limit
|
|
2204
|
+
/** List deployed monitors (active by default). `includeConsumers` requires a limit of 20 or fewer. */
|
|
2205
2205
|
list: (options?: MonitorsListOptions) => Promise<MonitorsListResult>;
|
|
2206
2206
|
/** Fetch one deployed monitor by public key with bounded current listener health. */
|
|
2207
2207
|
get: (key: string) => Promise<MonitorDetail>;
|
package/dist/index.d.ts
CHANGED
|
@@ -2201,7 +2201,7 @@ type MonitorsNamespace = {
|
|
|
2201
2201
|
deploy: (definition: MonitorDefinition, options?: {
|
|
2202
2202
|
dryRun?: boolean;
|
|
2203
2203
|
}) => Promise<MonitorDeployResult>;
|
|
2204
|
-
/** List deployed monitors (active by default). `includeConsumers` requires limit
|
|
2204
|
+
/** List deployed monitors (active by default). `includeConsumers` requires a limit of 20 or fewer. */
|
|
2205
2205
|
list: (options?: MonitorsListOptions) => Promise<MonitorsListResult>;
|
|
2206
2206
|
/** Fetch one deployed monitor by public key with bounded current listener health. */
|
|
2207
2207
|
get: (key: string) => Promise<MonitorDetail>;
|
package/dist/index.js
CHANGED
|
@@ -763,7 +763,7 @@ var SDK_RELEASE = {
|
|
|
763
763
|
// 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
|
|
764
764
|
// exposed storage-dependent synchronous access. This deliberate minor
|
|
765
765
|
// release keeps lazy paging semantics independent of row residency.
|
|
766
|
-
version: "0.2.
|
|
766
|
+
version: "0.2.53",
|
|
767
767
|
contracts: {
|
|
768
768
|
api: {
|
|
769
769
|
name: "sdk-http-api",
|
package/dist/index.mjs
CHANGED
|
@@ -689,7 +689,7 @@ var SDK_RELEASE = {
|
|
|
689
689
|
// 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
|
|
690
690
|
// exposed storage-dependent synchronous access. This deliberate minor
|
|
691
691
|
// release keeps lazy paging semantics independent of row residency.
|
|
692
|
-
version: "0.2.
|
|
692
|
+
version: "0.2.53",
|
|
693
693
|
contracts: {
|
|
694
694
|
api: {
|
|
695
695
|
name: "sdk-http-api",
|
package/dist/viewer/viewer.css
CHANGED
|
@@ -341,10 +341,11 @@ body {
|
|
|
341
341
|
min-width: 70px;
|
|
342
342
|
}
|
|
343
343
|
.tool-name.bash { color: var(--blue); }
|
|
344
|
-
.tool-name.read, .tool-name.write, .tool-name.edit { color: var(--purple); }
|
|
344
|
+
.tool-name.read, .tool-name.write, .tool-name.edit, .tool-name.delete { color: var(--purple); }
|
|
345
345
|
.tool-name.skill, .tool-name.task { color: var(--orange); }
|
|
346
|
-
.tool-name.grep, .tool-name.glob { color: var(--cyan); }
|
|
346
|
+
.tool-name.grep, .tool-name.glob, .tool-name.toolsearch { color: var(--cyan); }
|
|
347
347
|
.tool-name.webfetch, .tool-name.websearch { color: var(--yellow); }
|
|
348
|
+
.tool-name.wait, .tool-name.stdin { color: var(--text-dim); }
|
|
348
349
|
.tool-cmd {
|
|
349
350
|
color: var(--text-dim);
|
|
350
351
|
overflow: hidden;
|
|
@@ -881,6 +882,14 @@ body {
|
|
|
881
882
|
.expectation-result.pass { color: var(--green); }
|
|
882
883
|
.expectation-result.fail { color: var(--red); }
|
|
883
884
|
.expectation-result.unknown { color: var(--yellow); }
|
|
885
|
+
.expectation-failure-reason {
|
|
886
|
+
margin-top: 5px;
|
|
887
|
+
color: color-mix(in srgb, var(--red) 80%, var(--text-dim));
|
|
888
|
+
font-size: 10px;
|
|
889
|
+
font-weight: 400;
|
|
890
|
+
line-height: 1.4;
|
|
891
|
+
overflow-wrap: anywhere;
|
|
892
|
+
}
|
|
884
893
|
.infra-signals {
|
|
885
894
|
margin-top: 10px;
|
|
886
895
|
border: 1px solid color-mix(in srgb, var(--red) 45%, var(--viewer-border));
|
|
@@ -899,6 +908,52 @@ body {
|
|
|
899
908
|
.lane-prompt, .lane-result { border-bottom: 1px solid var(--viewer-border); }
|
|
900
909
|
.lane-prompt summary, .lane-result summary { cursor: pointer; padding: 8px 13px; color: var(--text-dim); font-size: 11px; font-weight: 650; }
|
|
901
910
|
.lane-prompt .prompt-text, .lane-result .md-rendered { padding: 0 13px 12px; font-size: 12px; }
|
|
911
|
+
|
|
912
|
+
.final-artifacts {
|
|
913
|
+
margin: 18px 0 4px;
|
|
914
|
+
border: 1px solid var(--viewer-border);
|
|
915
|
+
border-radius: 8px;
|
|
916
|
+
background: var(--bg-secondary);
|
|
917
|
+
overflow: hidden;
|
|
918
|
+
}
|
|
919
|
+
.final-artifacts-heading {
|
|
920
|
+
padding: 12px 14px;
|
|
921
|
+
border-bottom: 1px solid var(--viewer-border);
|
|
922
|
+
background: var(--bg-tertiary);
|
|
923
|
+
}
|
|
924
|
+
.final-artifacts-heading > div { display: flex; align-items: center; gap: 8px; }
|
|
925
|
+
.final-artifacts-heading span { font-size: 12px; font-weight: 700; color: var(--text); text-transform: uppercase; letter-spacing: .04em; }
|
|
926
|
+
.final-artifacts-heading strong { color: var(--viewer-primary); font-size: 12px; }
|
|
927
|
+
.final-artifacts-heading p { margin: 4px 0 0; color: var(--text-dim); font-size: 11px; }
|
|
928
|
+
.final-artifact-file { border-bottom: 1px solid var(--viewer-border); }
|
|
929
|
+
.final-artifact-file:last-child { border-bottom: 0; }
|
|
930
|
+
.final-artifact-file summary {
|
|
931
|
+
display: flex;
|
|
932
|
+
justify-content: space-between;
|
|
933
|
+
gap: 16px;
|
|
934
|
+
cursor: pointer;
|
|
935
|
+
padding: 10px 14px;
|
|
936
|
+
color: var(--text);
|
|
937
|
+
font-size: 12px;
|
|
938
|
+
}
|
|
939
|
+
.final-artifact-file summary span { color: var(--text-dim); font-weight: 400; }
|
|
940
|
+
.final-artifact-meta { padding: 0 14px 9px; color: var(--text-dim); font-size: 10px; }
|
|
941
|
+
.final-artifact-meta code { color: var(--text-dim); overflow-wrap: anywhere; }
|
|
942
|
+
.final-artifact-meta span { margin-left: 10px; color: var(--viewer-primary); }
|
|
943
|
+
.final-artifact-file pre {
|
|
944
|
+
max-height: 560px;
|
|
945
|
+
overflow: auto;
|
|
946
|
+
margin: 0;
|
|
947
|
+
padding: 13px 14px;
|
|
948
|
+
border-top: 1px solid var(--viewer-border);
|
|
949
|
+
background: var(--bg);
|
|
950
|
+
color: var(--text);
|
|
951
|
+
white-space: pre;
|
|
952
|
+
font-size: 11px;
|
|
953
|
+
line-height: 1.5;
|
|
954
|
+
}
|
|
955
|
+
.final-artifact-omitted { padding: 0 14px 12px; color: var(--text-dim); font-size: 11px; }
|
|
956
|
+
.lane-session .final-artifacts { margin: 0; border-width: 1px 0 0; border-radius: 0; }
|
|
902
957
|
.lane-timeline { padding: 10px 7px 16px; }
|
|
903
958
|
.lane-timeline .tool-row { gap: 8px; padding-left: 5px; padding-right: 5px; font-size: 11px; }
|
|
904
959
|
.lane-timeline .tool-name { min-width: 58px; }
|
package/dist/viewer/viewer.js
CHANGED
|
@@ -78,7 +78,7 @@ function extractSessionMetaCodex(events) {
|
|
|
78
78
|
for (const d of events) {
|
|
79
79
|
if (d.type !== 'session_meta') continue;
|
|
80
80
|
const p = d.payload || {};
|
|
81
|
-
meta.model = p.model ||
|
|
81
|
+
meta.model = p.model || null;
|
|
82
82
|
meta.session_id = p.id || null;
|
|
83
83
|
meta.cwd = p.cwd || null;
|
|
84
84
|
meta.environment = {
|
|
@@ -89,9 +89,42 @@ function extractSessionMetaCodex(events) {
|
|
|
89
89
|
};
|
|
90
90
|
break;
|
|
91
91
|
}
|
|
92
|
+
// session_meta usually carries only model_provider ("openai"); the real model
|
|
93
|
+
// name lives on the first turn_context.
|
|
94
|
+
if (!meta.model) {
|
|
95
|
+
for (const d of events) {
|
|
96
|
+
if (d.type !== 'turn_context') continue;
|
|
97
|
+
const p = d.payload || {};
|
|
98
|
+
if (typeof p.model === 'string' && p.model) { meta.model = p.model; break; }
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (!meta.model) {
|
|
102
|
+
for (const d of events) {
|
|
103
|
+
if (d.type !== 'session_meta') continue;
|
|
104
|
+
meta.model = (d.payload || {}).model_provider || null;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
92
108
|
return meta;
|
|
93
109
|
}
|
|
94
110
|
|
|
111
|
+
function extractResultCodex(events) {
|
|
112
|
+
let result = null;
|
|
113
|
+
for (const d of events) {
|
|
114
|
+
const p = d.payload || {};
|
|
115
|
+
if (d.type !== 'event_msg' || p.type !== 'task_complete') continue;
|
|
116
|
+
result = {
|
|
117
|
+
is_error: false,
|
|
118
|
+
duration_ms: Number(p.duration_ms) || 0,
|
|
119
|
+
duration_api_ms: 0,
|
|
120
|
+
num_turns: null,
|
|
121
|
+
result_text: String(p.last_agent_message || '').slice(0, 2000),
|
|
122
|
+
total_cost_usd: null,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
|
|
95
128
|
function extractResultStream(events) {
|
|
96
129
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
97
130
|
const d = events[i];
|
|
@@ -192,15 +225,43 @@ function codexContentText(content) {
|
|
|
192
225
|
return parts.join('\n').trim();
|
|
193
226
|
}
|
|
194
227
|
|
|
228
|
+
// Codex injects several synthetic "user" turns before the real prompt (plugin
|
|
229
|
+
// catalog, AGENTS.md, environment context). They are transport, not intent —
|
|
230
|
+
// the Claude path skips its own equivalent ("Base directory for this skill:").
|
|
231
|
+
const CODEX_SYNTHETIC_PREFIXES = [
|
|
232
|
+
'<recommended_plugins>',
|
|
233
|
+
'<environment_context>',
|
|
234
|
+
'<INSTRUCTIONS>',
|
|
235
|
+
'<user_instructions>',
|
|
236
|
+
'<skills_instructions>',
|
|
237
|
+
'<permissions instructions>',
|
|
238
|
+
'# AGENTS.md instructions',
|
|
239
|
+
];
|
|
240
|
+
|
|
241
|
+
function isCodexSyntheticMessage(text) {
|
|
242
|
+
const trimmed = String(text || '').replace(/^\s+/, '');
|
|
243
|
+
if (!trimmed) return true;
|
|
244
|
+
for (const prefix of CODEX_SYNTHETIC_PREFIXES) {
|
|
245
|
+
if (trimmed.startsWith(prefix)) return true;
|
|
246
|
+
}
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
|
|
195
250
|
function extractPromptCodex(events) {
|
|
251
|
+
// event_msg carries the prompt the user actually typed; response_item
|
|
252
|
+
// user messages also include the injected preambles, so they are a fallback.
|
|
196
253
|
for (const d of events) {
|
|
197
254
|
const p = d.payload || {};
|
|
198
|
-
if (d.type === 'event_msg' && p.type === 'user_message'
|
|
255
|
+
if (d.type === 'event_msg' && p.type === 'user_message'
|
|
256
|
+
&& typeof p.message === 'string' && !isCodexSyntheticMessage(p.message)) {
|
|
199
257
|
return p.message;
|
|
200
258
|
}
|
|
259
|
+
}
|
|
260
|
+
for (const d of events) {
|
|
261
|
+
const p = d.payload || {};
|
|
201
262
|
if (d.type === 'response_item' && p.type === 'message' && p.role === 'user') {
|
|
202
263
|
const text = codexContentText(p.content);
|
|
203
|
-
if (text) return text;
|
|
264
|
+
if (text && !isCodexSyntheticMessage(text)) return text;
|
|
204
265
|
}
|
|
205
266
|
}
|
|
206
267
|
return null;
|
|
@@ -476,33 +537,395 @@ function codexToolName(p) {
|
|
|
476
537
|
return p.name || p.tool_name || p.type || 'tool';
|
|
477
538
|
}
|
|
478
539
|
|
|
479
|
-
function codexToolCommand(name, input) {
|
|
480
|
-
const normalized = String(name || '').toLowerCase();
|
|
481
|
-
if (typeof input.cmd === 'string') return input.cmd;
|
|
482
|
-
if (typeof input.command === 'string') return input.command;
|
|
483
|
-
if (typeof input.query === 'string') return input.query;
|
|
484
|
-
if (typeof input.url === 'string') return input.url;
|
|
485
|
-
if (typeof input.path === 'string') return input.path;
|
|
486
|
-
if (typeof input.file_path === 'string') return input.file_path;
|
|
487
|
-
if (typeof input.arguments === 'string') return input.arguments.slice(0, 120);
|
|
488
|
-
if (normalized === 'apply patch') return 'apply patch';
|
|
489
|
-
return JSON.stringify(input).slice(0, 120);
|
|
490
|
-
}
|
|
491
|
-
|
|
492
540
|
function codexSkillReadName(command) {
|
|
493
|
-
|
|
541
|
+
// The path is often workspace-relative (".agents/skills/x/SKILL.md"), so do
|
|
542
|
+
// not require a leading slash.
|
|
543
|
+
const match = String(command || '')
|
|
544
|
+
.match(/(?:^|[/\s'"])\.(?:agents|claude|codex)\/skills\/([^/\s'"]+)\/SKILL\.md\b/);
|
|
494
545
|
return match ? match[1] : null;
|
|
495
546
|
}
|
|
496
547
|
|
|
548
|
+
// ---------------------------------------------------------------------------
|
|
549
|
+
// Codex tool-call normalization
|
|
550
|
+
//
|
|
551
|
+
// Subscription Codex does not send structured tool arguments. It sends a
|
|
552
|
+
// JavaScript snippet that calls tools.exec_command / tools.apply_patch and
|
|
553
|
+
// prints the result, and returns the result as an array of content blocks
|
|
554
|
+
// wrapped in a "Script completed / Wall time / Output:" preamble.
|
|
555
|
+
//
|
|
556
|
+
// Everything below unwraps that into the same {tool, command, input,
|
|
557
|
+
// result_content, is_error} shape buildTimeline() produces for Claude
|
|
558
|
+
// transcripts, so both agents share renderTimeline(), toolCommandSummary(),
|
|
559
|
+
// formatToolInputHtml() and buildFileTouchMap().
|
|
560
|
+
// ---------------------------------------------------------------------------
|
|
561
|
+
|
|
562
|
+
const CODEX_SHELL_TOOLS = [
|
|
563
|
+
'exec', 'exec_command', 'execcommand', 'shell', 'bash',
|
|
564
|
+
'unified_exec', 'local_shell', 'run_command', 'container.exec',
|
|
565
|
+
];
|
|
566
|
+
|
|
567
|
+
// Walks a JS source string from an opening bracket to its match, respecting
|
|
568
|
+
// string literals and escapes. Returns the index just past the closer, or -1.
|
|
569
|
+
function codexScanBalanced(src, start) {
|
|
570
|
+
const closers = {'(': ')', '[': ']', '{': '}'};
|
|
571
|
+
const stack = [];
|
|
572
|
+
let quote = null;
|
|
573
|
+
for (let i = start; i < src.length; i++) {
|
|
574
|
+
const ch = src[i];
|
|
575
|
+
if (quote) {
|
|
576
|
+
if (ch === '\\') { i++; continue; }
|
|
577
|
+
if (ch === quote) quote = null;
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
if (ch === '"' || ch === "'" || ch === '`') { quote = ch; continue; }
|
|
581
|
+
if (closers[ch]) { stack.push(closers[ch]); continue; }
|
|
582
|
+
if (ch === ')' || ch === ']' || ch === '}') {
|
|
583
|
+
if (!stack.length || stack.pop() !== ch) return -1;
|
|
584
|
+
if (!stack.length) return i + 1;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return -1;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function codexScanString(src, start) {
|
|
591
|
+
const quote = src[start];
|
|
592
|
+
for (let i = start + 1; i < src.length; i++) {
|
|
593
|
+
if (src[i] === '\\') { i++; continue; }
|
|
594
|
+
if (src[i] === quote) return i + 1;
|
|
595
|
+
}
|
|
596
|
+
return -1;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function codexParseLiteral(text) {
|
|
600
|
+
const trimmed = String(text || '').trim();
|
|
601
|
+
if (!trimmed) return null;
|
|
602
|
+
const first = trimmed[0];
|
|
603
|
+
if (first === '{' || first === '[' || first === '"') {
|
|
604
|
+
try { return JSON.parse(trimmed); } catch(e) { /* not a JSON literal */ }
|
|
605
|
+
}
|
|
606
|
+
if (first === "'" && trimmed[trimmed.length - 1] === "'") {
|
|
607
|
+
const body = trimmed.slice(1, -1).replace(/\\'/g, "'").replace(/"/g, '\\"');
|
|
608
|
+
try { return JSON.parse('"' + body + '"'); } catch(e) { /* not a JS string */ }
|
|
609
|
+
}
|
|
610
|
+
return null;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Codex often binds the patch or args to a const first, then passes the name.
|
|
614
|
+
function codexLocalConstants(src) {
|
|
615
|
+
const constants = {};
|
|
616
|
+
const re = /(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*/g;
|
|
617
|
+
let match;
|
|
618
|
+
while ((match = re.exec(src))) {
|
|
619
|
+
const valueStart = match.index + match[0].length;
|
|
620
|
+
const ch = src[valueStart];
|
|
621
|
+
let end = -1;
|
|
622
|
+
if (ch === '{' || ch === '[') end = codexScanBalanced(src, valueStart);
|
|
623
|
+
else if (ch === '"' || ch === "'") end = codexScanString(src, valueStart);
|
|
624
|
+
if (end < 0) continue;
|
|
625
|
+
const parsed = codexParseLiteral(src.slice(valueStart, end));
|
|
626
|
+
if (parsed !== null) constants[match[1]] = parsed;
|
|
627
|
+
re.lastIndex = end;
|
|
628
|
+
}
|
|
629
|
+
return constants;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Spans of string literals in the snippet. A patch payload is itself a string,
|
|
633
|
+
// and the file it writes can contain `tools.execute(...)` in its own source —
|
|
634
|
+
// matching that would invent tool calls the agent never made.
|
|
635
|
+
function codexStringSpans(src) {
|
|
636
|
+
const spans = [];
|
|
637
|
+
for (let i = 0; i < src.length; i++) {
|
|
638
|
+
const ch = src[i];
|
|
639
|
+
if (ch !== '"' && ch !== "'" && ch !== '`') continue;
|
|
640
|
+
const end = codexScanString(src, i);
|
|
641
|
+
if (end < 0) break;
|
|
642
|
+
spans.push([i, end]);
|
|
643
|
+
i = end - 1;
|
|
644
|
+
}
|
|
645
|
+
return spans;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function parseCodexExecScript(source) {
|
|
649
|
+
const src = String(source || '');
|
|
650
|
+
if (src.indexOf('tools.') < 0) return [];
|
|
651
|
+
const constants = codexLocalConstants(src);
|
|
652
|
+
const stringSpans = codexStringSpans(src);
|
|
653
|
+
const insideString = (index) =>
|
|
654
|
+
stringSpans.some(([start, end]) => index >= start && index < end);
|
|
655
|
+
const calls = [];
|
|
656
|
+
const re = /tools\.([A-Za-z_$][\w$]*)\s*\(/g;
|
|
657
|
+
let match;
|
|
658
|
+
while ((match = re.exec(src))) {
|
|
659
|
+
if (insideString(match.index)) continue;
|
|
660
|
+
const openParen = match.index + match[0].length - 1;
|
|
661
|
+
const end = codexScanBalanced(src, openParen);
|
|
662
|
+
if (end < 0) continue;
|
|
663
|
+
const argText = src.slice(openParen + 1, end - 1).trim();
|
|
664
|
+
let args = codexParseLiteral(argText);
|
|
665
|
+
if (args === null && /^[A-Za-z_$][\w$]*$/.test(argText) && Object.prototype.hasOwnProperty.call(constants, argText)) {
|
|
666
|
+
args = constants[argText];
|
|
667
|
+
}
|
|
668
|
+
if (args === null) args = argText ? {source: argText} : {};
|
|
669
|
+
calls.push({tool: match[1], args});
|
|
670
|
+
re.lastIndex = end;
|
|
671
|
+
}
|
|
672
|
+
return calls;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function codexCommandFromArgs(args) {
|
|
676
|
+
if (typeof args === 'string') return args;
|
|
677
|
+
if (!args || typeof args !== 'object') return '';
|
|
678
|
+
if (typeof args.cmd === 'string') return args.cmd;
|
|
679
|
+
if (typeof args.command === 'string') return args.command;
|
|
680
|
+
if (Array.isArray(args.cmd)) return args.cmd.join(' ');
|
|
681
|
+
if (Array.isArray(args.command)) return args.command.join(' ');
|
|
682
|
+
if (typeof args.script === 'string') return args.script;
|
|
683
|
+
return '';
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
function codexPatchText(args) {
|
|
687
|
+
if (typeof args === 'string') return args;
|
|
688
|
+
if (!args || typeof args !== 'object') return '';
|
|
689
|
+
for (const key of ['patch', 'input', 'source', 'diff', 'content']) {
|
|
690
|
+
if (typeof args[key] === 'string') return args[key];
|
|
691
|
+
}
|
|
692
|
+
return '';
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function parseCodexPatch(patchText) {
|
|
696
|
+
const text = String(patchText || '');
|
|
697
|
+
const ops = [];
|
|
698
|
+
if (text.indexOf('***') < 0) return ops;
|
|
699
|
+
let current = null;
|
|
700
|
+
const flush = () => { if (current) ops.push(current); current = null; };
|
|
701
|
+
for (const line of text.split('\n')) {
|
|
702
|
+
const header = line.match(/^\*\*\* (Add|Update|Delete) File:\s*(.+)$/);
|
|
703
|
+
if (header) {
|
|
704
|
+
flush();
|
|
705
|
+
current = {op: header[1].toLowerCase(), file_path: header[2].trim(), added: [], removed: []};
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
708
|
+
if (/^\*\*\* (Begin|End) Patch/.test(line)) { flush(); continue; }
|
|
709
|
+
if (!current) continue;
|
|
710
|
+
if (line[0] === '+') current.added.push(line.slice(1));
|
|
711
|
+
else if (line[0] === '-') current.removed.push(line.slice(1));
|
|
712
|
+
}
|
|
713
|
+
flush();
|
|
714
|
+
return ops;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Maps one codex tool invocation onto one or more Claude-shaped entries.
|
|
718
|
+
// `seenSkills` makes the first read of a skill's SKILL.md the Skill invocation
|
|
719
|
+
// (Claude emits exactly one Skill call per skill); later paged reads of the same
|
|
720
|
+
// file stay Bash, so sequential `sed -n '241,520p'` reads are not mistaken for a
|
|
721
|
+
// retry loop by annotateTimeline().
|
|
722
|
+
function codexToolIdentity(rawName, args, seenSkills) {
|
|
723
|
+
const name = String(rawName || '').toLowerCase();
|
|
724
|
+
|
|
725
|
+
if (CODEX_SHELL_TOOLS.indexOf(name) >= 0) {
|
|
726
|
+
const command = codexCommandFromArgs(args);
|
|
727
|
+
if (!command) return [{tool: 'Bash', input: parseCodexArguments(args)}];
|
|
728
|
+
const skill = codexSkillReadName(command);
|
|
729
|
+
if (skill && (!seenSkills || !seenSkills.has(skill))) {
|
|
730
|
+
if (seenSkills) seenSkills.add(skill);
|
|
731
|
+
return [{tool: 'Skill', input: {skill, command}, command: skill}];
|
|
732
|
+
}
|
|
733
|
+
return [{tool: 'Bash', input: {command}}];
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
if (name === 'apply_patch' || name === 'applypatch' || name === 'edit_file') {
|
|
737
|
+
const ops = parseCodexPatch(codexPatchText(args));
|
|
738
|
+
if (ops.length) {
|
|
739
|
+
return ops.map(op => {
|
|
740
|
+
if (op.op === 'add') {
|
|
741
|
+
return {tool: 'Write', input: {file_path: op.file_path, content: op.added.join('\n')}};
|
|
742
|
+
}
|
|
743
|
+
if (op.op === 'delete') {
|
|
744
|
+
return {tool: 'Delete', input: {file_path: op.file_path}, command: op.file_path};
|
|
745
|
+
}
|
|
746
|
+
return {tool: 'Edit', input: {
|
|
747
|
+
file_path: op.file_path,
|
|
748
|
+
old_string: op.removed.join('\n'),
|
|
749
|
+
new_string: op.added.join('\n'),
|
|
750
|
+
}};
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
return [{tool: 'Write', input: parseCodexArguments(args)}];
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
if (name === 'wait') {
|
|
757
|
+
const input = parseCodexArguments(args);
|
|
758
|
+
const cell = input.cell_id != null ? String(input.cell_id) : '';
|
|
759
|
+
return [{tool: 'Wait', input, command: cell ? 'cell ' + cell : 'wait'}];
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// Feeds keystrokes to a still-running exec cell; the Wait row shows the result.
|
|
763
|
+
if (name === 'write_stdin' || name === 'writestdin') {
|
|
764
|
+
const input = parseCodexArguments(args);
|
|
765
|
+
const cell = input.session_id != null ? String(input.session_id) : '';
|
|
766
|
+
return [{tool: 'Stdin', input, command: cell ? 'cell ' + cell : 'stdin'}];
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
if (name === 'read_file' || name === 'view_file' || name === 'read') {
|
|
770
|
+
const input = parseCodexArguments(args);
|
|
771
|
+
return [{tool: 'Read', input: {file_path: input.file_path || input.path || ''}}];
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
if (name === 'write_file' || name === 'create_file') {
|
|
775
|
+
const input = parseCodexArguments(args);
|
|
776
|
+
return [{tool: 'Write', input: {
|
|
777
|
+
file_path: input.file_path || input.path || '',
|
|
778
|
+
content: input.content || input.contents || '',
|
|
779
|
+
}}];
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// Codex's web tool batches work into one call: `search_query` is a list of
|
|
783
|
+
// searches, `open` a list of pages. Each becomes its own step, the same way
|
|
784
|
+
// Claude issues one WebSearch/WebFetch tool_use per query or URL.
|
|
785
|
+
if (name === 'web__run' || name === 'web_run' || name === 'webrun') {
|
|
786
|
+
const input = parseCodexArguments(args);
|
|
787
|
+
const entries = [];
|
|
788
|
+
for (const item of asArray(input.search_query)) {
|
|
789
|
+
const query = typeof item === 'string' ? item : (item && (item.q || item.query)) || '';
|
|
790
|
+
if (query) entries.push({tool: 'WebSearch', input: {query}});
|
|
791
|
+
}
|
|
792
|
+
for (const item of asArray(input.open)) {
|
|
793
|
+
const url = typeof item === 'string' ? item : (item && (item.ref_id || item.url)) || '';
|
|
794
|
+
if (url) entries.push({tool: 'WebFetch', input: {url}});
|
|
795
|
+
}
|
|
796
|
+
if (entries.length) return entries;
|
|
797
|
+
return [{tool: 'WebSearch', input}];
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
if (name === 'web_search' || name === 'websearch' || name === 'web_search_call') {
|
|
801
|
+
const input = parseCodexArguments(args);
|
|
802
|
+
return [{tool: 'WebSearch', input: {query: input.query || input.q || ''}}];
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
if (name === 'tool_search' || name === 'tool_search_call') {
|
|
806
|
+
const input = parseCodexArguments(args);
|
|
807
|
+
return [{tool: 'ToolSearch', input: {query: input.query || input.q || ''}}];
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
return [{tool: normalizeStepToolName(rawName), input: parseCodexArguments(args)}];
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function codexCallEntries(p, seenSkills) {
|
|
814
|
+
const rawArgs = p.arguments != null ? p.arguments : p.input;
|
|
815
|
+
|
|
816
|
+
// Unwrap the JS wrapper first; it is the only place the real command lives.
|
|
817
|
+
if (typeof rawArgs === 'string') {
|
|
818
|
+
const nested = parseCodexExecScript(rawArgs);
|
|
819
|
+
if (nested.length) {
|
|
820
|
+
const entries = [];
|
|
821
|
+
for (const call of nested) {
|
|
822
|
+
for (const entry of codexToolIdentity(call.tool, call.args, seenSkills)) entries.push(entry);
|
|
823
|
+
}
|
|
824
|
+
if (entries.length) return entries;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
if (p.type === 'web_search_call' || p.type === 'tool_search_call') {
|
|
829
|
+
return codexToolIdentity(p.type, rawArgs != null ? rawArgs : (p.action || p.query || {}), seenSkills);
|
|
830
|
+
}
|
|
831
|
+
return codexToolIdentity(codexToolName(p), rawArgs, seenSkills);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// A wrapper that fans out with Promise.all prints the outputs joined by a
|
|
835
|
+
// separator of its own choosing, e.g. `.join("\n\n---NEXT---\n\n")`. Recover the
|
|
836
|
+
// separator from the source so each parallel step shows only its own output.
|
|
837
|
+
function codexSplitJoinedOutput(source, text, count) {
|
|
838
|
+
if (count < 2 || !text) return null;
|
|
839
|
+
const match = String(source || '').match(/\.join\(\s*("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*')\s*\)/);
|
|
840
|
+
if (!match) return null;
|
|
841
|
+
const separator = codexParseLiteral(match[1]);
|
|
842
|
+
if (typeof separator !== 'string' || !separator.trim()) return null;
|
|
843
|
+
const parts = String(text).split(separator);
|
|
844
|
+
return parts.length === count ? parts : null;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
function codexOutputBlocksText(output) {
|
|
848
|
+
if (typeof output === 'string') return output;
|
|
849
|
+
if (!Array.isArray(output)) return '';
|
|
850
|
+
const parts = [];
|
|
851
|
+
for (const block of output) {
|
|
852
|
+
if (typeof block === 'string') { parts.push(block); continue; }
|
|
853
|
+
if (!block || typeof block !== 'object') continue;
|
|
854
|
+
if (typeof block.text === 'string') parts.push(block.text);
|
|
855
|
+
else if (typeof block.output_text === 'string') parts.push(block.output_text);
|
|
856
|
+
else if (typeof block.content === 'string') parts.push(block.content);
|
|
857
|
+
}
|
|
858
|
+
return parts.join('');
|
|
859
|
+
}
|
|
860
|
+
|
|
497
861
|
function codexToolOutputText(p) {
|
|
498
862
|
if (typeof p.output === 'string') return p.output;
|
|
863
|
+
if (Array.isArray(p.output)) return codexOutputBlocksText(p.output);
|
|
499
864
|
if (typeof p.content === 'string') return p.content;
|
|
865
|
+
if (Array.isArray(p.content)) return codexOutputBlocksText(p.content);
|
|
500
866
|
if (typeof p.message === 'string') return p.message;
|
|
501
867
|
if (p.output && typeof p.output === 'object') return JSON.stringify(p.output, null, 2);
|
|
502
868
|
if (p.content && typeof p.content === 'object') return JSON.stringify(p.content, null, 2);
|
|
503
869
|
return '';
|
|
504
870
|
}
|
|
505
871
|
|
|
872
|
+
// Strips the wrapper's "Script completed / Wall time / Output:" preamble and,
|
|
873
|
+
// when the snippet printed JSON.stringify({exit_code, output}), unwraps that too.
|
|
874
|
+
function normalizeCodexOutput(rawText) {
|
|
875
|
+
let text = String(rawText || '');
|
|
876
|
+
let pending = false;
|
|
877
|
+
let wallS = null;
|
|
878
|
+
let exitCode = null;
|
|
879
|
+
|
|
880
|
+
const preamble = text.match(/^Script (completed|running with cell ID [^\n]*)\n(?:Wall time ([\d.]+) seconds\n)?Output:\n?/);
|
|
881
|
+
if (preamble) {
|
|
882
|
+
pending = preamble[1] !== 'completed';
|
|
883
|
+
wallS = preamble[2] ? Number(preamble[2]) : null;
|
|
884
|
+
text = text.slice(preamble[0].length);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
const trimmed = text.trim();
|
|
888
|
+
if (trimmed.startsWith('{') && trimmed.indexOf('"exit_code"') >= 0) {
|
|
889
|
+
try {
|
|
890
|
+
const parsed = JSON.parse(trimmed);
|
|
891
|
+
if (parsed && typeof parsed === 'object' && parsed.exit_code != null) {
|
|
892
|
+
exitCode = Number(parsed.exit_code);
|
|
893
|
+
text = typeof parsed.output === 'string' ? parsed.output : JSON.stringify(parsed, null, 2);
|
|
894
|
+
}
|
|
895
|
+
} catch(e) { /* not the wrapper's JSON envelope */ }
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
if (pending) {
|
|
899
|
+
const note = '(backgrounded' + (wallS != null ? ' after ' + wallS + 's' : '')
|
|
900
|
+
+ ' — output arrives with the following Wait call)';
|
|
901
|
+
text = text.trim() ? note + '\n\n' + text : note;
|
|
902
|
+
}
|
|
903
|
+
return {text, pending, wall_s: wallS, exit_code: exitCode};
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// Same markers scripts/eval-extract-metrics.py uses: the wrapper always reports
|
|
907
|
+
// "Script completed", so failure has to be read out of the command output.
|
|
908
|
+
function codexTextIndicatesFailure(text) {
|
|
909
|
+
const s = String(text || '');
|
|
910
|
+
const exited = s.match(/Process exited with code\s+(\d+)/);
|
|
911
|
+
if (exited) return Number(exited[1]) !== 0;
|
|
912
|
+
const exitCode = s.match(/^\s*exit_code:\s*(\d+)/m);
|
|
913
|
+
if (exitCode) return Number(exitCode[1]) !== 0;
|
|
914
|
+
if (/^Error:/m.test(s)) return true;
|
|
915
|
+
return false;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
function codexOutputResult(p) {
|
|
919
|
+
const normalized = normalizeCodexOutput(codexToolOutputText(p));
|
|
920
|
+
let isError;
|
|
921
|
+
if (p.is_error === true || p.status === 'error' || p.status === 'failed') isError = true;
|
|
922
|
+
else if (normalized.exit_code != null) isError = normalized.exit_code !== 0;
|
|
923
|
+
else if (normalized.pending) isError = null;
|
|
924
|
+
else if (codexTextIndicatesFailure(normalized.text)) isError = true;
|
|
925
|
+
else isError = false;
|
|
926
|
+
return {text: normalized.text, is_error: isError};
|
|
927
|
+
}
|
|
928
|
+
|
|
506
929
|
function buildCodexTimeline(events, firstTs) {
|
|
507
930
|
const outputsByCallId = {};
|
|
508
931
|
const hasEventUserMessages = events.some(d => {
|
|
@@ -519,12 +942,25 @@ function buildCodexTimeline(events, firstTs) {
|
|
|
519
942
|
if (p.type !== 'function_call_output' && p.type !== 'custom_tool_call_output' && p.type !== 'tool_search_output') continue;
|
|
520
943
|
const callId = p.call_id || p.id || '';
|
|
521
944
|
if (!callId) continue;
|
|
522
|
-
outputsByCallId[callId] =
|
|
523
|
-
text: codexToolOutputText(p),
|
|
524
|
-
is_error: p.is_error === true || p.status === 'error' || p.status === 'failed',
|
|
525
|
-
};
|
|
945
|
+
outputsByCallId[callId] = codexOutputResult(p);
|
|
526
946
|
}
|
|
527
947
|
|
|
948
|
+
// patch_apply_end carries the authoritative result of an apply_patch, but its
|
|
949
|
+
// call_id ("exec-<uuid>") does not match the originating call ("call_<id>"),
|
|
950
|
+
// so it is consumed in document order instead.
|
|
951
|
+
const patchResults = [];
|
|
952
|
+
for (const d of events) {
|
|
953
|
+
const p = d.payload || {};
|
|
954
|
+
if (d.type === 'event_msg' && p.type === 'patch_apply_end') {
|
|
955
|
+
patchResults.push({
|
|
956
|
+
text: String(p.stdout || '') + (p.stderr ? '\n' + p.stderr : ''),
|
|
957
|
+
is_error: p.success === false,
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
let patchResultIndex = 0;
|
|
962
|
+
const seenSkills = new Set();
|
|
963
|
+
|
|
528
964
|
const timeline = [];
|
|
529
965
|
let step = 0;
|
|
530
966
|
let eventIndex = 0;
|
|
@@ -549,7 +985,9 @@ function buildCodexTimeline(events, firstTs) {
|
|
|
549
985
|
if (p.role === 'user') {
|
|
550
986
|
if (hasEventUserMessages) continue;
|
|
551
987
|
const text = codexContentText(p.content);
|
|
552
|
-
if (text
|
|
988
|
+
if (text && !isCodexSyntheticMessage(text)) {
|
|
989
|
+
timeline.push({kind: 'user_message', text, event_index: eventIndex, elapsed});
|
|
990
|
+
}
|
|
553
991
|
} else if (p.role === 'assistant') {
|
|
554
992
|
if (hasEventAgentMessages) continue;
|
|
555
993
|
const text = codexContentText(p.content);
|
|
@@ -572,31 +1010,44 @@ function buildCodexTimeline(events, firstTs) {
|
|
|
572
1010
|
|| p.type === 'web_search_call';
|
|
573
1011
|
if (!isToolCall) continue;
|
|
574
1012
|
|
|
575
|
-
step++;
|
|
576
|
-
const input = parseCodexArguments(p.arguments || p.input);
|
|
577
1013
|
const callId = p.call_id || p.id || '';
|
|
578
1014
|
const output = outputsByCallId[callId] || null;
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
1015
|
+
const entries = codexCallEntries(p, seenSkills);
|
|
1016
|
+
|
|
1017
|
+
// One record can carry several commands (Promise.all of exec_command, or a
|
|
1018
|
+
// multi-file patch). Each becomes its own step flagged `parallel`, mirroring
|
|
1019
|
+
// how buildTimeline() expands multiple tool_use blocks from one Claude
|
|
1020
|
+
// assistant message. scripts/eval-extract-metrics.py still counts one call
|
|
1021
|
+
// per record, so the viewer's tool-call total can legitimately exceed the
|
|
1022
|
+
// tool_calls value in results.jsonl. Do not "reconcile" one to the other.
|
|
1023
|
+
const parallel = entries.length > 1;
|
|
1024
|
+
const rawArgs = p.arguments != null ? p.arguments : p.input;
|
|
1025
|
+
const splitOutputs = output
|
|
1026
|
+
? codexSplitJoinedOutput(rawArgs, output.text, entries.length)
|
|
1027
|
+
: null;
|
|
1028
|
+
|
|
1029
|
+
for (let entryIndex = 0; entryIndex < entries.length; entryIndex++) {
|
|
1030
|
+
const entry = entries[entryIndex];
|
|
1031
|
+
step++;
|
|
1032
|
+
const isPatch = entry.tool === 'Write' || entry.tool === 'Edit' || entry.tool === 'Delete';
|
|
1033
|
+
const patchResult = isPatch && patchResultIndex < patchResults.length
|
|
1034
|
+
? patchResults[patchResultIndex++]
|
|
1035
|
+
: null;
|
|
1036
|
+
const resolved = patchResult
|
|
1037
|
+
|| (splitOutputs ? {text: splitOutputs[entryIndex], is_error: output.is_error} : output);
|
|
1038
|
+
timeline.push({
|
|
1039
|
+
kind: 'tool_call',
|
|
1040
|
+
step,
|
|
1041
|
+
tool: entry.tool,
|
|
1042
|
+
command: entry.command != null ? entry.command : toolCommandSummary(entry.tool, entry.input),
|
|
1043
|
+
input: entry.input,
|
|
1044
|
+
result_content: resolved ? resolved.text : null,
|
|
1045
|
+
is_error: resolved ? resolved.is_error : null,
|
|
1046
|
+
parallel,
|
|
1047
|
+
event_index: eventIndex,
|
|
1048
|
+
elapsed,
|
|
1049
|
+
});
|
|
587
1050
|
}
|
|
588
|
-
timeline.push({
|
|
589
|
-
kind: 'tool_call',
|
|
590
|
-
step,
|
|
591
|
-
tool,
|
|
592
|
-
command,
|
|
593
|
-
input,
|
|
594
|
-
result_content: output ? output.text : null,
|
|
595
|
-
is_error: output ? output.is_error : null,
|
|
596
|
-
parallel: false,
|
|
597
|
-
event_index: eventIndex,
|
|
598
|
-
elapsed,
|
|
599
|
-
});
|
|
600
1051
|
}
|
|
601
1052
|
return timeline;
|
|
602
1053
|
}
|
|
@@ -831,7 +1282,7 @@ function processRawSession(raw) {
|
|
|
831
1282
|
} else if (isCodex) {
|
|
832
1283
|
processedEvents = events;
|
|
833
1284
|
meta = extractSessionMetaCodex(events);
|
|
834
|
-
result =
|
|
1285
|
+
result = extractResultCodex(events);
|
|
835
1286
|
const [dur, fts] = computeCodexDuration(events);
|
|
836
1287
|
totalDurationS = Math.round(dur * 10) / 10;
|
|
837
1288
|
firstTs = fts;
|
|
@@ -1346,7 +1797,10 @@ function renderExpectationMiniTable(session) {
|
|
|
1346
1797
|
const rows = expectations.map(item => {
|
|
1347
1798
|
const result = String(item.result || '—').toUpperCase();
|
|
1348
1799
|
const resultClass = result === 'PASS' ? 'pass' : (result === 'FAIL' ? 'fail' : 'unknown');
|
|
1349
|
-
|
|
1800
|
+
const reason = result === 'FAIL' && item.reason
|
|
1801
|
+
? `<div class="expectation-failure-reason">${esc(item.reason)}</div>`
|
|
1802
|
+
: '';
|
|
1803
|
+
return `<tr><td>${esc(item.expectation || '—')}${reason}</td><td>${esc(item.category || 'other')}</td><td><span class="expectation-result ${resultClass}">${esc(result)}</span></td></tr>`;
|
|
1350
1804
|
}).join('');
|
|
1351
1805
|
return `<div class="expectations-mini ${statusClass}">
|
|
1352
1806
|
<div class="expectations-mini-heading"><span>Eval results</span><strong>${passed}/${total} passed</strong></div>
|
|
@@ -1370,6 +1824,51 @@ function renderInfraSignals(metric) {
|
|
|
1370
1824
|
return `<div class="infra-signals"><div class="infra-signals-heading">Infrastructure signals</div><ul>${rows}</ul></div>`;
|
|
1371
1825
|
}
|
|
1372
1826
|
|
|
1827
|
+
function formatFileSize(bytes) {
|
|
1828
|
+
const value = Number(bytes || 0);
|
|
1829
|
+
if (value < 1024) return value + ' B';
|
|
1830
|
+
if (value < 1024 * 1024) return (value / 1024).toFixed(1) + ' KB';
|
|
1831
|
+
return (value / (1024 * 1024)).toFixed(1) + ' MB';
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
function artifactDisplayContent(artifact) {
|
|
1835
|
+
const content = typeof artifact.content === 'string' ? artifact.content : null;
|
|
1836
|
+
if (content == null) return null;
|
|
1837
|
+
const language = String(artifact.language || '').toLowerCase();
|
|
1838
|
+
try {
|
|
1839
|
+
if (language === 'json') return JSON.stringify(JSON.parse(content), null, 2);
|
|
1840
|
+
if (language === 'jsonl') {
|
|
1841
|
+
return content.split('\n').filter(line => line.trim()).map(line => JSON.stringify(JSON.parse(line), null, 2)).join('\n');
|
|
1842
|
+
}
|
|
1843
|
+
} catch (_) {
|
|
1844
|
+
return content;
|
|
1845
|
+
}
|
|
1846
|
+
return content;
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
function renderFinalArtifacts(session) {
|
|
1850
|
+
const metric = session.evalMetric || {};
|
|
1851
|
+
const artifacts = Array.isArray(metric.final_artifacts) ? metric.final_artifacts : [];
|
|
1852
|
+
if (!artifacts.length) return '';
|
|
1853
|
+
const files = artifacts.map((artifact, index) => {
|
|
1854
|
+
const content = artifactDisplayContent(artifact);
|
|
1855
|
+
const digest = String(artifact.sha256 || '');
|
|
1856
|
+
const formatted = content != null && ['json', 'jsonl'].includes(String(artifact.language || '').toLowerCase());
|
|
1857
|
+
const omission = artifact.content_omitted_reason
|
|
1858
|
+
? `<div class="final-artifact-omitted">Content not embedded: ${esc(artifact.content_omitted_reason)}</div>`
|
|
1859
|
+
: '';
|
|
1860
|
+
return `<details class="final-artifact-file" ${index === 0 ? 'open' : ''}>
|
|
1861
|
+
<summary><strong>${esc(artifact.path || 'artifact')}</strong><span>${esc(formatFileSize(artifact.size_bytes))}</span></summary>
|
|
1862
|
+
<div class="final-artifact-meta">SHA-256 <code>${esc(digest)}</code>${formatted ? '<span>JSON formatted for display</span>' : ''}</div>
|
|
1863
|
+
${content != null ? `<pre><code class="language-${esc(artifact.language || 'text')}">${esc(content)}</code></pre>` : omission}
|
|
1864
|
+
</details>`;
|
|
1865
|
+
}).join('');
|
|
1866
|
+
return `<section class="final-artifacts">
|
|
1867
|
+
<div class="final-artifacts-heading"><div><span>Final files</span><strong>${artifacts.length}</strong></div><p>Captured from the run directory after the agent exited.</p></div>
|
|
1868
|
+
${files}
|
|
1869
|
+
</section>`;
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1373
1872
|
function renderLaneSession(session) {
|
|
1374
1873
|
const i = session.sourceIndex;
|
|
1375
1874
|
const d = session.dimensions;
|
|
@@ -1397,6 +1896,7 @@ function renderLaneSession(session) {
|
|
|
1397
1896
|
if (session.result && session.result.result_text) {
|
|
1398
1897
|
html += `<details class="lane-result"><summary>Final output</summary><div class="md-rendered">${renderMarkdown(session.result.result_text)}</div></details>`;
|
|
1399
1898
|
}
|
|
1899
|
+
html += renderFinalArtifacts(session);
|
|
1400
1900
|
return html + '</article>';
|
|
1401
1901
|
}
|
|
1402
1902
|
|
|
@@ -1576,6 +2076,8 @@ function renderSession(i) {
|
|
|
1576
2076
|
</div>`;
|
|
1577
2077
|
}
|
|
1578
2078
|
|
|
2079
|
+
html += renderFinalArtifacts(s);
|
|
2080
|
+
|
|
1579
2081
|
main.innerHTML = html;
|
|
1580
2082
|
}
|
|
1581
2083
|
|