@workerdeck/protocol 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.d.mts CHANGED
@@ -20,8 +20,32 @@ type SessionState = 'attention' | 'working' | 'idle' | 'ended';
20
20
  declare const STATE_ORDER: readonly SessionState[];
21
21
  declare const STATE_LABELS: Record<SessionState, string>;
22
22
  declare function sessionState(info: SessionInfo): SessionState;
23
+ /**
24
+ * The sub-agents a list row draws as live.
25
+ *
26
+ * `sessionState` deliberately does **not** grow a `subagents` bucket — a fifth
27
+ * state would split `working` in two for every client that filters by it,
28
+ * including the ones that have not shipped this yet. Instead `working` *counts*
29
+ * them: a synchronous `Task` keeps the turn in flight so the status already
30
+ * says `working`, and a **background** agent — which outlives its turn on
31
+ * purpose — is the carve-out the extra arm in `sessionState` exists for.
32
+ * That is what makes "sub-agents are an annotation on a working row" true
33
+ * rather than assumed: the row is in the working bucket whichever kind is
34
+ * running, and this list only says more about it.
35
+ */
36
+ declare function runningSubagents(info: SessionInfo): SubagentInfo[];
37
+ /**
38
+ * A sub-agent's identity on one line: `Explore · find the auth check`.
39
+ *
40
+ * The same two fields `taskLabel` builds its transcript row from, minus the
41
+ * `Task(…)` wrapper — a list row is already inside a session, so naming the tool
42
+ * spends the width that the description needs. Falls back to the bare agent type,
43
+ * then to a generic word: a row with no label at all reads as a rendering bug,
44
+ * and an engine is free to send neither field.
45
+ */
46
+ declare function subagentLabel(sub: SubagentInfo): string;
23
47
  /** The facets a session can be grouped or sorted by. */
24
- type Facet = 'gateway' | 'adapter' | 'state';
48
+ type Facet = 'gateway' | 'adapter' | 'state' | 'project';
25
49
  type GroupBy = 'none' | Facet;
26
50
  type SortBy = 'recent' | 'name' | Facet;
