@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.
@@ -129,6 +129,50 @@ export interface EditorRecord {
129
129
  viewType?: string;
130
130
  }
131
131
 
132
+ /**
133
+ * The persisted identity and restore state of one dock panel — a
134
+ * {@link DockPanelKind} panel that declared `persistence: "recorded"` (RFC
135
+ * 0041).
136
+ *
137
+ * This is what makes a dock panel first-class the way an editor or a terminal
138
+ * is: it exists as a workspace-scoped record ({@link Workspace.panels}), not
139
+ * merely as opaque geometry inside the saved dock layout. The record list is
140
+ * the source of truth for *which* recorded panels a workspace has; the dock
141
+ * layout keeps only *where* they sit, and the two are reconciled on restore
142
+ * exactly as editors and terminals already are.
143
+ *
144
+ * An editor and a terminal are not `DockPanelRecord`s in this release —
145
+ * {@link EditorRecord} / {@link TerminalRecord} keep their own shape. Folding
146
+ * the three lists into one is deliberately left for a later phase.
147
+ *
148
+ * @category Core Types
149
+ * @public
150
+ */
151
+ export interface DockPanelRecord {
152
+ /**
153
+ * Stable id, unique within the workspace. This is **not** the transient
154
+ * dock-view panel id — it survives the panel being closed and reopened from
155
+ * its record across a restart.
156
+ */
157
+ id: string;
158
+ /** The {@link DockPanelKind} id this panel is an instance of. */
159
+ kindId: string;
160
+ /** The id of the workspace this panel belongs to. */
161
+ workspaceId: string;
162
+ /**
163
+ * The panel kind's own restore state — a serializable bag whose shape is the
164
+ * panel's contract, not the host's (the host never inspects or validates it).
165
+ * The Agent Chat panel keeps `{ sessionId, profileId, cwd }` here (RFC
166
+ * 0042); a panel with nothing to restore leaves it `{}`. Handed back to the
167
+ * kind's component as its params when the panel is recreated on restore.
168
+ */
169
+ state: Readonly<Record<string, unknown>>;
170
+ /** ISO-8601 timestamp of when the panel was first opened. */
171
+ createdAt: string;
172
+ /** ISO-8601 timestamp of when the panel was last the active tab. */
173
+ lastActiveAt: string;
174
+ }
175
+
132
176
  /**
133
177
  * The four fixed slots a side dock used to divide into.
134
178
  *
@@ -176,6 +220,15 @@ export interface Workspace {
176
220
  terminals: readonly TerminalRecord[];
177
221
  /** Editor tabs — text editors and diffs alike (a diff is a record with `mode: "diff"`). */
178
222
  editors: readonly EditorRecord[];
223
+ /**
224
+ * Recorded dock panels — every panel of a {@link DockPanelKind} that declared
225
+ * `persistence: "recorded"` (RFC 0041). Editors and terminals are **not** in
226
+ * this list; they keep their own {@link Workspace.editors} /
227
+ * {@link Workspace.terminals} lists. A transient panel (a picker, a preview)
228
+ * whose kind did not opt in is absent here — it lives only in the saved dock
229
+ * layout.
230
+ */
231
+ panels: readonly DockPanelRecord[];
179
232
  }
180
233
 
