@prismatic-io/lux 0.0.2-preview.4 → 0.0.2-preview.7
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/lib/assertions/authoring.d.ts +2 -2
- package/lib/assertions/authoring.d.ts.map +1 -1
- package/lib/assertions/authoring.js.map +1 -1
- package/lib/assertions/core/index.d.ts.map +1 -1
- package/lib/assertions/core/index.js +2 -1
- package/lib/assertions/core/index.js.map +1 -1
- package/lib/assertions/core/resource-checks.d.ts +19 -0
- package/lib/assertions/core/resource-checks.d.ts.map +1 -1
- package/lib/assertions/core/resource-checks.js +32 -0
- package/lib/assertions/core/resource-checks.js.map +1 -1
- package/lib/cli/program.d.ts.map +1 -1
- package/lib/cli/program.js +6 -65
- package/lib/cli/program.js.map +1 -1
- package/lib/cli/view.d.ts +13 -0
- package/lib/cli/view.d.ts.map +1 -0
- package/lib/cli/view.js +126 -0
- package/lib/cli/view.js.map +1 -0
- package/lib/core/action-events.d.ts +29 -0
- package/lib/core/action-events.d.ts.map +1 -0
- package/lib/core/action-events.js +51 -0
- package/lib/core/action-events.js.map +1 -0
- package/lib/core/agent-activity.d.ts +49 -0
- package/lib/core/agent-activity.d.ts.map +1 -0
- package/lib/core/agent-activity.js +206 -0
- package/lib/core/agent-activity.js.map +1 -0
- package/lib/core/agent-events.d.ts +26 -0
- package/lib/core/agent-events.d.ts.map +1 -0
- package/lib/core/agent-events.js +13 -0
- package/lib/core/agent-events.js.map +1 -0
- package/lib/core/driver-metrics.d.ts +98 -0
- package/lib/core/driver-metrics.d.ts.map +1 -0
- package/lib/core/driver-metrics.js +36 -0
- package/lib/core/driver-metrics.js.map +1 -0
- package/lib/core/events.d.ts +34 -0
- package/lib/core/events.d.ts.map +1 -1
- package/lib/core/events.js +11 -2
- package/lib/core/events.js.map +1 -1
- package/lib/core/experiment.d.ts +19 -0
- package/lib/core/experiment.d.ts.map +1 -1
- package/lib/core/experiment.js +2 -0
- package/lib/core/experiment.js.map +1 -1
- package/lib/core/index.d.ts +4 -0
- package/lib/core/index.d.ts.map +1 -1
- package/lib/core/index.js +4 -0
- package/lib/core/index.js.map +1 -1
- package/lib/core/run.d.ts +149 -0
- package/lib/core/run.d.ts.map +1 -1
- package/lib/core/run.js +9 -0
- package/lib/core/run.js.map +1 -1
- package/lib/core/wire.d.ts +11 -0
- package/lib/core/wire.d.ts.map +1 -1
- package/lib/drivers/claude-code/parse-events.d.ts.map +1 -1
- package/lib/drivers/claude-code/parse-events.js +73 -8
- package/lib/drivers/claude-code/parse-events.js.map +1 -1
- package/lib/drivers/codex/app-events.d.ts +9 -2
- package/lib/drivers/codex/app-events.d.ts.map +1 -1
- package/lib/drivers/codex/app-events.js +93 -12
- package/lib/drivers/codex/app-events.js.map +1 -1
- package/lib/drivers/codex/index.d.ts.map +1 -1
- package/lib/drivers/codex/index.js +10 -14
- package/lib/drivers/codex/index.js.map +1 -1
- package/lib/index.d.ts +6 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +6 -2
- package/lib/index.js.map +1 -1
- package/lib/orchestrator/compare.d.ts +13 -1
- package/lib/orchestrator/compare.d.ts.map +1 -1
- package/lib/orchestrator/compare.js +12 -0
- package/lib/orchestrator/compare.js.map +1 -1
- package/lib/orchestrator/experiment-contracts.d.ts +19 -0
- package/lib/orchestrator/experiment-contracts.d.ts.map +1 -1
- package/lib/orchestrator/experiment-evaluation.d.ts.map +1 -1
- package/lib/orchestrator/experiment-evaluation.js +17 -0
- package/lib/orchestrator/experiment-evaluation.js.map +1 -1
- package/lib/orchestrator/experiment-report.d.ts +19 -0
- package/lib/orchestrator/experiment-report.d.ts.map +1 -1
- package/lib/orchestrator/experiment-runs.d.ts +38 -0
- package/lib/orchestrator/experiment-runs.d.ts.map +1 -1
- package/lib/orchestrator/index.d.ts +1 -1
- package/lib/orchestrator/index.d.ts.map +1 -1
- package/lib/orchestrator/index.js +1 -1
- package/lib/orchestrator/index.js.map +1 -1
- package/lib/orchestrator/list-runs.d.ts +7 -1
- package/lib/orchestrator/list-runs.d.ts.map +1 -1
- package/lib/orchestrator/list-runs.js +50 -6
- package/lib/orchestrator/list-runs.js.map +1 -1
- package/lib/orchestrator/run-execution.d.ts.map +1 -1
- package/lib/orchestrator/run-execution.js +17 -3
- package/lib/orchestrator/run-execution.js.map +1 -1
- package/lib/orchestrator/suite-compare.d.ts +34 -0
- package/lib/orchestrator/suite-compare.d.ts.map +1 -1
- package/lib/orchestrator/suite-summary.d.ts +68 -0
- package/lib/orchestrator/suite-summary.d.ts.map +1 -1
- package/lib/orchestrator/suite-summary.js +45 -7
- package/lib/orchestrator/suite-summary.js.map +1 -1
- package/lib/previewer/artifacts.d.ts +25 -0
- package/lib/previewer/artifacts.d.ts.map +1 -0
- package/lib/previewer/artifacts.js +180 -0
- package/lib/previewer/artifacts.js.map +1 -0
- package/lib/previewer/server.d.ts +105 -0
- package/lib/previewer/server.d.ts.map +1 -0
- package/lib/previewer/server.js +542 -0
- package/lib/previewer/server.js.map +1 -0
- package/package.json +2 -1
- package/skills/lux/references/eval-authoring.md +4 -2
- package/skills/lux-answerer/SKILL.md +1 -1
- package/src/assertions/authoring.ts +2 -0
- package/src/assertions/core/index.ts +2 -0
- package/src/assertions/core/resource-checks.ts +35 -0
- package/src/cli/program.ts +5 -91
- package/src/cli/view.ts +168 -0
- package/src/core/action-events.ts +59 -0
- package/src/core/agent-activity.ts +236 -0
- package/src/core/agent-events.ts +17 -0
- package/src/core/driver-metrics.ts +51 -0
- package/src/core/events.ts +13 -2
- package/src/core/experiment.ts +2 -0
- package/src/core/index.ts +4 -0
- package/src/core/run.ts +9 -0
- package/src/drivers/claude-code/parse-events.ts +98 -12
- package/src/drivers/codex/app-events.ts +116 -16
- package/src/drivers/codex/index.ts +16 -14
- package/src/index.ts +40 -0
- package/src/orchestrator/compare.ts +18 -0
- package/src/orchestrator/experiment-evaluation.ts +23 -0
- package/src/orchestrator/index.ts +1 -0
- package/src/orchestrator/list-runs.ts +66 -5
- package/src/orchestrator/run-execution.ts +19 -2
- package/src/orchestrator/suite-summary.ts +101 -8
- package/src/previewer/artifacts.ts +221 -0
- package/src/previewer/server.ts +765 -0
- package/viewer/app.css +2075 -0
- package/viewer/app.js +606 -0
- package/viewer/artifact-view.js +48 -0
- package/viewer/charts.js +86 -0
- package/viewer/detail.js +1214 -0
- package/viewer/format.js +129 -0
- package/viewer/index.html +151 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type AgentAttribution,
|
|
2
3
|
type ParsedDriverEvent,
|
|
4
|
+
type Progress,
|
|
3
5
|
type TokenUsage,
|
|
4
6
|
TURN_GLYPHS,
|
|
5
7
|
toolResultProgress,
|
|
@@ -20,8 +22,98 @@ const resultText = (content: unknown): string => {
|
|
|
20
22
|
.join("\n");
|
|
21
23
|
};
|
|
22
24
|
|
|
25
|
+
const agentCompletionStatus = (status: unknown): "completed" | "failed" | "cancelled" => {
|
|
26
|
+
if (status === "failed") return "failed";
|
|
27
|
+
if (status === "cancelled" || status === "interrupted") return "cancelled";
|
|
28
|
+
return "completed";
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const appAgentLifecycleProgress = (
|
|
32
|
+
item: RpcRecord,
|
|
33
|
+
phase: "started" | "completed",
|
|
34
|
+
actor?: AgentAttribution,
|
|
35
|
+
ts = Date.now(),
|
|
36
|
+
): Progress[] => {
|
|
37
|
+
if (item.type !== "collabAgentToolCall" || typeof item.id !== "string") return [];
|
|
38
|
+
const receiverThreadIds = Array.isArray(item.receiverThreadIds)
|
|
39
|
+
? item.receiverThreadIds.filter((value): value is string => typeof value === "string")
|
|
40
|
+
: [];
|
|
41
|
+
const completionStatus = agentCompletionStatus(item.status);
|
|
42
|
+
return receiverThreadIds.map((receiverThreadId) =>
|
|
43
|
+
phase === "started"
|
|
44
|
+
? {
|
|
45
|
+
id: `agent-spawn-${item.id}-${receiverThreadId}`,
|
|
46
|
+
kind: "agent-spawn",
|
|
47
|
+
payload: {
|
|
48
|
+
agent: {
|
|
49
|
+
id: receiverThreadId,
|
|
50
|
+
role: "subagent",
|
|
51
|
+
parentId: actor?.id ?? "codex",
|
|
52
|
+
spawnToolUseId: item.id as string,
|
|
53
|
+
...(typeof item.model === "string" ? { model: item.model } : {}),
|
|
54
|
+
},
|
|
55
|
+
...(typeof item.prompt === "string" ? { prompt: item.prompt } : {}),
|
|
56
|
+
},
|
|
57
|
+
...(actor ? { agent: actor } : {}),
|
|
58
|
+
ts,
|
|
59
|
+
}
|
|
60
|
+
: {
|
|
61
|
+
id: `agent-completed-${item.id}-${receiverThreadId}`,
|
|
62
|
+
kind: "agent-completed",
|
|
63
|
+
payload: {
|
|
64
|
+
agentId: receiverThreadId,
|
|
65
|
+
status: completionStatus,
|
|
66
|
+
},
|
|
67
|
+
...(actor ? { agent: actor } : {}),
|
|
68
|
+
ts,
|
|
69
|
+
},
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
23
73
|
export type RpcId = string | number;
|
|
24
74
|
export type RpcRecord = Record<string, unknown>;
|
|
75
|
+
|
|
76
|
+
export class CodexSubagentTracker {
|
|
77
|
+
private readonly spawnToolByThread = new Map<string, string>();
|
|
78
|
+
private readonly parentAgentByThread = new Map<string, string>();
|
|
79
|
+
|
|
80
|
+
record(
|
|
81
|
+
item: RpcRecord | null,
|
|
82
|
+
spawningThreadId: string | undefined,
|
|
83
|
+
mainThreadId: string | null,
|
|
84
|
+
): void {
|
|
85
|
+
if (item?.type !== "collabAgentToolCall" || typeof item.id !== "string") return;
|
|
86
|
+
const receiverThreadIds = Array.isArray(item.receiverThreadIds)
|
|
87
|
+
? item.receiverThreadIds.filter((value): value is string => typeof value === "string")
|
|
88
|
+
: [];
|
|
89
|
+
for (const receiverThreadId of receiverThreadIds) {
|
|
90
|
+
this.spawnToolByThread.set(receiverThreadId, item.id);
|
|
91
|
+
if (spawningThreadId) {
|
|
92
|
+
this.parentAgentByThread.set(
|
|
93
|
+
receiverThreadId,
|
|
94
|
+
spawningThreadId === mainThreadId ? "codex" : spawningThreadId,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
attribution(
|
|
101
|
+
threadId: string | undefined,
|
|
102
|
+
mainThreadId: string | null,
|
|
103
|
+
): AgentAttribution | undefined {
|
|
104
|
+
if (!threadId) return undefined;
|
|
105
|
+
const spawnToolUseId = this.spawnToolByThread.get(threadId);
|
|
106
|
+
if (!spawnToolUseId) {
|
|
107
|
+
return { id: threadId === mainThreadId ? "codex" : threadId, role: "main" };
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
id: threadId,
|
|
111
|
+
role: "subagent",
|
|
112
|
+
parentId: this.parentAgentByThread.get(threadId) ?? mainThreadId ?? "codex",
|
|
113
|
+
spawnToolUseId,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
25
117
|
export type AppTurnFacets = {
|
|
26
118
|
tools: string[];
|
|
27
119
|
asked: boolean;
|
|
@@ -131,8 +223,10 @@ const appItemResultText = (item: RpcRecord, error: RpcRecord | null, content: un
|
|
|
131
223
|
export const appItemProgress = (
|
|
132
224
|
item: RpcRecord,
|
|
133
225
|
phase: "started" | "completed",
|
|
134
|
-
|
|
226
|
+
attribution?: AgentAttribution | string,
|
|
135
227
|
): ParsedDriverEvent | null => {
|
|
228
|
+
const agent = typeof attribution === "string" ? undefined : attribution;
|
|
229
|
+
const parentToolUseId = typeof attribution === "string" ? attribution : agent?.spawnToolUseId;
|
|
136
230
|
const id = typeof item.id === "string" ? item.id : `${String(item.type)}-${Date.now()}`;
|
|
137
231
|
if (item.type === "agentMessage" && phase === "completed" && typeof item.text === "string") {
|
|
138
232
|
return {
|
|
@@ -142,6 +236,7 @@ export const appItemProgress = (
|
|
|
142
236
|
kind: "agent-message",
|
|
143
237
|
payload: { text: item.text, ...(parentToolUseId ? { parentToolUseId } : {}) },
|
|
144
238
|
ts: Date.now(),
|
|
239
|
+
...(agent ? { agent } : {}),
|
|
145
240
|
},
|
|
146
241
|
};
|
|
147
242
|
}
|
|
@@ -162,6 +257,7 @@ export const appItemProgress = (
|
|
|
162
257
|
...(parentToolUseId ? { parentToolUseId } : {}),
|
|
163
258
|
},
|
|
164
259
|
ts: Date.now(),
|
|
260
|
+
...(agent ? { agent } : {}),
|
|
165
261
|
},
|
|
166
262
|
};
|
|
167
263
|
}
|
|
@@ -184,6 +280,7 @@ export const appItemProgress = (
|
|
|
184
280
|
...(parentToolUseId ? { parentToolUseId } : {}),
|
|
185
281
|
},
|
|
186
282
|
ts: Date.now(),
|
|
283
|
+
...(agent ? { agent } : {}),
|
|
187
284
|
},
|
|
188
285
|
};
|
|
189
286
|
}
|
|
@@ -199,20 +296,23 @@ export const appItemProgress = (
|
|
|
199
296
|
error !== null;
|
|
200
297
|
return {
|
|
201
298
|
tag: "progress",
|
|
202
|
-
progress:
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
299
|
+
progress: {
|
|
300
|
+
...toolResultProgress(
|
|
301
|
+
0,
|
|
302
|
+
{
|
|
303
|
+
name: tool,
|
|
304
|
+
toolUseId: id,
|
|
305
|
+
content,
|
|
306
|
+
...(asRecord(structuredContent)
|
|
307
|
+
? { structuredContent: asRecord(structuredContent) ?? {} }
|
|
308
|
+
: {}),
|
|
309
|
+
isError,
|
|
310
|
+
text: appItemResultText(item, error, content),
|
|
311
|
+
...(parentToolUseId ? { parentToolUseId } : {}),
|
|
312
|
+
},
|
|
313
|
+
Date.now(),
|
|
314
|
+
),
|
|
315
|
+
...(agent ? { agent } : {}),
|
|
316
|
+
},
|
|
217
317
|
};
|
|
218
318
|
};
|
|
@@ -24,11 +24,13 @@ import { copyRegularFileNoFollow } from "../shared/secure-copy.js";
|
|
|
24
24
|
import { subprocessDriver } from "../subprocess/index.js";
|
|
25
25
|
import {
|
|
26
26
|
type AppTurnFacets,
|
|
27
|
+
appAgentLifecycleProgress,
|
|
27
28
|
appItemProgress,
|
|
28
29
|
appItemToolName,
|
|
29
30
|
appTurnLabel,
|
|
30
31
|
appUsage,
|
|
31
32
|
asRecord,
|
|
33
|
+
CodexSubagentTracker,
|
|
32
34
|
messageOf,
|
|
33
35
|
type RpcId,
|
|
34
36
|
type RpcRecord,
|
|
@@ -316,7 +318,7 @@ class CodexAppServerDriverImpl implements AgentDriver {
|
|
|
316
318
|
{ resolve: (result: RpcRecord) => void; reject: (error: Error) => void }
|
|
317
319
|
>();
|
|
318
320
|
private readonly interrupts = new Map<string, { id: RpcId; method: string; params: RpcRecord }>();
|
|
319
|
-
private readonly
|
|
321
|
+
private readonly subagents = new CodexSubagentTracker();
|
|
320
322
|
private readonly turnFacets = new Map<string, AppTurnFacets>();
|
|
321
323
|
private interruptCount = 0;
|
|
322
324
|
private idleTimer: NodeJS.Timeout | null = null;
|
|
@@ -702,22 +704,22 @@ class CodexAppServerDriverImpl implements AgentDriver {
|
|
|
702
704
|
const toolName = item ? appItemToolName(item) : null;
|
|
703
705
|
if (toolName && !facets.tools.includes(toolName)) facets.tools.push(toolName);
|
|
704
706
|
if (turnId) this.turnFacets.set(turnId, facets);
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
707
|
+
const threadId = typeof params.threadId === "string" ? params.threadId : undefined;
|
|
708
|
+
this.subagents.record(item, threadId, this.threadId);
|
|
709
|
+
const agent = this.subagents.attribution(threadId, this.threadId);
|
|
708
710
|
const event = item
|
|
709
|
-
? appItemProgress(item, method === "item/started" ? "started" : "completed",
|
|
711
|
+
? appItemProgress(item, method === "item/started" ? "started" : "completed", agent)
|
|
710
712
|
: null;
|
|
711
713
|
if (event?.tag === "progress") await this.emitter.emit("progress", event.progress);
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
714
|
+
if (item) {
|
|
715
|
+
for (const lifecycle of appAgentLifecycleProgress(
|
|
716
|
+
item,
|
|
717
|
+
method === "item/started" ? "started" : "completed",
|
|
718
|
+
agent,
|
|
719
|
+
event?.tag === "progress" ? event.progress.ts : Date.now(),
|
|
720
|
+
)) {
|
|
721
|
+
await this.emitter.emit("progress", lifecycle);
|
|
722
|
+
}
|
|
721
723
|
}
|
|
722
724
|
}
|
|
723
725
|
|
package/src/index.ts
CHANGED
|
@@ -87,6 +87,8 @@ export {
|
|
|
87
87
|
registerCoreAssertions,
|
|
88
88
|
runSucceededAssertion,
|
|
89
89
|
subagentToolCalls,
|
|
90
|
+
type TimeToFirstActionUnderConfig,
|
|
91
|
+
TimeToFirstActionUnderConfigSchema,
|
|
90
92
|
type TokensUnderConfig,
|
|
91
93
|
TokensUnderConfigSchema,
|
|
92
94
|
type ToolCall,
|
|
@@ -104,6 +106,7 @@ export {
|
|
|
104
106
|
ToolResultIsErrorConfigSchema,
|
|
105
107
|
type ToolResultJsonPointerEqualsConfig,
|
|
106
108
|
ToolResultJsonPointerEqualsConfigSchema,
|
|
109
|
+
timeToFirstActionUnderAssertion,
|
|
107
110
|
tokensUnderAssertion,
|
|
108
111
|
toolCallCountAssertion,
|
|
109
112
|
toolCalledAssertion,
|
|
@@ -141,6 +144,28 @@ export {
|
|
|
141
144
|
rubricAssertion,
|
|
142
145
|
} from "./assertions/rubric/index.js";
|
|
143
146
|
// Authoring and extension contracts.
|
|
147
|
+
export {
|
|
148
|
+
type ActionCategory,
|
|
149
|
+
ActionCategorySchema,
|
|
150
|
+
type ActionProgressPayload,
|
|
151
|
+
ActionProgressPayloadSchema,
|
|
152
|
+
type ActionTiming,
|
|
153
|
+
ActionTimingSchema,
|
|
154
|
+
actionTiming,
|
|
155
|
+
} from "./core/action-events.js";
|
|
156
|
+
export {
|
|
157
|
+
type AgentActivity,
|
|
158
|
+
AgentActivitySchema,
|
|
159
|
+
summarizeAgentActivity,
|
|
160
|
+
} from "./core/agent-activity.js";
|
|
161
|
+
export {
|
|
162
|
+
AGENT_COMPLETED_KIND,
|
|
163
|
+
AGENT_SPAWN_KIND,
|
|
164
|
+
type AgentCompletedPayload,
|
|
165
|
+
AgentCompletedPayloadSchema,
|
|
166
|
+
type AgentSpawnPayload,
|
|
167
|
+
AgentSpawnPayloadSchema,
|
|
168
|
+
} from "./core/agent-events.js";
|
|
144
169
|
export {
|
|
145
170
|
defineGraderAnnotations,
|
|
146
171
|
type GraderAnnotation,
|
|
@@ -227,8 +252,23 @@ export {
|
|
|
227
252
|
ResolvedFixturesSchema,
|
|
228
253
|
type StartContext,
|
|
229
254
|
} from "./core/driver.js";
|
|
255
|
+
export {
|
|
256
|
+
type DriverMetric,
|
|
257
|
+
type DriverMetricDescriptor,
|
|
258
|
+
DriverMetricDescriptorSchema,
|
|
259
|
+
DriverMetricSchema,
|
|
260
|
+
type DriverMetrics,
|
|
261
|
+
DriverMetricsSchema,
|
|
262
|
+
type DriverMetricUnit,
|
|
263
|
+
DriverMetricUnitSchema,
|
|
264
|
+
normalizeDriverMetrics,
|
|
265
|
+
type ReportedDriverMetrics,
|
|
266
|
+
ReportedDriverMetricsSchema,
|
|
267
|
+
} from "./core/driver-metrics.js";
|
|
230
268
|
export { type EventHandler, TypedEmitter } from "./core/emitter.js";
|
|
231
269
|
export {
|
|
270
|
+
type AgentAttribution,
|
|
271
|
+
AgentAttributionSchema,
|
|
232
272
|
type Answer,
|
|
233
273
|
AnswerSchema,
|
|
234
274
|
ApproveAnswerSchema,
|
|
@@ -2,6 +2,9 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import {
|
|
5
|
+
type ActionTiming,
|
|
6
|
+
type AgentActivity,
|
|
7
|
+
type DriverMetrics,
|
|
5
8
|
emptyRunUsage,
|
|
6
9
|
type GradingReport,
|
|
7
10
|
GradingReportSchema,
|
|
@@ -48,6 +51,9 @@ export type RunComparison = {
|
|
|
48
51
|
exitReason: { a: string; b: string };
|
|
49
52
|
durationMsA: number | null;
|
|
50
53
|
durationMsB: number | null;
|
|
54
|
+
actionTiming: { a: ActionTiming; b: ActionTiming };
|
|
55
|
+
driverMetrics: { a: DriverMetrics; b: DriverMetrics };
|
|
56
|
+
agentActivity: { a: AgentActivity[]; b: AgentActivity[] };
|
|
51
57
|
interruptCounts: {
|
|
52
58
|
a: { ask: number; approve: number; custom: number };
|
|
53
59
|
b: { ask: number; approve: number; custom: number };
|
|
@@ -260,6 +266,18 @@ export const compareRuns = async (input: ComparisonInput): Promise<RunComparison
|
|
|
260
266
|
},
|
|
261
267
|
durationMsA: durationFromMeta(a.runJson),
|
|
262
268
|
durationMsB: durationFromMeta(b.runJson),
|
|
269
|
+
actionTiming: {
|
|
270
|
+
a: a.runJson?.actionTiming ?? {},
|
|
271
|
+
b: b.runJson?.actionTiming ?? {},
|
|
272
|
+
},
|
|
273
|
+
driverMetrics: {
|
|
274
|
+
a: a.runJson?.driverMetrics ?? {},
|
|
275
|
+
b: b.runJson?.driverMetrics ?? {},
|
|
276
|
+
},
|
|
277
|
+
agentActivity: {
|
|
278
|
+
a: a.runJson?.agentActivity ?? [],
|
|
279
|
+
b: b.runJson?.agentActivity ?? [],
|
|
280
|
+
},
|
|
263
281
|
interruptCounts: {
|
|
264
282
|
a: a.runJson?.interruptCounts ?? { ask: 0, approve: 0, custom: 0 },
|
|
265
283
|
b: b.runJson?.interruptCounts ?? { ask: 0, approve: 0, custom: 0 },
|
|
@@ -101,6 +101,23 @@ const evaluationMetrics = (
|
|
|
101
101
|
),
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
+
const evaluationMetricDefinitions = (summary: SuiteSummary) =>
|
|
105
|
+
Object.fromEntries(
|
|
106
|
+
Object.entries(summary.metrics).flatMap(([name, metric]) => {
|
|
107
|
+
if (!metric.unit && !metric.label && !metric.better) return [];
|
|
108
|
+
return [
|
|
109
|
+
[
|
|
110
|
+
name,
|
|
111
|
+
{
|
|
112
|
+
unit: metric.unit ?? "number",
|
|
113
|
+
better: metric.better ?? "neutral",
|
|
114
|
+
...(metric.label ? { label: metric.label } : {}),
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
];
|
|
118
|
+
}),
|
|
119
|
+
);
|
|
120
|
+
|
|
104
121
|
const summaryUsage = (summary: SuiteSummary) =>
|
|
105
122
|
summary.samples.reduce(
|
|
106
123
|
(usage, sample) =>
|
|
@@ -277,6 +294,7 @@ const agentEvaluationFromSuite = (input: {
|
|
|
277
294
|
summary.cases.map((entry) => [entry.canonicalCaseId, entry.passRate]),
|
|
278
295
|
),
|
|
279
296
|
metrics: evaluationMetrics(summary, candidate),
|
|
297
|
+
metricDefinitions: evaluationMetricDefinitions(summary),
|
|
280
298
|
samples: evaluationSamples(summary),
|
|
281
299
|
metricCalls: summary.samples.length,
|
|
282
300
|
judgeCalls: results.reduce(
|
|
@@ -352,6 +370,10 @@ const aggregateAgentUnits = (
|
|
|
352
370
|
.map(([, values]) => average(values)),
|
|
353
371
|
]),
|
|
354
372
|
);
|
|
373
|
+
const metricDefinitions = Object.assign(
|
|
374
|
+
{},
|
|
375
|
+
...units.map(({ evaluation }) => evaluation.metricDefinitions ?? {}),
|
|
376
|
+
);
|
|
355
377
|
return {
|
|
356
378
|
candidateId: candidate.id,
|
|
357
379
|
profileId,
|
|
@@ -367,6 +389,7 @@ const aggregateAgentUnits = (
|
|
|
367
389
|
name === "subject.bytes" ? candidate.sourceBytes : average(values),
|
|
368
390
|
]),
|
|
369
391
|
),
|
|
392
|
+
metricDefinitions,
|
|
370
393
|
samples,
|
|
371
394
|
metricCalls: units.reduce((total, unit) => total + (unit.evaluation.metricCalls ?? 0), 0),
|
|
372
395
|
judgeCalls: units.reduce((total, unit) => total + (unit.evaluation.judgeCalls ?? 0), 0),
|
|
@@ -2,6 +2,11 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import { constants, lstat, open, readdir } from "node:fs/promises";
|
|
3
3
|
import { basename, join, relative, sep } from "node:path";
|
|
4
4
|
import {
|
|
5
|
+
ActionProgressPayloadSchema,
|
|
6
|
+
AgentCompletedPayloadSchema,
|
|
7
|
+
AgentSpawnPayloadSchema,
|
|
8
|
+
type Artifact,
|
|
9
|
+
ArtifactSchema,
|
|
5
10
|
type GradingReport,
|
|
6
11
|
GradingReportSchema,
|
|
7
12
|
harnessUsage,
|
|
@@ -31,6 +36,11 @@ export type ListedRun = {
|
|
|
31
36
|
startedAt: string | null;
|
|
32
37
|
endedAt: string | null;
|
|
33
38
|
durationMs: number | null;
|
|
39
|
+
timeToFirstActionMs: number | null;
|
|
40
|
+
timeToFirstMutationMs: number | null;
|
|
41
|
+
timeToFirstDurableMutationMs: number | null;
|
|
42
|
+
driverMetrics: NonNullable<RunMetadata["driverMetrics"]>;
|
|
43
|
+
subagentCount: number;
|
|
34
44
|
casePassed: boolean | null;
|
|
35
45
|
caseScore: number | null;
|
|
36
46
|
assertionPassed: number;
|
|
@@ -63,6 +73,12 @@ const summarizeRun = async (
|
|
|
63
73
|
startedAt,
|
|
64
74
|
endedAt,
|
|
65
75
|
durationMs,
|
|
76
|
+
timeToFirstActionMs: meta?.actionTiming?.timeToFirstActionMs ?? null,
|
|
77
|
+
timeToFirstMutationMs: meta?.actionTiming?.timeToFirstMutationMs ?? null,
|
|
78
|
+
timeToFirstDurableMutationMs: meta?.actionTiming?.timeToFirstDurableMutationMs ?? null,
|
|
79
|
+
driverMetrics: meta?.driverMetrics ?? {},
|
|
80
|
+
subagentCount:
|
|
81
|
+
meta?.agentActivity?.filter((activity) => activity.agent.role === "subagent").length ?? 0,
|
|
66
82
|
casePassed: grading?.casePassed ?? null,
|
|
67
83
|
caseScore: grading?.caseScore ?? null,
|
|
68
84
|
assertionPassed: passing,
|
|
@@ -127,7 +143,7 @@ export const listRuns = async (runsRoot: string): Promise<ListedRun[]> => {
|
|
|
127
143
|
export const formatRunsTable = (runs: ListedRun[]): string => {
|
|
128
144
|
if (runs.length === 0) return "(no runs found)";
|
|
129
145
|
const rows: string[][] = [
|
|
130
|
-
["status", "case", "driver", "ts", "exit", "score", "duration"],
|
|
146
|
+
["status", "case", "driver", "agents", "ts", "exit", "score", "duration"],
|
|
131
147
|
...runs.map((r) => {
|
|
132
148
|
let status = "ungraded";
|
|
133
149
|
if (r.casePassed === true) status = "PASS";
|
|
@@ -137,7 +153,8 @@ export const formatRunsTable = (runs: ListedRun[]): string => {
|
|
|
137
153
|
? `${(r.caseScore * 100).toFixed(0)}% (${r.assertionPassed}/${r.assertionTotal})`
|
|
138
154
|
: "—";
|
|
139
155
|
const duration = r.durationMs !== null ? `${(r.durationMs / 1000).toFixed(1)}s` : "—";
|
|
140
|
-
|
|
156
|
+
const agents = r.subagentCount > 0 ? `1+${r.subagentCount}` : "1";
|
|
157
|
+
return [status, r.caseSlug, r.driverName, agents, r.ts, r.exitReason ?? "?", score, duration];
|
|
141
158
|
}),
|
|
142
159
|
];
|
|
143
160
|
const header = rows[0] ?? [];
|
|
@@ -156,6 +173,9 @@ export type RunDetail = {
|
|
|
156
173
|
artifacts: string[];
|
|
157
174
|
};
|
|
158
175
|
|
|
176
|
+
export const loadRunArtifacts = async (runDir: string): Promise<Artifact[]> =>
|
|
177
|
+
(await safeReadJson(join(runDir, "artifacts.json"), ArtifactSchema.array())) ?? [];
|
|
178
|
+
|
|
159
179
|
const readEventsJsonl = async (path: string): Promise<RunEvent[]> => {
|
|
160
180
|
if (!existsSync(path)) return [];
|
|
161
181
|
if ((await lstat(path)).isSymbolicLink()) {
|
|
@@ -291,6 +311,27 @@ const appendMetadataDetail = (lines: string[], detail: RunDetail): void => {
|
|
|
291
311
|
`phases: agent=${secs(phases.agent)} collect=${secs(phases.collect)} grading=${secs(phases.grading)}`,
|
|
292
312
|
);
|
|
293
313
|
}
|
|
314
|
+
const actions = detail.metadata.actionTiming;
|
|
315
|
+
if (actions) {
|
|
316
|
+
const value = (ms: number | undefined): string => (ms === undefined ? "—" : `${ms}ms`);
|
|
317
|
+
lines.push(
|
|
318
|
+
`first action: any=${value(actions.timeToFirstActionMs)} mutation=${value(actions.timeToFirstMutationMs)} durable-mutation=${value(actions.timeToFirstDurableMutationMs)}`,
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
const activities = detail.metadata.agentActivity ?? [];
|
|
322
|
+
const subagents = activities.filter((activity) => activity.agent.role === "subagent");
|
|
323
|
+
if (subagents.length > 0) {
|
|
324
|
+
lines.push(`subagents: ${subagents.length}`);
|
|
325
|
+
for (const activity of subagents) {
|
|
326
|
+
const label = activity.agent.name
|
|
327
|
+
? `${activity.agent.name} (${activity.agent.id})`
|
|
328
|
+
: activity.agent.id;
|
|
329
|
+
const agentActions = activity.actions;
|
|
330
|
+
lines.push(
|
|
331
|
+
` ${label}: ${activity.status}, ${(activity.durationMs / 1000).toFixed(1)}s, events=${activity.eventCount}, tokens=${totalTokens(activity.usage)}, tools=${activity.toolCalls.total} (${activity.toolCalls.succeeded} ok/${activity.toolCalls.failed} failed/${activity.toolCalls.unresolved} unresolved), actions=${agentActions.total}, mutations=${agentActions.mutations}`,
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
294
335
|
}
|
|
295
336
|
};
|
|
296
337
|
|
|
@@ -361,17 +402,37 @@ const formatEventLine = (event: RunEvent): string => {
|
|
|
361
402
|
}`;
|
|
362
403
|
}
|
|
363
404
|
return `responded(custom): ${JSON.stringify(event.payload).slice(0, 80)}`;
|
|
364
|
-
case "progress":
|
|
405
|
+
case "progress": {
|
|
406
|
+
const agent = event.agent?.role === "subagent" ? ` [subagent ${event.agent.id}]` : "";
|
|
407
|
+
if (event.kind === "agent-spawn") {
|
|
408
|
+
const payload = AgentSpawnPayloadSchema.safeParse(event.payload).data;
|
|
409
|
+
return payload
|
|
410
|
+
? `agent-spawn: ${payload.agent.name ?? payload.agent.id} (${payload.agent.id})`
|
|
411
|
+
: "agent-spawn: malformed";
|
|
412
|
+
}
|
|
413
|
+
if (event.kind === "agent-completed") {
|
|
414
|
+
const payload = AgentCompletedPayloadSchema.safeParse(event.payload).data;
|
|
415
|
+
return payload
|
|
416
|
+
? `agent-completed: ${payload.agentId} (${payload.status})`
|
|
417
|
+
: "agent-completed: malformed";
|
|
418
|
+
}
|
|
419
|
+
if (event.kind === "action") {
|
|
420
|
+
const payload = ActionProgressPayloadSchema.safeParse(event.payload).data;
|
|
421
|
+
return payload
|
|
422
|
+
? `action${agent}: ${payload.name} (${payload.category})`
|
|
423
|
+
: "action: malformed";
|
|
424
|
+
}
|
|
365
425
|
if (event.kind === "tool-call") {
|
|
366
426
|
const payload = ToolCallPayloadSchema.safeParse(event.payload).data;
|
|
367
|
-
return payload ? `tool-call: ${payload.name}` : "tool-call: malformed";
|
|
427
|
+
return payload ? `tool-call${agent}: ${payload.name}` : "tool-call: malformed";
|
|
368
428
|
}
|
|
369
429
|
if (event.kind === "tool-result") {
|
|
370
430
|
const payload = ToolResultPayloadSchema.safeParse(event.payload).data;
|
|
371
431
|
if (!payload) return "tool-result: malformed";
|
|
372
|
-
return `tool-result: ${payload.name} (${payload.isError ? "failed" : "succeeded"})`;
|
|
432
|
+
return `tool-result${agent}: ${payload.name} (${payload.isError ? "failed" : "succeeded"})`;
|
|
373
433
|
}
|
|
374
434
|
return `progress(${event.kind})`;
|
|
435
|
+
}
|
|
375
436
|
case "lifecycle":
|
|
376
437
|
return `lifecycle: ${event.stage}`;
|
|
377
438
|
case "ready":
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
AnswererModelCallAccountingSchema,
|
|
11
11
|
type AnswerHistoryEntry,
|
|
12
12
|
type Artifact,
|
|
13
|
+
actionTiming,
|
|
13
14
|
type EvalCase,
|
|
14
15
|
emptyUsage,
|
|
15
16
|
type FixturesRef,
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
guardRunObserver,
|
|
19
20
|
inspectRunArtifact,
|
|
20
21
|
type LifecycleStage,
|
|
22
|
+
normalizeDriverMetrics,
|
|
21
23
|
type OperationalFailure,
|
|
22
24
|
type PhaseDurations,
|
|
23
25
|
type ReadyState,
|
|
@@ -28,6 +30,7 @@ import {
|
|
|
28
30
|
type RunMetadata,
|
|
29
31
|
type RunProvenance,
|
|
30
32
|
type RunUsage,
|
|
33
|
+
summarizeAgentActivity,
|
|
31
34
|
summarizeToolOutcomes,
|
|
32
35
|
sumUsage,
|
|
33
36
|
type TokenUsage,
|
|
@@ -183,6 +186,11 @@ const modelAccountingFromSummary = (
|
|
|
183
186
|
): { modelCalls: number; unpricedModelCalls?: number | undefined } | null =>
|
|
184
187
|
SummaryModelAccountingSchema.safeParse(summary).data ?? null;
|
|
185
188
|
|
|
189
|
+
const metricsFromSummary = (summary: unknown) => {
|
|
190
|
+
if (!summary || typeof summary !== "object") return {};
|
|
191
|
+
return normalizeDriverMetrics((summary as { metrics?: unknown }).metrics);
|
|
192
|
+
};
|
|
193
|
+
|
|
186
194
|
const artifactMeasurements = async (
|
|
187
195
|
artifacts: readonly Artifact[],
|
|
188
196
|
runDir: string,
|
|
@@ -390,6 +398,7 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
390
398
|
metadata.exitReason = "error";
|
|
391
399
|
metadata.operationalFailure = { phase: operationalPhase, reason };
|
|
392
400
|
metadata.phaseDurationMs = phaseDurations(this.runDir.recorded);
|
|
401
|
+
metadata.actionTiming = actionTiming(this.runDir.recorded);
|
|
393
402
|
await this.runDir.writeJson("run.json", metadata);
|
|
394
403
|
await this.args.request.recordPreGradingAccounting?.(metadata);
|
|
395
404
|
await this.args.request.recordModelAttemptsComplete?.(this.subjectAttemptIds);
|
|
@@ -407,6 +416,7 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
407
416
|
this.lifecycle("collect-end");
|
|
408
417
|
await this.runDir.events.drain();
|
|
409
418
|
const completed = await this.completedAgentRun();
|
|
419
|
+
completed.metadata.actionTiming = actionTiming(this.runDir.recorded);
|
|
410
420
|
this.metadata = completed.metadata;
|
|
411
421
|
this.usage = completed.usage;
|
|
412
422
|
this.run = completed.run;
|
|
@@ -430,6 +440,7 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
430
440
|
grading.assertions.flatMap((assertion) => (assertion.usage ? [assertion.usage] : [])),
|
|
431
441
|
);
|
|
432
442
|
metadata.phaseDurationMs = phaseDurations(this.runDir.recorded);
|
|
443
|
+
metadata.actionTiming = actionTiming(this.runDir.recorded);
|
|
433
444
|
await this.runDir.events.drain();
|
|
434
445
|
await this.runDir.writeJson("run.json", metadata);
|
|
435
446
|
await this.runDir.writeJson("artifacts.json", this.collection.artifacts);
|
|
@@ -506,6 +517,7 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
506
517
|
private fallbackFailureMetadata(): RunMetadata {
|
|
507
518
|
const failedAt = new Date();
|
|
508
519
|
const answererAccounting = readAnswererModelAccounting(this.args.answerer);
|
|
520
|
+
const ready = this.ready ?? { agentId: "start-failed" };
|
|
509
521
|
return {
|
|
510
522
|
schemaVersion: 1,
|
|
511
523
|
luxVersion: this.args.luxVersion,
|
|
@@ -514,7 +526,7 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
514
526
|
agentEndedAt: failedAt.toISOString(),
|
|
515
527
|
endedAt: failedAt.toISOString(),
|
|
516
528
|
exitReason: "error",
|
|
517
|
-
driver: { name: this.args.driverName, ...
|
|
529
|
+
driver: { name: this.args.driverName, ...ready },
|
|
518
530
|
answerer: answererMetadata(this.args.answererName, this.args.answererConfig),
|
|
519
531
|
usage: {
|
|
520
532
|
agent: emptyUsage(),
|
|
@@ -530,6 +542,7 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
530
542
|
interruptCounts: this.interruptCounts,
|
|
531
543
|
toolCallCount: this.toolCallCount,
|
|
532
544
|
toolOutcomes: summarizeToolOutcomes(this.runDir.recorded),
|
|
545
|
+
agentActivity: summarizeAgentActivity(this.runDir.recorded, ready),
|
|
533
546
|
artifacts: { count: this.collection.artifacts.length },
|
|
534
547
|
provenance: this.runProvenance(),
|
|
535
548
|
};
|
|
@@ -563,6 +576,7 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
563
576
|
const summary = outcome.kind === "done" ? outcome.done.summary : outcome.error.summary;
|
|
564
577
|
const driverCost = costFromSummary(summary);
|
|
565
578
|
const reportedModelAccounting = modelAccountingFromSummary(summary);
|
|
579
|
+
const driverMetrics = metricsFromSummary(summary);
|
|
566
580
|
const answererAccounting = readAnswererModelAccounting(this.args.answerer);
|
|
567
581
|
const agentCalls = this.ready ? (reportedModelAccounting?.modelCalls ?? 1) : 0;
|
|
568
582
|
const operationalFailure = loopFailure(outcome, this.collection.error);
|
|
@@ -571,6 +585,7 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
571
585
|
answerer: readAnswererUsage(this.args.answerer),
|
|
572
586
|
grading: emptyUsage(),
|
|
573
587
|
};
|
|
588
|
+
const ready = this.ready ?? { agentId: "not-started" };
|
|
574
589
|
const metadata: RunMetadata = {
|
|
575
590
|
schemaVersion: 1,
|
|
576
591
|
luxVersion: this.args.luxVersion,
|
|
@@ -579,7 +594,7 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
579
594
|
agentEndedAt: agentEndedAt.toISOString(),
|
|
580
595
|
endedAt: agentEndedAt.toISOString(),
|
|
581
596
|
exitReason,
|
|
582
|
-
driver: { name: this.args.driverName, ...
|
|
597
|
+
driver: { name: this.args.driverName, ...ready },
|
|
583
598
|
answerer: answererMetadata(this.args.answererName, this.args.answererConfig),
|
|
584
599
|
usage,
|
|
585
600
|
modelAccounting: {
|
|
@@ -593,10 +608,12 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
593
608
|
interruptCounts: this.interruptCounts,
|
|
594
609
|
toolCallCount: this.toolCallCount,
|
|
595
610
|
toolOutcomes: summarizeToolOutcomes(this.runDir.recorded),
|
|
611
|
+
agentActivity: summarizeAgentActivity(this.runDir.recorded, ready),
|
|
596
612
|
artifacts: await artifactMeasurements(this.collection.artifacts, this.runDir.path),
|
|
597
613
|
...(operationalFailure ? { operationalFailure } : {}),
|
|
598
614
|
provenance: this.runProvenance(),
|
|
599
615
|
...(driverCost !== undefined ? { cost: { usd: driverCost } } : {}),
|
|
616
|
+
...(Object.keys(driverMetrics).length > 0 ? { driverMetrics } : {}),
|
|
600
617
|
};
|
|
601
618
|
return {
|
|
602
619
|
metadata,
|