@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,745 @@
1
+ /**
2
+ * The protocol, as a client reads it.
3
+ *
4
+ * These are AHP's own names for AHP's own shapes - `SessionSummary.status` is
5
+ * the bitset the host sends, `responseParts` is one ordered stream, a pending
6
+ * input is either a tool confirmation or a question. Renaming them here would
7
+ * only mean translating twice, and the whole point of the example is to find
8
+ * out which components the *protocol's* shapes need.
9
+ *
10
+ * It is a subset: what a chat client has to render. The authority is the
11
+ * `@agent-host-protocol` package's own `src/types/`.
12
+ */
13
+ /** `ahp-session:/<uuid>`, or whatever scheme the provider registered. */
14
+ export type SessionUri = string;
15
+ /**
16
+ * Status is activity and client flags in one number.
17
+ *
18
+ * `InputNeeded` carries `InProgress`, so it has to be tested first - a turn
19
+ * waiting on a confirmation otherwise reads as merely running, and nobody
20
+ * goes to answer it.
21
+ */
22
+ export declare const SessionFlag: {
23
+ readonly Idle: 1;
24
+ readonly Error: 2;
25
+ readonly InProgress: 8;
26
+ readonly InputNeeded: 24;
27
+ readonly IsRead: 32;
28
+ readonly IsArchived: 64;
29
+ };
30
+ export interface SessionSummary {
31
+ resource: SessionUri;
32
+ provider: string;
33
+ title: string;
34
+ status: number;
35
+ createdAt: string;
36
+ modifiedAt: string;
37
+ workingDirectories: string[];
38
+ /** What the host says it is doing, in its own words. Often absent. */
39
+ activity?: string;
40
+ /**
41
+ * What started this, when it was not a person.
42
+ *
43
+ * New in protocol 0.9.0, and the only way a catalogue can tell a session
44
+ * somebody typed from one that started itself at nine this morning. Absent
45
+ * for a session a person opened, which is most of them.
46
+ */
47
+ origin?: {
48
+ kind: 'automation';
49
+ automation: string;
50
+ run: string;
51
+ };
52
+ /** The footprint, so a list can show it without subscribing to a changeset. */
53
+ changes?: {
54
+ files?: number;
55
+ additions?: number;
56
+ deletions?: number;
57
+ };
58
+ /**
59
+ * The project this session is in, as the *host* names it.
60
+ *
61
+ * Not the same as the last segment of `workingDirectories[0]`, which is what
62
+ * this client falls back to: a host may call a project something its
63
+ * directory is not called, and it is the authority on its own names.
64
+ */
65
+ project?: {
66
+ uri: string;
67
+ displayName: string;
68
+ };
69
+ /**
70
+ * Provider-specific metadata, opaque but for the keys a client knows.
71
+ *
72
+ * `git.branch` is the one read here - it is the protocol's well-known key
73
+ * and what the reference host puts there. Anything else is carried and
74
+ * ignored rather than dropped, because the next reader of this row may know
75
+ * a key this one does not.
76
+ */
77
+ _meta?: Record<string, unknown>;
78
+ }
79
+ /**
80
+ * Everything else about a session, which the catalogue does not carry.
81
+ *
82
+ * `listSessions` returns summaries, and a summary is deliberately thin - it is
83
+ * what a list row needs. The chat URI, the lifecycle and the configuration in
84
+ * force all live on the session channel, and a client that wants them
85
+ * subscribes and reads its state. Kept apart here for the same reason: the
86
+ * catalogue can be refreshed without asking every session about itself.
87
+ */
88
+ export interface SessionDetail {
89
+ resource: SessionUri;
90
+ /**
91
+ * The default chat.
92
+ *
93
+ * A session is not a conversation - it *holds* chats, and everything said is
94
+ * dispatched to one of them. `defaultChat` is the one a session created the
95
+ * ordinary way has, and guessing a chat URI is what having it avoids.
96
+ */
97
+ chat: string | null;
98
+ chats: {
99
+ resource: string;
100
+ title: string;
101
+ }[];
102
+ lifecycle: 'creating' | 'ready' | 'failed';
103
+ config: SessionConfig;
104
+ /**
105
+ * What the last turn ran on. A session has no model; each message has one.
106
+ *
107
+ * Resolved against the catalogue rather than carried whole: a turn names an
108
+ * id, and the name, the harness and the options belong to the model row the
109
+ * root channel advertises. A host whose harness nobody has signed into
110
+ * advertises no models, so an id that resolves to nothing is a real answer
111
+ * and stands in for itself.
112
+ */
113
+ model?: ModelRow;
114
+ activity?: string;
115
+ /**
116
+ * Why the host would not talk about this session, in its own words.
117
+ *
118
+ * A live catalogue lists sessions whose agent is gone, and the host answers
119
+ * `-32001 No agent for session` to anything that tries to watch one. The row
120
+ * is still real - it is what the catalogue returned - so this says what is
121
+ * missing rather than the pane quietly showing a session's worth of blanks.
122
+ */
123
+ refusal?: string;
124
+ }
125
+ export type ToolCallStatus = 'pending' | 'pending-confirmation' | 'running' | 'completed' | 'failed' | 'cancelled';
126
+ /**
127
+ * One tool call, flattened.
128
+ *
129
+ * `ToolCallState` is a union of eight states whose fields differ by state.
130
+ * A reader wants one shape, so the union is flattened on the way in and the
131
+ * fields that are not there yet are simply absent.
132
+ */
133
+ export interface ToolCall {
134
+ id: string;
135
+ /** What the host calls it. */
136
+ name: string;
137
+ /** Kept apart: hosts give many tools one display name. */
138
+ toolName: string;
139
+ status: ToolCallStatus;
140
+ /** The command. The only thing separating twenty identical rows. */
141
+ input?: string;
142
+ /** What it meant to do. Markdown. */
143
+ intention?: string;
144
+ /** What it did, past tense. */
145
+ outcome?: string;
146
+ /** What came back. */
147
+ output?: string;
148
+ exitCode?: number;
149
+ files?: string[];
150
+ /** Set while `pending-confirmation`. */
151
+ confirmationTitle?: string;
152
+ options?: {
153
+ id: string;
154
+ label: string;
155
+ }[];
156
+ }
157
+ export type ResponsePart = {
158
+ kind: 'markdown';
159
+ id: string;
160
+ content: string;
161
+ } | {
162
+ kind: 'reasoning';
163
+ id: string;
164
+ content: string;
165
+ } | {
166
+ kind: 'systemNotification';
167
+ id: string;
168
+ content: string;
169
+ } | {
170
+ kind: 'toolCall';
171
+ id: string;
172
+ call: ToolCall;
173
+ }
174
+ /** How a turn failed, in the host's words. `resumable`: the host can carry on from it. */
175
+ | {
176
+ kind: 'error';
177
+ id: string;
178
+ message: string;
179
+ resumable: boolean;
180
+ };
181
+ /**
182
+ * A turn.
183
+ *
184
+ * `parts` is one ordered stream, not prose and calls kept apart: "let me search
185
+ * for those" means something before the searches and nothing after them.
186
+ *
187
+ * The running turn is `activeTurn` on the chat and is *not* in `turns` until it
188
+ * finishes, so a client that reads only the history shows an empty conversation
189
+ * for exactly as long as somebody is watching one happen.
190
+ */
191
+ export interface Turn {
192
+ id: string;
193
+ role: 'user' | 'agent';
194
+ /** What the person sent, on a user turn. */
195
+ message?: string;
196
+ parts: ResponsePart[];
197
+ state: 'running' | 'complete' | 'cancelled' | 'failed';
198
+ /**
199
+ * What this turn ran on, as the host reported it.
200
+ *
201
+ * The id and whatever settings went with it. Carried whole because the
202
+ * settings are the only record of what a turn was actually asked for -
203
+ * a thinking level is chosen per turn and takes effect from that turn
204
+ * onwards, so an id alone cannot say what any given answer cost.
205
+ */
206
+ model?: ModelSelection;
207
+ at: string;
208
+ elapsedMs?: number;
209
+ }
210
+ export type QuestionKind = 'text' | 'number' | 'integer' | 'boolean' | 'single-select' | 'multi-select';
211
+ export interface Question {
212
+ id: string;
213
+ kind: QuestionKind;
214
+ message: string;
215
+ required?: boolean;
216
+ options?: {
217
+ id: string;
218
+ label: string;
219
+ }[];
220
+ /** Answering in words *instead of* choosing, not as a choice. */
221
+ allowFreeformInput?: boolean;
222
+ }
223
+ /**
224
+ * What the agent is waiting for.
225
+ *
226
+ * Two kinds, and they are nothing alike. A confirmation is a yes or a no about
227
+ * a tool call. A question carries no tool call at all - its prose is the
228
+ * request's message and what is being asked is its questions. Rendering the
229
+ * second as the first loses the entire request: the choices vanish and what is
230
+ * left on screen is a heading and an Approve button.
231
+ */
232
+ export interface ToolConfirmation {
233
+ kind: 'toolConfirmation';
234
+ id: string;
235
+ call: ToolCall;
236
+ }
237
+ export interface ChatInputRequest {
238
+ kind: 'chatInput';
239
+ id: string;
240
+ message: string;
241
+ questions: Question[];
242
+ }
243
+ export type PendingInput = ToolConfirmation | ChatInputRequest;
244
+ /**
245
+ * A message waiting for the running turn to finish.
246
+ *
247
+ * The host's, not the client's: `queuedMessages` is on the chat, a client
248
+ * appends to it with `chat/pendingMessageSet` and withdraws one with
249
+ * `chat/pendingMessageRemoved`, and the host starts the next turn from the
250
+ * head as soon as it is idle. A client that kept its own list instead would
251
+ * be showing a queue nothing was ever going to send, and hiding one that
252
+ * another client had already filled.
253
+ */
254
+ export interface QueuedMessage {
255
+ id: string;
256
+ text: string;
257
+ }
258
+ /** Keyed by question id. The value names its own kind. */
259
+ export type Answer = {
260
+ kind: 'text';
261
+ value: string;
262
+ } | {
263
+ kind: 'number';
264
+ value: number;
265
+ } | {
266
+ kind: 'boolean';
267
+ value: boolean;
268
+ } | {
269
+ kind: 'selected';
270
+ value: string;
271
+ } | {
272
+ kind: 'selected-many';
273
+ value: string[];
274
+ };
275
+ export interface FileEdit {
276
+ uri: string;
277
+ /**
278
+ * Whether somebody has ticked this file off.
279
+ *
280
+ * Absent is not-yet-reviewed, which is what the protocol says a missing
281
+ * value means - so a client must not read absence as a third state.
282
+ */
283
+ reviewed?: boolean;
284
+ /** Absent `before` is a creation, absent `after` a deletion. */
285
+ before?: string;
286
+ after?: string;
287
+ diff: {
288
+ added: number;
289
+ removed: number;
290
+ };
291
+ /**
292
+ * Where the two versions of the file actually are.
293
+ *
294
+ * `before` and `after` are the file's own URIs - what it is called on either
295
+ * side of the edit, which is how a rename shows. The content is somewhere
296
+ * else: the protocol keeps it out of the state tree behind a `ContentRef`,
297
+ * because a changeset of two hundred files is a list a client wants and four
298
+ * megabytes it does not. So a row is cheap and opening one is a fetch.
299
+ */
300
+ content?: {
301
+ before?: ContentRef;
302
+ after?: ContentRef;
303
+ };
304
+ }
305
+ /**
306
+ * A pointer to content the state tree does not carry.
307
+ *
308
+ * `sizeHint` is worth keeping rather than reading past: it is the only thing
309
+ * that says, before the fetch, that the answer is a hundred megabytes. A
310
+ * viewer that reads first and measures after is a viewer that reads first.
311
+ */
312
+ export interface ContentRef {
313
+ uri: string;
314
+ sizeHint?: number;
315
+ contentType?: string;
316
+ }
317
+ /** What came back for a `ContentRef`, decoded. */
318
+ export interface FileContent {
319
+ text: string;
320
+ /** Set instead of `text` when the bytes are not text this can show. */
321
+ binary?: {
322
+ bytes: number;
323
+ contentType?: string;
324
+ };
325
+ }
326
+ export interface Changeset {
327
+ status: 'computing' | 'complete';
328
+ files: FileEdit[];
329
+ /**
330
+ * The verbs the host offers on this changeset.
331
+ *
332
+ * Server-advertised, and that is the access model rather than a hint: a host
333
+ * refuses an `operationId` it did not put in this list, so a client may
334
+ * offer nothing that is not here. Absent means there is nothing to do to
335
+ * this changeset, which is a real answer for a host that computes diffs and
336
+ * never acts on one.
337
+ */
338
+ operations?: ChangesetOperation[];
339
+ }
340
+ /**
341
+ * One verb a changeset offers.
342
+ *
343
+ * `confirmation` is not decoration: the protocol says a client **MUST**
344
+ * display it before invoking, and its presence is also how the host says the
345
+ * operation is destructive - so a client that dropped it would be one that
346
+ * deletes somebody's work without asking.
347
+ */
348
+ export interface ChangesetOperation {
349
+ id: string;
350
+ label: string;
351
+ description?: string;
352
+ /** Whether it applies to the whole changeset, one file, or a range in one. */
353
+ scopes: ('changeset' | 'resource' | 'range')[];
354
+ /** Ask this first. Present iff the host considers the operation destructive. */
355
+ confirmation?: string;
356
+ /** A hint, e.g. `git-commit` or `discard`. */
357
+ icon?: string;
358
+ /** Operations sharing one are drawn together. */
359
+ group?: string;
360
+ /**
361
+ * What may be pressed, and what is happening.
362
+ *
363
+ * The host's, not this client's: `disabled` while a turn is running,
364
+ * `running` while an invocation of it is out, `error` after one failed. Two
365
+ * clients watching one changeset see the same spinner because the host is
366
+ * what they see it through.
367
+ */
368
+ status: 'idle' | 'running' | 'error' | 'disabled';
369
+ /** Why the last invocation failed. Present iff `status` is `error`. */
370
+ error?: {
371
+ message: string;
372
+ };
373
+ }
374
+ /** The file, or lines of it, an operation is pointed at. */
375
+ export interface ChangesetOperationTarget {
376
+ kind: 'resource' | 'range';
377
+ /** The row's id, which is a `file://` URI. */
378
+ resource: string;
379
+ side?: 'before' | 'after';
380
+ range?: {
381
+ startLine: number;
382
+ endLine: number;
383
+ };
384
+ }
385
+ /**
386
+ * What `ahp-root://` advertises: the harnesses, and the models each offers.
387
+ *
388
+ * `models` is routinely empty, and that is a real answer rather than a
389
+ * failure: a harness enumerates its models once the host has a token for the
390
+ * resources it declares in `protectedResources`, so a host nobody has signed
391
+ * into advertises the harness and nothing to run on it. A client that treats
392
+ * an empty list as "still loading" shows a blank panel forever.
393
+ *
394
+ */
395
+ /**
396
+ * One model a harness offers.
397
+ *
398
+ * The same shape wherever a model appears - what the catalogue advertises and
399
+ * what a session says it ran on are one protocol object, and two readings of
400
+ * it would be two answers to "which model is this".
401
+ *
402
+ * `options` is the model's own `configSchema`, which the protocol says a
403
+ * client presents as a form and returns through `ModelSelection.config`. It is
404
+ * read and shown here and not offered as a control, because the hosts that
405
+ * send it do not yet consume what comes back - see the roadmap. The labels are
406
+ * the host's: three implementations spell the same effort levels three
407
+ * different ways, so a client with its own words is one that disagrees with
408
+ * whichever host it is connected to.
409
+ */
410
+ /**
411
+ * A model, as a turn names one.
412
+ *
413
+ * The protocol's own shape: an id, and the resolved answers to whatever
414
+ * `ModelRow.options` asked. Distinct from the catalogue row - this is the
415
+ * choice, that is what there was to choose from.
416
+ */
417
+ /**
418
+ * Where a new chat comes from, when it comes from an existing one.
419
+ *
420
+ * A fork copies the source's history through a completed turn into the new
421
+ * chat's visible turns; a side chat supplies the same context without copying
422
+ * it into what a person reads. Both are gated on the agent advertising them -
423
+ * `capabilities.multipleChats: { fork, sideChat }` - and a host that does not
424
+ * is one where the option is not offered rather than offered and refused.
425
+ */
426
+ export type ChatSource = {
427
+ kind: 'fork';
428
+ chat: string;
429
+ turnId: string;
430
+ } | {
431
+ kind: 'sideChat';
432
+ chat: string;
433
+ turnId: string;
434
+ };
435
+ export interface ModelSelection {
436
+ id: string;
437
+ /** Answers by property key, in the host's own vocabulary. */
438
+ config?: Record<string, string>;
439
+ }
440
+ export interface ModelRow {
441
+ /** What rides on a turn. */
442
+ id: string;
443
+ /** The protocol's `name`. Ids are things like `claude-sonnet-4-5-20250929`. */
444
+ displayName: string;
445
+ /**
446
+ * The harness it belongs to.
447
+ *
448
+ * Required by the protocol and always the enclosing agent's own, so it
449
+ * identifies rather than informs: worth carrying, not worth a row of its
450
+ * own beside a model already listed under its harness.
451
+ */
452
+ provider: string;
453
+ /** The model's own settings, where it has any. */
454
+ options?: ConfigProperty[];
455
+ }
456
+ /**
457
+ * One thing the host offers to complete what is being typed.
458
+ *
459
+ * Carries the range it replaces rather than only the text, because what is
460
+ * being completed is a *fragment*: `@src/ho` becomes `@src/host.ts` by
461
+ * replacing from the at-sign, and a client that appended would produce
462
+ * `@src/ho@src/host.ts`.
463
+ */
464
+ export interface Completion {
465
+ /** What to put in the draft. */
466
+ insertText: string;
467
+ /** Where the replaced fragment starts, as an offset into the draft. */
468
+ rangeStart: number;
469
+ /** Where it ends. */
470
+ rangeEnd: number;
471
+ /** What a person reads in the menu. */
472
+ label: string;
473
+ /** One line under it, when the host said something worth reading. */
474
+ description?: string;
475
+ }
476
+ /** One terminal the host is running, as the root channel lists it. */
477
+ export interface TerminalRow {
478
+ /** Its channel URI. */
479
+ resource: string;
480
+ /** Display title. */
481
+ title: string;
482
+ /** The process's exit code, once it has one. Absent while it runs. */
483
+ exitCode?: number;
484
+ }
485
+ /** A terminal's own state, as its channel reports it. */
486
+ export interface TerminalState {
487
+ /** Display title. */
488
+ title: string;
489
+ /** Everything written so far, flattened from the protocol's content parts. */
490
+ output: string;
491
+ /** Where it is running. */
492
+ cwd?: string;
493
+ /** The process's exit code, once it has one. */
494
+ exitCode?: number;
495
+ /**
496
+ * Whether a pseudoterminal is behind it.
497
+ *
498
+ * `false` means the output is plain text and carries no VT sequences - so a
499
+ * client neither has to parse them nor should expect anything that draws
500
+ * itself with cursor movement to look right.
501
+ */
502
+ isPty: boolean;
503
+ }
504
+ export interface Agent {
505
+ provider: string;
506
+ displayName: string;
507
+ description?: string;
508
+ models: ModelRow[];
509
+ /**
510
+ * What this harness wants a token for before it will work.
511
+ *
512
+ * `AgentInfo.protectedResources`, and the only place a `resource` name may
513
+ * come from other than a live MCP challenge - `authenticate` MUST name one
514
+ * the host advertised, so a client that invents a name is one the host is
515
+ * obliged to refuse.
516
+ */
517
+ protectedResources?: {
518
+ resource: string;
519
+ name?: string;
520
+ scopes?: string[];
521
+ }[];
522
+ /**
523
+ * Whether this agent can hold more than one chat in a session.
524
+ *
525
+ * A gate, not a hint: a host that does not advertise it is one where
526
+ * `createChat` MUST NOT be called, so the command is not offered either.
527
+ */
528
+ multipleChats?: boolean;
529
+ /** Whether it can fork a chat, and whether it can hold a side chat. */
530
+ chatSources?: {
531
+ fork?: boolean;
532
+ sideChat?: boolean;
533
+ };
534
+ /**
535
+ * What this harness offers, before any session exists.
536
+ *
537
+ * The protocol puts the same list in two places on purpose: here, where a
538
+ * client can read it without creating anything, and on a session, where it
539
+ * has been resolved against that session's directory. The first is what a
540
+ * new-session screen needs - somebody choosing a skill to open with is
541
+ * choosing before there is a session to ask.
542
+ *
543
+ * Empty is a real answer, and the one to expect from a host whose harness
544
+ * nobody has signed into.
545
+ */
546
+ customizations?: Customization[];
547
+ }
548
+ /**
549
+ * A session's configuration, as the host describes it.
550
+ *
551
+ * The host sends a JSON Schema with titles, `enumLabels` and
552
+ * `enumDescriptions`; this is that, flattened to what a form needs.
553
+ * `sessionMutable` is the property that decides whether a control is offered
554
+ * at all: `permissionMode` can be changed on a running session and `isolation`
555
+ * cannot, and a form that lets you try produces a refusal instead of an edit.
556
+ */
557
+ export interface ConfigProperty {
558
+ key: string;
559
+ title: string;
560
+ description?: string;
561
+ values: {
562
+ value: string;
563
+ label: string;
564
+ description?: string;
565
+ }[];
566
+ sessionMutable: boolean;
567
+ /**
568
+ * Whether the host has to be asked for the values rather than sending them.
569
+ *
570
+ * The reference host sets this on `branch` while isolation is `worktree`: a
571
+ * branch list on a large repository is not something to put in a schema, so
572
+ * the schema says "ask me" and `sessionConfigCompletions` is the asking.
573
+ */
574
+ enumDynamic?: boolean;
575
+ /**
576
+ * What the host opens with, where it said.
577
+ *
578
+ * Routinely absent, and absent is not "the first one": a model that takes a
579
+ * single effort level below the one its harness defaults to carries the
580
+ * choice and no default at all, so a form that filled the gap in from the
581
+ * top of the list would show a setting the host never named.
582
+ */
583
+ default?: string;
584
+ }
585
+ export interface SessionConfig {
586
+ properties: ConfigProperty[];
587
+ /** What is in force. A change dispatches the one key, never the object. */
588
+ values: Record<string, string>;
589
+ }
590
+ /**
591
+ * What a plugin, a directory or the host itself contributed to this session.
592
+ *
593
+ * One flat shape for eight `CustomizationType`s, because a reader wants a
594
+ * list. The protocol nests them - a plugin or a directory is a *container*
595
+ * whose `children` are the skills, prompts, rules, hooks, agents and MCP
596
+ * servers it brought - and an MCP server can also arrive at the top level,
597
+ * contributed by the host rather than by anything. Flattening keeps `from`
598
+ * so a panel can still say where a skill came from, which is the question
599
+ * somebody looking at a list of forty of them actually has.
600
+ *
601
+ * `enabled` is derived, not copied. A child's own flag is independent of its
602
+ * container's, and the effective answer is both: a disabled plugin disables
603
+ * everything it brought whatever each child says about itself. A panel that
604
+ * showed the child's flag alone would list a skill as on inside a plugin that
605
+ * is off.
606
+ */
607
+ export type CustomizationKind = 'plugin' | 'directory' | 'agent' | 'skill' | 'prompt' | 'rule' | 'hook' | 'mcpServer';
608
+ export interface Customization {
609
+ /** Session-unique and opaque. What every action targeting one sends. */
610
+ id: string;
611
+ kind: CustomizationKind;
612
+ name: string;
613
+ /** The file, directory or plugin URL it was read from. */
614
+ uri: string;
615
+ description?: string;
616
+ /** The container's and its own, resolved together. */
617
+ enabled: boolean;
618
+ /** The plugin or directory it came from. Absent at the top level. */
619
+ from?: string;
620
+ /**
621
+ * Whether a person may invoke it, for the kinds where that is a question.
622
+ *
623
+ * A skill can be marked as the agent's alone - `disable-user-invocation` in
624
+ * its frontmatter - and offering it in a slash menu is then offering
625
+ * something the host will refuse. The other direction, an agent-only skill
626
+ * hidden from the menu, is why this is a field rather than an assumption.
627
+ */
628
+ userInvocable?: boolean;
629
+ /** MCP servers: `starting`, `ready`, `authRequired`, `error` or `stopped`. */
630
+ state?: McpState;
631
+ /** Why it is not ready, in the host's own words. */
632
+ problem?: string;
633
+ }
634
+ export type McpState = 'starting' | 'ready' | 'authRequired' | 'error' | 'stopped';
635
+ /**
636
+ * Something a person can put after a slash.
637
+ *
638
+ * Two sources that look alike and behave nothing alike, which is why `kind` is
639
+ * here rather than left to be guessed at the call site. A `client` command is
640
+ * one of ours: it opens a screen or changes a setting, and sending it down the
641
+ * session channel would put `/theme` in the transcript and ask an agent to
642
+ * make sense of it. A `session` command is a skill or a prompt the *host*
643
+ * contributed, and the only way to invoke one is to send its name as the
644
+ * message - which is exactly what the composer does with a slash it does not
645
+ * recognise.
646
+ */
647
+ export interface SlashCommand {
648
+ id: string;
649
+ kind: 'client' | 'session';
650
+ title: string;
651
+ description?: string;
652
+ /** Where a session command came from: the plugin or directory. */
653
+ from?: string;
654
+ /**
655
+ * What goes after the name, written the way it would be typed.
656
+ *
657
+ * `/autocompact <tokens>` says more about the command than a sentence
658
+ * describing it, and it is the one thing a menu cannot show in a row: the
659
+ * row is the name.
660
+ */
661
+ hint?: string;
662
+ }
663
+ /**
664
+ * One entry of a directory the host serves.
665
+ *
666
+ * AHP's own shape, kept to its own names: `uri` is a `file://` URI on the
667
+ * *host's* machine, never on this one.
668
+ */
669
+ export interface ResourceEntry {
670
+ uri: string;
671
+ name: string;
672
+ /** `file`, `directory`, or whatever else the host distinguishes. */
673
+ kind: string;
674
+ size?: number;
675
+ }
676
+ /**
677
+ * One changeset a session offers, as its catalogue advertises it.
678
+ *
679
+ * A *scope*, not a diff: what this conversation changed, what one turn
680
+ * changed, what changed between two, what the working tree has. The host names
681
+ * them and a client picks; `variables` is what still has to be filled in
682
+ * before the template is a URI - empty for one that already is.
683
+ */
684
+ export interface ChangesetScope {
685
+ label: string;
686
+ uriTemplate: string;
687
+ description?: string;
688
+ /**
689
+ * What kind of changeset this is, for grouping and icons.
690
+ *
691
+ * An advisory hint, and the protocol says to fall back sensibly on a value
692
+ * this client has never heard of rather than to drop the entry.
693
+ */
694
+ changeKind?: string;
695
+ /**
696
+ * Whether files here can be ticked off as read.
697
+ *
698
+ * On the *catalogue* entry, which is what lets a client decide whether to
699
+ * draw the checkbox before it subscribes to anything.
700
+ */
701
+ reviewable?: boolean;
702
+ /** The `{name}` placeholders left in the template, in the order they appear. */
703
+ variables: string[];
704
+ }
705
+ /**
706
+ * One run of an automation, flattened to what a list row shows.
707
+ *
708
+ * The protocol carries a lifecycle object and an origin object; a reader wants
709
+ * to know whether it worked, whether anybody asked for it, and what to open.
710
+ */
711
+ export interface AutomationRun {
712
+ resource: string;
713
+ /** `pending`, `running`, `completed`, `failed` or `cancelled`. */
714
+ status: string;
715
+ /** The session it started, once it has one. What opening the run opens. */
716
+ session?: string;
717
+ /** Whether a trigger started it, rather than somebody pressing Run. */
718
+ triggered: boolean;
719
+ }
720
+ /**
721
+ * One automation: a session the host starts without anybody asking.
722
+ *
723
+ * `schedule` and `nextRunAt` answer different questions and both are worth
724
+ * showing. The first is what somebody wrote and is true whatever the host
725
+ * does with it; the second is what the host will actually do, and its absence
726
+ * is how a host says it will not fire this - because it holds no clock,
727
+ * because the automation is switched off, or because the expression is one it
728
+ * could not read.
729
+ */
730
+ export interface Automation {
731
+ resource: string;
732
+ title: string;
733
+ enabled: boolean;
734
+ /** The cron expression and zone, as written. Absent for a manual-only one. */
735
+ schedule?: {
736
+ expression: string;
737
+ timeZone: string;
738
+ };
739
+ /** ISO 8601. Absent when nothing will fire it. */
740
+ nextRunAt?: string;
741
+ /** Newest first, and a bounded window of them. */
742
+ runs: AutomationRun[];
743
+ /** Which of `update`, `remove` and `run` the host will accept for it now. */
744
+ operations: string[];
745
+ }