181
234
  /**
package/src/index.ts CHANGED
@@ -53,6 +53,7 @@ export type {
53
53
  Workspace,
54
54
  EditorMode,
55
55
  EditorRecord,
56
+ DockPanelRecord,
56
57
  SidePanelSlot,
57
58
  } from "./domain-types";
58
59
 
@@ -106,6 +107,7 @@ export type {
106
107
  export type {
107
108
  AgentsService,
108
109
  AgentInfo,
110
+ AgentSessionKind,
109
111
  AgentActivity,
110
112
  AgentIcon,
111
113
  AgentIconMode,
@@ -115,6 +117,26 @@ export type {
115
117
  LaunchAgentProfileOptions,
116
118
  LaunchAgentProfileResult,
117
119
  PromptRefusal,
120
+ AgentSessionsService,
121
+ AgentSessionHandle,
122
+ AgentSessionConnectOptions,
123
+ AgentSessionRestore,
124
+ ChatResumeState,
125
+ AgentPromptBlock,
126
+ AgentPromptResult,
127
+ AgentStopReason,
128
+ AgentSessionUpdate,
129
+ AgentContentBlock,
130
+ AgentToolCall,
131
+ AgentToolCallContent,
132
+ AgentToolCallLocation,
133
+ AgentPlanEntry,
134
+ AgentSessionConfigOption,
135
+ AgentSessionConfigChoice,
136
+ AgentPermissionRequest,
137
+ AgentPermissionOption,
138
+ AgentCommand,
139
+ AgentPromptCapabilities,
118
140
  } from "./agents-service";
119
141
  export type {
120
142
  TerminalService,
@@ -21,6 +21,17 @@
21
21
  * including cross-origin content. Declare it because this reaches into
22
22
  * arbitrary embedded pages, not because it touches the filesystem/network
23
23
  * directly.
24
+ * - `agents` — use {@link AgentSessionsService} (`ctx.agents.sessions`) to
25
+ * start a **Chat session**: the host spawns the process named by a
26
+ * user-authored Chat Agent Profile and speaks the Agent Client Protocol to
27
+ * it. {@link AgentSessionsService.connect | connect()} throws without this,
28
+ * the way {@link FileService} throws {@link PathDeniedError} without
29
+ * `fs:read`. Distinct from `process`: the child is spawned by the host from a
30
+ * profile the user wrote, not by the extension through
31
+ * {@link ExtensionContext.process}. Trusted (built-in) extensions are exempt,
32
+ * the same way they're exempt from declaring `fs:*`/`process` — a narrower
33
+ * grant than either, since it only launches a profile the user already
34
+ * authored.
24
35
  *
25
36
  * @category Extension Contract
26
37
  * @public
@@ -30,7 +41,8 @@ export type Permission =
30
41
  | "fs:write"
31
42
  | "process"
32
43
  | "network"
33
- | "webview";
44
+ | "webview"
45
+ | "agents";
34
46
 
35
47
  /**
36
48
  * Thrown by {@link FileService} and {@link ProcessService} when an extension
@@ -6,17 +6,26 @@ import type { MenuEntry } from "./ui-service";
6
6
  * Built-in surfaces that accept {@link ToolbarItemContribution}s. One surface
7
7
  * per registration.
8
8
  *
9
- * `"editor"` and `"terminal"` are CenterDock breadcrumb toolbars; `"navigator"`
10
- * is the header naming the Navigator's active view — the bar between its view
11
- * list and the view body — where contributions become that view's action
12
- * buttons. A navigator item's `when` receives the active
9
+ * `"editor"` is the editor breadcrumb toolbar (beside the Text | Preview view
10
+ * switcher). `"navigator"` is the header naming the Navigator's active view —
11
+ * the bar between its view list and the view body — where contributions become
12
+ * that view's action buttons; a navigator item's `when` receives the active
13
13
  * {@link NavigatorView | view}'s id, so an action can be scoped to one view or
14
14
  * left unscoped to follow the user across all of them.
15
15
  *
16
+ * `"panel"` is the host-drawn strip above any {@link DockPanelKind} that
17
+ * declares `toolbar` (RFC 0039) — including the built-in **terminal**. Its
18
+ * target carries the panel instance id, its `kindId`, its `workspaceId`, and
19
+ * the panel's own `params`, so an item meant for one kind of panel scopes
20
+ * itself with
21
+ * `when: (_keys, t) => t.kindId === "terminal"` and reads instance data off
22
+ * `t.params` (e.g. `t.params.terminalId`). Without a `kindId` guard a
23
+ * `"panel"` item shows on every panel that has a strip.
24
+ *
16
25
  * @category Registration
17
26
  * @public
18
27
  */
19
- export type ToolbarSurface = "editor" | "terminal" | "navigator";
28
+ export type ToolbarSurface = "editor" | "navigator" | "panel";
20
29
 
21
30
  /**
22
31
  * The typed target each {@link ToolbarSurface} passes to an invoked command
@@ -27,8 +36,21 @@ export type ToolbarSurface = "editor" | "terminal" | "navigator";
27
36
  */
