@silo-code/sdk 0.46.0 → 0.47.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.
@@ -1,5 +1,5 @@
1
1
  import type { Disposable } from "./types.js";
2
- import type { TerminalKind } from "./domain-types.js";
2
+ import type { TabActivityBinder, TabIconBinder } from "./tab-adornment.js";
3
3
  /**
4
4
  * What a terminal's agent is currently doing, as classified by the host from
5
5
  * OSC/output signals. `"none"` means no agent activity has been observed
@@ -21,10 +21,30 @@ import type { TerminalKind } from "./domain-types.js";
21
21
  */
22
22
  export type AgentActivity = "none" | "working" | "idle" | "error" | "dead";
23
23
  /**
24
- * Live agent-activity and resume-identity state for one terminal, computed
25
- * once by the host and shared across every subscriber — never recomputed
26
- * per-extension. Returned by {@link AgentsService.getState} and
27
- * {@link AgentsService.getByTerminalId}; delivered to
24
+ * Which kind of **Agent Session** an {@link AgentInfo} describes (RFC 0038):
25
+ *
26
+ * - `"terminal"` — the agent runs in a PTY and draws its own TUI; Silo
27
+ * *infers* what it is doing from OSC/output signals, and its identity comes
28
+ * from detection (ADR 0028).
29
+ * - `"chat"` — the agent is an Agent Client Protocol child speaking structured
30
+ * JSON-RPC over piped stdio; it *reports* what it is doing and declares its
31
+ * identity at `initialize`, and Silo renders the conversation.
32
+ *
33
+ * `ctx.agents` reports **one shape regardless of kind** — the promise is
34
+ * observation parity, not capability parity. Most consumers never need to
35
+ * branch on this; use {@link AgentsService.reveal} to bring a session into
36
+ * view without knowing which kind it is.
37
+ *
38
+ * @category Core Types
39
+ * @public
40
+ * @beta
41
+ */
42
+ export type AgentSessionKind = "terminal" | "chat";
43
+ /**
44
+ * Live agent-activity and resume-identity state for one **Agent Session**
45
+ * (RFC 0038), computed once by the host and shared across every subscriber —
46
+ * never recomputed per-extension. Returned by {@link AgentsService.getState}
47
+ * and {@link AgentsService.getByTerminalId}; delivered to
28
48
  * {@link AgentsService.subscribe} listeners on every change.
29
49
  *
30
50
  * @category Consumer Services
@@ -32,20 +52,62 @@ export type AgentActivity = "none" | "working" | "idle" | "error" | "dead";
32
52
  * @beta
33
53
  */
34
54
  export interface AgentInfo {
35
- /** The terminal record id this state belongs to. */
36
- readonly terminalId: string;
37
- /** The workspace this terminal belongs to. */
55
+ /**
56
+ * Stable id for this Agent Session — the key {@link AgentsService.reveal},
57
+ * {@link AgentsService.resume}, and {@link AgentsService.acknowledge} take.
58
+ * For a Terminal session this is the terminal record id; for a Chat session
59
+ * it is the session handle's id. Prefer this over
60
+ * {@link AgentInfo.terminalId} for anything that should not care which kind
61
+ * of session it is.
62
+ */
63
+ readonly id: string;
64
+ /**
65
+ * The terminal record id backing this session, when there is one — the same
66
+ * id {@link AgentsService.getByTerminalId} and `ctx.terminals` take.
67
+ * Present for every `kind: "terminal"` session; absent for a `kind: "chat"`
68
+ * session, which has no PTY. A consumer that needs a terminal-tab id should
69
+ * check {@link AgentInfo.kind} first, or use {@link AgentsService.reveal}.
70
+ */
71
+ readonly terminalId?: string;
72
+ /** The workspace this session belongs to. */
38
73
  readonly workspaceId: string;
39
74
  /**
40
- * The terminal's kind at registration time.
75
+ * The session's display label — host-computed, the same string for either
76
+ * kind, and the one a consumer should render. A status row, a navigator row
77
+ * and the session's own dock tab all showing this field is what keeps them
78
+ * from drifting.
79
+ *
80
+ * It is a three-step fallback, and the two kinds are exact parallels:
81
+ *
82
+ * | step | Terminal | Chat |
83
+ * | ------------------ | ----------------------------------- | --------------------------------- |
84
+ * | the agent's words | the OSC window title | `session_info_update.title` |
85
+ * | the user's name | `TerminalRecord.customName` | *(no rename gesture yet)* |
86
+ * | fallback | the terminal's derived name | declared agent name → profile label |
87
+ *
88
+ * The user's name wins where they set one; otherwise the agent's own words
89
+ * win, with agent **status markers stripped** (Claude's spinner, Cursor's
90
+ * trailing ` - Working …`) — the status is already structured state on this
91
+ * same record, so repeating it in the label is noise.
92
+ *
93
+ * Not every agent volunteers a title, and whether a given one does is its
94
+ * own choice — and its adapter's, version to version. A session that never
95
+ * gets one sits on the fallback, exactly as a terminal tab does for a CLI
96
+ * that writes no OSC title. Silo does not paper over that by inventing a
97
+ * summary from the first prompt.
98
+ */
99
+ readonly title: string;
100
+ /**
101
+ * Which kind of Agent Session this is — `"terminal"` or `"chat"`. See
102
+ * {@link AgentSessionKind}. `ctx.agents` reports the same fields for both;
103
+ * this exists for the rare consumer that genuinely needs a PTY tab id or a
104
+ * transcript panel.
41
105
  *
42
- * @deprecated Always `"shell"` after RFC 0033 — the deprecated `"claude"` /
43
- * `"pi"` kinds are normalized away at load and nothing creates them, so a
44
- * consumer branching on this is reading a constant. Use
45
- * {@link AgentInfo.agentId} for which agent is running, and
46
- * {@link AgentInfo.isAgent} for whether one is.
106
+ * (This field was the vestigial `TerminalKind` before RFC 0038 — always
107
+ * `"shell"` and read by nothing — and carries the session discriminator
108
+ * now.)
47
109
  */
48
- readonly kind: TerminalKind;
110
+ readonly kind: AgentSessionKind;
49
111
  /**
50
112
  * Whether this terminal currently hosts an agent — true if it was created
51
113
  * as one, or an agent-specific signal was observed in it (e.g. typing
@@ -94,6 +156,27 @@ export interface AgentInfo {
94
156
  * both live and at `activity === "dead"`.
95
157
  */
96
158
  readonly resumeCommand?: string;
159
+ /**
160
+ * Whether this session can be resumed **through Silo** — i.e. whether
161
+ * {@link AgentsService.resume} will do something for this id.
162
+ *
163
+ * - Terminal session: `true` once an exact session id was resolved (a
164
+ * Settings → Agents hook, or an agent's native session file), which is
165
+ * also when {@link AgentInfo.resumeCommand} becomes exact rather than an
166
+ * honest note. `resume()` is still a no-op for a Terminal session in this
167
+ * release — the user runs `resumeCommand` themselves; the flag is the
168
+ * forward-looking capability signal RFC 0038 replaces the shell-string
169
+ * `resumeCommand` contract with.
170
+ * - Chat session: `true` when the agent advertises `session/load`, so a
171
+ * fresh process can reload the transcript after the old one died.
172
+ *
173
+ * - Chat session: `true` when the agent advertises **either**
174
+ * `sessionCapabilities.resume` or `agentCapabilities.loadSession` (RFC
175
+ * 0042) — the two are distinct capabilities (see {@link ChatResumeState})
176
+ * but either is enough for {@link AgentsService.resume} to bring the
177
+ * conversation back.
178
+ */
179
+ readonly canResume: boolean;
97
180
  /**
98
181
  * Human-readable agent name, e.g. `"Claude Code"` or `"Codex CLI"`. Tells
99
182
  * you *which* agent CLI is running in this terminal, independent of
@@ -111,7 +194,40 @@ export interface AgentInfo {
111
194
  * the same moment and lifecycle as `agentName`.
112
195
  */
113
196
  readonly agentId?: string;
197
+ /**
198
+ * For a `kind: "chat"` session, where it stands in **Chat session
199
+ * resurrection** (RFC 0042) — absent for a `kind: "terminal"` session, which
200
+ * has no such lifecycle. See {@link ChatResumeState}.
201
+ */
202
+ readonly chatResumeState?: ChatResumeState;
114
203
  }