27
51
  type ViewConfig = {
@@ -29,6 +53,16 @@ type ViewConfig = {
29
53
  gateways: string[];
30
54
  adapters: string[];
31
55
  states: SessionState[];
56
+ /**
57
+ * Empty = no filter. Keys are {@link projectKey} output — never names, which
58
+ * are neither unique (two repos both called "api") nor stable (editing
59
+ * `.workerdeck.json` renames every session at once and must not empty a
60
+ * saved filter). Optional, unlike its three siblings, because stored view
61
+ * configs predate it: a config restored from `localStorage`/`globalState`
62
+ * without the key must keep filtering, so absent and empty mean the same
63
+ * thing.
64
+ */
65
+ projects?: string[];
32
66
  /** Show only sessions inside the host's own folders. Inert where there is no
33
67
  * such notion (no folder open, a dashboard with no workspace), which is why it
34
68
  * can default on. */
@@ -74,7 +108,51 @@ type SessionGroup = {
74
108
  /** The adapters actually present, for the filter chips — derived rather than
75
109
  * enumerated, so a new engine needs no change here. */
76
110
  declare function adaptersOf(rows: readonly SessionRow[]): string[];
111
+ /**
112
+ * The projects actually present, as `{ key, label }` for a filter control —
113
+ * derived like {@link adaptersOf}, and paired because the two halves differ:
114
+ * the *key* is what {@link ViewConfig.projects} holds (gateway-qualified root,
115
+ * so a rename regroups nothing) and the *label* is what a person picks by.
116
+ *
117
+ * Sorted by label, deduped by key. Two projects with the same name on two
118
+ * gateways therefore stay two entries wearing one word — which is honest: they
119
+ * really are two different directories, and the alternative is a filter that
120
+ * silently selects both.
121
+ */
122
+ declare function projectsOf(rows: readonly SessionRow[]): {
123
+ key: string;
124
+ label: string;
125
+ }[];
77
126
  declare function sessionLabel(info: SessionInfo): string;
127
+ /**
128
+ * The project facet's grouping key: gateway id + the project root, falling
129
+ * back to the session's cwd when no project is declared.
130
+ *
131
+ * The root and not the name, because a name is not a key (two repos can both
132
+ * be called "api", and a rename must regroup nothing); qualified by gateway,
133
+ * because a remote gateway's identical-looking path is another machine's
134
+ * directory — the same rule `ScopeRoot` states. The cwd fallback is what makes
135
+ * grouping by project useful before anyone has written a `.workerdeck.json`:
136
+ * undeclared sessions group by their folder, declared ones by their root, and
137
+ * a session in `packages/ui` joins its repo's group the moment the file
138
+ * exists. Sessions with no cwd at all (a filesystem-less engine) share one
139
+ * per-gateway bucket — see {@link projectLabel}.
140
+ */
141
+ declare function projectKey(row: SessionRow): string;
142
+ /**
143
+ * What a project group (or a row's project slot) is called: the declared name,
144
+ * else the cwd's basename — the exact string clients rendered before this
145
+ * feature existed, so an undeclared project looks like today. 'No project' is
146
+ * only ever the no-cwd case (a sandboxed provider session), where there is no
147
+ * folder to name.
148
+ *
149
+ * Takes only the `info` it reads, so a surface holding a bare `SessionInfo` —
150
+ * a row component, an iOS cell — can call it without inventing the rest of a
151
+ * `SessionRow`. That matters more than it looks: this string is what a client
152
+ * renders *in place of* the cwd basename it used to draw, and two spellings of
153
+ * it would put the list and its group headers on different names.
154
+ */
155
+ declare function projectLabel(row: Pick<SessionRow, 'info'>): string;
78
156
  /**
79
157
  * This session is a job run — the queue created it, and `JobInfo.sessionId`
80
158
  * points at it.
@@ -329,7 +407,127 @@ type ToolResultBlock = {
329
407
  [key: string]: unknown;
330
408
  }>;
331
409
  is_error?: boolean;
410
+ /**
411
+ * This block carries only the **head** of the result: the replay truncated it
412
+ * (see {@link TOOL_RESULT_HEAD_CHARS}), and the whole thing is one fetch away
413
+ * at `GET /sessions/:id/events/:seq/result?toolUseId=`.
414
+ *
415
+ * On the **block**, never the event, and that is the same argument
416
+ * `user_message.patch` has to make in reverse: the patch sits on the event and
417
+ * its doc must therefore caveat "only when the message carries exactly one
418
+ * `tool_result` block — with two, nothing says which one it belongs to". A
419
+ * message answering three calls truncates whichever of them is large, so
420
+ * paying that caveat a second time would make the marker unusable exactly
421
+ * when it matters. {@link FilePatch.truncated} is the shipped precedent.
422
+ *
423
+ * Only ever set on a **replay** a client asked for (`truncateResults`), so a
424
+ * client that has never heard of this field cannot receive one — which is why
425
+ * this is additive at protocol 7 rather than a bump. Absent means the block is
426
+ * whole.
427
+ */
428
+ truncated?: boolean;
429
+ /** How many characters the untruncated result had. Set iff `truncated`.
430
+ *
431
+ * A client cannot compute it — it holds the head — and the number is not
432
+ * cosmetic: a collapsed row spells "… +N chars", and `height.ts` sizes the row
433
+ * by wrapping **that exact string**, so a count derived from the head would be
434
+ * both a lie and a different pixel height. */
435
+ total_chars?: number;
332
436
  };
437
+ /**
438
+ * How much of a tool result a truncating replay keeps.
439
+ *
440
+ * Chosen against the two clients' *own* budgets, and the relationship is the
441
+ * whole point: the terminal theme shows ~400 characters collapsed and ~2,000
442
+ * open, so at 8,000 the collapsed and open states are **byte-identical to an
443
+ * untruncated attach** and only the uncapped "show everything" press ever
444
+ * fetches. That collapses the entire feature to one press, and it is asserted
445
+ * in a test rather than trusted — lowered below the open budget, this would
446
+ * silently clip the open state with no marker, which is the one failure this
447
+ * design must not have.
448
+ *
449
+ * Measured justification: on one 1,270-row session three `tool_result` frames
450
+ * were 641 / 463 / 396 KB, 68% of a 3.1 MB attach. The cut is *structural* —
451
+ * proportional to the thing that is actually large, wherever in the log it sits
452
+ * — which a row window is not.
453
+ */
454
+ declare const TOOL_RESULT_HEAD_CHARS = 8000;
455
+ /**
456
+ * A base64 image part, delivered as an address instead of its bytes.
457
+ *
458
+ * The **seventh** rule of the family, and the first written *after* its
459
+ * measurement rather than before it. Across 214 local sessions, 91% of all
460
+ * tool-result payload is base64 image data — 489 MB against 44 MB of text — and
461
+ * **no client renders a byte of it**: `blockText` in the reducer and
462
+ * `joinedText` on iOS both fold a `tool_result` to its text parts, and both
463
+ * clients draw a tool's picture from a host *path* (`savedPath` → `/produced`,
464
+ * `/fs/read`), never from block content. So it is `replayRetains`' argument at
465
+ * nine times the size of the case that rule was written for: bytes whose entire
466
+ * effect on the reader is `return base`.
467
+ *
468
+ * A **new part type rather than a hollowed-out `image`**, and that is the one
469
+ * judgement here worth stating. `headOf`'s shape-preservation rule — "a
470
+ * truncation is a shorter result, never a different kind of one" — cuts the
471
+ * other way for pixels: a head *is* a valid shorter text, but an image with no
472
+ * bytes is not a smaller image, and spelling it `{ type: 'image', source }` with
473
+ * no `data` invites precisely the failure shape-preservation exists to prevent,
474
+ * a renderer that trusts `source.data` drawing `data:;base64,undefined`. An
475
+ * unfamiliar type instead falls through every fold that already exists, exactly
476
+ * as the CLI's own `tool_reference` part does: no `text`, so it contributes
477
+ * nothing, and an unaware consumer renders what it renders today, which is
478
+ * nothing. That is this family's safe failure.
479
+ *
480
+ * Only ever produced for a socket that asked (`imageRefs`), so a client that has
481
+ * never heard of this type cannot receive one — which is why this is additive at
482
+ * protocol 7, the same argument {@link ToolResultBlock.truncated} makes. Unlike
483
+ * truncation it applies to **live events as well as replays**: the client's one
484
+ * render path is ref-then-fetch, so bytes on a live event would either be
485
+ * discarded (335 KB median, once per attached watcher) or need a second
486
+ * decode-from-event path pinning megabytes inside the transcript cache — the
487
+ * disease relocated rather than cured.
488
+ */
489
+ type ImageRefPart = {
490
+ type: 'image_ref';
491
+ /** The stored part's own media type (`image/png`, `image/jpeg` and
492
+ * `image/webp` are the three observed), or `application/octet-stream` when it
493
+ * had none. Never the membership test — that is `image` plus a base64 source. */
494
+ media_type: string;
495
+ /** Decoded size, which a client cannot compute from an address it has not
496
+ * fetched yet. Not cosmetic: the placeholder spells it, and in the terminal
497
+ * theme a rendered string *is* a row height. */
498
+ bytes: number;
499
+ /**
500
+ * Index of this part in the **stored** block's content array, and the address
501
+ * a fetch is made with.
502
+ *
503
+ * A stamped field rather than the position it arrives at, because that
504
+ * position is not stable: `headOf` builds a truncated head by keeping text
505
+ * parts up to budget and dropping every other part, so a block that is both
506
+ * over the text budget and image-bearing has its parts renumbered the moment
507
+ * the two rules compose. Stamped, the address survives any later reshaping —
508
+ * and the route verifies it against the stored block rather than trusting it.
509
+ */
510
+ part_index: number;
511
+ };
512
+ /**
513
+ * Project one `tool_result` content part onto its {@link ImageRefPart}, or
514
+ * `undefined` when the part is not a base64 image and must be delivered as it
515
+ * stands.
516
+ *
517
+ * The rule's **one spelling**, shared by the transform that replaces parts
518
+ * (core), the route that serves them back (server) and the property test that
519
+ * proves the fold is otherwise unchanged (react) — the same reason every other
520
+ * member of this family lives here rather than in whichever package applies it.
521
+ *
522
+ * Deliberately narrow. The corpus holds exactly two non-text part kinds: this
523
+ * one, and the CLI's `tool_reference`, of which every instance across 214
524
+ * sessions totals 122 KB. A "drop non-text parts" rule would sweep those in for
525
+ * no measurable gain, and narrowness is this family's standing habit.
526
+ */
527
+ declare function imagePartRef(part: {
528
+ type?: string;
529
+ [key: string]: unknown;
530
+ }, index: number): ImageRefPart | undefined;
333
531
  /** Forward-compatible fallback for block types this protocol version doesn't model. */
334
532
  type UnknownBlock = {
335
533
  type: string;
@@ -1301,6 +1499,178 @@ type CreateSessionRequest = {
1301
1499
  */
1302
1500
  scope?: Record<string, string>;
1303
1501
  };
1502
+ /**
1503
+ * One sub-agent (a `Task` call and the sidechain it spawned), as a *list* surface
1504
+ * sees it — without attaching.
1505
+ *
1506
+ * Sub-agent work is otherwise attach-only: it exists on the wire as
1507
+ * `parentToolUseId` on three event bodies, and is reconstructed into rows by the
1508
+ * react reducer and grouped per-Task by `terminalBlocks`. A sessions list never
1509
+ * attaches (one live attach per session, owned by the panel), so it reads
1510
+ * `SessionInfo` over REST and would otherwise have no way to know a session has
1511
+ * six agents running inside one turn.
1512
+ *
1513
+ * This is a **runner-owned rollup computed at read time**, exactly like
1514
+ * {@link SessionInfo.pendingPermissionCount}: it is not an event, it is not
1515
+ * persisted separately, and it therefore rides the REST list, the WS attach
1516
+ * snapshot and parking snapshots for free. Only the claude engine produces it —
1517
+ * codex and provider emit `parentToolUseId: null` on every event, so an empty
1518
+ * list there is the truth rather than a gap.
1519
+ *
1520
+ * It is deliberately **not** the input to `taskSummary`. That string is spelled
1521
+ * from the absorbed transcript items and must stay that way, so a transcript
1522
+ * replayed tomorrow spells the same line from the same items it holds today.
1523
+ */
1524
+ type SubagentInfo = {
1525
+ /** The `tool_use` id of the `Task` call that spawned it — the same id its
1526
+ * nested events carry as `parentToolUseId`, and therefore the handle a client
1527
+ * uses to jump to that Task's row. */
1528
+ toolUseId: string; /** The Task input's `subagent_type` (e.g. "Explore"), when it named one. */
1529
+ agentType?: string;
1530
+ /** The Task input's short `description`, clipped by the runner. Together with
1531
+ * `agentType` this is what makes two parallel sub-agents tell apart in a list;
1532
+ * a row reading only `Task` answers nothing. */
1533
+ description?: string;
1534
+ /**
1535
+ * `running` until the Task's own `tool_result` arrives, then `done`/`failed`
1536
+ * from that result's `is_error`. A turn that ends without that result — an
1537
+ * interrupt, a session error, a turn or budget cap — settles what is still
1538
+ * running as `failed`: the report never came, which is the one thing `done`
1539
+ * could have claimed, and a `running` badge on an idle session would be a
1540
+ * lie a list re-renders at every poll.
1541
+ *
1542
+ * Deliberately **narrower than `taskFailed`** in `@workerdeck/ui`'s
1543
+ * `tool-run.ts`, which reddens a Task row when *any child call* failed. That is
1544
+ * right for a transcript row the reader can expand — the failure is one press
1545
+ * away and hiding it would be worse. It is wrong for a list: a grep that
1546
+ * matched nothing inside an otherwise successful Explore agent would put
1547
+ * `failed` beside the session's name with nothing to open. So this reports the
1548
+ * sub-agent's own outcome. If you are here to "fix" the inconsistency, this is
1549
+ * the reason it exists.
1550
+ */
1551
+ status: 'running' | 'done' | 'failed'; /** Epoch ms the `Task` call was emitted. */
1552
+ startedAt: number;
1553
+ /** Tool calls the sub-agent has made so far — its progress reading while
1554
+ * running, counted from nested `tool_use` blocks. */
1555
+ toolCount: number;
1556
+ };
1557
+ /**
1558
+ * How many *settled* sub-agents {@link SessionInfo.subagents} keeps behind the
1559
+ * running ones. Small on purpose: the point of the tail is that a list row does
1560
+ * not go blank the instant a run finishes, not that it is a history.
1561
+ */
1562
+ declare const SUBAGENT_HISTORY = 8;
1563
+ /**
1564
+ * A project's icon, as declared by its `.workerdeck.json` — either a named
1565
+ * glyph or a reference to an image the gateway serves.
1566
+ *
1567
+ * A discriminated union rather than one stringly field, because the two arms
1568
+ * have opposite render paths: a glyph is looked up in the client's own icon
1569
+ * set with no I/O, an image is a fetch. Collapsing them would put "is this a
1570
+ * name or an address" back on every renderer, which is the inference this
1571
+ * family keeps refusing (`ImageRefPart` is a new part type, never a
1572
+ * hollowed-out `image`, for the same reason).
1573
+ *
1574
+ * `glyph.name` is a lucide icon name, validated by the gateway for *shape*
1575
+ * only (lowercase kebab-case): the gateway has no lucide catalog and must not
1576
+ * grow one — icon sets version independently of this protocol. A client whose
1577
+ * set lacks the name renders its no-project fallback rather than erroring;
1578
+ * an unknown name is a stale row, never withheld state.
1579
+ *
1580
+ * `image` carries an **address, never bytes** — the attachment-bytes rule.
1581
+ * `SessionInfo` rides every row of `GET /sessions`, which clients poll at 1.2s
1582
+ * while anything is working, so an inlined base64 icon would be paid for on
1583
+ * every poll of every session forever (the same argument that keeps
1584
+ * `originalFile` off {@link FilePatch} and message bytes off events). The
1585
+ * bytes come from `GET {basePath}/sessions/:id/project/icon` — session-scoped
1586
+ * on purpose, so the fetch rides the same `canSee` gate as every other
1587
+ * `/sessions/:id/*` route and a scoped principal's miss is the uniform 404. A
1588
+ * project-keyed route would need the project root in the URL, and a route
1589
+ * addressed by host paths is an existence oracle for the gateway's
1590
+ * filesystem. `hash` (sha256 hex of the bytes) is the cross-session cache
1591
+ * key: two sessions in one project serve identical bytes, so a client caches
1592
+ * by hash rather than by URL and fetches once per project, not once per
1593
+ * session. The route answers with `ETag: "<hash>"` and honors
1594
+ * `If-None-Match`.
1595
+ */
1596
+ type ProjectIcon = {
1597
+ type: 'glyph';
1598
+ name: string;
1599
+ } | {
1600
+ type: 'image';
1601
+ mediaType: 'image/png' | 'image/svg+xml';
1602
+ hash: string;
1603
+ };
1604
+ /**
1605
+ * Project identity for a session — what a `.workerdeck.json` in the session's
1606
+ * ancestry declares, resolved by the **gateway** and shipped on
1607
+ * {@link SessionInfo.project}.
1608
+ *
1609
+ * The gateway reads the file, not each client: the iOS app and a browser
1610
+ * pointed at a remote gateway have no access to that filesystem, so a
1611
+ * per-client reader would make the feature exist on exactly one client.
1612
+ * Discovery is an ancestor walk from the session's `cwd` upward — nearest
1613
+ * `.workerdeck.json` wins, so a session started in `packages/ui` still says
1614
+ * "WorkerDeck" — over the *realpath'd* cwd, which is what makes `root`
1615
+ * canonical below.
1616
+ *
1617
+ * The file's schema, stated here because this type is its wire projection
1618
+ * (clients never read the file; the gateway is its only parser):
1619
+ *
1620
+ * ```json
1621
+ * { "name": "WorkerDeck", "icon": "layers" }
1622
+ * { "name": "WorkerDeck", "icon": "./docs/assets/icon.png" }
1623
+ * ```
1624
+ *
1625
+ * Both keys optional, unknown keys ignored (forward compatibility). An empty
1626
+ * `{}` still marks its directory as the project root — grouping is the point,
1627
+ * and the name falls back to the root's basename. `icon` is one string with a
1628
+ * total classification rule: a value ending in `.png`/`.svg`
1629
+ * (case-insensitive) is a repo-relative image path — relative only, since the
1630
+ * file is checked into a repo that clones onto other machines, where an
1631
+ * absolute path is wrong by construction — and anything else must be a
1632
+ * lucide-shaped glyph name (`^[a-z0-9]+(-[a-z0-9]+)*$`) or it is ignored. The
1633
+ * two shapes cannot collide (a glyph name contains no dot), so the rule is a
1634
+ * classification, not a guess. Every degradation degrades *fieldwise and
1635
+ * silently*: a malformed or oversized file is skipped and the walk continues
1636
+ * to an ancestor (a broken nested file must not shadow the repo root's valid
1637
+ * one), a junk name falls back to the basename, a junk or escaping icon is
1638
+ * dropped — a session must never fail, or even warn, because of a display
1639
+ * declaration.
1640
+ *
1641
+ * `root` — the canonical (realpath'd) absolute directory holding the file, on
1642
+ * the **gateway's** filesystem — is the grouping key: two sessions are in the
1643
+ * same project iff same root *on the same gateway* (a remote gateway's
1644
+ * identical-looking path is another machine's directory — the `ScopeRoot`
1645
+ * argument). A *name* is not a key: two repos can both be called "api".
1646
+ * Canonicalizing at discovery is what makes two differently-spelled cwds of
1647
+ * one project agree on it.
1648
+ *
1649
+ * Resolved at **serve time** from a TTL cache, never persisted — the same
1650
+ * placement argument as the profile tracker's 0%-after-reset inference: it is
1651
+ * a function of the gateway's current filesystem, and a copy captured into a
1652
+ * parking record would replay a stale name forever. Editing the file shows up
1653
+ * on every session in the project within the TTL, with no migration and no
1654
+ * event.
1655
+ *
1656
+ * Additive at protocol **7**: an optional field on `SessionInfo`, where
1657
+ * absent means exactly what today's wire means (no project declared — render
1658
+ * the folder basename), an old client ignores it, and a new client against an
1659
+ * old gateway sees absent. The icon route is likewise unreachable by
1660
+ * accident: a client only fetches it when this gateway told it an image
1661
+ * exists.
1662
+ */
1663
+ type ProjectInfo = {
1664
+ /** Display name — the file's `name`, else the root's basename. Never empty. */name: string;
1665
+ /** Canonical absolute path of the directory holding `.workerdeck.json`, on
1666
+ * the gateway's filesystem. The grouping key (per gateway); an opaque string
1667
+ * to clients beyond equality and display. */
1668
+ root: string;
1669
+ /** Absent = the file declared none (or declared one the gateway refused —
1670
+ * malformed, escaping, oversized — which a client cannot and must not
1671
+ * distinguish). */
1672
+ icon?: ProjectIcon;
1673
+ };
1304
1674
  type SessionInfo = {
1305
1675
  /** Server-assigned id (stable across SDK session forks/resumes). */id: string; /** Underlying Agent SDK session id, once known; use for `resume`. */
1306
1676
  sdkSessionId?: string;
@@ -1331,6 +1701,22 @@ type SessionInfo = {
1331
1701
  createdAt: number; /** Highest event seq emitted so far; attach with `afterSeq` to catch up. */
1332
1702
  lastSeq: number;
1333
1703
  pendingPermissionCount: number;
1704
+ /**
1705
+ * Sub-agents this session has running, plus a short tail of settled ones — see
1706
+ * {@link SubagentInfo}. Absent on an engine that has no sidechains and on an
1707
+ * older server; **absent and empty mean the same thing to a client**, so render
1708
+ * nothing rather than "0 sub-agents".
1709
+ *
1710
+ * Bounded on purpose. This rides every row of `GET /sessions`, which a busy
1711
+ * client polls at 1.2s, and it is captured into parking snapshots — the same
1712
+ * attachment-bytes rule that keeps whole files off {@link FilePatch}. Every
1713
+ * *running* sub-agent is always present (they are the live reading and there
1714
+ * are never many at once); settled ones are kept newest-first to
1715
+ * {@link SUBAGENT_HISTORY} and then dropped, so a day-long session with two
1716
+ * hundred Tasks does not grow an unbounded field. A client must therefore not
1717
+ * treat this as the session's full Task history — the transcript is that.
1718
+ */
1719
+ subagents?: SubagentInfo[];
1334
1720
  meta?: Record<string, unknown>; /** Display title: `meta.title` if the host set one, else derived (e.g. first prompt). */
1335
1721
  title?: string; /** Cumulative cost across all turns so far (sum of turn_result totals). */
1336
1722
  totalCostUsd?: number; /** Cumulative turn count across the session. */
@@ -1359,6 +1745,15 @@ type SessionInfo = {
1359
1745
  * {@link CreateSessionRequest.scope}. Echoed by the runner, re-stamped by the
1360
1746
  * gateway, and never editable. */
1361
1747
  scope?: Record<string, string>;
1748
+ /**
1749
+ * Project identity discovered from the session's `cwd` — see
1750
+ * {@link ProjectInfo}. Stamped by the **gateway at serve time** (runners
1751
+ * never set it; a runner-echoed value would be persisted into parking
1752
+ * records and replay a stale name forever). Absent = no `.workerdeck.json`
1753
+ * in the cwd's ancestry, and also = an older gateway: both mean "render the
1754
+ * folder basename", which is exactly today's behaviour.
1755
+ */
1756
+ project?: ProjectInfo;
1362
1757
  };
1363
1758
  /**
1364
1759
  * How many transcript rows an event materializes — the unit behind
@@ -1450,6 +1845,93 @@ declare function transcriptContent(body: SessionEventBody): boolean;
1450
1845
  * on a blank panel forever if a coalescer could swallow the final event.
1451
1846
  */
1452
1847
  declare function replayCoalesceKey(body: SessionEventBody): string | undefined;
1848
+ /**
1849
+ * Does a **replay** have to deliver this event, or may it be dropped outright?
1850
+ *
1851
+ * The fifth of the family, and the closest relative of {@link snapshotRetains} —
1852
+ * the same claim ("no client can tell") pointed at the wire instead of at a
1853
+ * store. The difference from {@link replayCoalesceKey} is that this is not
1854
+ * last-write-wins: there is nothing to keep. These are events the reducer reads
1855
+ * and *discards*, so a replay that sends them is spending the reader's network
1856
+ * on frames whose whole effect is `return base`.
1857
+ *
1858
+ * Today that is exactly one thing, and it is the second-largest item in a real
1859
+ * attach: the `stream_delta`s the reducer does not model. Measured over one
1860
+ * 1,270-row session, the delta run was 774 KB, and **~85% of it was frames the
1861
+ * reducer throws away** — `input_json_delta` (a tool call's arguments, streamed
1862
+ * character by character, 383 KB), `signature_delta` (encrypted-thinking
1863
+ * signatures, 153 KB) and the `message_start`/`content_block_start`/`_stop`
1864
+ * scaffolding (244 KB). The reducer models two delta kinds, `text_delta` and
1865
+ * `thinking_delta`; everything else falls through its switch untouched.
1866
+ *
1867
+ * What is deliberately **not** dropped, though the arithmetic would allow it:
1868
+ *
1869
+ * - `thinking_delta` — the Claude SDK delivers thinking blocks whose `thinking`
1870
+ * is `''`, and the reducer backfills them from the accumulated streamed text
1871
+ * (`streamedThinking`). Dropping these erases every thought from a replayed
1872
+ * transcript. This is the same carve-out `snapshotRetains` documents, and it
1873
+ * is the reason that rule is provider-engine-only.
1874
+ * - `text_delta` — superseded by the `assistant_message` that follows it, which
1875
+ * filters the streaming id and rebuilds from the full content blocks. It could
1876
+ * go, but only with a lookahead proving the message arrived, and at 24 KB in
1877
+ * the measured session it is not worth a rule that has to be right about
1878
+ * supersession. A merge is likewise not worth it: a *drop* needs no synthesized
1879
+ * event and therefore no invented seq.
1880
+ *
1881
+ * A live event is never affected — this is about the buffered replay alone — and
1882
+ * the caller must never drop the log's highest-seq event whatever this says, for
1883
+ * the reason {@link replayCoalesceKey} gives: the replay hold waits for
1884
+ * `state.lastSeq` to reach the attach's `session.lastSeq` and would hang on a
1885
+ * blank panel forever.
1886
+ *
1887
+ * The property is the family's usual one and is a test rather than an argument:
1888
+ * folding the full log and the retained log through `applyEvent` yields
1889
+ * identical state (`packages/react/test/replay-retain.test.ts`).
1890
+ */
1891
+ declare function replayRetains(body: SessionEventBody): boolean;
1892
+ /**
1893
+ * Does a `RunnerSnapshot` keep this event in its persisted log?
1894
+ *
1895
+ * The fourth of the same family, and the same shape of claim as
1896
+ * {@link replayCoalesceKey}: which events a *store* may drop without any client
1897
+ * being able to tell. It exists because a snapshot embeds the whole event log,
1898
+ * and a log is mostly stream deltas — a four-character token rides a ~180-byte
1899
+ * JSON envelope, so the delta run is tens of times the size of the text it
1900
+ * spells, sitting on disk *beside* the `assistant_message` that respells it in
1901
+ * full. That was affordable while a snapshot was written once, at a park. It is
1902
+ * not affordable written after every turn, which is what restart-survival needs.
1903
+ *
1904
+ * So: everything is retained except `stream_delta`. The reason that is safe is
1905
+ * not that deltas are unimportant but that they are **superseded by
1906
+ * construction**. The reducer upserts them under one constant id and the
1907
+ * following `assistant_message` filters exactly that id out and rebuilds from
1908
+ * the full content blocks — and a snapshot may only be taken at a rest point,
1909
+ * where the stream loop has exited and flushed. Both exits flush, including the
1910
+ * error path: an interrupted turn pushes its half-finished buffers into a
1911
+ * durable `assistant_message` before it emits the failed `turn_result`. There is
1912
+ * no rest state in which a delta is the only record of anything.
1913
+ *
1914
+ * **Provider engine only**, and this is the carve-out that must not be lost:
1915
+ * against a *Claude* log the rule would be wrong. The Claude SDK delivers
1916
+ * thinking blocks whose text is `''`, with the human-readable summary existing
1917
+ * only in the delta stream, and the reducer carries the streamed text over to
1918
+ * fill them (`transcript.ts`, the `streamedThinking` backfill). Dropping deltas
1919
+ * there would silently erase every thought from a restored transcript. Today
1920
+ * that is unreachable rather than merely avoided — only the provider engine
1921
+ * implements `park()`/`snapshot()` at all, and `#restore` refuses a snapshot
1922
+ * from another engine — but an engine that gains one inherits this obligation.
1923
+ *
1924
+ * Two properties hold it up, both of which are tests rather than arguments:
1925
+ * folding the full log and the retained log through `applyEvent` yields
1926
+ * identical state (`packages/react/test/snapshot-retain.test.ts`, the same
1927
+ * property `replay-coalesce.test.ts` asserts), and the retained log's last event
1928
+ * still carries the snapshot's own `seq`. The second matters more than it looks:
1929
+ * `transcriptActivity(stream_delta)` is 0, so the count `#restore` recomputes
1930
+ * from the log is bit-identical — a client's unread cursor cannot move — and the
1931
+ * replay hold waits for `state.lastSeq` to reach the attach's `lastSeq`, which a
1932
+ * rule that could drop the final event would hang forever.
1933
+ */
1934
+ declare function snapshotRetains(body: SessionEventBody): boolean;
1453
1935
  /**
1454
1936
  * A session in an engine's on-disk store (independent of this server's registry):
1455
1937
  * the Agent SDK's session files, or a codex profile's CODEX_HOME threads. Listed
@@ -1885,5 +2367,5 @@ type QueueStatsResponse = {
1885
2367
  stats: QueueStats;
1886
2368
  };
1887
2369
  //#endregion
1888
- export { ApiMessage, AttachedFrame, ClientFrame, ContentBlock, ContextUsage, ContextUsageCategory, CreateJobRequest, CreateJobResponse, CreateProfileRequest, CreateSessionRequest, CreateSessionResponse, DEFAULT_VIEW_CONFIG, ENGINE_CAPABILITIES, EngineCapabilities, ErrorResponse, Facet, FilePatch, FindHostFilesResponse, GetJobResponse, GetProfileResponse, GetSessionResponse, GroupBy, HostDirEntry, HostFileMatch, HostFileRoot, JobEvent, JobInfo, JobProgress, JobResult, JobStatus, JobUsage, ListHostDirResponse, ListHostRootsResponse, ListJobsResponse, ListProfilesResponse, ListSdkSessionsResponse, ListSessionFilesResponse, ListSessionsResponse, McpServerActionRequest, McpServerConfigWire, McpServerStatusInfo, McpServerToolInfo, McpServersResponse, MessageAttachment, ModelOption, PROTOCOL_VERSION, PROVIDER_PERMISSION_MODES, PatchHunk, PermissionDecisionSource, PermissionMode, PermissionRequest, ProfileConfigSnapshot, ProfileDefaults, ProfileEngine, ProfileInfo, ProfileSessionDefaults, ProfileUsage, ProfileUsageWindow, ProviderConfig, QuestionBehavior, QueueServerFrame, QueueStats, QueueStatsResponse, RateLimitInfo, ReadHostFileResponse, ResolvePermissionRequest, ResolvePermissionResponse, STATE_LABELS, STATE_ORDER, SaveProfileResponse, ScopeRoot, SdkSessionSummary, ServerFrame, SessionCapability, SessionCommand, SessionEvent, SessionEventBody, SessionFileInfo, SessionGroup, SessionInfo, SessionNotification, SessionNotificationType, SessionRow, SessionState, SessionStatus, SessionUsage, SessionWebhookConfig, SkillInfo, SlashCommandInfo, SortBy, SubmitExecutionResultRequest, SubmitExecutionResultResponse, SubsetSummary, TextBlock, ThinkingBlock, ToolCallRequestFrame, ToolExecutionBackend, ToolExecutionOutput, ToolExecutionStatus, ToolResultBlock, ToolUseBlock, UnknownBlock, UpdateProfileRequest, UpdateSessionRequest, UpdateSessionResponse, UploadAttachmentResponse, UsageWindowRow, UserQuestion, UserQuestionOption, ViewConfig, Watermark, WatermarkStore, Watermarks, WebhookConfig, WorkspaceScope, WriteHostFileRequest, WriteHostFileResponse, adaptersOf, clearFilters, filterRows, groupRows, hasFacetFilter, inScope, isJobRun, mergeUsage, orderUsageWindows, replayCoalesceKey, scopeActive, sessionLabel, sessionState, subsetSummary, supportsPermissionMode, transcriptActivity, transcriptContent, unseenCount, usageInfos, watermarkKey };
2370
+ export { ApiMessage, AttachedFrame, ClientFrame, ContentBlock, ContextUsage, ContextUsageCategory, CreateJobRequest, CreateJobResponse, CreateProfileRequest, CreateSessionRequest, CreateSessionResponse, DEFAULT_VIEW_CONFIG, ENGINE_CAPABILITIES, EngineCapabilities, ErrorResponse, Facet, FilePatch, FindHostFilesResponse, GetJobResponse, GetProfileResponse, GetSessionResponse, GroupBy, HostDirEntry, HostFileMatch, HostFileRoot, ImageRefPart, JobEvent, JobInfo, JobProgress, JobResult, JobStatus, JobUsage, ListHostDirResponse, ListHostRootsResponse, ListJobsResponse, ListProfilesResponse, ListSdkSessionsResponse, ListSessionFilesResponse, ListSessionsResponse, McpServerActionRequest, McpServerConfigWire, McpServerStatusInfo, McpServerToolInfo, McpServersResponse, MessageAttachment, ModelOption, PROTOCOL_VERSION, PROVIDER_PERMISSION_MODES, PatchHunk, PermissionDecisionSource, PermissionMode, PermissionRequest, ProfileConfigSnapshot, ProfileDefaults, ProfileEngine, ProfileInfo, ProfileSessionDefaults, ProfileUsage, ProfileUsageWindow, ProjectIcon, ProjectInfo, ProviderConfig, QuestionBehavior, QueueServerFrame, QueueStats, QueueStatsResponse, RateLimitInfo, ReadHostFileResponse, ResolvePermissionRequest, ResolvePermissionResponse, STATE_LABELS, STATE_ORDER, SUBAGENT_HISTORY, SaveProfileResponse, ScopeRoot, SdkSessionSummary, ServerFrame, SessionCapability, SessionCommand, SessionEvent, SessionEventBody, SessionFileInfo, SessionGroup, SessionInfo, SessionNotification, SessionNotificationType, SessionRow, SessionState, SessionStatus, SessionUsage, SessionWebhookConfig, SkillInfo, SlashCommandInfo, SortBy, SubagentInfo, SubmitExecutionResultRequest, SubmitExecutionResultResponse, SubsetSummary, TOOL_RESULT_HEAD_CHARS, TextBlock, ThinkingBlock, ToolCallRequestFrame, ToolExecutionBackend, ToolExecutionOutput, ToolExecutionStatus, ToolResultBlock, ToolUseBlock, UnknownBlock, UpdateProfileRequest, UpdateSessionRequest, UpdateSessionResponse, UploadAttachmentResponse, UsageWindowRow, UserQuestion, UserQuestionOption, ViewConfig, Watermark, WatermarkStore, Watermarks, WebhookConfig, WorkspaceScope, WriteHostFileRequest, WriteHostFileResponse, adaptersOf, clearFilters, filterRows, groupRows, hasFacetFilter, imagePartRef, inScope, isJobRun, mergeUsage, orderUsageWindows, projectKey, projectLabel, projectsOf, replayCoalesceKey, replayRetains, runningSubagents, scopeActive, sessionLabel, sessionState, snapshotRetains, subagentLabel, subsetSummary, supportsPermissionMode, transcriptActivity, transcriptContent, unseenCount, usageInfos, watermarkKey };
1889
2371
  //# sourceMappingURL=index.d.mts.map