28
37
  export interface ToolbarItemContext {
29
38
  editor: { editorId: string };
30
- terminal: { terminalId: string };
31
39
  navigator: { viewId: string };
40
+ /**
41
+ * `panelId` and `kindId` identify the panel; `workspaceId` is the workspace
42
+ * whose dock the panel lives in (each workspace has its own dock, and the
43
+ * host knows which at the time it draws the strip); `params` is the panel's
44
+ * own serialized parameters (`DockPanelProps["params"]`) — read-only, so an
45
+ * item can key on instance data (e.g. the terminal's `params.terminalId`) or
46
+ * act on the owning workspace without a lookup the SDK does not offer.
47
+ */
48
+ panel: {
49
+ panelId: string;
50
+ kindId: string;
51
+ workspaceId: string;
52
+ params: Readonly<Record<string, unknown>>;
53
+ };
32
54
  }
33
55
 
34
56
  /**
@@ -175,8 +197,9 @@ export interface ToolbarSpacerContribution<
175
197
  }
176
198
 
177
199
  /**
178
- * Adds a control or chrome element to the trailing cluster of a built-in
179
- * editor or terminal toolbar. Register via
200
+ * Adds a control or chrome element to the trailing cluster of a host-drawn
201
+ * toolbar — the editor breadcrumb, the Navigator header, or a dock panel's
202
+ * strip (the terminal included; see {@link ToolbarSurface}). Register via
180
203
  * {@link ExtensionContext.registerToolbarItem}.
181
204
  *
182
205
  * Interactive items set exactly one of
package/src/types.ts CHANGED
@@ -111,6 +111,52 @@ export interface DockPanelApi {
111
111
  * state (e.g. the open URL in a web-viewer panel) consistent with the UI.
112
112
  */
113
113
  updateParameters(params: object): void;
114
+ /**
115
+ * Declare — or, with `null`, withdraw — the **Agent Session** this panel is
116
+ * showing (an `AgentInfo.id` — the `id` on the handle
117
+ * `ctx.agents.sessions.connect()` returned).
118
+ *
119
+ * This is the only thing the host lacks about a Chat panel, and declaring it
120
+ * is what makes the panel an ordinary *subject* of agent chrome rather than
121
+ * an author of it. Once declared, the host routes this panel's tab through
122
+ * the same path a terminal tab takes: an {@link AgentsService.bindActivity} /
123
+ * {@link AgentsService.bindIcon} binder's `provide(agentSessionId)` reaches
124
+ * this tab, {@link AgentsService.getActive} reports the session while the tab
125
+ * is the active one (so a finish the user watched raises no badge), and
126
+ * {@link AgentsService.close} closes this panel. None of that requires the
127
+ * panel to know a badge exists.
128
+ *
129
+ * Call it once the session is connected, and again with `null` on a teardown
130
+ * that outlives the panel (switching to a different agent). The host
131
+ * withdraws it automatically when the panel unmounts.
132
+ */
133
+ setAgentSession(agentSessionId: string | null): void;
134
+ /**
135
+ * Publish the path this panel is showing, for the host-drawn breadcrumb
136
+ * strip — or, with `null`, show no path crumbs.
137
+ *
138
+ * Only meaningful for a {@link DockPanelKind} that declares
139
+ * `toolbar: { breadcrumb: true }`; the host draws the strip and this fills in
140
+ * its crumbs. `null` leaves a strip that carries only contributed
141
+ * `registerToolbarItem({ surface: "panel" })` items — which is also how a
142
+ * panel honours a "hide breadcrumbs" setting of its own.
143
+ *
144
+ * This is the shape of {@link DockPanelApi.setAgentSession}: the panel states
145
+ * a fact about itself and the host routes on it. The path is *not* part of
146
+ * the panel's persisted identity, so it goes here rather than through
147
+ * {@link DockPanelApi.updateParameters}. The host withdraws it automatically
148
+ * when the panel unmounts.
149
+ */
150
+ setBreadcrumb(
151
+ crumb: {
152
+ /** Absolute path shown as workspace-relative crumbs. */
153
+ filePath: string;
154
+ /** Workspace folder the path is relativised against, when it is inside one. */
155
+ workspaceFolder?: string;
156
+ /** Glyph on the trailing crumb. Defaults to `"file"`. */
157
+ leafIcon?: "file" | "folder";
158
+ } | null,
159
+ ): void;
114
160
  }