204
+ /**
205
+ * Where a **Chat** Agent Session stands in Chat session resurrection (RFC
206
+ * 0042) — bringing its transcript, and the ability to keep talking, back after
207
+ * its process died with the app.
208
+ *
209
+ * - `"dormant"` — known only from persistence: the conversation exists and its
210
+ * panel is recorded, but nothing has connected to it this run and no agent
211
+ * process is alive. This is what a Chat session in a workspace you have not
212
+ * visited since launching looks like — listing it is the point, and opening
213
+ * it is what starts the reconnect. Its `activity` is always `"idle"`.
214
+ * - `"live"` — an ordinary, uninterrupted connection (a plain `session/new`
215
+ * this run; never went through the resume machinery).
216
+ * - `"resuming"` — a restore is in flight: `session/resume` or `session/load`
217
+ * is being probed and called.
218
+ * - `"resumed"` — reconnected: `session/resume` (no replay, the transcript
219
+ * comes from the **transcript journal**) or `session/load` (replay,
220
+ * reconciled against the journal) succeeded.
221
+ * - `"journal-only"` — the agent could do neither; the transcript is the
222
+ * journal alone and the session cannot be prompted in place — the panel's
223
+ * own "Continue in a new session" action is the way forward.
224
+ * - `"unavailable"` — no session id and no journal; nothing to show.
225
+ *
226
+ * @category Consumer Services
227
+ * @public
228
+ * @beta
229
+ */
230
+ export type ChatResumeState = "dormant" | "live" | "resuming" | "resumed" | "journal-only" | "unavailable";
115
231
  /**
116
232
  * A Catalog Agent's brand mark — SVG path data plus the two theme-dependent
117
233
  * hexes. Consumed by {@link AgentIconGlyph}; a single hex cannot have enough
@@ -189,6 +305,22 @@ export interface AgentProfileSummary {
189
305
  * `"agent-takes-none"` after the user has already typed one.
190
306
  */
191
307
  readonly acceptsPrompt: boolean;
308
+ /**
309
+ * Which **interface** this profile starts the agent through — the profile's
310
+ * `launch` arm, expressed in the same {@link AgentSessionKind} vocabulary
311
+ * `AgentInfo.kind` uses (RFC 0038):
312
+ *
313
+ * - `"terminal"` — {@link AgentProfilesService.launch} runs it in a PTY and
314
+ * the agent draws its own TUI.
315
+ * - `"chat"` — {@link AgentSessionsService.connect} speaks the Agent Client
316
+ * Protocol to it and *you* render the conversation.
317
+ *
318
+ * The two are driven through different services, so a picker must filter on
319
+ * this rather than offer every profile to both: `launch()`ing a Chat profile
320
+ * and `connect()`ing a Terminal profile both fail, and the user should never
321
+ * be offered a profile that cannot work in the surface they are looking at.
322
+ */
323
+ readonly interface: AgentSessionKind;
192
324
  }
193
325
  /**
194
326
  * Why an opening prompt could not be delivered. Silo refuses rather than
@@ -334,6 +466,818 @@ export interface AgentProfilesService {
334
466
  */
335
467
  launch(options?: LaunchAgentProfileOptions): LaunchAgentProfileResult;
336
468
  }
