@workerdeck/protocol 0.16.0 → 0.18.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,49 @@ 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
+ /**
47
+ * Does this record name an **agent**, as opposed to a task the model merely
48
+ * described?
49
+ *
50
+ * The tracker opens a record for every spawner call and for any nested event
51
+ * whose parent it has not seen, so the list holds two different things wearing
52
+ * one shape. One carries a `subagent_type` — a delegated agent with an identity
53
+ * (`Explore`), whose own work is worth a surface of its own. The other carries
54
+ * only a description, and there is no agent there to open: a row that offered a
55
+ * screen and then showed a frame with nothing in it would be worse than a row
56
+ * that offered nothing.
57
+ *
58
+ * Here rather than in a client because it decides two things a list must not
59
+ * disagree about across surfaces — what is pressable, and what wears the
60
+ * sub-agent colour.
61
+ */
62
+ declare function isAgentRecord(sub: SubagentInfo): boolean;
63
+ declare function subagentLabel(sub: SubagentInfo): string;
23
64
  /** The facets a session can be grouped or sorted by. */
24
- type Facet = 'gateway' | 'adapter' | 'state';
65
+ type Facet = 'gateway' | 'adapter' | 'state' | 'project';
25
66
  type GroupBy = 'none' | Facet;
26
67
  type SortBy = 'recent' | 'name' | Facet;