115
161
 
116
162
  /**
@@ -127,6 +173,42 @@ export interface DockPanelProps<T extends object = Record<string, unknown>> {
127
173
  api: DockPanelApi;
128
174
  /** Serializable parameters forwarded to the panel at open time. */
129
175
  params: T;
176
+ /**
177
+ * The workspace this panel lives in.
178
+ *
179
+ * **Not** the same as "the active workspace": every open workspace keeps its
180
+ * dock mounted, so a background panel can be doing work while the user is
181
+ * somewhere else. Anything a panel reports about itself — an Agent Session it
182
+ * connects (`ctx.agents.sessions.connect({ workspaceId })`), a file it opens —
183
+ * belongs to *this* workspace, and reading `ctx.workspaces` for it instead
184
+ * files the result wherever the user happened to be standing. Caught live
185
+ * (2026-09-10): a Chat session moved workspaces mid-session because its
186
+ * `connect()` defaulted to the active one.
187
+ */
188
+ workspaceId: string;
189
+ /**
190
+ * Whether this panel's pixels are actually on screen right now — its tab is
191
+ * the selected one in its group **and** its workspace is the one the user is
192
+ * looking at. The host resolves both halves; a panel must not reconstruct
193
+ * this from {@link DockPanelApi.isVisible} plus
194
+ * {@link ExtensionContext.workspaces} itself.
195
+ *
196
+ * Distinct from the two api members it is easy to confuse it with.
197
+ * {@link DockPanelApi.isVisible} covers only the tab half, so a panel in a
198
+ * background workspace still reports `isVisible: true`;
199
+ * {@link DockPanelApi.isActive} is about focus — the single panel in the
200
+ * whole dock that has it.
201
+ *
202
+ * A panel is **never unmounted** for going off screen, which is what makes
203
+ * this prop load-bearing rather than a convenience. A deselected tab has its
204
+ * DOM detached and later re-attached — discarding scroll offsets and
205
+ * anything else the browser keeps on a layout box — while its React state
206
+ * and refs survive untouched, and a background workspace's dock stays in the
207
+ * tree as it was. So whatever a panel measures from or restores into the DOM
208
+ * belongs on a transition of this flag, never on mount: mount happens once,
209
+ * when the tab is created, and never again.
210
+ */
211
+ onScreen: boolean;
130
212
  }
131
213
 
132
214
  /**
@@ -588,6 +670,72 @@ export interface DockPanelKind<T extends object = Record<string, unknown>> {
588
670
  id: string;
589
671
  /** The React component that renders this panel; receives {@link DockPanelProps}. */
590
672
  component: React.ComponentType<DockPanelProps<T>>;