469
+ /**
470
+ * One block of an outgoing prompt turn for a **Chat session**
471
+ * ({@link AgentSessionHandle.prompt}). Structured content, never a shell
472
+ * string — the entire quoting/escaping risk surface of a Terminal session's
473
+ * opening prompt (RFC 0033 phase 3) does not exist here.
474
+ *
475
+ * - `"text"` — a run of plain text. `$HOME`, backticks, quotes and newlines
476
+ * are all literal.
477
+ * - `"resource_link"` — a pointer to a file (or other URI) the agent may read
478
+ * if it chooses. `uri` is typically a `file://` path inside the workspace.
479
+ * Always accepted — this is a pointer, not embedded content, so it needs no
480
+ * {@link AgentPromptCapabilities} gate.
481
+ * - `"resource"` — the file's own text **inlined** into the prompt, rather
482
+ * than a pointer the agent may or may not follow. Only send this to a
483
+ * session whose {@link AgentSessionHandle.promptCapabilities} has
484
+ * `embeddedContext: true`.
485
+ *
486
+ * Images are deferred — no probe has yet checked what each agent actually
487
+ * accepts for one, and this union grows behind that once one has (RFC 0040
488
+ * open question 3).
489
+ *
490
+ * @category Consumer Services
491
+ * @public
492
+ * @beta
493
+ */
494
+ export type AgentPromptBlock = {
495
+ readonly type: "text";
496
+ readonly text: string;
497
+ } | {
498
+ readonly type: "resource_link";
499
+ readonly uri: string;
500
+ /** A short display name for the link; defaults to the last path segment. */
501
+ readonly name?: string;
502
+ } | {
503
+ readonly type: "resource";
504
+ /** The resource's own URI, e.g. a `file://` path. */
505
+ readonly uri: string;
506
+ readonly mimeType?: string;
507
+ /** The embedded text content. Binary (blob) resources are not modelled —
508
+ * nothing in Silo has needed to embed one yet. */
509
+ readonly text: string;
510
+ };
511
+ /**
512
+ * Why a prompt turn ended, straight from the agent's Agent Client Protocol
513
+ * stop reason. `"end_turn"` is the normal completion; `"max_tokens"` /
514
+ * `"max_turn_requests"` are budget cutoffs; `"refusal"` means the agent
515
+ * declined the request; `"cancelled"` follows {@link AgentSessionHandle.cancel}.
516
+ *
517
+ * @category Consumer Services
518
+ * @public
519
+ * @beta
520
+ */
521
+ export type AgentStopReason = "end_turn" | "max_tokens" | "max_turn_requests" | "refusal" | "cancelled";
522
+ /**
523
+ * What {@link AgentSessionHandle.prompt} resolves to. A prompt that cannot be
524
+ * delivered — the connection dropped, the agent errored mid-turn — **rejects**
525
+ * instead, with the agent's own message where there is one.
526
+ *
527
+ * @category Consumer Services
528
+ * @public
529
+ * @beta
530
+ */
531
+ export interface AgentPromptResult {
532
+ readonly stopReason: AgentStopReason;
533
+ }
534
+ /**
535
+ * One piece of agent-authored content — the Agent Client Protocol content
536
+ * block, which appears both in a streamed message chunk
537
+ * ({@link AgentSessionUpdate.content}) and inside a tool call's output
538
+ * ({@link AgentToolCallContent.content}).
539
+ *
540
+ * **`type` is deliberately a `string`, not a union.** The protocol names
541
+ * `"text"`, `"image"`, `"audio"`, `"resource_link"` and `"resource"`, and a
542
+ * vendor may add its own; the same tolerance rule the rest of this surface
543
+ * follows applies — render the types you know and **name, rather than drop**,
544
+ * one you do not, so a transcript never silently loses a block.
545
+ *
546
+ * Every field but `type` is optional because which ones arrive depends on the
547
+ * type _and_ the agent: only `"text"` blocks were seen from Cursor
548
+ * (2026.09.02) and `claude-agent-acp` (0.75.1) in the 2026-09-08 probe, so
549
+ * treat the rest as modelled-but-unverified and check before you read.
550
+ *
551
+ * @category Consumer Services
552
+ * @public
553
+ * @beta
554
+ */
555
+ export interface AgentContentBlock {
556
+ /** The protocol's block type — `"text"`, `"image"`, `"audio"`,
557
+ * `"resource_link"`, `"resource"`, or a vendor's own. */
558
+ readonly type: string;
559
+ /** The text, for a `"text"` block. */
560
+ readonly text?: string;
561
+ /** MIME type, for an `"image"` / `"audio"` / `"resource_link"` block. */
562
+ readonly mimeType?: string;
563
+ /** Where the content lives, for a `"resource_link"` (or an image given by
564
+ * reference rather than inline). Typically a `file://` URI. */
565
+ readonly uri?: string;
566
+ /** A short display name, for a `"resource_link"`. */
567
+ readonly name?: string;
568
+ }
569
+ /**
570
+ * One block of a tool call's content — what the agent wants shown _inside_ the
571
+ * tool row. The protocol wraps each in a `{ type }` envelope, and the three it
572
+ * names are `"content"` (an {@link AgentContentBlock}), `"diff"` (a file edit)
573
+ * and `"terminal"` (output streaming into an agent-side terminal).
574
+ *
575
+ * `type` is a `string` for the usual reason: tolerate what you do not know.
576
+ * The remaining fields are the union's arms flattened, so check `type` before
577
+ * reading them.
578
+ *
579
+ * @category Consumer Services
580
+ * @public
581
+ * @beta
582
+ */
583
+ export interface AgentToolCallContent {
584
+ /** `"content"`, `"diff"`, `"terminal"`, or a vendor's own. */
585
+ readonly type: string;
586
+ /** The content block, when `type` is `"content"`. */
587
+ readonly content?: AgentContentBlock;
588
+ /** The file being edited, when `type` is `"diff"`. */
589
+ readonly path?: string;
590
+ /** The file's contents before the edit, when `type` is `"diff"`. Absent for
591
+ * a file the agent is creating — `claude-agent-acp` 0.75.1 sends `null`
592
+ * there, which arrives here as `undefined`. */
593
+ readonly oldText?: string;
594
+ /** The file's contents after the edit, when `type` is `"diff"`. */
595
+ readonly newText?: string;
596
+ /** The agent-side terminal this call is streaming into, when `type` is
597
+ * `"terminal"`. Silo declines `terminal/*` today, so a consumer can name the
598
+ * block but cannot read its output. */
599
+ readonly terminalId?: string;
600
+ }
601
+ /**
602
+ * A file (and optionally a line) a tool call touches — the protocol's
603
+ * `locations`, which is how an agent says "this call is about _this_ file"
604
+ * independently of its title. A Chat UI can turn these into "open the file"
605
+ * affordances.
606
+ *
607
+ * Both agents probed on 2026-09-08 send it; only `claude-agent-acp` 0.75.1 was
608
+ * seen sending `line`.
609
+ *
610
+ * @category Consumer Services
611
+ * @public
612
+ * @beta
613
+ */
614
+ export interface AgentToolCallLocation {
615
+ /** Absolute path to the file. */
616
+ readonly path: string;
617
+ /** 1-based line the call is focused on, when the agent named one. */
618
+ readonly line?: number;
619
+ }
620
+ /**
621
+ * One tool call an agent is making, carried by a `tool_call` (the call opening)
622
+ * or a `tool_call_update` (a change to one already open) — see
623
+ * {@link AgentSessionUpdate.toolCall}.
624
+ *
625
+ * **Both kinds map to this same type, so only {@link toolCallId} is
626
+ * guaranteed.** A `tool_call` typically carries `title`, `kind` and `status`;
627
+ * a `tool_call_update` carries **only the fields that changed** (in the
628
+ * 2026-09-08 probe, most updates were `{ toolCallId, status }` alone). So a
629
+ * consumer keys rows by {@link toolCallId} and patches the fields that are
630
+ * present — never overwrite a title with `undefined`.
631
+ *
632
+ * A `tool_call_update` for a call whose opening `tool_call` never arrived is a
633
+ * real shape; render it rather than dropping it.
634
+ *
635
+ * @category Consumer Services
636
+ * @public
637
+ * @beta
638
+ */
639
+ export interface AgentToolCall {
640
+ /** The call's id — stable across its `tool_call` and every
641
+ * `tool_call_update`, and the same id an
642
+ * {@link AgentPermissionRequest.toolCallId} refers to. */
643
+ readonly toolCallId: string;
644
+ /** Human-readable label, e.g. `"Read notes.md"`. Present on the opening
645
+ * `tool_call`, and on an update only when it changed — agents do relabel a
646
+ * call as it progresses. */
647
+ readonly title?: string;
648
+ /** The protocol's coarse category: `"read"`, `"edit"`, `"delete"`,
649
+ * `"move"`, `"search"`, `"execute"`, `"think"`, `"fetch"`,
650
+ * `"switch_mode"`, `"other"`, or a vendor's own. Tolerate unknown values;
651
+ * do not switch exhaustively. */
652
+ readonly kind?: string;
653
+ /** `"pending"`, `"in_progress"`, `"completed"`, `"failed"`, or a vendor's
654
+ * own. Not every agent walks the whole ladder — `claude-agent-acp` 0.75.1
655
+ * went `pending` → `completed` with no `in_progress`. */
656
+ readonly status?: string;
657
+ /** What to show inside the row. Absent on an update that changed something
658
+ * else — treat that as "unchanged", not "now empty". */
659
+ readonly content?: readonly AgentToolCallContent[];
660
+ /** The files this call touches. */
661
+ readonly locations?: readonly AgentToolCallLocation[];
662
+ /** The arguments the agent passed to its own tool, exactly as it sent them.
663
+ * **Vendor-shaped by definition** — the protocol places no schema on it, so
664
+ * it is typed `unknown`: narrow it yourself, and expect a different shape
665
+ * from each agent. */
666
+ readonly rawInput?: unknown;
667
+ /** The tool's own result, same caveat as {@link rawInput}. */
668
+ readonly rawOutput?: unknown;
669
+ }
670
+ /**
671
+ * One row of the agent's plan — the protocol's `plan` update entry.
672
+ *
673
+ * @category Consumer Services
674
+ * @public
675
+ * @beta
676
+ */
677
+ export interface AgentPlanEntry {
678
+ /** What the step is, in the agent's words. */
679
+ readonly content: string;
680
+ /** `"pending"`, `"in_progress"`, `"completed"`, or a vendor's own. */
681
+ readonly status?: string;
682
+ /** `"high"`, `"medium"`, `"low"`, or a vendor's own, when the agent ranked
683
+ * the step. */
684
+ readonly priority?: string;
685
+ }
686
+ /**
687
+ * One `session/update` notification from a **Chat session**, lightly
688
+ * normalized. The Agent Client Protocol streams a turn as a sequence of these:
689
+ * assistant text, agent "thinking", tool-call rows, plan updates, and more.
690
+ *
691
+ * **A consumer must tolerate `kind` values it does not recognize** — agents
692
+ * emit different subsets and vendors add their own (recon §3). Switch on the
693
+ * kinds you render and ignore the rest; never treat an unknown kind as an
694
+ * error.
695
+ *
696
+ * Everything a Chat UI must draw to render a transcript is a modelled field:
697
+ * {@link text} / {@link content} for the message kinds, {@link toolCall} for
698
+ * `tool_call` and `tool_call_update`, {@link plan} for `plan`. {@link raw} is
699
+ * for what is deliberately left out — see its own note.
700
+ *
701
+ * @category Consumer Services
702
+ * @public
703
+ * @beta
704
+ */
705
+ export interface AgentSessionUpdate {
706
+ /**
707
+ * The Agent Client Protocol `sessionUpdate` discriminator, e.g.
708
+ * `"agent_message_chunk"`, `"agent_thought_chunk"`, `"tool_call"`,
709
+ * `"tool_call_update"`, `"plan"`, `"available_commands_update"`.
710
+ */
711
+ readonly kind: string;
712
+ /**
713
+ * Text payload for the streaming-text kinds (`agent_message_chunk`,
714
+ * `agent_thought_chunk`, `user_message_chunk`); `undefined` for every other
715
+ * kind — and also for a text kind whose block is not text, in which case
716
+ * read {@link content}.
717
+ */
718
+ readonly text?: string;
719
+ /**
720
+ * The whole content block a streaming-text kind carried, of which
721
+ * {@link text} is the `"text"` shorthand. Read it when you want to render an
722
+ * image or a resource link an agent sent as a message rather than dropping
723
+ * it. `undefined` for every non-message kind.
724
+ */
725
+ readonly content?: AgentContentBlock;
726
+ /**
727
+ * Stable id for the run of chunks this update belongs to. Consecutive
728
+ * same-kind chunks share one — **synthesized by Silo when the agent omits
729
+ * `messageId`**, which real agents do (recon Finding 7), so a consumer can
730
+ * group a streamed sentence into one bubble without minting ids itself.
731
+ */
732
+ readonly messageId?: string;
733
+ /**
734
+ * The tool call, for `kind` `"tool_call"` and `"tool_call_update"`;
735
+ * `undefined` otherwise. Key rows by {@link AgentToolCall.toolCallId} and
736
+ * patch in place — an update carries only what changed.
737
+ */
738
+ readonly toolCall?: AgentToolCall;
739
+ /**
740
+ * The agent's plan **in full**, for `kind` `"plan"`; `undefined` otherwise.
741
+ * The agent reissues the entire list every time, so replace the plan you are
742
+ * showing rather than appending to it. May be empty.
743
+ */
744
+ readonly plan?: readonly AgentPlanEntry[];
745
+ /**
746
+ * The raw Agent Client Protocol `update` object — the **escape hatch**, for
747
+ * the kinds and fields this surface does not model.
748
+ *
749
+ * What is deliberately not modelled, and why: `available_commands_update`
750
+ * (an agent's slash commands — a menu, not a transcript row),
751
+ * `usage_update` (token counts, which no agent reports the same way),
752
+ * `current_mode_update` (already surfaced as
753
+ * {@link AgentSessionConfigOption.currentValue}), `session_info_update`
754
+ * (already surfaced as {@link AgentInfo.title}), and vendor extensions such
755
+ * as `claude-agent-acp`'s `_meta`. None is needed to draw a transcript; all
756
+ * are readable here.
757
+ *
758
+ * **`raw` tracks the protocol, not this SDK's semver.** A field inside it can
759
+ * change shape, or vanish, when an agent or its adapter changes — no SDK
760
+ * major required, and that has already happened three times inside one sprint.
761
+ * Read it defensively, and if you find yourself needing it for something
762
+ * every Chat UI must render, that is a gap in this surface worth reporting.
763
+ * Treat it as read-only.
764
+ */
765
+ readonly raw: Readonly<Record<string, unknown>>;
766
+ }
767
+ /**
768
+ * One choice offered by an {@link AgentPermissionRequest} — pass its
769
+ * {@link AgentPermissionOption.optionId} to {@link AgentPermissionRequest.respond}.
770
+ *
771
+ * @category Consumer Services
772
+ * @public
773
+ * @beta
774
+ */
775
+ export interface AgentPermissionOption {
776
+ readonly optionId: string;
777
+ /** Label to show on the button, e.g. `"Allow"`, `"Allow for this session"`,
778
+ * `"Reject"`. */
779
+ readonly name: string;
780
+ /** The protocol's coarse category for the option, e.g. `"allow_once"`,
781
+ * `"allow_always"`, `"reject_once"` — for styling a set of buttons
782
+ * consistently. Tolerate unknown values. */
783
+ readonly kind: string;
784
+ }
785
+ /**
786
+ * The agent is **blocked on the user**: it wants permission to run a tool and
787
+ * the turn will not proceed until {@link AgentPermissionRequest.respond} is
788
+ * called. Delivered to {@link AgentSessionHandle.onPermission}.
789
+ *
790
+ * **This is not a safety boundary.** An agent routes a request through here
791
+ * only if it chooses to; nothing stops it touching the filesystem directly
792
+ * (recon Finding 1). Do not present this as Silo gating the agent's actions.
793
+ *
794
+ * If no `onPermission` listener is registered, Silo answers `cancelled` on the
795
+ * extension's behalf so the agent is never left hanging.
796
+ *
797
+ * @category Consumer Services
798
+ * @public
799
+ * @beta
800
+ */
801
+ export interface AgentPermissionRequest {
802
+ /** The tool call this permission is for — the same id as the matching
803
+ * {@link AgentToolCall.toolCallId} in the update stream. */
804
+ readonly toolCallId: string;
805
+ /** A human-readable description of what the agent wants to do. */
806
+ readonly title: string;
807
+ /** The choices to present. Always at least one; order is the agent's. */
808
+ readonly options: readonly AgentPermissionOption[];
809
+ /**
810
+ * The call the agent is asking to make, when it sent one — the protocol's
811
+ * `toolCall` params, the same shape the update stream carries. It is how a
812
+ * Chat UI can show the diff **before** the user answers rather than only the
813
+ * title; `claude-agent-acp` 0.75.1 sends a full one including `content`.
814
+ */
815
+ readonly toolCall?: AgentToolCall;
816
+ /** The raw Agent Client Protocol `session/request_permission` params. */
817
+ readonly raw: Readonly<Record<string, unknown>>;
818
+ /**
819
+ * Answer the request with one of {@link AgentPermissionRequest.options}.
820
+ * Idempotent — the first call wins, later calls are ignored. Passing an
821
+ * `optionId` that is not in `options` answers `cancelled`.
822
+ */
823
+ respond(optionId: string): void;
824
+ }
825
+ /**
826
+ * One selectable value inside an {@link AgentSessionConfigOption}.
827
+ *
828
+ * @category Consumer Services
829
+ * @public
830
+ * @beta
831
+ */
832
+ export interface AgentSessionConfigChoice {
833
+ /** The value to pass to {@link AgentSessionHandle.setConfigOption}. */
834
+ readonly value: string;
835
+ /** Label to show, e.g. `"Claude Sonnet"`, `"Plan"`. */
836
+ readonly name: string;
837
+ /** A longer explanation, when the agent gave one. */
838
+ readonly description?: string;
839
+ }
840
+ /**
841
+ * One session-level control the agent advertised at connect — the Agent Client
842
+ * Protocol `session/new` `configOptions` list (recon 2026-09-08). It is
843
+ * **self-describing and subsumes** the older `modes` / `models` fields: Cursor
844
+ * advertises a `"mode"` entry and a `"model"` entry, Claude a single `"mode"`
845
+ * entry (its permission mode), and a vendor may add its own.
846
+ *
847
+ * The write path is **generic too**:
848
+ * {@link AgentSessionHandle.setConfigOption} goes through the protocol's
849
+ * `session/set_config_option`, so a category Silo has never heard of still
850
+ * works — Claude's `"thought_level"` (Effort) has no typed method anywhere in
851
+ * the protocol and sets fine. Silo keeps `session/set_mode` /
852
+ * `session/set_model` only as a fallback for an agent that does not implement
853
+ * the generic setter.
854
+ *
855
+ * Render one control per entry and **skip an entry whose {@link type} you do
856
+ * not recognise** — the same tolerance rule {@link AgentSessionUpdate} follows
857
+ * for unknown `kind`s. Do not assume every advertised entry is settable: an
858
+ * adapter may list an id its own handler rejects, so treat a failed
859
+ * {@link AgentSessionHandle.setConfigOption} as "stop offering this one".
860
+ *
861
+ * @category Consumer Services
862
+ * @public
863
+ * @beta
864
+ */
865
+ export interface AgentSessionConfigOption {
866
+ /** Stable id — pass it to {@link AgentSessionHandle.setConfigOption}. */
867
+ readonly id: string;
868
+ /** Label for the control, e.g. `"Mode"`, `"Model"`. */
869
+ readonly name: string;
870
+ /** A longer explanation, when the agent gave one. */
871
+ readonly description?: string;
872
+ /**
873
+ * The protocol category that decides how a write is delivered — `"mode"`,
874
+ * `"model"`, or a vendor's own. {@link AgentSessionHandle.setConfigOption}
875
+ * rejects a category with no verified writer.
876
+ */
877
+ readonly category: string;
878
+ /**
879
+ * The control shape. Only `"select"` is modelled today; treat any other
880
+ * value as "do not render".
881
+ */
882
+ readonly type: string;
883
+ /**
884
+ * The currently-selected {@link AgentSessionConfigChoice.value}. Kept
885
+ * current when the agent moves it itself (an ACP `current_mode_update`), so
886
+ * a bound control always reflects reality — subscribe with
887
+ * {@link AgentSessionHandle.onConfigOptionsChanged}.
888
+ */
889
+ readonly currentValue: string;
890
+ /** The choices, in the agent's order. */
891
+ readonly options: readonly AgentSessionConfigChoice[];
892
+ }
893
+ /**
894
+ * One command the agent advertises (Agent Client Protocol
895
+ * `available_commands_update`, RFC 0040). **Skills arrive in this list too**
896
+ * — the protocol has no separate skills concept, and the two agents probed
897
+ * mark them differently: pi prefixes the name (`skill:code-review`), Claude
898
+ * does not mark them at all outside a `(user)` / `(project)` suffix in
899
+ * {@link description}'s prose. Splitting them back out would mean
900
+ * vendor-sniffing a label neither agent is contractually bound to keep, so
901
+ * this surface deliberately does not try.
902
+ *
903
+ * @category Consumer Services
904
+ * @public
905
+ * @beta
906
+ */
907
+ export interface AgentCommand {
908
+ /** What to send after `/` to invoke it — `prompt([{ type: "text", text:
909
+ * "/" + name }])`. There is no separate invocation method. */
910
+ readonly name: string;
911
+ /** One-line summary for a palette row. */
912
+ readonly description?: string;
913
+ /**
914
+ * Present when the command takes a free-text argument; `hint` is the
915
+ * agent's own placeholder, when it gave one. Absent means the command takes
916
+ * none — normalised at the host boundary, since Claude sends `input: null`
917
+ * where pi omits the key entirely; this type only ever sees one spelling.
918
+ */
919
+ readonly input?: {
920
+ readonly hint?: string;
921
+ };
922
+ }
923
+ /**
924
+ * What a Chat session accepts as prompt content, read once from `initialize`
925
+ * and fixed for the session's life (RFC 0040). A UI gates its attachment
926
+ * affordance on this rather than offering one that fails at prompt time.
927
+ *
928
+ * A field the agent's `initialize` response omitted defaults to `false`
929
+ * rather than being left `undefined` — both `codex-acp` and `claude-agent-acp`
930
+ * omit `audio` entirely (recon 2026-09-09), so treating an absent field as
931
+ * "not accepted" is the only reading that doesn't require every consumer to
932
+ * repeat the same `?? false`.
933
+ *
934
+ * @category Consumer Services
935
+ * @public
936
+ * @beta
937
+ */
938
+ export interface AgentPromptCapabilities {
939
+ /** Image content blocks are accepted. Not yet a sendable
940
+ * {@link AgentPromptBlock} — see its doc comment. */
941
+ readonly image: boolean;
942
+ /** Audio content blocks are accepted. */
943
+ readonly audio: boolean;
944
+ /**
945
+ * Embedded `"resource"` {@link AgentPromptBlock}s are accepted. A
946
+ * `"resource_link"` (a pointer the agent may read itself) is always
947
+ * allowed and is not gated on this.
948
+ */
949
+ readonly embeddedContext: boolean;
950
+ }
951
+ /**
952
+ * A live handle to one **Chat session** (RFC 0038) — an Agent Client Protocol
953
+ * child Silo spawned from a user-authored Chat profile, speaking structured
954
+ * JSON-RPC over piped stdio. Returned by {@link AgentSessionsService.connect}.
955
+ *
956
+ * The same session shows up in {@link AgentsService.getState} as an
957
+ * {@link AgentInfo} with `kind: "chat"` and this handle's {@link id} — so the
958
+ * Agents navigator, attention badges and status all work for it exactly as for
959
+ * a Terminal session, with no extra wiring.
960
+ *
961
+ * Drive one turn at a time: call {@link prompt}, await its
962
+ * {@link AgentPromptResult}, then prompt again.
963
+ *
964
+ * @category Consumer Services
965
+ * @public
966
+ * @beta
967
+ */
968
+ export interface AgentSessionHandle {
969
+ /**
970
+ * This session's {@link AgentInfo.id} — the key {@link AgentsService.reveal},
971
+ * {@link AgentsService.resume} and {@link AgentsService.acknowledge} take.
972
+ * Stable across a {@link AgentsService.resume}.
973
+ */
974
+ readonly id: string;
975
+ /**
976
+ * The agent's own session id (`session/new`'s `sessionId`, or the fresh id
977
+ * `session/load` adopted) — what to pass back as
978
+ * {@link AgentSessionRestore.sessionId} on a future {@link
979
+ * AgentSessionsService.connect} to restore this conversation (RFC 0042).
980
+ * Distinct from {@link AgentSessionHandle.id}, which is namespaced for
981
+ * `ctx.agents` and does not change even when this does (an adopted id after
982
+ * `session/load`).
983
+ */
984
+ readonly sessionId: string;
985
+ /** The user's asserted catalog agent id for the profile, if any — the same
986
+ * value that selects the profile's `+`-menu icon. */
987
+ readonly agentId?: string;
988
+ /** Display name for the agent: what it declared at connect (`initialize`),
989
+ * falling back to the profile label when it declared none (recon Finding 4). */
990
+ readonly agentName: string;
991
+ /**
992
+ * Whether the agent advertises `session/resume` or `session/load`, i.e.
993
+ * whether {@link AgentsService.resume} can bring this conversation back
994
+ * after the process dies. Mirrors {@link AgentInfo.canResume}.
995
+ */
996
+ readonly canResume: boolean;
997
+ /**
998
+ * How this handle came to be connected (RFC 0042) — see
999
+ * {@link AgentSessionRestore}:
1000
+ *
1001
+ * - `"new"` — an ordinary `session/new` (no {@link
1002
+ * AgentSessionConnectOptions.resume} was given, or the persisted session
1003
+ * had gone stale and `connect()` fell through to a fresh one).
1004
+ * - `"resumed"` — a persisted session reconnected, via `session/resume` or
1005
+ * `session/load`.
1006
+ * - `"journal-only"` — the agent could do neither. There is no live
1007
+ * session: {@link prompt} rejects. Reconnect with
1008
+ * `resume: { sessionId, startFresh: true }` to continue in a new one,
1009
+ * preserving {@link journal} for continuity.
1010
+ */
1011
+ readonly resumeOutcome: "new" | "resumed" | "journal-only";
1012
+ /**
1013
+ * Prior turns to paint **before** subscribing to {@link onUpdate} — the
1014
+ * **transcript journal** (RFC 0042), read from disk. Empty when
1015
+ * {@link resumeOutcome} is `"new"` and nothing preceded this connection;
1016
+ * populated for `"resumed"` (a `session/resume` reconnect, which itself
1017
+ * replays nothing — this is the only record) and `"journal-only"`. Feed
1018
+ * these through the same reducer as {@link onUpdate} before subscribing to
1019
+ * it, e.g. `journal.reduce(applyUpdate, emptyTranscript)`.
1020
+ */
1021
+ readonly journal: readonly AgentSessionUpdate[];
1022
+ /**
1023
+ * Send a prompt turn and resolve when it ends. Content is structured blocks,
1024
+ * never a shell string. Rejects if the turn cannot be completed (connection
1025
+ * lost, agent error) — with the agent's message where it gave one. Also
1026
+ * rejects immediately when {@link resumeOutcome} is `"journal-only"` — there
1027
+ * is no live agent to prompt.
1028
+ */
1029
+ prompt(blocks: readonly AgentPromptBlock[]): Promise<AgentPromptResult>;
1030
+ /**
1031
+ * Ask the agent to stop the current turn (Agent Client Protocol
1032
+ * `session/cancel`). The in-flight {@link prompt} promise then resolves with
1033
+ * `stopReason: "cancelled"` rather than rejecting.
1034
+ */
1035
+ cancel(): void;
1036
+ /**
1037
+ * Subscribe to the turn's {@link AgentSessionUpdate} stream — streaming text,
1038
+ * tool calls, plans. **Not confined to between {@link prompt} and its
1039
+ * resolution**: some agents send one at connect, before any prompt at all
1040
+ * (pi's own startup banner arrives as an `agent_message_chunk` — recon
1041
+ * 2026-09-09), and a replay of prior turns can follow
1042
+ * {@link AgentsService.resume}. Subscribe before assuming nothing will
1043
+ * arrive yet. Returns a {@link Disposable}.
1044
+ */
1045
+ onUpdate(listener: (update: AgentSessionUpdate) => void): Disposable;
1046
+ /**
1047
+ * Subscribe to {@link AgentPermissionRequest}s. Returns a {@link Disposable}.
1048
+ * With at least one listener registered, answering is your responsibility;
1049
+ * with none, Silo answers `cancelled`.
1050
+ */
1051
+ onPermission(listener: (request: AgentPermissionRequest) => void): Disposable;
1052
+ /**
1053
+ * The session-level controls the agent advertised at connect — mode, model,
1054
+ * and whatever else it offers, each a self-describing
1055
+ * {@link AgentSessionConfigOption}. Empty when it advertised none.
1056
+ *
1057
+ * This is a **live snapshot**: {@link setConfigOption} and a mode the agent
1058
+ * changes itself both update it in place. Subscribe with
1059
+ * {@link onConfigOptionsChanged} and re-read.
1060
+ */
1061
+ readonly configOptions: readonly AgentSessionConfigOption[];
1062
+ /**
1063
+ * Change one advertised control. `id` names an entry in
1064
+ * {@link configOptions}; `value` is one of that entry's
1065
+ * {@link AgentSessionConfigChoice.value}s.
1066
+ *
1067
+ * Works for **any** category the agent advertises, including ones Silo has
1068
+ * never heard of — the host writes through the protocol's generic
1069
+ * `session/set_config_option`, falling back to the typed
1070
+ * `session/set_mode` / `session/set_model` only for an agent that does not
1071
+ * implement it.
1072
+ *
1073
+ * Rejects, with nothing written, on an unknown `id` or a `value` outside
1074
+ * that entry's options — and with the agent's own message when the agent
1075
+ * refuses (an adapter may advertise an entry its own handler does not know).
1076
+ * A rejection is a signal to stop offering that control.
1077
+ *
1078
+ * Resolves once the agent has acknowledged the change. {@link configOptions}
1079
+ * is then replaced from the agent's own updated list — setting one option can
1080
+ * move another — and {@link onConfigOptionsChanged} fires just before it
1081
+ * resolves.
1082
+ */
1083
+ setConfigOption(id: string, value: string): Promise<void>;
1084
+ /**
1085
+ * Fires whenever {@link configOptions} changes — a {@link setConfigOption}
1086
+ * landing, or the agent moving a value on its own (an ACP
1087
+ * `current_mode_update`). Re-read {@link configOptions} from the handle.
1088
+ * Returns a {@link Disposable}.
1089
+ */
1090
+ onConfigOptionsChanged(listener: () => void): Disposable;
1091
+ /**
1092
+ * The commands the agent advertises, live — a `/` palette's data source
1093
+ * (RFC 0040). **Empty until the agent's first
1094
+ * `available_commands_update`**: both agents probed send one at connect,
1095
+ * before any prompt, but nothing in the protocol requires it, and pi took
1096
+ * ~12.5s to get there (behind its startup banner) — `connect()` does not
1097
+ * wait for it. Subscribe with {@link onCommandsChanged} and re-read.
1098
+ *
1099
+ * There is no `runCommand()`. Invoke one the same way you'd send anything
1100
+ * else: `prompt([{ type: "text", text: "/" + command.name }])`.
1101
+ */
1102
+ readonly commands: readonly AgentCommand[];
1103
+ /** Fires whenever {@link commands} is replaced. Returns a
1104
+ * {@link Disposable}. */
1105
+ onCommandsChanged(listener: () => void): Disposable;
1106
+ /**
1107
+ * What this session accepts as prompt content, read once from `initialize`
1108
+ * and fixed for its life (RFC 0040) — unlike {@link commands}, this never
1109
+ * changes, so there is no change event for it.
1110
+ */
1111
+ readonly promptCapabilities: AgentPromptCapabilities;
1112
+ /**
1113
+ * Tear the session down: kill the agent process and drop it from
1114
+ * {@link AgentsService.getState}. Idempotent. The process is a piped child of
1115
+ * Silo — it does **not** survive this, and closing the workspace or quitting
1116
+ * the app reaps it the same way.
1117
+ */
1118
+ dispose(): void;
1119
+ }
1120
+ /**
1121
+ * Options for {@link AgentSessionsService.connect}.
1122
+ *
1123
+ * @category Consumer Services
1124
+ * @public
1125
+ * @beta
1126
+ */
1127
+ export interface AgentSessionConnectOptions {
1128
+ /** Working directory for the agent. Defaults to the workspace folder. */
1129
+ cwd?: string;
1130
+ /** Which workspace the session belongs to (for
1131
+ * {@link AgentInfo.workspaceId} and `reveal`). Defaults to the active one. */
1132
+ workspaceId?: string;
1133
+ /**
1134
+ * A last-known display title, shown immediately with {@link resume} — before
1135
+ * the connection (`initialize` alone can take several seconds) or a
1136
+ * `session/resume` / `session/load` round trip resolves. Without it, a
1137
+ * restoring session's dock tab, workspace row, and Agents navigator entry
1138
+ * fall back to the profile's plain label for however long reconnecting
1139
+ * takes, even when the agent volunteered a real title before the app
1140
+ * closed. It **outlives the handshake**: it is still the session's title
1141
+ * once the connection is up, until the agent volunteers a real one of its
1142
+ * own (a `session_info_update`). The agent's product name from `initialize`
1143
+ * (`agentInfo.title`, e.g. "Claude Agent") does *not* supersede it — that is
1144
+ * the fallback for a session that has never had a title, not a replacement
1145
+ * for one this conversation already earned. Persist whatever
1146
+ * {@link AgentInfo.title} last was and pass it back here on the next
1147
+ * restore. Ignored without {@link resume} — a fresh session has no prior
1148
+ * title to show early.
1149
+ */
1150
+ title?: string;
1151
+ /**
1152
+ * How to bring **your** UI for this session into view. Silo calls this from
1153
+ * {@link AgentsService.reveal} — after activating the session's workspace —
1154
+ * so a kind-agnostic caller (the Agents navigator, a notification, a
1155
+ * command) can focus a Chat session's transcript without knowing that a
1156
+ * transcript is what it is.
1157
+ *
1158
+ * Implement it with whatever "come to the front" means for your surface: a
1159
+ * dock panel calls `api.setActive()` on its own `DockPanelApi`, a side
1160
+ * panel reveals itself through `ctx.layout`. Called on the main thread,
1161
+ * possibly more than once; keep it cheap and idempotent, and expect it after
1162
+ * an `await` — capture the handle you need in a ref rather than closing over
1163
+ * render-scoped state.
1164
+ *
1165
+ * Omit it and `reveal(id)` still activates the workspace, which is all Silo
1166
+ * can honestly do for a session whose UI it does not own.
1167
+ */
1168
+ reveal?: () => void;
1169
+ /**
1170
+ * Reconnect a previously-persisted session instead of starting a fresh one
1171
+ * — Chat session resurrection (RFC 0042). Pass the {@link
1172
+ * AgentSessionHandle.sessionId} a prior `connect()` on this same profile
1173
+ * returned (what a recorded panel keeps in its `DockPanelState`).
1174
+ *
1175
+ * `connect()` never rejects because the target has gone stale: it probes
1176
+ * `session/resume` and `session/load` separately (`resume` wins when both
1177
+ * are advertised), and falls through to a fresh `session/new` if neither
1178
+ * works and there is no journal to fall back to. See {@link
1179
+ * AgentSessionHandle.resumeOutcome} for which path was taken.
1180
+ */
1181
+ resume?: AgentSessionRestore;
1182
+ }
1183
+ /**
1184
+ * A restore target for {@link AgentSessionConnectOptions.resume} — Chat
1185
+ * session resurrection (RFC 0042).
1186
+ *
1187
+ * @category Consumer Services
1188
+ * @public
1189
+ * @beta
1190
+ */
1191
+ export interface AgentSessionRestore {
1192
+ /**
1193
+ * The session id to reconnect — an earlier connection's {@link
1194
+ * AgentSessionHandle.sessionId}.
1195
+ */
1196
+ sessionId: string;
1197
+ /**
1198
+ * Skip probing `session/resume` / `session/load` and start a fresh
1199
+ * `session/new` directly, carrying the **transcript journal** kept under
1200
+ * {@link sessionId} into the new session's — "Continue in a new session"
1201
+ * from a `"journal-only"` {@link AgentSessionHandle.resumeOutcome}. The new
1202
+ * handle's {@link AgentSessionHandle.sessionId} is whatever `session/new`
1203
+ * mints, **not** this `sessionId` — persist that returned id for the next
1204
+ * restore, exactly as after a plain `connect()`. Keeping the original,
1205
+ * already-known-unresumable id instead would mean every future restore
1206
+ * keeps retrying an id the agent will never resume, forever, even while the
1207
+ * new conversation itself works fine turn after turn (found live,
1208
+ * 2026-09-09).
1209
+ */
1210
+ startFresh?: boolean;
1211
+ }
1212
+ /**
1213
+ * Connect to a **Chat session** and drive it — exposed as
1214
+ * `ctx.agents.sessions` (RFC 0038 phase 2). The counterpart to
1215
+ * {@link AgentProfilesService} for the `chat` launch arm: where `profiles`
1216
+ * *starts a terminal and walks away*, this *holds a live connection* an
1217
+ * extension can prompt, watch and cancel.
1218
+ *
1219
+ * **Sourced from user-authored profiles only.** There is deliberately no
1220
+ * "connect to this command" — an extension cannot point Silo at an arbitrary
1221
+ * binary to spawn with pipes. The user defines a Chat profile on
1222
+ * Settings → Agents; an extension names it.
1223
+ *
1224
+ * Needs the **`"agents"` {@link Permission}**, declared in the extension's
1225
+ * `silo.permissions` and granted at install: {@link connect} throws without it.
1226
+ *
1227
+ * @example
1228
+ * ```ts
1229
+ * const session = await ctx.agents.sessions.connect("claude-chat");
1230
+ * const off = session.onUpdate((u) => {
1231
+ * if (u.kind === "agent_message_chunk") appendToTranscript(u.messageId, u.text);
1232
+ * });
1233
+ * const result = await session.prompt([{ type: "text", text: "Explain this repo." }]);
1234
+ * ctx.log.info(`turn ended: ${result.stopReason}`);
1235
+ * ctx.subscriptions.push(off, { dispose: () => session.dispose() });
1236
+ * ```
1237
+ *
1238
+ * @category Consumer Services
1239
+ * @public
1240
+ * @beta
1241
+ */
1242
+ export interface AgentSessionsService {
1243
+ /**
1244
+ * Spawn the agent for the named Chat profile, run the Agent Client Protocol
1245
+ * `initialize` + `session/new` handshake, and resolve with a live
1246
+ * {@link AgentSessionHandle}.
1247
+ *
1248
+ * Rejects when: the extension lacks the `"agents"` permission; no profile has
1249
+ * that id; the profile is a Terminal profile, not a Chat one; there is no
1250
+ * target workspace; the agent needs authentication (its `session/new`
1251
+ * failed); or the agent reported a startup error (the rejection carries its
1252
+ * message).
1253
+ *
1254
+ * With {@link AgentSessionConnectOptions.resume}, `connect()` still never
1255
+ * rejects merely because the target has gone stale — it falls all the way
1256
+ * through to a fresh `session/new` (RFC 0042); see {@link
1257
+ * AgentSessionHandle.resumeOutcome}.
1258
+ *
1259
+ * @param profileId — an {@link AgentProfileSummary.id} whose profile uses the
1260
+ * `chat` launch arm.
1261
+ */
1262
+ connect(profileId: string, options?: AgentSessionConnectOptions): Promise<AgentSessionHandle>;
1263
+ /**
1264
+ * Read a session's **transcript journal** (RFC 0042) without connecting —
1265
+ * for painting a restored panel *before* `connect({ resume })` resolves,
1266
+ * which itself also pays for the `session/resume` / `session/load` network
1267
+ * round trip. This is the "paint from journal" half of the restore flow;
1268
+ * `connect()` is the "reconnect the agent" half, and the two run
1269
+ * independently on purpose.
1270
+ *
1271
+ * Resolves `[]` for a session that never wrote a journal entry, or an
1272
+ * unknown id — never rejects on a merely-missing journal. Needs the
1273
+ * `"agents"` {@link Permission}, same as {@link connect}.
1274
+ *
1275
+ * @param sessionId — an {@link AgentSessionHandle.sessionId}.
1276
+ */
1277
+ readJournal(sessionId: string, options?: {
1278
+ workspaceId?: string;
1279
+ }): Promise<readonly AgentSessionUpdate[]>;
1280
+ }
337
1281
  /**
338
1282
  * Host-computed coding-agent observability — exposed as
339
1283
  * {@link ExtensionContext.agents}. Detection (what OSC/output signals mean
@@ -366,7 +1310,10 @@ export interface AgentsService {
366
1310
  getState(options?: {
367
1311
  allWorkspaces?: boolean;
368
1312
  }): AgentInfo[];
369
- /** Look up {@link AgentInfo} for a specific terminal tab by its record id. */
1313
+ /** Look up {@link AgentInfo} for a specific terminal tab by its record id.
1314
+ * Only ever resolves a `kind: "terminal"` session — use
1315
+ * {@link AgentsService.getState} and match on {@link AgentInfo.id} to find a
1316
+ * Chat session. */
370
1317
  getByTerminalId(terminalId: string): AgentInfo | undefined;