27
68
  type ViewConfig = {
@@ -29,6 +70,16 @@ type ViewConfig = {
29
70
  gateways: string[];
30
71
  adapters: string[];
31
72
  states: SessionState[];
73
+ /**
74
+ * Empty = no filter. Keys are {@link projectKey} output — never names, which
75
+ * are neither unique (two repos both called "api") nor stable (editing
76
+ * `.workerdeck.json` renames every session at once and must not empty a
77
+ * saved filter). Optional, unlike its three siblings, because stored view
78
+ * configs predate it: a config restored from `localStorage`/`globalState`
79
+ * without the key must keep filtering, so absent and empty mean the same
80
+ * thing.
81
+ */
82
+ projects?: string[];
32
83
  /** Show only sessions inside the host's own folders. Inert where there is no
33
84
  * such notion (no folder open, a dashboard with no workspace), which is why it
34
85
  * can default on. */
@@ -74,7 +125,68 @@ type SessionGroup = {
74
125
  /** The adapters actually present, for the filter chips — derived rather than
75
126
  * enumerated, so a new engine needs no change here. */
76
127
  declare function adaptersOf(rows: readonly SessionRow[]): string[];
128
+ /**
129
+ * The projects actually present, as `{ key, label }` for a filter control —
130
+ * derived like {@link adaptersOf}, and paired because the two halves differ:
131
+ * the *key* is what {@link ViewConfig.projects} holds (gateway-qualified root,
132
+ * so a rename regroups nothing) and the *label* is what a person picks by.
133
+ *
134
+ * Sorted by label, deduped by key. Two projects with the same name on two
135
+ * gateways therefore stay two entries wearing one word — which is honest: they
136
+ * really are two different directories, and the alternative is a filter that
137
+ * silently selects both.
138
+ */
139
+ declare function projectsOf(rows: readonly SessionRow[]): {
140
+ key: string;
141
+ label: string;
142
+ }[];
77
143
  declare function sessionLabel(info: SessionInfo): string;
144
+ /**
145
+ * The project facet's grouping key: gateway id + the project root, falling
146
+ * back to the session's cwd when no project is declared.
147
+ *
148
+ * The root and not the name, because a name is not a key (two repos can both
149
+ * be called "api", and a rename must regroup nothing); qualified by gateway,
150
+ * because a remote gateway's identical-looking path is another machine's
151
+ * directory — the same rule `ScopeRoot` states. The cwd fallback is what makes
152
+ * grouping by project useful before anyone has written a `.workerdeck.json`:
153
+ * undeclared sessions group by their folder, declared ones by their root, and
154
+ * a session in `packages/ui` joins its repo's group the moment the file
155
+ * exists. Sessions with no cwd at all (a filesystem-less engine) share one
156
+ * per-gateway bucket — see {@link projectLabel}.
157
+ */
158
+ declare function projectKey(row: SessionRow): string;
159
+ /**
160
+ * What a project group (or a row's project slot) is called: the declared name,
161
+ * else the cwd's basename — the exact string clients rendered before this
162
+ * feature existed, so an undeclared project looks like today. 'No project' is
163
+ * only ever the no-cwd case (a sandboxed provider session), where there is no
164
+ * folder to name.
165
+ *
166
+ * Takes only the `info` it reads, so a surface holding a bare `SessionInfo` —
167
+ * a row component, an iOS cell — can call it without inventing the rest of a
168
+ * `SessionRow`. That matters more than it looks: this string is what a client
169
+ * renders *in place of* the cwd basename it used to draw, and two spellings of
170
+ * it would put the list and its group headers on different names.
171
+ */
172
+ declare function projectLabel(row: Pick<SessionRow, 'info'>): string;
173
+ /**
174
+ * Where inside its project a session actually sits — the cwd with the project
175
+ * root taken off the front, or `undefined` when it sits at the root, has no
176
+ * declared project, or has no cwd at all.
177
+ *
178
+ * The companion to {@link projectLabel}, and it exists for one situation: a list
179
+ * **grouped by project**. There the header has already said the project's name,
180
+ * so repeating it on every row spends the row's most valuable line on the one
181
+ * fact the reader already has. What the header cannot say is which *part* of the
182
+ * project a session is working in, and two sessions in the same repo are told
183
+ * apart by exactly that.
184
+ *
185
+ * Undefined is the honest answer for a session at the project root, and callers
186
+ * must render nothing rather than a `.` or a repeated name — the slot simply
187
+ * goes away, which is the point.
188
+ */
189
+ declare function projectSubpath(row: Pick<SessionRow, 'info'>): string | undefined;
78
190
  /**
79
191
  * This session is a job run — the queue created it, and `JobInfo.sessionId`
80
192
  * points at it.
@@ -329,7 +441,127 @@ type ToolResultBlock = {
329
441
  [key: string]: unknown;
330
442
  }>;
331
443
  is_error?: boolean;
444
+ /**
445
+ * This block carries only the **head** of the result: the replay truncated it
446
+ * (see {@link TOOL_RESULT_HEAD_CHARS}), and the whole thing is one fetch away
447
+ * at `GET /sessions/:id/events/:seq/result?toolUseId=`.
448
+ *
449
+ * On the **block**, never the event, and that is the same argument
450
+ * `user_message.patch` has to make in reverse: the patch sits on the event and
451
+ * its doc must therefore caveat "only when the message carries exactly one
452
+ * `tool_result` block — with two, nothing says which one it belongs to". A
453
+ * message answering three calls truncates whichever of them is large, so
454
+ * paying that caveat a second time would make the marker unusable exactly
455
+ * when it matters. {@link FilePatch.truncated} is the shipped precedent.
456
+ *
457
+ * Only ever set on a **replay** a client asked for (`truncateResults`), so a
458
+ * client that has never heard of this field cannot receive one — which is why
459
+ * this is additive at protocol 7 rather than a bump. Absent means the block is
460
+ * whole.
461
+ */
462
+ truncated?: boolean;
463
+ /** How many characters the untruncated result had. Set iff `truncated`.
464
+ *
465
+ * A client cannot compute it — it holds the head — and the number is not
466
+ * cosmetic: a collapsed row spells "… +N chars", and `height.ts` sizes the row
467
+ * by wrapping **that exact string**, so a count derived from the head would be
468
+ * both a lie and a different pixel height. */
469
+ total_chars?: number;
332
470
  };
471
+ /**
472
+ * How much of a tool result a truncating replay keeps.
473
+ *
474
+ * Chosen against the two clients' *own* budgets, and the relationship is the
475
+ * whole point: the terminal theme shows ~400 characters collapsed and ~2,000
476
+ * open, so at 8,000 the collapsed and open states are **byte-identical to an
477
+ * untruncated attach** and only the uncapped "show everything" press ever
478
+ * fetches. That collapses the entire feature to one press, and it is asserted
479
+ * in a test rather than trusted — lowered below the open budget, this would
480
+ * silently clip the open state with no marker, which is the one failure this
481
+ * design must not have.
482
+ *
483
+ * Measured justification: on one 1,270-row session three `tool_result` frames
484
+ * were 641 / 463 / 396 KB, 68% of a 3.1 MB attach. The cut is *structural* —
485
+ * proportional to the thing that is actually large, wherever in the log it sits
486
+ * — which a row window is not.
487
+ */
488
+ declare const TOOL_RESULT_HEAD_CHARS = 8000;
489
+ /**
490
+ * A base64 image part, delivered as an address instead of its bytes.
491
+ *
492
+ * The **seventh** rule of the family, and the first written *after* its
493
+ * measurement rather than before it. Across 214 local sessions, 91% of all
494
+ * tool-result payload is base64 image data — 489 MB against 44 MB of text — and
495
+ * **no client renders a byte of it**: `blockText` in the reducer and
496
+ * `joinedText` on iOS both fold a `tool_result` to its text parts, and both
497
+ * clients draw a tool's picture from a host *path* (`savedPath` → `/produced`,
498
+ * `/fs/read`), never from block content. So it is `replayRetains`' argument at
499
+ * nine times the size of the case that rule was written for: bytes whose entire
500
+ * effect on the reader is `return base`.
501
+ *
502
+ * A **new part type rather than a hollowed-out `image`**, and that is the one
503
+ * judgement here worth stating. `headOf`'s shape-preservation rule — "a
504
+ * truncation is a shorter result, never a different kind of one" — cuts the
505
+ * other way for pixels: a head *is* a valid shorter text, but an image with no
506
+ * bytes is not a smaller image, and spelling it `{ type: 'image', source }` with
507
+ * no `data` invites precisely the failure shape-preservation exists to prevent,
508
+ * a renderer that trusts `source.data` drawing `data:;base64,undefined`. An
509
+ * unfamiliar type instead falls through every fold that already exists, exactly
510
+ * as the CLI's own `tool_reference` part does: no `text`, so it contributes
511
+ * nothing, and an unaware consumer renders what it renders today, which is
512
+ * nothing. That is this family's safe failure.
513
+ *
514
+ * Only ever produced for a socket that asked (`imageRefs`), so a client that has
515
+ * never heard of this type cannot receive one — which is why this is additive at
516
+ * protocol 7, the same argument {@link ToolResultBlock.truncated} makes. Unlike
517
+ * truncation it applies to **live events as well as replays**: the client's one
518
+ * render path is ref-then-fetch, so bytes on a live event would either be
519
+ * discarded (335 KB median, once per attached watcher) or need a second
520
+ * decode-from-event path pinning megabytes inside the transcript cache — the
521
+ * disease relocated rather than cured.
522
+ */
523
+ type ImageRefPart = {
524
+ type: 'image_ref';
525
+ /** The stored part's own media type (`image/png`, `image/jpeg` and
526
+ * `image/webp` are the three observed), or `application/octet-stream` when it
527
+ * had none. Never the membership test — that is `image` plus a base64 source. */
528
+ media_type: string;
529
+ /** Decoded size, which a client cannot compute from an address it has not
530
+ * fetched yet. Not cosmetic: the placeholder spells it, and in the terminal
531
+ * theme a rendered string *is* a row height. */
532
+ bytes: number;
533
+ /**
534
+ * Index of this part in the **stored** block's content array, and the address
535
+ * a fetch is made with.
536
+ *
537
+ * A stamped field rather than the position it arrives at, because that
538
+ * position is not stable: `headOf` builds a truncated head by keeping text
539
+ * parts up to budget and dropping every other part, so a block that is both
540
+ * over the text budget and image-bearing has its parts renumbered the moment
541
+ * the two rules compose. Stamped, the address survives any later reshaping —
542
+ * and the route verifies it against the stored block rather than trusting it.
543
+ */
544
+ part_index: number;
545
+ };
546
+ /**
547
+ * Project one `tool_result` content part onto its {@link ImageRefPart}, or
548
+ * `undefined` when the part is not a base64 image and must be delivered as it
549
+ * stands.
550
+ *
551
+ * The rule's **one spelling**, shared by the transform that replaces parts
552
+ * (core), the route that serves them back (server) and the property test that
553
+ * proves the fold is otherwise unchanged (react) — the same reason every other
554
+ * member of this family lives here rather than in whichever package applies it.
555
+ *
556
+ * Deliberately narrow. The corpus holds exactly two non-text part kinds: this
557
+ * one, and the CLI's `tool_reference`, of which every instance across 214
558
+ * sessions totals 122 KB. A "drop non-text parts" rule would sweep those in for
559
+ * no measurable gain, and narrowness is this family's standing habit.
560
+ */
561
+ declare function imagePartRef(part: {
562
+ type?: string;
563
+ [key: string]: unknown;
564
+ }, index: number): ImageRefPart | undefined;
333
565
  /** Forward-compatible fallback for block types this protocol version doesn't model. */
334
566
  type UnknownBlock = {
335
567
  type: string;
@@ -537,6 +769,27 @@ type ContextUsage = {
537
769
  percentage: number; /** Model the window sizing applies to. */
538
770
  model?: string;
539
771
  };
772
+ /**
773
+ * The context-window reading that rides the **sessions list**, as opposed to the
774
+ * full {@link ContextUsage} that rides the event stream.
775
+ *
776
+ * Three numbers, and the omission is the design: `categories` is a breakdown for
777
+ * a dialog that has a live session behind it, and this field is on every row of
778
+ * `GET /sessions`, which a busy client polls at 1.2s. Same attachment-bytes
779
+ * discipline as {@link SessionInfo.subagents}. `percentage` alone would size a
780
+ * ring, but the token pair is what lets a row *say* `142k / 200k` on hover or
781
+ * long-press without a second round trip, and it is two numbers.
782
+ *
783
+ * **Absent is a real state and is not zero.** A parked session, one that has
784
+ * never run a turn, or an engine that reports no window has no reading — render
785
+ * nothing, never an empty ring, which claims "context is empty" rather than "no
786
+ * answer". Also absent on an older server.
787
+ */
788
+ type ContextReading = {
789
+ totalTokens: number;
790
+ maxTokens: number; /** Used share of the window, 0–100. */
791
+ percentage: number;
792
+ };
540
793
  /**
541
794
  * One rate-limit window snapshot (SDK SDKRateLimitInfo mirror). Emitted only for
542
795
  * claude.ai subscription sessions — API-key sessions may never produce one, so
@@ -1301,6 +1554,187 @@ type CreateSessionRequest = {
1301
1554
  */
1302
1555
  scope?: Record<string, string>;
1303
1556
  };
1557
+ /**
1558
+ * One sub-agent (a `Task` call and the sidechain it spawned), as a *list* surface
1559
+ * sees it — without attaching.
1560
+ *
1561
+ * Sub-agent work is otherwise attach-only: it exists on the wire as
1562
+ * `parentToolUseId` on three event bodies, and is reconstructed into rows by the
1563
+ * react reducer and grouped per-Task by `terminalBlocks`. A sessions list never
1564
+ * attaches (one live attach per session, owned by the panel), so it reads
1565
+ * `SessionInfo` over REST and would otherwise have no way to know a session has
1566
+ * six agents running inside one turn.
1567
+ *
1568
+ * This is a **runner-owned rollup computed at read time**, exactly like
1569
+ * {@link SessionInfo.pendingPermissionCount}: it is not an event, it is not
1570
+ * persisted separately, and it therefore rides the REST list, the WS attach
1571
+ * snapshot and parking snapshots for free.
1572
+ *
1573
+ * **The claude and codex engines both produce it; the provider engine's absence
1574
+ * is the truth.** The AI SDK has no multi-agent primitive and no tool that runs
1575
+ * a nested agent loop, so `parentToolUseId: null` on every provider event is
1576
+ * honest. Codex's spawn signal is the `subAgentActivity` item, whose own `id` is
1577
+ * the model's `spawn_agent` call id — a genuine tool-use id — so `toolUseId`
1578
+ * keeps its documented meaning there: the codex runner authors the anchor
1579
+ * `tool_use` itself and keys every event of the agent's *thread* to it
1580
+ * (`engines/codex/subagents.ts`). The earlier version of this comment asserted
1581
+ * codex had no sidechains; that was true of the exec era and has not been true
1582
+ * for a while.
1583
+ *
1584
+ * It is deliberately **not** the input to `taskSummary`. That string is spelled
1585
+ * from the absorbed transcript items and must stay that way, so a transcript
1586
+ * replayed tomorrow spells the same line from the same items it holds today.
1587
+ */
1588
+ type SubagentInfo = {
1589
+ /** The `tool_use` id of the `Task` call that spawned it — the same id its
1590
+ * nested events carry as `parentToolUseId`, and therefore the handle a client
1591
+ * uses to jump to that Task's row. */
1592
+ toolUseId: string; /** The Task input's `subagent_type` (e.g. "Explore"), when it named one. */
1593
+ agentType?: string;
1594
+ /** The Task input's short `description`, clipped by the runner. Together with
1595
+ * `agentType` this is what makes two parallel sub-agents tell apart in a list;
1596
+ * a row reading only `Task` answers nothing. */
1597
+ description?: string;
1598
+ /**
1599
+ * `running` until the Task's own `tool_result` arrives, then `done`/`failed`
1600
+ * from that result's `is_error`. A turn that ends without that result — an
1601
+ * interrupt, a session error, a turn or budget cap — settles what is still
1602
+ * running as `failed`: the report never came, which is the one thing `done`
1603
+ * could have claimed, and a `running` badge on an idle session would be a
1604
+ * lie a list re-renders at every poll.
1605
+ *
1606
+ * Deliberately **narrower than `taskFailed`** in `@workerdeck/ui`'s
1607
+ * `tool-run.ts`, which reddens a Task row when *any child call* failed. That is
1608
+ * right for a transcript row the reader can expand — the failure is one press
1609
+ * away and hiding it would be worse. It is wrong for a list: a grep that
1610
+ * matched nothing inside an otherwise successful Explore agent would put
1611
+ * `failed` beside the session's name with nothing to open. So this reports the
1612
+ * sub-agent's own outcome. If you are here to "fix" the inconsistency, this is
1613
+ * the reason it exists.
1614
+ */
1615
+ status: 'running' | 'done' | 'failed'; /** Epoch ms the `Task` call was emitted. */
1616
+ startedAt: number;
1617
+ /** Tool calls the sub-agent has made so far — its progress reading while
1618
+ * running, counted from nested `tool_use` blocks. */
1619
+ toolCount: number;
1620
+ };
1621
+ /**
1622
+ * How many *settled* sub-agents {@link SessionInfo.subagents} keeps behind the
1623
+ * running ones. Small on purpose: the point of the tail is that a list row does
1624
+ * not go blank the instant a run finishes, not that it is a history.
1625
+ */
1626
+ declare const SUBAGENT_HISTORY = 8;
1627
+ /**
1628
+ * A project's icon, as declared by its `.workerdeck.json` — either a named
1629
+ * glyph or a reference to an image the gateway serves.
1630
+ *
1631
+ * A discriminated union rather than one stringly field, because the two arms
1632
+ * have opposite render paths: a glyph is looked up in the client's own icon
1633
+ * set with no I/O, an image is a fetch. Collapsing them would put "is this a
1634
+ * name or an address" back on every renderer, which is the inference this
1635
+ * family keeps refusing (`ImageRefPart` is a new part type, never a
1636
+ * hollowed-out `image`, for the same reason).
1637
+ *
1638
+ * `glyph.name` is a lucide icon name, validated by the gateway for *shape*
1639
+ * only (lowercase kebab-case): the gateway has no lucide catalog and must not
1640
+ * grow one — icon sets version independently of this protocol. A client whose
1641
+ * set lacks the name renders its no-project fallback rather than erroring;
1642
+ * an unknown name is a stale row, never withheld state.
1643
+ *
1644
+ * `image` carries an **address, never bytes** — the attachment-bytes rule.
1645
+ * `SessionInfo` rides every row of `GET /sessions`, which clients poll at 1.2s
1646
+ * while anything is working, so an inlined base64 icon would be paid for on
1647
+ * every poll of every session forever (the same argument that keeps
1648
+ * `originalFile` off {@link FilePatch} and message bytes off events). The
1649
+ * bytes come from `GET {basePath}/sessions/:id/project/icon` — session-scoped
1650
+ * on purpose, so the fetch rides the same `canSee` gate as every other
1651
+ * `/sessions/:id/*` route and a scoped principal's miss is the uniform 404. A
1652
+ * project-keyed route would need the project root in the URL, and a route
1653
+ * addressed by host paths is an existence oracle for the gateway's
1654
+ * filesystem. `hash` (sha256 hex of the bytes) is the cross-session cache
1655
+ * key: two sessions in one project serve identical bytes, so a client caches
1656
+ * by hash rather than by URL and fetches once per project, not once per
1657
+ * session. The route answers with `ETag: "<hash>"` and honors
1658
+ * `If-None-Match`.
1659
+ */
1660
+ type ProjectIcon = {
1661
+ type: 'glyph';
1662
+ name: string;
1663
+ } | {
1664
+ type: 'image';
1665
+ mediaType: 'image/png' | 'image/svg+xml';
1666
+ hash: string;
1667
+ };
1668
+ /**
1669
+ * Project identity for a session — what a `.workerdeck.json` in the session's
1670
+ * ancestry declares, resolved by the **gateway** and shipped on
1671
+ * {@link SessionInfo.project}.
1672
+ *
1673
+ * The gateway reads the file, not each client: the iOS app and a browser
1674
+ * pointed at a remote gateway have no access to that filesystem, so a
1675
+ * per-client reader would make the feature exist on exactly one client.
1676
+ * Discovery is an ancestor walk from the session's `cwd` upward — nearest
1677
+ * `.workerdeck.json` wins, so a session started in `packages/ui` still says
1678
+ * "WorkerDeck" — over the *realpath'd* cwd, which is what makes `root`
1679
+ * canonical below.
1680
+ *
1681
+ * The file's schema, stated here because this type is its wire projection
1682
+ * (clients never read the file; the gateway is its only parser):
1683
+ *
1684
+ * ```json
1685
+ * { "name": "WorkerDeck", "icon": "layers" }
1686
+ * { "name": "WorkerDeck", "icon": "./docs/assets/icon.png" }
1687
+ * ```
1688
+ *
1689
+ * Both keys optional, unknown keys ignored (forward compatibility). An empty
1690
+ * `{}` still marks its directory as the project root — grouping is the point,
1691
+ * and the name falls back to the root's basename. `icon` is one string with a
1692
+ * total classification rule: a value ending in `.png`/`.svg`
1693
+ * (case-insensitive) is a repo-relative image path — relative only, since the
1694
+ * file is checked into a repo that clones onto other machines, where an
1695
+ * absolute path is wrong by construction — and anything else must be a
1696
+ * lucide-shaped glyph name (`^[a-z0-9]+(-[a-z0-9]+)*$`) or it is ignored. The
1697
+ * two shapes cannot collide (a glyph name contains no dot), so the rule is a
1698
+ * classification, not a guess. Every degradation degrades *fieldwise and
1699
+ * silently*: a malformed or oversized file is skipped and the walk continues
1700
+ * to an ancestor (a broken nested file must not shadow the repo root's valid
1701
+ * one), a junk name falls back to the basename, a junk or escaping icon is
1702
+ * dropped — a session must never fail, or even warn, because of a display
1703
+ * declaration.
1704
+ *
1705
+ * `root` — the canonical (realpath'd) absolute directory holding the file, on
1706
+ * the **gateway's** filesystem — is the grouping key: two sessions are in the
1707
+ * same project iff same root *on the same gateway* (a remote gateway's
1708
+ * identical-looking path is another machine's directory — the `ScopeRoot`
1709
+ * argument). A *name* is not a key: two repos can both be called "api".
1710
+ * Canonicalizing at discovery is what makes two differently-spelled cwds of
1711
+ * one project agree on it.
1712
+ *
1713
+ * Resolved at **serve time** from a TTL cache, never persisted — the same
1714
+ * placement argument as the profile tracker's 0%-after-reset inference: it is
1715
+ * a function of the gateway's current filesystem, and a copy captured into a
1716
+ * parking record would replay a stale name forever. Editing the file shows up
1717
+ * on every session in the project within the TTL, with no migration and no
1718
+ * event.
1719
+ *
1720
+ * Additive at protocol **7**: an optional field on `SessionInfo`, where
1721
+ * absent means exactly what today's wire means (no project declared — render
1722
+ * the folder basename), an old client ignores it, and a new client against an
1723
+ * old gateway sees absent. The icon route is likewise unreachable by
1724
+ * accident: a client only fetches it when this gateway told it an image
1725
+ * exists.
1726
+ */
1727
+ type ProjectInfo = {
1728
+ /** Display name — the file's `name`, else the root's basename. Never empty. */name: string;
1729
+ /** Canonical absolute path of the directory holding `.workerdeck.json`, on
1730
+ * the gateway's filesystem. The grouping key (per gateway); an opaque string
1731
+ * to clients beyond equality and display. */
1732
+ root: string;
1733
+ /** Absent = the file declared none (or declared one the gateway refused —
1734
+ * malformed, escaping, oversized — which a client cannot and must not
1735
+ * distinguish). */
1736
+ icon?: ProjectIcon;
1737
+ };
1304
1738
  type SessionInfo = {
1305
1739
  /** Server-assigned id (stable across SDK session forks/resumes). */id: string; /** Underlying Agent SDK session id, once known; use for `resume`. */
1306
1740
  sdkSessionId?: string;
@@ -1331,6 +1765,22 @@ type SessionInfo = {
1331
1765
  createdAt: number; /** Highest event seq emitted so far; attach with `afterSeq` to catch up. */
1332
1766
  lastSeq: number;
1333
1767
  pendingPermissionCount: number;
1768
+ /**
1769
+ * Sub-agents this session has running, plus a short tail of settled ones — see
1770
+ * {@link SubagentInfo}. Absent on an engine that has no sidechains and on an
1771
+ * older server; **absent and empty mean the same thing to a client**, so render
1772
+ * nothing rather than "0 sub-agents".
1773
+ *
1774
+ * Bounded on purpose. This rides every row of `GET /sessions`, which a busy
1775
+ * client polls at 1.2s, and it is captured into parking snapshots — the same
1776
+ * attachment-bytes rule that keeps whole files off {@link FilePatch}. Every
1777
+ * *running* sub-agent is always present (they are the live reading and there
1778
+ * are never many at once); settled ones are kept newest-first to
1779
+ * {@link SUBAGENT_HISTORY} and then dropped, so a day-long session with two
1780
+ * hundred Tasks does not grow an unbounded field. A client must therefore not
1781
+ * treat this as the session's full Task history — the transcript is that.
1782
+ */
1783
+ subagents?: SubagentInfo[];
1334
1784
  meta?: Record<string, unknown>; /** Display title: `meta.title` if the host set one, else derived (e.g. first prompt). */
1335
1785
  title?: string; /** Cumulative cost across all turns so far (sum of turn_result totals). */
1336
1786
  totalCostUsd?: number; /** Cumulative turn count across the session. */
@@ -1355,11 +1805,50 @@ type SessionInfo = {
1355
1805
  */
1356
1806
  activityCount?: number; /** Epoch ms of the most recent emitted event. */
1357
1807
  lastActivityAt?: number;
1808
+ /**
1809
+ * The session's latest context-window reading — see {@link ContextReading}.
1810
+ *
1811
+ * The same number the session screen draws, served on the list so a row can
1812
+ * show where a session is bloating **without attaching to it**. That is the
1813
+ * whole reason it is here: context fill is the one session metric you want
1814
+ * across *all* sessions at once, and until now it existed only as an event on
1815
+ * an attached socket.
1816
+ *
1817
+ * Retained by the runner from the last `context_usage` it emitted, so it is
1818
+ * exactly what the transcript last showed — never recomputed on the serve
1819
+ * path, which would be a second answer to a question that already has one.
1820
+ * Absent until the first reading (a promptless session has none), and cleared
1821
+ * by a `conversation_reset` for the same reason the transcript state clears
1822
+ * it: the old window is not this conversation's.
1823
+ */
1824
+ contextUsage?: ContextReading;
1358
1825
  /** Opaque scope tags this session was created with — see
1359
1826
  * {@link CreateSessionRequest.scope}. Echoed by the runner, re-stamped by the
1360
1827
  * gateway, and never editable. */
1361
1828
  scope?: Record<string, string>;
1829
+ /**
1830
+ * Project identity discovered from the session's `cwd` — see
1831
+ * {@link ProjectInfo}. Stamped by the **gateway at serve time** (runners
1832
+ * never set it; a runner-echoed value would be persisted into parking
1833
+ * records and replay a stale name forever). Absent = no `.workerdeck.json`
1834
+ * in the cwd's ancestry, and also = an older gateway: both mean "render the
1835
+ * folder basename", which is exactly today's behaviour.
1836
+ */
1837
+ project?: ProjectInfo;
1362
1838
  };
1839
+ /**
1840
+ * The list-sized context reading an event carries, or `undefined` for the events
1841
+ * that carry none — the rule behind {@link SessionInfo.contextUsage}.
1842
+ *
1843
+ * Here rather than in each runner for the same reason {@link transcriptActivity}
1844
+ * is: it is one rule both sides have to agree on, and three copies of "which
1845
+ * events move the reading" is three chances to disagree. Runners fold it in
1846
+ * their emit path; **clearing on `conversation_reset` is the caller's half** —
1847
+ * this function answers "what does this event say the reading is", and a reset
1848
+ * says nothing about the window, it retires the conversation the window
1849
+ * described.
1850
+ */
1851
+ declare function contextReading(body: SessionEventBody): ContextReading | undefined;
1363
1852
  /**
1364
1853
  * How many transcript rows an event materializes — the unit behind
1365
1854
  * {@link SessionInfo.activityCount}.
@@ -1450,6 +1939,93 @@ declare function transcriptContent(body: SessionEventBody): boolean;
1450
1939
  * on a blank panel forever if a coalescer could swallow the final event.
1451
1940
  */
1452
1941
  declare function replayCoalesceKey(body: SessionEventBody): string | undefined;
1942
+ /**
1943
+ * Does a **replay** have to deliver this event, or may it be dropped outright?
1944
+ *
1945
+ * The fifth of the family, and the closest relative of {@link snapshotRetains} —
1946
+ * the same claim ("no client can tell") pointed at the wire instead of at a
1947
+ * store. The difference from {@link replayCoalesceKey} is that this is not
1948
+ * last-write-wins: there is nothing to keep. These are events the reducer reads
1949
+ * and *discards*, so a replay that sends them is spending the reader's network
1950
+ * on frames whose whole effect is `return base`.
1951
+ *
1952
+ * Today that is exactly one thing, and it is the second-largest item in a real
1953
+ * attach: the `stream_delta`s the reducer does not model. Measured over one
1954
+ * 1,270-row session, the delta run was 774 KB, and **~85% of it was frames the
1955
+ * reducer throws away** — `input_json_delta` (a tool call's arguments, streamed
1956
+ * character by character, 383 KB), `signature_delta` (encrypted-thinking
1957
+ * signatures, 153 KB) and the `message_start`/`content_block_start`/`_stop`
1958
+ * scaffolding (244 KB). The reducer models two delta kinds, `text_delta` and
1959
+ * `thinking_delta`; everything else falls through its switch untouched.
1960
+ *
1961
+ * What is deliberately **not** dropped, though the arithmetic would allow it:
1962
+ *
1963
+ * - `thinking_delta` — the Claude SDK delivers thinking blocks whose `thinking`
1964
+ * is `''`, and the reducer backfills them from the accumulated streamed text
1965
+ * (`streamedThinking`). Dropping these erases every thought from a replayed
1966
+ * transcript. This is the same carve-out `snapshotRetains` documents, and it
1967
+ * is the reason that rule is provider-engine-only.
1968
+ * - `text_delta` — superseded by the `assistant_message` that follows it, which
1969
+ * filters the streaming id and rebuilds from the full content blocks. It could
1970
+ * go, but only with a lookahead proving the message arrived, and at 24 KB in
1971
+ * the measured session it is not worth a rule that has to be right about
1972
+ * supersession. A merge is likewise not worth it: a *drop* needs no synthesized
1973
+ * event and therefore no invented seq.
1974
+ *
1975
+ * A live event is never affected — this is about the buffered replay alone — and
1976
+ * the caller must never drop the log's highest-seq event whatever this says, for
1977
+ * the reason {@link replayCoalesceKey} gives: the replay hold waits for
1978
+ * `state.lastSeq` to reach the attach's `session.lastSeq` and would hang on a
1979
+ * blank panel forever.
1980
+ *
1981
+ * The property is the family's usual one and is a test rather than an argument:
1982
+ * folding the full log and the retained log through `applyEvent` yields
1983
+ * identical state (`packages/react/test/replay-retain.test.ts`).
1984
+ */
1985
+ declare function replayRetains(body: SessionEventBody): boolean;
1986
+ /**
1987
+ * Does a `RunnerSnapshot` keep this event in its persisted log?
1988
+ *
1989
+ * The fourth of the same family, and the same shape of claim as
1990
+ * {@link replayCoalesceKey}: which events a *store* may drop without any client
1991
+ * being able to tell. It exists because a snapshot embeds the whole event log,
1992
+ * and a log is mostly stream deltas — a four-character token rides a ~180-byte
1993
+ * JSON envelope, so the delta run is tens of times the size of the text it
1994
+ * spells, sitting on disk *beside* the `assistant_message` that respells it in
1995
+ * full. That was affordable while a snapshot was written once, at a park. It is
1996
+ * not affordable written after every turn, which is what restart-survival needs.
1997
+ *
1998
+ * So: everything is retained except `stream_delta`. The reason that is safe is
1999
+ * not that deltas are unimportant but that they are **superseded by
2000
+ * construction**. The reducer upserts them under one constant id and the
2001
+ * following `assistant_message` filters exactly that id out and rebuilds from
2002
+ * the full content blocks — and a snapshot may only be taken at a rest point,
2003
+ * where the stream loop has exited and flushed. Both exits flush, including the
2004
+ * error path: an interrupted turn pushes its half-finished buffers into a
2005
+ * durable `assistant_message` before it emits the failed `turn_result`. There is
2006
+ * no rest state in which a delta is the only record of anything.
2007
+ *
2008
+ * **Provider engine only**, and this is the carve-out that must not be lost:
2009
+ * against a *Claude* log the rule would be wrong. The Claude SDK delivers
2010
+ * thinking blocks whose text is `''`, with the human-readable summary existing
2011
+ * only in the delta stream, and the reducer carries the streamed text over to
2012
+ * fill them (`transcript.ts`, the `streamedThinking` backfill). Dropping deltas
2013
+ * there would silently erase every thought from a restored transcript. Today
2014
+ * that is unreachable rather than merely avoided — only the provider engine
2015
+ * implements `park()`/`snapshot()` at all, and `#restore` refuses a snapshot
2016
+ * from another engine — but an engine that gains one inherits this obligation.
2017
+ *
2018
+ * Two properties hold it up, both of which are tests rather than arguments:
2019
+ * folding the full log and the retained log through `applyEvent` yields
2020
+ * identical state (`packages/react/test/snapshot-retain.test.ts`, the same
2021
+ * property `replay-coalesce.test.ts` asserts), and the retained log's last event
2022
+ * still carries the snapshot's own `seq`. The second matters more than it looks:
2023
+ * `transcriptActivity(stream_delta)` is 0, so the count `#restore` recomputes
2024
+ * from the log is bit-identical — a client's unread cursor cannot move — and the
2025
+ * replay hold waits for `state.lastSeq` to reach the attach's `lastSeq`, which a
2026
+ * rule that could drop the final event would hang forever.
2027
+ */
2028
+ declare function snapshotRetains(body: SessionEventBody): boolean;
1453
2029
  /**
1454
2030
  * A session in an engine's on-disk store (independent of this server's registry):
1455
2031
  * the Agent SDK's session files, or a codex profile's CODEX_HOME threads. Listed
@@ -1885,5 +2461,5 @@ type QueueStatsResponse = {
1885
2461
  stats: QueueStats;
1886
2462
  };
1887
2463
  //#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 };
2464
+ export { ApiMessage, AttachedFrame, ClientFrame, ContentBlock, ContextReading, 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, contextReading, filterRows, groupRows, hasFacetFilter, imagePartRef, inScope, isAgentRecord, isJobRun, mergeUsage, orderUsageWindows, projectKey, projectLabel, projectSubpath, projectsOf, replayCoalesceKey, replayRetains, runningSubagents, scopeActive, sessionLabel, sessionState, snapshotRetains, subagentLabel, subsetSummary, supportsPermissionMode, transcriptActivity, transcriptContent, unseenCount, usageInfos, watermarkKey };
1889
2465
  //# sourceMappingURL=index.d.mts.map