673
+ /**
674
+ * Host-drawn chrome above this panel's component (RFC 0039). Omit for a bare
675
+ * frame — the default, and correct for a panel that fills its own space.
676
+ *
677
+ * With it set, the dock frame draws the same strip an editor gets: the
678
+ * breadcrumb path (fed by {@link DockPanelApi.setBreadcrumb}) plus a
679
+ * contribution point, so any extension can
680
+ * `registerToolbarItem({ surface: "panel", when: (_k, t) => t.kindId === "…" })`
681
+ * and its button lands here. A first-party panel's own controls arrive the
682
+ * same way — there is no `children` slot past the contribution point. The
683
+ * built-in terminal declares this, so its toolbar contributions are ordinary
684
+ * `"panel"` items.
685
+ *
686
+ * `toolbar: {}` — declared but empty — still reserves the strip, for a panel
687
+ * that wants only contributed items.
688
+ */
689
+ toolbar?: {
690
+ /**
691
+ * Draw path crumbs in the strip, filled in by
692
+ * {@link DockPanelApi.setBreadcrumb}. Without it the strip carries only
693
+ * contributed items.
694
+ */
695
+ breadcrumb?: boolean;
696
+ };
697
+ /**
698
+ * Declares that this panel kind renders a **Chat session** for an Agent
699
+ * Profile (RFC 0038), so Silo can open it on the user's behalf.
700
+ *
701
+ * A Chat-armed Agent Profile has no terminal to launch — it needs a
702
+ * transcript UI, and Silo does not own one. With this set, picking such a
703
+ * profile from the center dock's **+** menu (or running its
704
+ * `core.newAgent.<id>` command) opens this kind with
705
+ * `params.profileId` set to that profile's id; your component passes it to
706
+ * {@link AgentSessionsService.connect}. Terminal-armed profiles are
707
+ * unaffected and still launch a terminal.
708
+ *
709
+ * This is how a third-party Chat panel becomes a first-class way to start an
710
+ * agent rather than something the user has to reach by a different route: it
711
+ * is a declaration, not a privilege, and the bundled panel claims it exactly
712
+ * the same way. With more than one such kind registered, the first
713
+ * registered wins — disable the one you don't want on Settings → Extensions.
714
+ *
715
+ * Requires `params.profileId` to be honoured; a kind that ignores it will be
716
+ * handed profiles it does not render.
717
+ */
718
+ chatProfileHost?: boolean;
719
+ /**
720
+ * Opt this kind's panels into being **recorded** (RFC 0041). With
721
+ * `persistence: "recorded"`, every open panel of this kind gets a
722
+ * {@link DockPanelRecord} in {@link Workspace.panels}: it is enumerable,
723
+ * workspace-scoped, and — this is the point — **reopened from its record on
724
+ * restart**, the same way an editor or a terminal tab is, rather than
725
+ * surviving only as opaque geometry in the saved dock layout.
726
+ *
727
+ * On restore the host recreates the panel and hands
728
+ * {@link DockPanelRecord.state} back as the component's params, so a panel
729
+ * that needs to restore itself (a Chat transcript restoring its session —
730
+ * RFC 0042) writes that state through {@link DockPanelApi.updateParameters}
731
+ * and reads it back from params on the next launch.
732
+ *
733
+ * Omit for a **transient** panel — a picker, a preview, anything that should
734
+ * not come back on its own. A transient panel still persists its position in
735
+ * the dock layout for the current session; it just has no record and is not
736
+ * recreated after a restart.
737
+ */
738
+ persistence?: "recorded";
591
739
  /**
592
740
  * When set, this kind appears as an entry in the center dock's **+** add
593
741
  * menu (the per-group header button). Omit to keep the kind internal.
@@ -733,19 +881,20 @@ export interface ExtensionContext {
733
881
  ): Disposable;
734
882
  /**
735
883
  * Register a {@link ToolbarItemContribution} (icon-only, text-only,
736
- * icon+text, or dropdown) in the trailing cluster of an editor or terminal
737
- * toolbar. Independent of {@link ExtensionContext.registerContextMenuItem}
738
- * — register either, both, or neither. Hosts only show items while that
739
- * surface's breadcrumbs setting is on. See
740
- * {@link ExtensionContext.invalidateToolbarItems}.
884
+ * icon+text, or dropdown) in the trailing cluster of a host-drawn toolbar —
885
+ * the editor breadcrumb, the Navigator header, or a dock panel's strip (the
886
+ * terminal included). Independent of
887
+ * {@link ExtensionContext.registerContextMenuItem} — register either, both,
888
+ * or neither. Hosts only show items while that surface's breadcrumbs setting
889
+ * is on. See {@link ExtensionContext.invalidateToolbarItems}.
741
890
  */
742
891
  registerToolbarItem<S extends ToolbarSurface>(
743
892
  item: ToolbarItemContribution<S>,
744
893
  ): Disposable;
745
894
  /**
746
895
  * Signal that toolbar-item `when` / `checked` data changed. Causes every
747
- * toolbar surface — editor, terminal, and the Navigator header — to re-query
748
- * contributions and re-render.
896
+ * toolbar surface — the editor breadcrumb, the Navigator header, and every
897
+ * dock panel strip — to re-query contributions and re-render.
749
898
  */
750
899
  invalidateToolbarItems(): void;
751
900
  /** Register a {@link Keybinding} (bind a shortcut to a command). */