@evolvingmachines/evolve 0.0.55-project-sable.20260916.db0bc0bb

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.
Files changed (98) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +25 -0
  3. package/dist/chunk-5GVMBEYI.js +11 -0
  4. package/dist/chunk-HQJI3HHU.js +1 -0
  5. package/dist/chunk-JS2UTK2I.js +427 -0
  6. package/dist/cli/index.cjs +73 -0
  7. package/dist/cli/index.d.cts +270 -0
  8. package/dist/cli/index.d.ts +270 -0
  9. package/dist/cli/index.js +54 -0
  10. package/dist/index.cjs +669 -0
  11. package/dist/index.d.cts +5023 -0
  12. package/dist/index.d.ts +5023 -0
  13. package/dist/index.js +230 -0
  14. package/dist/managed-modal-IJW62SSK.js +4 -0
  15. package/dist/resumable-HFSICSH3.js +1 -0
  16. package/dist/tar-GVC4KE52.js +1 -0
  17. package/dist/types-DlpTxdR_.d.cts +4777 -0
  18. package/dist/types-DlpTxdR_.d.ts +4777 -0
  19. package/dist/upload-KPYDMWFJ.js +1 -0
  20. package/docs-agents/SKILL.md +138 -0
  21. package/docs-agents/index.md +81 -0
  22. package/docs-agents/python/01-getting-started.md +511 -0
  23. package/docs-agents/python/02-configuration.md +855 -0
  24. package/docs-agents/python/03-runtime.md +899 -0
  25. package/docs-agents/python/04-streaming.md +451 -0
  26. package/docs-agents/python/05-swarm-pipeline.md +953 -0
  27. package/docs-agents/python/index.md +122 -0
  28. package/docs-agents/typescript/01-getting-started.md +494 -0
  29. package/docs-agents/typescript/02-configuration.md +841 -0
  30. package/docs-agents/typescript/03-runtime.md +864 -0
  31. package/docs-agents/typescript/04-streaming.md +478 -0
  32. package/docs-agents/typescript/05-swarm-pipeline.md +957 -0
  33. package/docs-agents/typescript/index.md +121 -0
  34. package/docs-evals/SKILL.md +112 -0
  35. package/docs-evals/cli-reference/agent.mdx +67 -0
  36. package/docs-evals/cli-reference/analysis.mdx +91 -0
  37. package/docs-evals/cli-reference/analyze.mdx +74 -0
  38. package/docs-evals/cli-reference/auth.mdx +60 -0
  39. package/docs-evals/cli-reference/check.mdx +146 -0
  40. package/docs-evals/cli-reference/dataset.mdx +133 -0
  41. package/docs-evals/cli-reference/index.mdx +108 -0
  42. package/docs-evals/cli-reference/job.mdx +239 -0
  43. package/docs-evals/cli-reference/run.mdx +196 -0
  44. package/docs-evals/cli-reference/secrets.mdx +95 -0
  45. package/docs-evals/cli-reference/skill.mdx +51 -0
  46. package/docs-evals/cli-reference/skills.mdx +81 -0
  47. package/docs-evals/cli-reference/trial.mdx +112 -0
  48. package/docs-evals/cli-reference/upload.mdx +36 -0
  49. package/docs-evals/core-concepts/agents.mdx +106 -0
  50. package/docs-evals/core-concepts/analyze.mdx +86 -0
  51. package/docs-evals/core-concepts/check.mdx +51 -0
  52. package/docs-evals/core-concepts/datasets.mdx +138 -0
  53. package/docs-evals/core-concepts/index.mdx +42 -0
  54. package/docs-evals/core-concepts/jobs.mdx +136 -0
  55. package/docs-evals/core-concepts/models.mdx +68 -0
  56. package/docs-evals/core-concepts/sandboxes.mdx +52 -0
  57. package/docs-evals/core-concepts/secrets.mdx +41 -0
  58. package/docs-evals/core-concepts/skills.mdx +42 -0
  59. package/docs-evals/core-concepts/tasks.mdx +216 -0
  60. package/docs-evals/core-concepts/trial-outputs.mdx +103 -0
  61. package/docs-evals/core-concepts/trials.mdx +103 -0
  62. package/docs-evals/core-concepts/upload.mdx +45 -0
  63. package/docs-evals/dashboard/checks.mdx +15 -0
  64. package/docs-evals/dashboard/datasets.mdx +24 -0
  65. package/docs-evals/dashboard/jobs.mdx +65 -0
  66. package/docs-evals/dashboard/traces.mdx +15 -0
  67. package/docs-evals/dashboard/trial-viewer.mdx +30 -0
  68. package/docs-evals/docs.json +180 -0
  69. package/docs-evals/getting-started/installation.mdx +56 -0
  70. package/docs-evals/getting-started/quick-start.mdx +85 -0
  71. package/docs-evals/index.mdx +36 -0
  72. package/docs-evals/sdk/python.mdx +139 -0
  73. package/docs-evals/sdk/typescript.mdx +141 -0
  74. package/docs-evals/sdk-reference/agents.mdx +105 -0
  75. package/docs-evals/sdk-reference/analyses.mdx +125 -0
  76. package/docs-evals/sdk-reference/auth.mdx +76 -0
  77. package/docs-evals/sdk-reference/checks.mdx +166 -0
  78. package/docs-evals/sdk-reference/datasets.mdx +305 -0
  79. package/docs-evals/sdk-reference/errors.mdx +199 -0
  80. package/docs-evals/sdk-reference/index.mdx +58 -0
  81. package/docs-evals/sdk-reference/jobs.mdx +731 -0
  82. package/docs-evals/sdk-reference/meta.mdx +52 -0
  83. package/docs-evals/sdk-reference/skills.mdx +68 -0
  84. package/docs-evals/sdk-reference/trials.mdx +232 -0
  85. package/docs-evals/sdk-reference/types.mdx +272 -0
  86. package/docs-evals/snippets/global-options.mdx +13 -0
  87. package/docs-evals/snippets/list-flags.mdx +23 -0
  88. package/harness-capabilities.json +430 -0
  89. package/hosted-error-codes.json +120 -0
  90. package/package.json +163 -0
  91. package/skills/create-adapter/SKILL.md +287 -0
  92. package/skills/create-task/SKILL.md +491 -0
  93. package/skills/evolve/SKILL.md +39 -0
  94. package/skills/publish/SKILL.md +142 -0
  95. package/skills/rewardkit/SKILL.md +330 -0
  96. package/spec/atif/README.md +45 -0
  97. package/spec/atif/trajectory.schema.json +865 -0
  98. package/spec/openapi.yaml +13447 -0