371
1318
  /**
372
1319
  * Subscribe to changes in the active workspace's agent state. Pass
@@ -398,8 +1345,134 @@ export interface AgentsService {
398
1345
  * }),
399
1346
  * );
400
1347
  * ```
1348
+ *
1349
+ * @param id — an {@link AgentInfo.id}. A terminal record id is one (every
1350
+ * Terminal session's `id` equals its `terminalId`), so existing callers
1351
+ * passing a terminal id keep working; a Chat session's id works too.
401
1352
  */
402
- acknowledge(terminalId: string): void;
1353
+ acknowledge(id: string): void;
1354
+ /**
1355
+ * The Agent Session the user is currently **looking at** — the one whose
1356
+ * surface is the active tab — or `null` when the active tab is not an agent
1357
+ * at all (an editor, a settings page, nothing).
1358
+ *
1359
+ * Kind-agnostic by construction: a terminal tab reports its Terminal
1360
+ * session, and a dock panel that declared
1361
+ * `DockPanelApi.setAgentSession` reports its Chat session. That is
1362
+ * what lets one consumer implement "clear the badge for the session I'm
1363
+ * watching" or "hide the row for the session I'm already looking at" without
1364
+ * knowing which kind it got.
1365
+ *
1366
+ * @example
1367
+ * ```ts
1368
+ * // Hide the status row for whatever the user is already watching.
1369
+ * const watching = ctx.agents.getActive();
1370
+ * const rows = ctx.agents.getState().filter((a) => a.id !== watching);
1371
+ * ```
1372
+ */
1373
+ getActive(): string | null;
1374
+ /**
1375
+ * Subscribe to changes in {@link AgentsService.getActive} — fired with the
1376
+ * new value (or `null`) whenever the active surface moves. Returns a
1377
+ * {@link Disposable} that cancels the subscription.
1378
+ */
1379
+ subscribeActive(listener: (id: string | null) => void): Disposable;
1380
+ /**
1381
+ * Bind a provider of **activity badges** for Agent Session tabs — the
1382
+ * host-owned `Activity` chrome (spinner / ready / warn / error) on the
1383
+ * trailing edge of a tab.
1384
+ *
1385
+ * The binder's `provide` is handed an {@link AgentInfo.id}, and the host
1386
+ * routes the result to whichever tab is showing that session: a terminal tab
1387
+ * for a Terminal session, or the dock panel that declared
1388
+ * `DockPanelApi.setAgentSession` for a Chat session. One binder,
1389
+ * both kinds — which is the point: the `ctx.terminals` equivalent takes a
1390
+ * *terminal* id, so an extension literally could not badge a Chat session.
1391
+ *
1392
+ * `provide` is called synchronously for every visible tab during render.
1393
+ * Keep it a lookup: no allocation, no async, no work proportional to the
1394
+ * number of sessions.
1395
+ *
1396
+ * @example
1397
+ * ```ts
1398
+ * ctx.subscriptions.push(
1399
+ * ctx.agents.bindActivity({
1400
+ * id: "my-ext.agent-badge",
1401
+ * provide(agentSessionId) {
1402
+ * const info = ctx.agents
1403
+ * .getState({ allWorkspaces: true })
1404
+ * .find((a) => a.id === agentSessionId);
1405
+ * if (info?.activity !== "working") return null;
1406
+ * return { activity: "working", tooltip: "Agent working" };
1407
+ * },
1408
+ * }),
1409
+ * );
1410
+ * ```
1411
+ */
1412
+ bindActivity(binder: TabActivityBinder): Disposable;
1413
+ /**
1414
+ * Bind a provider of **leading icons** for Agent Session tabs — a brand mark
1415
+ * so a tab running an agent is identifiable at a glance. Same routing and
1416
+ * same synchronous-`provide` contract as
1417
+ * {@link AgentsService.bindActivity}.
1418
+ *
1419
+ * Return `null` for "no icon". Take care that a component which renders
1420
+ * nothing produces `null` here rather than a truthy element descriptor, or
1421
+ * the host reserves tab space for an icon that never appears.
1422
+ */
1423
+ bindIcon(binder: TabIconBinder): Disposable;
1424
+ /**
1425
+ * Re-query every bound {@link AgentsService.bindActivity} /
1426
+ * {@link AgentsService.bindIcon} provider. Call it when something *outside*
1427
+ * the agent snapshot changed what a provider would return — a setting, the
1428
+ * active theme — since the host cannot know about those.
1429
+ */
1430
+ invalidateAdornments(): void;
1431
+ /**
1432
+ * End an Agent Session, either kind: close its terminal tab, or close the
1433
+ * dock panel that declared `DockPanelApi.setAgentSession` for it
1434
+ * (which reaps the agent process as the panel unmounts).
1435
+ *
1436
+ * A no-op for an unknown id, or for a Chat session with no panel mounted —
1437
+ * Silo will not kill a connection whose UI it cannot account for.
1438
+ *
1439
+ * @param id — an {@link AgentInfo.id}.
1440
+ */
1441
+ close(id: string): void;
1442
+ /**
1443
+ * Bring an Agent Session into view: focus its terminal tab if it is a
1444
+ * Terminal session, or its transcript panel if it is a Chat session —
1445
+ * activating the owning workspace first. The caller does not need to know
1446
+ * which kind it is, which is the whole point of the verb (RFC 0038): a
1447
+ * consumer holding an {@link AgentInfo.id} should never have to branch to
1448
+ * `ctx.terminals.focus` vs. some chat-panel API.
1449
+ *
1450
+ * A no-op for an unknown id, or when the session's backing surface is gone
1451
+ * (a closed terminal, a disposed panel).
1452
+ *
1453
+ * @param id — an {@link AgentInfo.id}.
1454
+ */
1455
+ reveal(id: string): void;
1456
+ /**
1457
+ * Resume a session **through Silo**, when {@link AgentInfo.canResume} is
1458
+ * `true`.
1459
+ *
1460
+ * - Chat session: spawn a fresh agent process and reconnect the persisted
1461
+ * id over `session/resume` or `session/load` — whichever the agent
1462
+ * advertises, `resume` preferred (RFC 0042) — so the transcript and
1463
+ * context come back after the old process died. Then
1464
+ * {@link AgentsService.reveal} it.
1465
+ * - Terminal session: currently a no-op — a dead PTY cannot be re-run in
1466
+ * place, and the resume path stays "the user runs
1467
+ * {@link AgentInfo.resumeCommand}". Present on the surface so a
1468
+ * kind-agnostic consumer can call it unconditionally once Chat sessions
1469
+ * ship (RFC 0038 phase 4).
1470
+ *
1471
+ * A no-op for an unknown id or one whose `canResume` is `false`.
1472
+ *
1473
+ * @param id — an {@link AgentInfo.id}.
1474
+ */
1475
+ resume(id: string): void;
403
1476
  /**
404
1477
  * Every coding agent Silo knows about, as read-only
405
1478
  * {@link CatalogAgentSummary} records. Detection stays sealed (ADR 0028) —
@@ -422,5 +1495,16 @@ export interface AgentsService {
422
1495
  * @beta
423
1496
  */
424
1497
  readonly profiles: AgentProfilesService;
1498
+ /**
1499
+ * Connect to and drive a **Chat session** — a user-authored `chat` profile
1500
+ * spawned as an Agent Client Protocol child (RFC 0038 phase 2). See
1501
+ * {@link AgentSessionsService}. Needs the `"agents"` {@link Permission}:
1502
+ * every `connect()` throws without it.
1503
+ *
1504
+ * @category Consumer Services
1505
+ * @public
1506
+ * @beta
1507
+ */
1508
+ readonly sessions: AgentSessionsService;
425
1509
  }
426
1510
  //# sourceMappingURL=agents-service.d.ts.map