@softov/ahpc 0.1.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.
Files changed (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +296 -0
  3. package/dist/src/ahp/channels.d.ts +146 -0
  4. package/dist/src/ahp/channels.js +423 -0
  5. package/dist/src/ahp/connection.d.ts +656 -0
  6. package/dist/src/ahp/connection.js +1 -0
  7. package/dist/src/ahp/fake.d.ts +69 -0
  8. package/dist/src/ahp/fake.js +1996 -0
  9. package/dist/src/ahp/live.d.ts +160 -0
  10. package/dist/src/ahp/live.js +2641 -0
  11. package/dist/src/ahp/operate.d.ts +36 -0
  12. package/dist/src/ahp/operate.js +56 -0
  13. package/dist/src/ahp/publish.d.ts +39 -0
  14. package/dist/src/ahp/publish.js +370 -0
  15. package/dist/src/ahp/status.d.ts +23 -0
  16. package/dist/src/ahp/status.js +23 -0
  17. package/dist/src/ahp/types.d.ts +745 -0
  18. package/dist/src/ahp/types.js +27 -0
  19. package/dist/src/app.d.ts +54 -0
  20. package/dist/src/app.js +369 -0
  21. package/dist/src/blocks.d.ts +88 -0
  22. package/dist/src/blocks.js +102 -0
  23. package/dist/src/cli/main.d.ts +13 -0
  24. package/dist/src/cli/main.js +1537 -0
  25. package/dist/src/cli/render.d.ts +43 -0
  26. package/dist/src/cli/render.js +91 -0
  27. package/dist/src/config.d.ts +63 -0
  28. package/dist/src/config.js +45 -0
  29. package/dist/src/connect.d.ts +53 -0
  30. package/dist/src/connect.js +69 -0
  31. package/dist/src/control.d.ts +189 -0
  32. package/dist/src/control.js +2241 -0
  33. package/dist/src/diff.d.ts +44 -0
  34. package/dist/src/diff.js +111 -0
  35. package/dist/src/flags.d.ts +28 -0
  36. package/dist/src/flags.js +76 -0
  37. package/dist/src/main.d.ts +10 -0
  38. package/dist/src/main.js +54 -0
  39. package/dist/src/schedule.d.ts +46 -0
  40. package/dist/src/schedule.js +140 -0
  41. package/dist/src/screens.d.ts +79 -0
  42. package/dist/src/screens.js +1322 -0
  43. package/dist/src/state.d.ts +355 -0
  44. package/dist/src/state.js +531 -0
  45. package/dist/src/terminal.d.ts +44 -0
  46. package/dist/src/terminal.js +101 -0
  47. package/dist/src/tui.d.ts +92 -0
  48. package/dist/src/tui.js +372 -0
  49. package/dist/src/view/automations.d.ts +31 -0
  50. package/dist/src/view/automations.js +76 -0
  51. package/dist/src/view/bood/art.d.ts +90 -0
  52. package/dist/src/view/bood/art.js +152 -0
  53. package/dist/src/view/bood/beetle.d.ts +7 -0
  54. package/dist/src/view/bood/beetle.js +123 -0
  55. package/dist/src/view/bood/bunny.d.ts +8 -0
  56. package/dist/src/view/bood/bunny.js +228 -0
  57. package/dist/src/view/bood/cat.d.ts +9 -0
  58. package/dist/src/view/bood/cat.js +358 -0
  59. package/dist/src/view/bood/crab.d.ts +7 -0
  60. package/dist/src/view/bood/crab.js +121 -0
  61. package/dist/src/view/bood/index.d.ts +29 -0
  62. package/dist/src/view/bood/index.js +25 -0
  63. package/dist/src/view/bood/motion.d.ts +178 -0
  64. package/dist/src/view/bood/motion.js +298 -0
  65. package/dist/src/view/bood/owl.d.ts +9 -0
  66. package/dist/src/view/bood/owl.js +285 -0
  67. package/dist/src/view/bood/registry.d.ts +36 -0
  68. package/dist/src/view/bood/registry.js +218 -0
  69. package/dist/src/view/bood/render.d.ts +31 -0
  70. package/dist/src/view/bood/render.js +196 -0
  71. package/dist/src/view/bood/sprite.d.ts +18 -0
  72. package/dist/src/view/bood/sprite.js +130 -0
  73. package/dist/src/view/bood/sprout.d.ts +8 -0
  74. package/dist/src/view/bood/sprout.js +121 -0
  75. package/dist/src/view/bood/types.d.ts +158 -0
  76. package/dist/src/view/bood/types.js +57 -0
  77. package/dist/src/view/bubble.d.ts +75 -0
  78. package/dist/src/view/bubble.js +86 -0
  79. package/dist/src/view/changes.d.ts +35 -0
  80. package/dist/src/view/changes.js +22 -0
  81. package/dist/src/view/composer.d.ts +64 -0
  82. package/dist/src/view/composer.js +192 -0
  83. package/dist/src/view/controls.d.ts +44 -0
  84. package/dist/src/view/controls.js +49 -0
  85. package/dist/src/view/creature.d.ts +69 -0
  86. package/dist/src/view/creature.js +83 -0
  87. package/dist/src/view/customizations.d.ts +28 -0
  88. package/dist/src/view/customizations.js +53 -0
  89. package/dist/src/view/details.d.ts +65 -0
  90. package/dist/src/view/details.js +65 -0
  91. package/dist/src/view/filediff.d.ts +29 -0
  92. package/dist/src/view/filediff.js +24 -0
  93. package/dist/src/view/files.d.ts +31 -0
  94. package/dist/src/view/files.js +39 -0
  95. package/dist/src/view/hitl.d.ts +43 -0
  96. package/dist/src/view/hitl.js +171 -0
  97. package/dist/src/view/icons.d.ts +13 -0
  98. package/dist/src/view/icons.js +71 -0
  99. package/dist/src/view/picker.d.ts +42 -0
  100. package/dist/src/view/picker.js +71 -0
  101. package/dist/src/view/sessionhead.d.ts +41 -0
  102. package/dist/src/view/sessionhead.js +60 -0
  103. package/dist/src/view/sessions.d.ts +34 -0
  104. package/dist/src/view/sessions.js +61 -0
  105. package/dist/src/view/terminal.d.ts +30 -0
  106. package/dist/src/view/terminal.js +32 -0
  107. package/dist/src/view/toolcall.d.ts +27 -0
  108. package/dist/src/view/toolcall.js +48 -0
  109. package/dist/src/view/transcript.d.ts +50 -0
  110. package/dist/src/view/transcript.js +60 -0
  111. package/package.json +72 -0
@@ -0,0 +1,656 @@
1
+ import type { Agent, Answer, Automation, AutomationRun, Changeset, Completion, ContentRef, Customization, FileContent, PendingInput, QueuedMessage, ChangesetOperationTarget, ChangesetScope, ChatSource, ModelSelection, ResourceEntry, SessionConfig, SessionDetail, SessionSummary, SessionUri, TerminalRow, TerminalState, ToolCall, Turn } from './types.js';
2
+ /**
3
+ * What a chat client needs from an agent host.
4
+ *
5
+ * A host is a *sessions server*: several clients watch and drive the same
6
+ * sessions and none of them owns the process running the agent. So everything
7
+ * here is either a question about state the host owns, or a fire-and-forget
8
+ * dispatch - there is no local "send and append what I sent". The turn appears
9
+ * when the host has reduced it, which is why the UI re-reads rather than
10
+ * echoing.
11
+ *
12
+ * This is the seam. `fakeHost` implements it with a scripted agent so the
13
+ * example runs, and is checked, with nothing installed; a real client
14
+ * implements the same shape over a WebSocket and changes nothing above it.
15
+ */
16
+ export interface HostConnection {
17
+ readonly id: string;
18
+ readonly url: string;
19
+ state(): 'connecting' | 'connected' | 'offline';
20
+ /** The catalogue. Deliberately thin - everything else is on the channel. */
21
+ listSessions(): Promise<SessionSummary[]>;
22
+ /** The harnesses the host advertises, and the models each offers. */
23
+ agents(): Promise<Agent[]>;
24
+ /**
25
+ * The configuration schema for a session that does not exist yet.
26
+ *
27
+ * `resolveSessionConfig`, which is the whole reason it is separate from
28
+ * `config`: the permission modes a harness offers have to be offerable
29
+ * *before* anything has been created, and they differ by provider. It is
30
+ * iterative on a real host - an answer can bring new questions, a git
31
+ * workspace is what makes a host offer a worktree - so it takes what has
32
+ * been chosen so far rather than only the provider.
33
+ */
34
+ resolveConfig(options: {
35
+ provider: string;
36
+ workingDirectory?: string;
37
+ /** What has been chosen so far. The host echoes it back with defaults applied. */
38
+ values?: Record<string, string>;
39
+ }): Promise<SessionConfig>;
40
+ /**
41
+ * Create one.
42
+ *
43
+ * `config` is what the composer's control row was set to. It belongs here
44
+ * rather than in a `setConfig` after the fact: most of what the schema
45
+ * offers is not `sessionMutable`, so a session created without it is a
46
+ * session that can never be given it.
47
+ */
48
+ createSession(options: {
49
+ provider: string;
50
+ workingDirectory?: string;
51
+ config?: Record<string, string>;
52
+ }): Promise<SessionUri>;
53
+ disposeSession(uri: SessionUri): Promise<void>;
54
+ setArchived(uri: SessionUri, archived: boolean): void;
55
+ /**
56
+ * Mark read, or put the bold back.
57
+ *
58
+ * A client flag, not activity: `IsRead` says a person has looked since the
59
+ * last change, and the host tells every other client that one of them has.
60
+ */
61
+ setRead(uri: SessionUri, read: boolean): void;
62
+ /**
63
+ * Subscribe to a session: a snapshot, then an ordered stream of what happens.
64
+ *
65
+ * Closing the returned handle drops *this consumer*, and a second subscribe
66
+ * on a channel already being drained must not unsubscribe it - that is
67
+ * channel-wide, and silently kills the stream everything else is reading.
68
+ */
69
+ subscribe(uri: SessionUri, observer: (event: HostEvent) => void, chat?: string): {
70
+ close(): void;
71
+ };
72
+ /**
73
+ * Pull the page of history that sits before the turns already loaded.
74
+ *
75
+ * The turns do not come back from this. A host inserts them into the chat's
76
+ * own state and dispatches `chat/turnsLoaded` before it answers, so anything
77
+ * subscribed to that chat sees them arrive the way it sees everything else -
78
+ * which is why there is no return value carrying turns and no second path
79
+ * from a turn to the screen.
80
+ *
81
+ * What comes back is whether there is still more behind it, so a caller that
82
+ * wants the whole conversation can ask again and one that wants a screenful
83
+ * can stop.
84
+ *
85
+ * How much history a snapshot arrives with is the host's business and the
86
+ * two that exist disagree: one sends a tail window, and one sends none at
87
+ * all and expects to be asked. So this is not only how a long conversation
88
+ * is read to its beginning - against some hosts it is how it is read at all.
89
+ */
90
+ loadOlderTurns(uri: SessionUri, chat?: string): Promise<boolean>;
91
+ /**
92
+ * Open a second conversation in the same session.
93
+ *
94
+ * A chat belongs to a session, and a session may hold several - the session
95
+ * is a container, not the conversation. The URI is chosen here, as a
96
+ * session's is, so it can be subscribed to without a round trip in between.
97
+ *
98
+ * Only where the agent advertises it: a host that does not is one where
99
+ * `createChat` MUST NOT be called at all.
100
+ */
101
+ createChat(uri: SessionUri, first?: string, source?: ChatSource): Promise<string>;
102
+ /**
103
+ * The terminals the host is running.
104
+ *
105
+ * The host's rather than a session's: one outlives the turn that opened it,
106
+ * several clients watch one, and the protocol lists them on the root
107
+ * channel - which is where something owned by no session belongs.
108
+ */
109
+ terminals(): Promise<TerminalRow[]>;
110
+ /** Open one. The URI is chosen here, so it can be watched without a round trip. */
111
+ createTerminal(options?: {
112
+ cwd?: string;
113
+ name?: string;
114
+ }): Promise<string>;
115
+ /** Kill it and let go. */
116
+ disposeTerminal(uri: string): Promise<void>;
117
+ /** Watch one: its state, and then everything that happens to it. */
118
+ watchTerminal(uri: string, observer: (state: TerminalState) => void): {
119
+ close(): void;
120
+ };
121
+ /** Send input. Nothing comes back but what the shell says. */
122
+ writeTerminal(uri: string, data: string): void;
123
+ /**
124
+ * Tell the host how big this client is drawing the terminal.
125
+ *
126
+ * `terminal-channel.md` lists `terminal/resized` among the client-dispatched
127
+ * actions and its reducer sets `cols` and `rows`. A host never told wraps
128
+ * its output at a width nobody chose.
129
+ */
130
+ resizeTerminal(uri: string, cols: number, rows: number): void;
131
+ /** Empty the scrollback. `terminal/cleared` resets `content` to nothing. */
132
+ clearTerminal(uri: string): void;
133
+ /** Rename it. `terminal/titleChanged` sets `title`. */
134
+ renameTerminal(uri: string, title: string): void;
135
+ /**
136
+ * Take it for this client.
137
+ *
138
+ * `TerminalClaimedAction.claim` is required and is a `TerminalClaim` - a
139
+ * client claim carrying this connection's `clientId`, or a session claim.
140
+ * There is no release: the protocol declares no action for giving one up,
141
+ * so this client takes and never pretends to hand back.
142
+ */
143
+ claimTerminal(uri: string): void;
144
+ /**
145
+ * What the host offers to complete what is being typed.
146
+ *
147
+ * The host's question, not this client's: a path is a path on *its*
148
+ * filesystem, and a skill is one it contributed. Asked with the whole draft
149
+ * and where the caret is, because what is being completed depends on the
150
+ * word the caret is in - an at-sign mid-word is an address and a slash
151
+ * mid-sentence is a path.
152
+ *
153
+ * Nothing is a real answer: a host that completes neither is one whose
154
+ * composer offers no menu, which is what it did before either was served.
155
+ */
156
+ completions(options: {
157
+ channel: string;
158
+ text: string;
159
+ offset?: number;
160
+ }): Promise<Completion[]>;
161
+ /**
162
+ * Send one action verbatim, without this client knowing what it means.
163
+ *
164
+ * The escape hatch, and optional because only a real host has one: the
165
+ * protocol has far more client-dispatchable actions than a chat client has
166
+ * controls for, and a way to send an arbitrary one is what makes the rest of
167
+ * them testable at all. `chat` targets the chat channel rather than the
168
+ * session's.
169
+ */
170
+ dispatch?(uri: SessionUri, action: Record<string, unknown>, chat?: boolean): void;
171
+ /**
172
+ * One directory of the host's filesystem, as far as it lets this client see.
173
+ *
174
+ * Optional, because a host may serve none: `createHost` takes its filesystem
175
+ * as a port, and one given none answers `-32601`. Absent here means the same
176
+ * thing a layer up - there is nothing to browse, rather than nothing there.
177
+ */
178
+ resourceList?(uri: string): Promise<ResourceEntry[]>;
179
+ /**
180
+ * One file's bytes, by `file://` URI on the *host's* machine.
181
+ *
182
+ * `encoding` is reported rather than assumed, because a host serves whatever
183
+ * is on its disk: a caller that treated every answer as text would print a
184
+ * PNG to a terminal.
185
+ */
186
+ resourceRead?(uri: string): Promise<{
187
+ data: string;
188
+ encoding: string;
189
+ contentType?: string;
190
+ }>;
191
+ /**
192
+ * What the host knows about one path without reading it.
193
+ *
194
+ * `type` is the host's own `ResourceType` rather than a boolean: a symlink
195
+ * is neither a file nor a directory, and narrowing it here would be this
196
+ * client deciding something the host already answered.
197
+ */
198
+ resourceResolve?(uri: string): Promise<{
199
+ uri: string;
200
+ type: string;
201
+ size?: number;
202
+ mtime?: string;
203
+ /**
204
+ * An opaque version token, where the host keeps one.
205
+ *
206
+ * Carried back as `ifMatch` on a write: the host MUST fail with
207
+ * `-32011 Conflict` when its copy has moved on, which is what stops a
208
+ * read-modify-write losing whatever changed in between. Absent for a
209
+ * directory, which has no bytes to have been changed under anyone.
210
+ */
211
+ etag?: string;
212
+ }>;
213
+ /**
214
+ * Write one file.
215
+ *
216
+ * The whole `resource*` family is symmetrical and optional: a host that
217
+ * serves no filesystem answers `-32601`, which is why these are optional
218
+ * here too. `-32009` is a refusal naming the grant that would lift it, and
219
+ * `createOnly` is the protocol's guard against replacing something that is
220
+ * already there.
221
+ */
222
+ /**
223
+ * Write one file.
224
+ *
225
+ * `ifMatch` is the etag a `resourceResolve` returned, and is the guard on a
226
+ * read-modify-write: without it, a write lands on whatever is there now.
227
+ * `createOnly` is a different guarantee and not a substitute - it refuses a
228
+ * file that has appeared, not one that moved between the resolve and the
229
+ * write.
230
+ */
231
+ resourceWrite?(uri: string, data: string, options?: {
232
+ encoding?: string;
233
+ createOnly?: boolean;
234
+ ifMatch?: string;
235
+ }): Promise<void>;
236
+ resourceDelete?(uri: string, options?: {
237
+ recursive?: boolean;
238
+ }): Promise<void>;
239
+ resourceMkdir?(uri: string): Promise<void>;
240
+ resourceMove?(from: string, to: string, options?: {
241
+ failIfExists?: boolean;
242
+ }): Promise<void>;
243
+ resourceCopy?(from: string, to: string, options?: {
244
+ failIfExists?: boolean;
245
+ }): Promise<void>;
246
+ /**
247
+ * Be told when something under a path changes, instead of asking again.
248
+ *
249
+ * `resource-watch-channel.md`: the receiver allocates the channel URI and it
250
+ * is opaque; there is no dispose command, and the receiver MUST release the
251
+ * watcher once every subscriber has unsubscribed - so letting go of the
252
+ * returned handle is the whole of closing one. Creating one goes through the
253
+ * same permission flow as the rest of the family, so a refusal is `-32009`
254
+ * naming the grant that would lift it.
255
+ */
256
+ watchResource?(uri: string, observer: (changes: {
257
+ uri: string;
258
+ kind: string;
259
+ }[]) => void, options?: {
260
+ recursive?: boolean;
261
+ }): Promise<{
262
+ close(): void;
263
+ }>;
264
+ /**
265
+ * Push a bearer token for one of the host's protected resources.
266
+ *
267
+ * `authentication.md`: `resource` MUST match one the server advertised -
268
+ * statically in `AgentInfo.protectedResources` or through a live MCP
269
+ * challenge. `expiresIn` MUST be a positive integer and MUST have elapsed
270
+ * time subtracted from it if the token response was held onto; it is
271
+ * omitted where the expiry is unknown. An empty token revokes.
272
+ */
273
+ authenticate?(resource: string, token: string, options?: {
274
+ scopes?: string[];
275
+ expiresIn?: number;
276
+ }): Promise<void>;
277
+ /**
278
+ * Ask the host for a config property's values.
279
+ *
280
+ * For a property whose schema says `enumDynamic`: the `enum` on it is not
281
+ * the answer and the host has to be queried. Optional, because a host that
282
+ * marks nothing dynamic never needs it.
283
+ */
284
+ configCompletions?(request: {
285
+ property: string;
286
+ provider?: string;
287
+ workingDirectory?: string;
288
+ values?: Record<string, string>;
289
+ query?: string;
290
+ }): Promise<{
291
+ value: string;
292
+ label: string;
293
+ description?: string;
294
+ }[]>;
295
+ /**
296
+ * The protected resources this host says it has, from `AgentInfo`.
297
+ *
298
+ * A `resource` this client invents is one the host MUST refuse, so this is
299
+ * what any token push has to name.
300
+ */
301
+ protectedResources?(): Promise<{
302
+ resource: string;
303
+ name?: string;
304
+ scopes?: string[];
305
+ }[]>;
306
+ /** Close one. The last chat in a session is the session; dispose that instead. */
307
+ disposeChat(chat: string): Promise<void>;
308
+ /**
309
+ * The catalogue moved: a session appeared, finished, or is now waiting.
310
+ *
311
+ * Separate from `subscribe`, which is one session's channel and says nothing
312
+ * about the ninety-nine a client is not watching. Without this the only way
313
+ * a list gets fresh is somebody navigating away and back, which is a reader
314
+ * doing by hand what the host already said.
315
+ *
316
+ * It carries no payload on purpose. The host owns the catalogue and
317
+ * `listSessions` is how you read it; an event that carried a row would be a
318
+ * second, staler source of the same answer.
319
+ */
320
+ onSessions(observer: () => void): {
321
+ close(): void;
322
+ };
323
+ /** Begin a turn. Any turn - this is not only how the first one starts. */
324
+ /**
325
+ * Put the message being composed where other clients can see it.
326
+ *
327
+ * `chat-channel.md`: clients MAY sync their input into `ChatState.draft` so
328
+ * it survives a reload and is visible to other clients on the same chat,
329
+ * SHOULD debounce rather than sync eagerly, and the host clears it when the
330
+ * message is sent. An empty string is the clear.
331
+ */
332
+ setDraft(uri: SessionUri, text: string): void;
333
+ say(uri: SessionUri, text: string, model?: ModelSelection): void;
334
+ stopTurn(uri: SessionUri): void;
335
+ /**
336
+ * Say it *after* the turn that is running.
337
+ *
338
+ * Not `say` with a wait in front of it. The queue is the host's - it starts
339
+ * the next turn from the head the moment it goes idle, and every client
340
+ * watching the chat sees the same list - so a client that held the message
341
+ * itself would be the only thing that could ever send it, and would not,
342
+ * because nothing in a client is watching for the turn to end.
343
+ */
344
+ queue(uri: SessionUri, text: string, model?: ModelSelection): void;
345
+ /** Take one back, while it is still waiting. */
346
+ unqueue(uri: SessionUri, id: string): void;
347
+ /** Answer a tool confirmation. */
348
+ confirmToolCall(uri: SessionUri, toolCallId: string, approved: boolean, optionId?: string): void;
349
+ /** Answer a question. An accept with no answers resumes the agent on none. */
350
+ completeInput(uri: SessionUri, requestId: string, accepted: boolean, answers: Record<string, Answer>): void;
351
+ /**
352
+ * Which changesets this session offers.
353
+ *
354
+ * The protocol has a session advertise several - what the conversation
355
+ * changed, what one turn changed, what the working tree has - and a client
356
+ * that reads only the first shows one of them and hides the rest.
357
+ */
358
+ changesets?(uri: SessionUri): Promise<ChangesetScope[]>;
359
+ /**
360
+ * One of them, by the URI its template became.
361
+ *
362
+ * Left out, the first that needs no filling in - which is what a screen
363
+ * showing a single changeset wants and what this answered before there was
364
+ * any way to ask for another.
365
+ */
366
+ changes(uri: SessionUri, uri_?: string): Promise<Changeset>;
367
+ /**
368
+ * Mark files in a changeset reviewed, or clear them.
369
+ *
370
+ * Optional, and only where the changeset's catalogue entry says it is
371
+ * reviewable. Sent to the changeset's own channel, not the session's.
372
+ */
373
+ review?(changeset: string, files: string[], reviewed: boolean): void;
374
+ /**
375
+ * Run one of the verbs a changeset advertised.
376
+ *
377
+ * Not fire-and-forget, unlike most of what a client sends: the host answers
378
+ * whether it accepted, and refuses out loud - so a button that failed can
379
+ * say why instead of looking like one that did nothing. What the operation
380
+ * *did* still arrives on the changeset's channel, because every other client
381
+ * has to see it too.
382
+ *
383
+ * `target` is omitted for a changeset-scoped operation and required for the
384
+ * others. A host refuses an `operationId` it did not advertise, which is why
385
+ * this takes one rather than an enum.
386
+ */
387
+ invoke?(changeset: string, operationId: string, target?: ChangesetOperationTarget): Promise<{
388
+ message?: string;
389
+ }>;
390
+ /**
391
+ * Ask to be allowed to write something.
392
+ *
393
+ * The other half of `invoke`, and the reason a refused operation is not a
394
+ * dead end: a host that will not run a write refuses with a payload naming
395
+ * the request that would unlock it, and this is what sends that request. A
396
+ * client that only knew how to press the button would show one that fails
397
+ * and cannot explain itself.
398
+ */
399
+ requestResource?(uri: string, access: {
400
+ read?: boolean;
401
+ write?: boolean;
402
+ }): Promise<void>;
403
+ /**
404
+ * One file out of a changeset, fetched.
405
+ *
406
+ * Separate from `changes` on purpose: a changeset is a list of rows and this
407
+ * is one file's worth of bytes, and a client that returned both together
408
+ * would download a session's entire diff to draw a list of names. Nothing
409
+ * calls this until somebody opens a row.
410
+ */
411
+ content(ref: ContentRef): Promise<FileContent>;
412
+ /**
413
+ * What this session was given: plugins, directories, skills, MCP servers.
414
+ *
415
+ * Read from the session channel rather than the catalogue, because it is
416
+ * per-session - two sessions on the same host, in different directories,
417
+ * are handed different skills. Flattened on the way out; see
418
+ * `Customization`.
419
+ */
420
+ customizations(uri: SessionUri): Promise<Customization[]>;
421
+ /**
422
+ * What a slash offers before any session exists.
423
+ *
424
+ * Separate from `customizations` because that one takes a session, and on
425
+ * the new-session screen there is not one yet - which is exactly when
426
+ * somebody wants to open with a skill. The host knows what its harness
427
+ * contributes without having been asked to run anything, so this is a
428
+ * question it can answer, and the only one that can: a client cannot derive
429
+ * it from a catalogue of sessions it is not opening.
430
+ *
431
+ * Answering with nothing is a real answer, and the one to give for a
432
+ * harness nobody has signed into.
433
+ */
434
+ harnessCommands(): Promise<Customization[]>;
435
+ /**
436
+ * Turn one on or off, by id.
437
+ *
438
+ * Fire-and-forget like the rest of the dispatches: the host decides, tells
439
+ * every client watching, and what comes back is the customization list
440
+ * having changed - not a return value here.
441
+ */
442
+ setCustomizationEnabled(uri: SessionUri, id: string, enabled: boolean): void;
443
+ /**
444
+ * The session channel's own state: its chat, its lifecycle, its settings.
445
+ *
446
+ * Separate from `listSessions` because a summary is what a *row* needs and
447
+ * this is what a reader needs - asking every session about itself to draw a
448
+ * catalogue is a round trip per row.
449
+ */
450
+ detail(uri: SessionUri): Promise<SessionDetail>;
451
+ /** What this session can be told to do differently. */
452
+ config(uri: SessionUri): Promise<SessionConfig>;
453
+ /**
454
+ * Change one key.
455
+ *
456
+ * The action merges into `config.values`, so sending the whole object writes
457
+ * back everything this client happened to be holding - including a value
458
+ * another client changed while the page had it on screen.
459
+ */
460
+ setConfig(uri: SessionUri, key: string, value: string): void;
461
+ /**
462
+ * Let go of whatever the connection is holding.
463
+ *
464
+ * Optional because not every host holds anything: the scripted one is a
465
+ * pile of objects and ends when the process does. A socket and a
466
+ * subprocess are the opposite - they keep the event loop alive on their
467
+ * own, so a program that has finished drawing and returned from `main`
468
+ * still does not exit. That is what this is for, and the reason it is on
469
+ * the seam rather than only on the implementations that need it: the caller
470
+ * cannot know which kind it was handed.
471
+ */
472
+ /**
473
+ * Wait for what has been dispatched to have actually left.
474
+ *
475
+ * For a caller that sends one thing and exits. Everything here is
476
+ * fire-and-forget, and one of those is still asynchronous on the way out -
477
+ * closing the connection in the same breath closes it first, and the
478
+ * dispatch is never sent at all.
479
+ */
480
+ /**
481
+ * Every automation this host holds.
482
+ *
483
+ * Optional, and the absence is the answer: a host that serves no automations
484
+ * channel answers `-32601`, and a client that drew an empty list for it
485
+ * would be claiming the host has none rather than that it has no such thing.
486
+ */
487
+ automations?(): Promise<Automation[]>;
488
+ /**
489
+ * Told when one moves, so the screen is not polled.
490
+ *
491
+ * The interesting change is the one nobody made: an automation firing at
492
+ * nine in the morning arrives here and nowhere else.
493
+ */
494
+ onAutomations?(observer: () => void): {
495
+ close(): void;
496
+ };
497
+ /**
498
+ * Write a new one, and answer with the URI it was given.
499
+ *
500
+ * The client picks the URI, as it does for a session and a chat, so the
501
+ * thing is addressable before the host has answered. `definition` is the
502
+ * protocol's own shape and is passed through rather than modelled here -
503
+ * what this client fills in is a subset, and a host may hold keys it never
504
+ * wrote.
505
+ */
506
+ createAutomation?(definition: Record<string, unknown>): Promise<string>;
507
+ /** Start one now, whatever its schedule says. */
508
+ runAutomation?(uri: string): Promise<void>;
509
+ /** Switch one on or off, which is a patch of its definition. */
510
+ setAutomationEnabled?(uri: string, enabled: boolean): Promise<void>;
511
+ /** Forget one, and everything it has done. */
512
+ removeAutomation?(uri: string): Promise<void>;
513
+ /**
514
+ * Which triggers this host has, so one can be offered.
515
+ *
516
+ * Without asking, only a schedule can be authored: an event trigger is not
517
+ * offerable because nothing here knows what events exist. The kinds are the
518
+ * host's, not a list kept here.
519
+ */
520
+ automationTriggers?(): Promise<{
521
+ kind: string;
522
+ title?: string;
523
+ description?: string;
524
+ }[]>;
525
+ /**
526
+ * Follow the host's own log.
527
+ *
528
+ * Absent where the host advertised no `telemetry.logs`, which the
529
+ * specification says is how a host that emits none says so. `level` expands
530
+ * the `{level}` template variable, the only one defined.
531
+ */
532
+ watchLogs?(observer: (record: {
533
+ at?: string;
534
+ severity?: string;
535
+ body: string;
536
+ attributes: Record<string, string>;
537
+ }) => void, options?: {
538
+ level?: string;
539
+ }): Promise<{
540
+ close(): void;
541
+ }>;
542
+ /** A page of one automation's run history, oldest last, with the host's cursor. */
543
+ automationRuns?(uri: string, cursor?: string): Promise<{
544
+ runs: AutomationRun[];
545
+ nextCursor?: string;
546
+ }>;
547
+ flush?(): Promise<void>;
548
+ close?(): void | Promise<void>;
549
+ }
550
+ /**
551
+ * What the host says happened.
552
+ *
553
+ * Named after the actions rather than after what a screen does with them: the
554
+ * host is describing its own state changing, and a client that renamed
555
+ * `chat/delta` to `appendToBubble` would have written the UI into the wire.
556
+ */
557
+ export type HostEvent = {
558
+ type: 'snapshot';
559
+ turns: Turn[];
560
+ active?: Turn;
561
+ input?: PendingInput;
562
+ status: number;
563
+ queued: QueuedMessage[];
564
+ /**
565
+ * The message being composed, as the host is holding it.
566
+ *
567
+ * `ChatState.draft` is shared: another client typing into this chat is
568
+ * visible here, and a draft survives this client being restarted. Present
569
+ * on every snapshot so a screen can take it when it opens; empty is a real
570
+ * answer, meaning the host holds no draft.
571
+ */
572
+ draft: string;
573
+ } | {
574
+ type: 'turnStarted';
575
+ turn: Turn;
576
+ } | {
577
+ type: 'delta';
578
+ partId: string;
579
+ kind: 'markdown' | 'reasoning';
580
+ text: string;
581
+ } | {
582
+ type: 'toolCall';
583
+ call: ToolCall;
584
+ } | {
585
+ type: 'inputNeeded';
586
+ input: PendingInput;
587
+ } | {
588
+ type: 'inputResolved';
589
+ } | {
590
+ type: 'turnComplete';
591
+ turn: Turn;
592
+ }
593
+ /** The whole queue, as the host now has it. */
594
+ | {
595
+ type: 'queued';
596
+ messages: QueuedMessage[];
597
+ }
598
+ /**
599
+ * The session's chats, as the host now has them.
600
+ *
601
+ * A session is a container and its chats come and go on their own - one
602
+ * opened from another client, one closed - so this is separate from the
603
+ * chat snapshot, which is about the conversation being watched.
604
+ */
605
+ | {
606
+ type: 'chats';
607
+ items: {
608
+ resource: string;
609
+ title: string;
610
+ }[];
611
+ defaultChat: string;
612
+ }
613
+ /**
614
+ * The session's skills, prompts and MCP servers, as the host now has them.
615
+ *
616
+ * Sent when the session channel says they changed - a server signing in, a
617
+ * switch answered, a plugin arriving late. Without it the panel shows what
618
+ * was true when it was opened, so a toggle that worked looked like one that
619
+ * did nothing.
620
+ */
621
+ | {
622
+ type: 'customizations';
623
+ items: Customization[];
624
+ }
625
+ /**
626
+ * Who else is in this session, as the host has it.
627
+ *
628
+ * `SessionState.activeClients` is host-kept membership: a client adds itself
629
+ * with `session/activeClientSet` and the host removes it when the last
630
+ * subscription goes. Two people on one session is the case this exists for.
631
+ */
632
+ | {
633
+ type: 'present';
634
+ clients: {
635
+ clientId: string;
636
+ displayName?: string;
637
+ }[];
638
+ } | {
639
+ type: 'status';
640
+ status: number;
641
+ } | {
642
+ type: 'changes';
643
+ changes: Changeset;
644
+ }
645
+ /**
646
+ * The host answered, and the answer was no.
647
+ *
648
+ * Not the same as the connection dropping, and worth its own event for that
649
+ * reason: a session whose agent has gone is refused for ever while the host
650
+ * is perfectly well, and a client that reported that as "offline" would send
651
+ * somebody to check their network.
652
+ */
653
+ | {
654
+ type: 'error';
655
+ message: string;
656
+ };
@@ -0,0 +1 @@
1
+ export {};