@@ -0,0 +1,478 @@
1
+ # Streaming Events
2
+
3
+ Real-time output from `run()` and `executeCommand()`. For basic usage, see [Getting Started](./01-getting-started.md#streaming).
4
+
5
+ ---
6
+
7
+ ## Event Listeners
8
+
9
+ `Evolve` extends Node's `EventEmitter`. Subscribe to real-time output from `run()` and `executeCommand()`:
10
+
11
+ ```typescript
12
+ import { Evolve } from "@evolvingmachines/evolve";
13
+ import type { OutputEvent, LifecycleEvent } from "@evolvingmachines/evolve";
14
+
15
+ const evolve = new Evolve().withAgent({ type: "claude" });
16
+
17
+ // Parsed events (recommended)
18
+ evolve.on("content", (event: OutputEvent) => {
19
+ console.log(event.update.sessionUpdate, event.update);
20
+ });
21
+
22
+ // Lifecycle events (sandbox + agent state transitions)
23
+ evolve.on("lifecycle", (event: LifecycleEvent) => {
24
+ console.log(event.reason, event.sandbox, event.agent);
25
+ });
26
+
27
+ // Raw output (debugging)
28
+ evolve.on("stdout", (chunk: string) => process.stdout.write(chunk));
29
+ evolve.on("stderr", (chunk: string) => process.stderr.write(chunk));
30
+
31
+ await evolve.run({ prompt: "Hello" });
32
+ ```
33
+
34
+ | Event | Type | Description |
35
+ |-------|------|-------------|
36
+ | `content` | `OutputEvent` | Parsed ACP-style events (recommended) |
37
+ | `lifecycle` | `LifecycleEvent` | Sandbox and agent state transitions |
38
+ | `stdout` | `string` | Raw JSONL output |
39
+ | `stderr` | `string` | Error output |
40
+
41
+ ---
42
+
43
+ ## LifecycleEvent
44
+
45
+ ```typescript
46
+ evolve.on("lifecycle", (event: LifecycleEvent) => {
47
+ console.log(event.reason, event.sandboxId);
48
+ });
49
+ ```
50
+
51
+ ```typescript
52
+ interface LifecycleEvent {
53
+ sandboxId: string | null;
54
+ sandbox: SandboxLifecycleState; // "booting" | "error" | "ready" | "running" | "paused" | "stopped"
55
+ agent: AgentRuntimeState; // "idle" | "running" | "interrupted" | "error"
56
+ timestamp: string; // ISO 8601
57
+ reason: LifecycleReason;
58
+ browser?: {
59
+ liveUrl: string; // Live browser view URL
60
+ sessionId?: string; // Use with sessions().browserReplay()
61
+ sessionTag?: string; // Use to correlate checkpoints
62
+ };
63
+ }
64
+
65
+ type LifecycleReason =
66
+ | "browser_ready" // Managed browser live view is available
67
+ | "sandbox_boot" // Sandbox is being created
68
+ | "sandbox_ready" // Sandbox is ready for commands
69
+ | "sandbox_connected" // Reconnected to existing sandbox
70
+ | "sandbox_pause" // Sandbox suspended
71
+ | "sandbox_resume" // Sandbox resumed
72
+ | "sandbox_killed" // Sandbox destroyed
73
+ | "sandbox_error" // Sandbox setup failed
74
+ | "run_start" // Agent run started
75
+ | "run_complete" // Agent run finished successfully
76
+ | "run_interrupted" // Agent run was interrupted
77
+ | "run_failed" // Agent run failed (non-zero exit or error)
78
+ | "run_background_complete" // Background run finished successfully
79
+ | "run_background_failed" // Background run failed
80
+ | "command_start" // Shell command started
81
+ | "command_complete" // Shell command finished successfully
82
+ | "command_failed" // Shell command failed (non-zero exit)
83
+ | "command_interrupted" // Shell command was interrupted
84
+ | "command_background_complete" // Background command finished successfully
85
+ | "command_background_failed"; // Background command failed
86
+ ```
87
+
88
+ ---
89
+
90
+ ## OutputEvent
91
+
92
+ Top-level event structure:
93
+
94
+ ```typescript
95
+ interface OutputEvent {
96
+ sessionId?: string;
97
+ update: SessionUpdate;
98
+ /** The harness's own clock for this line, ISO 8601 (absent when the wire line has none). */
99
+ timestamp?: string;
100
+ /** The model the harness named for this line. */
101
+ model?: string;
102
+ /** The harness's id for the LLM message this line belongs to (claude, qwen). */
103
+ messageId?: string;
104
+ /** On a SUBAGENT's line: the parent's tool call that delegated to it. */
105
+ parentToolCallId?: string;
106
+ /** Other facts of the line under the harness's own key names (e.g. stop_reason). */
107
+ extra?: Record<string, unknown>;
108
+ }
109
+ ```
110
+
111
+ Everything beyond `update` is optional and comes straight from the wire line the update was parsed
112
+ from — a field the harness did not print is absent, never guessed. `timestamp` is the harness's
113
+ clock (claude, gemini, opencode and droid stamp every line; qwen and kimi stamp none); `model` is
114
+ the model named on the line, or on the harness's init line for gemini and droid; `messageId` lets you
115
+ tell which lines belong to one LLM message (claude prints one line per content block, all with the
116
+ same `message.id`); `parentToolCallId` is set only on a subagent's lines and names the `toolCallId`
117
+ of the `Task`/`agent` call that spawned it.
118
+
119
+ ---
120
+
121
+ ## SessionUpdate Types
122
+
123
+ Discriminated union on `sessionUpdate` field:
124
+
125
+ ```typescript
126
+ type SessionUpdate =
127
+ | AgentMessageChunk
128
+ | AgentThoughtChunk
129
+ | UserMessageChunk
130
+ | ToolCall
131
+ | ToolCallUpdate
132
+ | Plan
133
+ | AgentError
134
+ | AgentUsage;
135
+ ```
136
+
137
+ ### Message Events
138
+
139
+ | Type | `sessionUpdate` | Description |
140
+ |------|-----------------|-------------|
141
+ | `AgentMessageChunk` | `"agent_message_chunk"` | Text/image streaming from agent |
142
+ | `AgentThoughtChunk` | `"agent_thought_chunk"` | Reasoning (Codex) or thinking (Claude) |
143
+ | `UserMessageChunk` | `"user_message_chunk"` | User message echo (Gemini) |
144
+
145
+ ```typescript
146
+ interface AgentMessageChunk {
147
+ sessionUpdate: "agent_message_chunk";
148
+ content: ContentBlock;
149
+ }
150
+
151
+ interface AgentThoughtChunk {
152
+ sessionUpdate: "agent_thought_chunk";
153
+ content: ContentBlock;
154
+ }
155
+
156
+ interface UserMessageChunk {
157
+ sessionUpdate: "user_message_chunk";
158
+ content: ContentBlock;
159
+ }
160
+ ```
161
+
162
+ ### Tool Events
163
+
164
+ | Type | `sessionUpdate` | Description |
165
+ |------|-----------------|-------------|
166
+ | `ToolCall` | `"tool_call"` | Tool execution started |
167
+ | `ToolCallUpdate` | `"tool_call_update"` | Tool execution finished |
168
+
169
+ ```typescript
170
+ interface ToolCall {
171
+ sessionUpdate: "tool_call";
172
+ toolCallId: string;
173
+ title: string;
174
+ toolName?: string;
175
+ kind: ToolKind;
176
+ status: ToolCallStatus;
177
+ rawInput?: unknown;
178
+ content?: ToolCallContent[];
179
+ locations?: ToolCallLocation[];
180
+ }
181
+
182
+ interface ToolCallUpdate {
183
+ sessionUpdate: "tool_call_update";
184
+ toolCallId: string;
185
+ status?: ToolCallStatus;
186
+ title?: string;
187
+ content?: ToolCallContent[];
188
+ locations?: ToolCallLocation[];
189
+ /** The harness's own structured record of the result, verbatim (ACP's rawOutput). */
190
+ rawOutput?: unknown;
191
+ }
192
+ ```
193
+
194
+ `toolName` is the harness-native tool name, verbatim — `Bash`, `Read`, or the joined `mcp__<server>__<tool>` an MCP call carries. Prefer it over parsing `title`, which is formatted per tool for people to read and is not round-trippable; `toolName` is the identifier the model actually called. It is a deliberate addition to the ACP shape, which names no tool and whose `kind` collapses every MCP tool to `other`, and it is optional — absent on traces recorded before the SDK carried it, and on the occasional call a harness cannot name, so fall back to `kind` there.
195
+
196
+ `content` is the result text exactly as the harness sent it — a failed call's error text is not
197
+ wrapped in a code fence or prefixed; frame it in your own UI. `rawOutput` is the harness's
198
+ structured record of the same result when it prints one beyond the text: claude's
199
+ `tool_use_result` (`stdout`, `stderr`, `exitCode`, `interrupted`, or the file it wrote), codex's
200
+ completed item (`aggregated_output`, `exit_code`, `status`), opencode's tool state (`output`,
201
+ `metadata` with the exit code, `time`). Read an exit code from there rather than from prose.
202
+
203
+ ### Plan Event
204
+
205
+ | Type | `sessionUpdate` | Description |
206
+ |------|-----------------|-------------|
207
+ | `Plan` | `"plan"` | TodoWrite updates (replaces entire list) |
208
+ | `AgentError` | `"error"` | A failure the HARNESS reported. **Not agent work** — see below |
209
+ | `AgentUsage` | `"usage"` | Token accounting the HARNESS reported. **Not agent work** — see below |
210
+
211
+ ```typescript
212
+ interface Plan {
213
+ sessionUpdate: "plan";
214
+ entries: PlanEntry[];
215
+ }
216
+
217
+ interface PlanEntry {
218
+ content: string;
219
+ status: "pending" | "in_progress" | "completed";
220
+ priority: "high" | "medium" | "low";
221
+ }
222
+ ```
223
+
224
+ ---
225
+
226
+ ## Content Types
227
+
228
+ ```typescript
229
+ type ContentBlock = TextContent | ImageContent;
230
+
231
+ interface TextContent {
232
+ type: "text";
233
+ text: string;
234
+ }
235
+
236
+ interface ImageContent {
237
+ type: "image";
238
+ data: string; // Base64-encoded
239
+ mimeType: string; // "image/png", "image/jpeg"
240
+ uri?: string;
241
+ }
242
+ ```
243
+
244
+ ---
245
+
246
+ ## Tool Metadata Types
247
+
248
+ ### ToolKind
249
+
250
+ Tool category for UI icons:
251
+
252
+ ```typescript
253
+ type ToolKind =
254
+ | "read" // Read, NotebookRead
255
+ | "edit" // Edit, Write, NotebookEdit
256
+ | "delete" // (future)
257
+ | "move" // (future)
258
+ | "search" // Glob, Grep, LS
259
+ | "execute" // Bash, BashOutput, KillShell
260
+ | "think" // Task (subagent)
261
+ | "fetch" // WebFetch, WebSearch
262
+ | "switch_mode" // ExitPlanMode
263
+ | "other"; // Unknown or third-party MCP tools
264
+ ```
265
+
266
+ ### ToolCallStatus
267
+
268
+ ```typescript
269
+ type ToolCallStatus = "pending" | "in_progress" | "completed" | "failed";
270
+ ```
271
+
272
+ ### ToolCallLocation
273
+
274
+ ```typescript
275
+ interface ToolCallLocation {
276
+ path: string;
277
+ line?: number;
278
+ }
279
+ ```
280
+
281
+ ### ToolCallContent
282
+
283
+ ```typescript
284
+ type ToolCallContent =
285
+ | { type: "content"; content: ContentBlock }
286
+ | DiffContent;
287
+
288
+ interface DiffContent {
289
+ type: "diff";
290
+ path: string;
291
+ oldText: string | null;
292
+ newText: string;
293
+ }
294
+ ```
295
+
296
+ ---
297
+
298
+ ## Browser Automation Streaming
299
+
300
+ The full browser guide is [Configuration → Browser Automation](./02-configuration.md#browser-automation).
301
+ This section only documents the streaming fields for browser live view.
302
+
303
+ | Need | API | Use |
304
+ |------|-----|-----|
305
+ | Show live browser during a run | `lifecycle` event with `reason === "browser_ready"` | `event.browser.liveUrl` |
306
+ | Save the browser/session id | same lifecycle event | `event.browser.sessionId` |
307
+
308
+ ### Managed Browser
309
+
310
+ Managed browser sessions emit the live-view URL as soon as the browser is ready:
311
+
312
+ ```typescript
313
+ evolve.on("lifecycle", (event) => {
314
+ if (event.reason === "browser_ready" && event.browser) {
315
+ openLiveView(event.browser.liveUrl);
316
+ rememberSessionId(event.browser.sessionId);
317
+ }
318
+ });
319
+
320
+ const result = await evolve.run({ prompt: "QA the checkout flow" });
321
+ openLiveView(result.browser?.liveUrl);
322
+ ```
323
+
324
+ The same URL is also stored in trace metadata for replay or embedding after the trace exists:
325
+
326
+ ```typescript
327
+ type TraceMetadata = {
328
+ browser_session_id?: string;
329
+ dashboard_session_id?: string;
330
+ browser_session_tag?: string;
331
+ browser_live_url?: string;
332
+ };
333
+ ```
334
+
335
+ Use `event.browser.liveUrl` or `result.browser?.liveUrl` for immediate UI display.
336
+ For replay after cleanup, use the `sessionId` with `sessions().browserReplay()`;
337
+ the full example lives in [Configuration → Browser Automation](./02-configuration.md#browser-automation).
338
+
339
+ ---
340
+
341
+ ## UI Integration Example
342
+
343
+ ```typescript
344
+ import type { OutputEvent } from "@evolvingmachines/evolve";
345
+
346
+ function handleEvent(event: OutputEvent): void {
347
+ const { update } = event;
348
+
349
+ switch (update.sessionUpdate) {
350
+ case "agent_message_chunk":
351
+ if (update.content.type === "text") {
352
+ ui.appendMessage(update.content.text);
353
+ } else {
354
+ ui.appendImage(update.content.data, update.content.mimeType);
355
+ }
356
+ break;
357
+
358
+ case "agent_thought_chunk":
359
+ ui.appendThought(update.content);
360
+ break;
361
+
362
+ case "user_message_chunk":
363
+ // Gemini echo - typically ignored
364
+ break;
365
+
366
+ case "tool_call":
367
+ ui.addTool({
368
+ id: update.toolCallId,
369
+ title: update.title,
370
+ kind: update.kind,
371
+ status: update.status,
372
+ locations: update.locations,
373
+ });
374
+ break;
375
+
376
+ case "tool_call_update":
377
+ ui.updateTool(update.toolCallId, {
378
+ status: update.status,
379
+ content: update.content,
380
+ });
381
+ break;
382
+
383
+ case "plan":
384
+ ui.renderPlan(update.entries);
385
+ break;
386
+ }
387
+ }
388
+
389
+ evolve.on("content", handleEvent);
390
+ ```
391
+
392
+ ---
393
+
394
+ ## Key Patterns
395
+
396
+ 1. **Handle all 6 event types** — Don't silently drop unknown events
397
+ 2. **Match tools by ID** — `tool_call` and `tool_call_update` share `toolCallId`
398
+ 3. **Handle out-of-order** — `tool_call_update` may arrive before `tool_call`
399
+ 4. **Concatenate chunks** — Message text arrives incrementally
400
+ 5. **Support images** — `ContentBlock` includes `ImageContent`
401
+ 6. **Use `kind` for icons** — Categorize tools visually (read, edit, execute, etc.)
402
+ 7. **Identify tools by `toolName`** — The harness-native name, not the human-readable `title`; fall back to `kind` when it is absent
403
+ 8. **Track `locations`** — Show affected file paths in UI
404
+
405
+ ---
406
+
407
+
408
+ ## Harness-reported failures (`error`)
409
+
410
+ A harness can fail without the process dying, and it reports that on the same stream it uses for
411
+ output. Codex, for example, writes `{"type":"error"}` while it retries and `{"type":"turn.failed"}`
412
+ when a turn gives up — on **stdout**, while stderr says only `Reading prompt from stdin...`. Those
413
+ are surfaced as their own update so a transcript shows what actually happened:
414
+
415
+ ```typescript
416
+ interface AgentError {
417
+ sessionUpdate: "error";
418
+ /** The harness's own message, verbatim. */
419
+ message: string;
420
+ /** True when the harness treated it as terminal for the turn. */
421
+ fatal: boolean;
422
+ }
423
+ ```
424
+
425
+ **It is deliberately not a message chunk.** If you are counting "did the agent do any work",
426
+ an error must not count — otherwise a run that never reached the model looks like a run that
427
+ produced output. Use the exported predicate rather than writing the check yourself:
428
+
429
+ ```typescript
430
+ import { isAgentWorkUpdate } from "@evolvingmachines/evolve";
431
+
432
+ const didWork = events.some((e) => isAgentWorkUpdate(e.update));
433
+ ```
434
+
435
+ ## Harness-reported usage (`usage`)
436
+
437
+ Every harness prints its own token accounting on the stream, and it arrives as its own update so
438
+ you can meter a run without reading the raw JSON: claude and qwen print each LLM message's usage,
439
+ opencode prints each step's tokens and cost, and codex, gemini, claude, qwen and droid print a
440
+ whole-run total on their terminal line. Kimi's stream-json prints no usage at all, so a kimi run
441
+ simply has no `usage` events.
442
+
443
+ ```typescript
444
+ interface AgentUsage {
445
+ sessionUpdate: "usage";
446
+ /** "call": one LLM inference. "run": the harness's total for the whole run. */
447
+ scope: "call" | "run";
448
+ usage: TokenUsage;
449
+ }
450
+
451
+ interface TokenUsage {
452
+ promptTokens?: number; // input INCLUDING the cached and cache-written shares
453
+ completionTokens?: number;
454
+ cachedTokens?: number; // the cache-read share of promptTokens
455
+ costUsd?: number; // only when the harness priced it (claude's total_cost_usd)
456
+ extra?: Record<string, unknown>; // the harness's other counters, its own key names verbatim
457
+ }
458
+ ```
459
+
460
+ The names are Harbor's ATIF `Metrics` fields, so a trajectory copies them without renaming. A
461
+ counter the harness did not print is absent, never `0`. Two things to know when you sum:
462
+
463
+ - A `"call"` event repeats for every line of the same `messageId` (claude prints one line per
464
+ content block, each with the message's running usage) — keep the **last** one per `messageId`,
465
+ then add across messages.
466
+ - A `"run"` event is the harness's own total, reported once at the end; it is not another call.
467
+
468
+ ```typescript
469
+ const perMessage = new Map<string, TokenUsage>();
470
+ for (const e of events) {
471
+ if (e.update.sessionUpdate !== "usage" || e.update.scope !== "call") continue;
472
+ perMessage.set(e.messageId ?? `line-${perMessage.size}`, e.update.usage);
473
+ }
474
+ const promptTokens = [...perMessage.values()].reduce((n, u) => n + (u.promptTokens ?? 0), 0);
475
+ ```
476
+
477
+ Like `error`, `usage` is **not agent work**: `isAgentWorkUpdate` answers `false` for it, so a
478
+ stream that carries only accounting still counts as a run that did nothing.