@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Luiz Fernando Softov <lfs@softov.dev>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,296 @@
1
+ # ahpc
2
+
3
+ [![npm](https://img.shields.io/npm/v/%40softov%2Fahpc)](https://www.npmjs.com/package/@softov/ahpc)
4
+ ![license MIT](https://img.shields.io/badge/license-MIT-blue)
5
+ ![node >=22](https://img.shields.io/badge/node-%3E%3D22-5fa04e)
6
+ ![Agent Host Protocol 0.9.0](https://img.shields.io/badge/AHP-0.9.0-0b7285)
7
+ ![built with TextUI](https://img.shields.io/badge/built%20with-TextUI-7048e8)
8
+
9
+ A terminal client for the [Agent Host Protocol](https://microsoft.github.io/agent-host-protocol/).
10
+ It can be used as cli (commands) or tui (interactive chat).
11
+
12
+ Connect to an AHP host, manage sessions, and work with agents directly from your terminal.
13
+
14
+ > [!NOTE]
15
+ > `ahpc` is a client. It does not run agents or models itself.
16
+ > You need an AHP-compatible host to connect to.
17
+
18
+ The interface is built with [TextUI](https://github.com/softov/textui), a component toolkit for terminal applications.
19
+
20
+ ## Quick start
21
+
22
+ ```sh
23
+ npm install -g @softov/ahpc
24
+ ```
25
+
26
+ Or run it without installing, with `npx @softov/ahpc`.
27
+
28
+ The package is scoped; the command it installs is `ahpc`.
29
+
30
+ A scripted host is built in, so the screen runs with nothing else to set up:
31
+
32
+ ```sh
33
+ ahpc
34
+ ```
35
+
36
+ Point it at a real host:
37
+
38
+ ```sh
39
+ ahpc --host ws://127.0.0.1:9187
40
+ ahpc session list --host ws://127.0.0.1:9187
41
+ ```
42
+
43
+ [`ahpd`](https://github.com/softov/ahpd) and VS Code's agent host are both AHP hosts.
44
+
45
+ ## What it does
46
+
47
+ | | |
48
+ |---|---|
49
+ | Sessions | List, create, configure, archive and delete sessions on a host. |
50
+ | Turns | Send a prompt, stream the reply, queue follow-ups, cancel. |
51
+ | Answering | Approve or deny tool calls, and answer questions the agent asks mid-turn. |
52
+ | Chats | Multiple conversations in one session. |
53
+ | Changes and files | Files a session changed, their diffs, and the host's filesystem. |
54
+ | Terminals | Shells running on the host, and their output. |
55
+ | Automations | Scheduled and triggered runs, with their history. |
56
+ | Customizations | Skills, prompts, agents and MCP servers, and which are enabled. |
57
+ | Telemetry | Stream the host's log. |
58
+
59
+ ## Interactive
60
+
61
+ `ahpc` with no command opens the screen.
62
+
63
+ ![The session list, with status and workspace for each](docs/img/sessions.svg)
64
+
65
+ The list shows every session on the host: its status, the agent, the workspace and branch, and what it is doing right now. Sessions waiting on you are counted at the top. `ctrl+f` filters by title, agent or workspace, and `x` shows the archived ones with a count of how many that is.
66
+
67
+ ![A session, its transcript and the facts about it](docs/img/transcript.svg)
68
+
69
+ Inside a session, the header shows the model, thinking level, permission mode, workspace and branch. All of it comes from the host, not from local guesses.
70
+
71
+ `ctrl+f` opens a find box in the top right. Type and the term is coloured wherever it appears; `enter` and `down` go to the next one, `up` to the one before, and both wrap. The box says which match you are on and how many there are.
72
+
73
+ ![Starting a session, and the questions the host asks first](docs/img/compose.svg)
74
+
75
+ Before the first message, a new session asks the agent, the model and its options, the permission mode and the workspace. The questions come from the host's `configSchema`, so options `ahpc` has never seen still get a row.
76
+
77
+ ### Keys
78
+
79
+ | Key | |
80
+ |---|---|
81
+ | `enter` | Send |
82
+ | `alt+enter` | Newline |
83
+ | `tab` | Move to the options row |
84
+ | `esc` | Close the menu, then leave the field, then go back |
85
+ | `/` | Slash commands, from the host and from `ahpc` — `/config` opens the settings palette |
86
+ | `@` | Complete a file path on the host |
87
+ | `ctrl+g` | Edit the message in `$VISUAL` or `$EDITOR` |
88
+ | `ctrl+p` | Command palette |
89
+ | `f1` | Every key that works where you are |
90
+ | `ctrl+f` | Filter the session list, or find in the open conversation |
91
+ | `ctrl+n` | New session |
92
+ | `ctrl+r` | Refresh |
93
+ | `alt+t` | Theme |
94
+ | `alt+m` | Markdown on or off |
95
+ | `ctrl+c` | Cancel the running turn, or quit |
96
+
97
+ Editing in the composer follows readline:
98
+
99
+ | Key | |
100
+ |---|---|
101
+ | `ctrl+a` / `ctrl+e` | Start or end of the line |
102
+ | `ctrl+k` / `ctrl+u` | Delete to the end, or to the start |
103
+ | `ctrl+w`, `alt+backspace` | Delete the word before the caret |
104
+ | `alt+d` | Delete the word after it |
105
+ | `ctrl+z` / `alt+z` | Undo, redo |
106
+ | `ctrl+←` / `ctrl+→` | Move a word at a time |
107
+
108
+ Undo groups a run of typing into one step, so it takes back a word rather than a character.
109
+
110
+ When a tool call is waiting: `a` approves, `d` denies, `1`-`9` pick an offered option. When the agent asks a question: `tab` moves between fields, `space` selects, `enter` sends.
111
+
112
+ ## Commands
113
+
114
+ `ahpc <command>` runs without the screen. Output is formatted for reading; `--json` gives the same data for scripts.
115
+
116
+ ### Sessions
117
+
118
+ | Command | | |
119
+ |---|---|---|
120
+ | `session list` | List sessions, newest first | `--archived` `--json` |
121
+ | `session show <uri>` | Session details | `--full` `--json` |
122
+ | `session new` | Create a new session | `--agent` `--cwd` `--set k=v` `--json` |
123
+ | `session rm <uri>` | Delete a session | |
124
+ | `session read <uri>` | Mark as read | `--unread` |
125
+ | `session archive <uri>` | Archive a session | `--undo` |
126
+ | `session history <uri>` | Show turns history | `--all` `--full` `--json` |
127
+ | `session config <uri>` | Show the config schema and current values | `--json` |
128
+ | `session set <uri> <k> <v>` | Change one config property | |
129
+ | `session customizations <uri>` | List skills, prompts, agents and MCP servers | `--json` |
130
+ | `session toggle <uri> <id>` | Toggle customization on/off | `--off` |
131
+ | `session export <uri>` | Export the session as one document | `--json` `--markdown` |
132
+
133
+ ### Turns
134
+
135
+ | Command | | |
136
+ |---|---|---|
137
+ | `prompt <uri> <text>` | Send a prompt and stream the reply | `--model` `--json` |
138
+ | `exec <text>` | Run one prompt in a throwaway session | `--agent` `--cwd` `--model` `--json` |
139
+ | `cancel <uri>` | Cancel the running turn | |
140
+ | `queue <uri> <text>` | Queue a prompt behind the running turn | `--model` |
141
+ | `unqueue <uri> <id>` | Remove a queued prompt | |
142
+
143
+ ### Answering
144
+
145
+ | Command | | |
146
+ |---|---|---|
147
+ | `watch <uri>` | Block until the agent needs input, print it, exit | `--until turn\|input\|idle` `--timeout` `--json` |
148
+ | `confirm <uri> <toolCallId>` | Approve a tool call | `--deny` `--option` |
149
+ | `answer <uri> <requestId>` | Answer a question | `--field k=v` `--reject` |
150
+
151
+ ### Chats
152
+
153
+ | Command | | |
154
+ |---|---|---|
155
+ | `chat list <uri>` | List the chats in a session | `--json` |
156
+ | `chat new <uri> [text]` | Start another chat | |
157
+ | `chat rm <chatUri>` | Close a chat | |
158
+
159
+ ### The host
160
+
161
+ | Command | | |
162
+ |---|---|---|
163
+ | `agents` | List the agents the host serves, with their models | `--json` |
164
+ | `models` | List every model, grouped by agent | `--json` |
165
+ | `commands` | List the slash commands the host offers | `--json` |
166
+ | `customizations` | List skills, prompts, agents and MCP servers | `--kind` `--json` |
167
+ | `completions <uri> <text>` | Show what the host would complete | `--offset` `--json` |
168
+ | `logs` | Stream the host's log | `--level` `--follow` |
169
+ | `auth` | List the resources this host protects | `--json` |
170
+ | `auth <resource>` | Send a token for one | `--token` `--expires-in` |
171
+ | `status` | Show the current connection | `--json` |
172
+
173
+ ### Changes and files
174
+
175
+ | Command | | |
176
+ |---|---|---|
177
+ | `changes <uri>` | List the files a session changed | `--list` `--scope` `--reviewed` `--unreviewed` `--operations` `--run` `--json` |
178
+ | `content <uri> <file>` | Print one changed file in full | |
179
+ | `resource list <uri>` | List a directory on the host | `--json` |
180
+ | `resource read <uri>` | Read a file on the host | |
181
+ | `resource stat <uri>` | Show a file's type and size | `--json` |
182
+ | `resource write <uri> [file]` | Write a file, from a path or stdin | `--create-only` `--force` |
183
+ | `resource rm <uri>` | Delete a file or directory | `--recursive` |
184
+ | `resource mkdir <uri>` | Create a directory | |
185
+ | `resource mv <uri> <to>` | Move or rename | `--fail-if-exists` |
186
+ | `resource cp <uri> <to>` | Copy | `--fail-if-exists` |
187
+
188
+ Writes are guarded by the file's etag unless you pass `--force`, so two clients editing the same file cannot silently overwrite each other.
189
+
190
+ ### Terminals
191
+
192
+ | Command | | |
193
+ |---|---|---|
194
+ | `terminal list` | List running terminals | `--json` |
195
+ | `terminal new` | Open a shell | `--cwd` `--name` |
196
+ | `terminal rm <uri>` | Close a terminal | |
197
+ | `terminal send <uri> <text>` | Send input to a terminal | |
198
+ | `terminal watch <uri>` | Follow a terminal's output | `--timeout` |
199
+
200
+ ### Automations
201
+
202
+ | Command | | |
203
+ |---|---|---|
204
+ | `automation list` | List automations | `--json` |
205
+ | `automation show <uri>` | Show one automation | `--json` |
206
+ | `automation triggers` | List the triggers this host supports | `--json` |
207
+ | `automation runs <uri>` | Show an automation's run history | `--json` |
208
+ | `automation run <uri>` | Run it now | |
209
+ | `automation enable <uri>` / `disable <uri>` | Enable or disable it | |
210
+ | `automation rm <uri>` | Delete it | |
211
+
212
+ ### Anything else
213
+
214
+ | Command | | |
215
+ |---|---|---|
216
+ | `dispatch <uri> <type>` | Send a raw protocol action | `--field k=v` `--chat` |
217
+ | `config` | Show the config file path and current values | `--json` |
218
+ | `help` | Print this command list | |
219
+
220
+ ## AHP support
221
+
222
+ All 30 client-to-server requests are implemented, and 21 of the 45 client-dispatchable actions are used. `ahpc` subscribes to the root, session, chat, terminal and automation channels, plus the telemetry channel the host advertises for its log.
223
+
224
+ AHP is symmetrical, so a host can also request things from the client. Nine of the ten server-initiated methods are implemented; `createResourceWatch` is not. Nothing is shared until `--publish <dir>` names a directory, and it stays read-only without `--publish-writable`:
225
+
226
+ ```sh
227
+ ahpc --host ws://127.0.0.1:9187 --publish ~/notes
228
+ ```
229
+
230
+ The host reads those files at `virtual://<clientId>/<path>`. Publishing lasts only while the screen is open.
231
+
232
+ [docs/CONFORMANCE.md](docs/CONFORMANCE.md) covers the full surface: which actions are dispatched, four divergences and why, undeclared fields hosts send in practice, and three defects that belong to the protocol package rather than any implementation.
233
+
234
+ ## Configuration
235
+
236
+ `$XDG_CONFIG_HOME/ahpc/config.json`, or `~/.config/ahpc/config.json`:
237
+
238
+ ```json
239
+ { "host": "ws://127.0.0.1:9187", "theme": "paper-light" }
240
+ ```
241
+
242
+ Precedence: a flag overrides an environment variable, which overrides the file.
243
+
244
+ | | |
245
+ |---|---|
246
+ | `--host`, `AHPC_HOST` | The host to connect to |
247
+ | `--token`, `AHPC_TOKEN` | A bearer token for it |
248
+ | `AHPC_TOKEN_<RESOURCE>` | A token for one protected resource |
249
+ | `--config-file` | Read this file instead |
250
+
251
+ `ahpc config` prints the file path and the values in force. It works without a host, which is what you need when the host is the problem.
252
+
253
+ ### Keys
254
+
255
+ `keys` maps a chord to a command id, or to `null` to unbind it:
256
+
257
+ ```json
258
+ {
259
+ "keys": {
260
+ "ctrl+g": "editor.open",
261
+ "ctrl+t": null,
262
+ "ctrl+y": "session.new"
263
+ }
264
+ }
265
+ ```
266
+
267
+ Naming a chord replaces every default on it, so a chord is either yours or the client's and never half of each. A chord bound to a name no command answers to is reported at startup rather than ignored. `ctrl+c`, `ctrl+d`, `ctrl+h`, `ctrl+i`, `ctrl+j`, `ctrl+m` and `ctrl+[` cannot be rebound usefully — a terminal sends them as interrupt, end-of-file, backspace, tab, newline, return and escape.
268
+
269
+ ## Development
270
+
271
+ ```sh
272
+ git clone https://github.com/softov/ahpc
273
+ cd ahpc
274
+ npm install
275
+
276
+ npm test
277
+ npm run typecheck
278
+ npm run build # dist/src, which is what the package ships
279
+ ```
280
+
281
+ Two tools check the client against the protocol itself:
282
+
283
+ ```sh
284
+ npm run schema # a strict JSON Schema from the package's own declarations
285
+ npm run wire -- <capture> # check a recording against it
286
+ ```
287
+
288
+ `AHPC_RECORD=<file>` appends every frame sent and received. `test/conformance.test.ts` runs the same check against frames produced by the test run itself, so it cannot pass on a stale recording.
289
+
290
+ The screens, widgets and input handling come from [TextUI](https://github.com/softov/textui) — `@textui/core` for components and state, `@textui/widgets` for the catalog, `@textui/terminal` for rendering and key decoding, and `@textui/testing` for the harness the tests run in. `ahpc` began as an example inside it.
291
+
292
+ [docs/DESIGN.md](docs/DESIGN.md) covers why the client is built this way: how a terminal handles a transcript differently from a browser, and what the widget catalog was missing.
293
+
294
+ ## License
295
+
296
+ MIT.
@@ -0,0 +1,146 @@
1
+ /** Who is holding which channel open, and what the host is saying on it. */
2
+ /** One thing the host said on a channel: an action, or a protocol notification. */
3
+ export interface ChannelEvent {
4
+ type: string;
5
+ params?: unknown;
6
+ }
7
+ /** A channel event with the channel it arrived on. */
8
+ export interface AddressedEvent {
9
+ channel: string;
10
+ event: ChannelEvent;
11
+ }
12
+ /** State as the host holds it, before this client has read anything out of it. */
13
+ export type ChannelState = Record<string, unknown> | null;
14
+ /**
15
+ * One reader of one channel.
16
+ *
17
+ * `opened` arrives before any `event`, and arrives *again* after a reconnect
18
+ * that could not be closed by replay: a consumer rebuilds from the state it is
19
+ * handed rather than assuming the one it had is still the truth.
20
+ */
21
+ export interface Consumer {
22
+ /** The channel's state, as of the moment this reader joined it. */
23
+ opened(state: ChannelState): void;
24
+ /** Everything the host sends on the channel, in the order it sent it. */
25
+ event(event: ChannelEvent): void;
26
+ /** The host would not serve the channel, in the words it used. */
27
+ refused?(message: string): void;
28
+ }
29
+ /** What this registry needs of a protocol client. */
30
+ export interface ChannelClient {
31
+ subscribe(uri: string): Promise<{
32
+ result: {
33
+ snapshot?: {
34
+ state?: unknown;
35
+ fromSeq?: number;
36
+ } | null;
37
+ };
38
+ }>;
39
+ unsubscribe(uri: string): Promise<void>;
40
+ events(): AsyncIterableIterator<AddressedEvent>;
41
+ }
42
+ /** A reader's hold on a channel, given up by calling `release`. */
43
+ export interface Hold {
44
+ release(): void;
45
+ }
46
+ /** A snapshot the host handed back for one channel during a reconnect. */
47
+ export interface ResumedSnapshot {
48
+ resource: string;
49
+ state?: unknown;
50
+ }
51
+ export interface Channels {
52
+ /**
53
+ * Take a hold on a channel and start reading it.
54
+ *
55
+ * Synchronous, because every caller is a screen being drawn: the subscribe
56
+ * happens behind this and the consumer hears about it through `opened`. A
57
+ * hold released before the subscribe lands never subscribes at all.
58
+ */
59
+ open(uri: string, consumer: Consumer): Hold;
60
+ /**
61
+ * The channel's state now, without holding it open afterwards.
62
+ *
63
+ * A read rather than a watch: this subscribes, takes the state, and
64
+ * unsubscribes again unless somebody else is holding the channel. A host
65
+ * has no other way to answer "what is in there" - there is no `getState` -
66
+ * so the subscription is the question and letting go is the whole point.
67
+ */
68
+ state(uri: string): Promise<ChannelState>;
69
+ /** Every channel held right now, which is what `reconnect` has to be told. */
70
+ held(): string[];
71
+ /** The highest `serverSeq` this client has seen on any channel. */
72
+ seq(): number;
73
+ /**
74
+ * Read one channel's refusal, if it was refused.
75
+ *
76
+ * A refusal is an answer about what the host is - `-32001` for a session
77
+ * whose agent has gone - and asking again on every keystroke turns one
78
+ * refusal into a stream of them.
79
+ */
80
+ refusal(uri: string): string | undefined;
81
+ /**
82
+ * Let a refusal be asked again: one channel, or every one of them.
83
+ *
84
+ * What a host refuses can stop being refused - a session is created, an
85
+ * agent comes back - and the answer is only worth keeping until something
86
+ * happens that could have changed it.
87
+ */
88
+ forget(uri?: string): void;
89
+ /** Start reading a connection's event stream. Ends when the stream does. */
90
+ drain(client: ChannelClient): void;
91
+ /**
92
+ * Take up the same channels again on a new connection.
93
+ *
94
+ * `replayed` is the branch where the host could name everything missed:
95
+ * the envelopes go to their consumers in order and nobody is re-opened.
96
+ * `resumed` is the branch where it could not, and every consumer is handed
97
+ * a fresh state. `missing` is refused either way.
98
+ */
99
+ resume(client: ChannelClient, result: {
100
+ replayed?: readonly unknown[];
101
+ resumed?: readonly ResumedSnapshot[];
102
+ missing?: readonly string[];
103
+ }): void;
104
+ /**
105
+ * Take a channel the handshake already opened, with the state it answered.
106
+ *
107
+ * `initialize` accepts `initialSubscriptions` and answers with a snapshot
108
+ * for each, which leaves the channel subscribed at the host before anything
109
+ * here has asked for it. Without this the first reader would subscribe again
110
+ * - the round trip the handshake exists to save, and a second `subscribe`
111
+ * for a channel this connection already holds.
112
+ */
113
+ adopt(uri: string, state: ChannelState): void;
114
+ /** Forget every subscription without unsubscribing: the socket is already gone. */
115
+ detach(): void;
116
+ }
117
+ export interface ChannelsOptions {
118
+ client: ChannelClient;
119
+ /** Told when a channel is refused, so the reason reaches a person. */
120
+ onRefusal?(uri: string, message: string): void;
121
+ /** Told when the host refuses an action this client dispatched. */
122
+ onRejection?(uri: string, message: string): void;
123
+ /**
124
+ * This client's id, as `initialize` gave it.
125
+ *
126
+ * Only so that a refusal can be told from somebody else's: an envelope
127
+ * naming another client is still not applied, and is not reported here.
128
+ */
129
+ clientId?: string;
130
+ /** The host's own words for a failure, as a client would show them. */
131
+ reason(error: unknown): string;
132
+ /**
133
+ * How long to keep a channel nobody is reading, in milliseconds.
134
+ *
135
+ * Not laziness about letting go - a pause before it. Reading a snapshot and
136
+ * opening a view are two holds on the same channel a moment apart, and
137
+ * navigating away and back is two more, so releasing on the instant the
138
+ * count reaches zero puts an `unsubscribe` between every pair of them. A
139
+ * host is entitled to act on that: the reference one evicts a session from
140
+ * memory when its last subscriber leaves and restores it from disk on the
141
+ * next subscribe, and a client that unsubscribes and immediately subscribes
142
+ * again is racing that restore. `0` releases at once.
143
+ */
144
+ lingerMs?: number;
145
+ }
146
+ export declare function openChannels(options: ChannelsOptions): Channels;