@anhlp/alp-client 0.0.0-stage → 0.0.1-beta.4

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 (75) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +7 -0
  3. package/README.md +28 -2
  4. package/dist/acp-probe.d.ts +44 -0
  5. package/dist/acp-probe.js +37 -0
  6. package/dist/antigravity-probe.d.ts +34 -0
  7. package/dist/antigravity-probe.js +95 -0
  8. package/dist/command.d.ts +5 -0
  9. package/dist/command.js +33 -0
  10. package/dist/doctor.d.ts +13 -0
  11. package/dist/doctor.js +587 -0
  12. package/dist/index.d.ts +151 -0
  13. package/dist/index.js +533 -0
  14. package/dist/integration-lock.d.ts +35 -0
  15. package/dist/integration-lock.js +179 -0
  16. package/dist/ipc.d.ts +42 -0
  17. package/dist/ipc.js +69 -0
  18. package/dist/library-test.d.ts +94 -0
  19. package/dist/library-test.js +37 -0
  20. package/dist/mcp-probe.d.ts +24 -0
  21. package/dist/mcp-probe.js +147 -0
  22. package/dist/portable.d.ts +19 -0
  23. package/dist/portable.js +33 -0
  24. package/dist/prompt.d.ts +32 -0
  25. package/dist/prompt.js +59 -0
  26. package/dist/render.d.ts +6 -0
  27. package/dist/render.js +120 -0
  28. package/dist/rpc/client.d.ts +82 -0
  29. package/dist/rpc/client.js +292 -0
  30. package/dist/rpc/errors.d.ts +42 -0
  31. package/dist/rpc/errors.js +34 -0
  32. package/dist/rpc/framing.d.ts +21 -0
  33. package/dist/rpc/framing.js +57 -0
  34. package/dist/rpc/handshake.d.ts +19 -0
  35. package/dist/rpc/handshake.js +8 -0
  36. package/dist/rpc/reconnect.d.ts +97 -0
  37. package/dist/rpc/reconnect.js +227 -0
  38. package/dist/rpc/registry.d.ts +211 -0
  39. package/dist/rpc/registry.js +145 -0
  40. package/dist/rpc/timers.d.ts +11 -0
  41. package/dist/rpc/timers.js +11 -0
  42. package/dist/service.d.ts +50 -0
  43. package/dist/service.js +143 -0
  44. package/dist/setup.d.ts +2 -0
  45. package/dist/setup.js +146 -0
  46. package/dist/supervise.d.ts +14 -0
  47. package/dist/supervise.js +87 -0
  48. package/dist/websocket.d.ts +51 -0
  49. package/dist/websocket.js +190 -0
  50. package/package.json +174 -4
  51. package/src/acp-probe.js +46 -0
  52. package/src/antigravity-probe.js +106 -0
  53. package/src/command.js +18 -0
  54. package/src/doctor.d.ts +13 -0
  55. package/src/doctor.js +570 -0
  56. package/src/index.js +596 -0
  57. package/src/integration-lock.js +179 -0
  58. package/src/ipc.js +92 -0
  59. package/src/library-test.js +41 -0
  60. package/src/mcp-probe.js +118 -0
  61. package/src/portable.js +28 -0
  62. package/src/prompt.js +64 -0
  63. package/src/render.js +136 -0
  64. package/src/rpc/client.js +296 -0
  65. package/src/rpc/errors.js +54 -0
  66. package/src/rpc/framing.js +61 -0
  67. package/src/rpc/handshake.js +16 -0
  68. package/src/rpc/reconnect.js +226 -0
  69. package/src/rpc/registry.js +161 -0
  70. package/src/rpc/timers.js +16 -0
  71. package/src/service.js +154 -0
  72. package/src/setup.js +150 -0
  73. package/src/supervise.d.ts +14 -0
  74. package/src/supervise.js +92 -0
  75. package/src/websocket.js +259 -0
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Newline-delimited JSON framing for stream transports (a Unix socket, a named pipe, a TCP
3
+ * stream). Portable: strings in, strings out; the stream owner decodes bytes to text first.
4
+ * Message transports such as a WebSocket carry whole frames and need none of this.
5
+ */
6
+ /** UTF-8 length of `text` without Buffer or TextEncoder; an unpaired surrogate counts as U+FFFD (3 bytes). @param {string} text */
7
+ export function utf8Length(text: string): number;
8
+ /**
9
+ * Splits decoded text chunks into frames. `push` returns the complete frames in order, blank
10
+ * lines skipped, and throws once a frame (complete or still arriving) exceeds `maxFrame` bytes;
11
+ * the decoder is unusable after that. Each character is measured once, so a frame that arrives
12
+ * in many chunks costs linear time.
13
+ * @param {{ maxFrame: number }} options
14
+ * @returns {{ push(chunk: string): string[], readonly buffered: number }}
15
+ */
16
+ export function createLineDecoder({ maxFrame }: {
17
+ maxFrame: number;
18
+ }): {
19
+ push(chunk: string): string[];
20
+ readonly buffered: number;
21
+ };
@@ -0,0 +1,57 @@
1
+ // packages/client/src/rpc/framing.js
2
+ function utf8Length(text) {
3
+ let bytes = 0;
4
+ for (let index = 0; index < text.length; index++) {
5
+ const code = text.charCodeAt(index);
6
+ if (code < 128) bytes += 1;
7
+ else if (code < 2048) bytes += 2;
8
+ else if (code >= 55296 && code <= 56319 && index + 1 < text.length && (text.charCodeAt(index + 1) & 64512) === 56320) {
9
+ bytes += 4;
10
+ index++;
11
+ } else bytes += 3;
12
+ }
13
+ return bytes;
14
+ }
15
+ function createLineDecoder({ maxFrame }) {
16
+ let parts = [];
17
+ let bytes = 0;
18
+ let failed = false;
19
+ const overflow = () => {
20
+ failed = true;
21
+ parts = [];
22
+ bytes = 0;
23
+ return new RangeError(`Frame exceeds ${maxFrame} bytes`);
24
+ };
25
+ return {
26
+ push(chunk) {
27
+ if (failed) throw new RangeError("Line decoder already failed");
28
+ const frames = [];
29
+ let start = 0;
30
+ for (let index = chunk.indexOf("\n"); index >= 0; index = chunk.indexOf("\n", start)) {
31
+ const piece = chunk.slice(start, index);
32
+ bytes += utf8Length(piece);
33
+ if (bytes > maxFrame) throw overflow();
34
+ parts.push(piece);
35
+ const line = parts.length === 1 ? parts[0] : parts.join("");
36
+ parts = [];
37
+ bytes = 0;
38
+ start = index + 1;
39
+ if (line.trim()) frames.push(line);
40
+ }
41
+ if (start < chunk.length) {
42
+ const rest = chunk.slice(start);
43
+ bytes += utf8Length(rest);
44
+ if (bytes > maxFrame) throw overflow();
45
+ parts.push(rest);
46
+ }
47
+ return frames;
48
+ },
49
+ get buffered() {
50
+ return bytes;
51
+ }
52
+ };
53
+ }
54
+ export {
55
+ createLineDecoder,
56
+ utf8Length
57
+ };
@@ -0,0 +1,19 @@
1
+ /** @typedef {{ name: string, version: string }} ClientInfo */
2
+ /**
3
+ * The version handshake every ALP connection starts with (plans/reference/ALPD.md §3): alpd
4
+ * answers nothing else before it. Shared by the Node `connect` and portable clients, so a change
5
+ * to the handshake (Task 06) lands in one place.
6
+ * @param {{ request(method: string, params?: unknown, options?: import('./client.js').RequestOptions): Promise<any> }} client
7
+ * @param {ClientInfo} [info]
8
+ * @param {import('./client.js').RequestOptions} [options]
9
+ * @returns {Promise<{ protocolVersion: number } & Record<string, unknown>>}
10
+ */
11
+ export function hello(client: {
12
+ request(method: string, params?: unknown, options?: import("./client.js").RequestOptions): Promise<any>;
13
+ }, info?: ClientInfo, options?: import("./client.js").RequestOptions): Promise<{
14
+ protocolVersion: number;
15
+ } & Record<string, unknown>>;
16
+ export type ClientInfo = {
17
+ name: string;
18
+ version: string;
19
+ };
@@ -0,0 +1,8 @@
1
+ // packages/client/src/rpc/handshake.js
2
+ import { PROTOCOL_VERSION } from "@anhlp/alp-protocol";
3
+ function hello(client, info = { name: "alp", version: "0" }, options) {
4
+ return client.request("daemon.hello", { protocolVersion: PROTOCOL_VERSION, client: info }, options);
5
+ }
6
+ export {
7
+ hello
8
+ };
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Keeps an ALP connection up: connects, runs the caller's `onConnect` (handshake, reattach and
3
+ * replay as the protocol defines them), and after a disconnect connects again with exponential
4
+ * backoff until stopped. A connection counts as up only once it has stayed open `stableMs`; one
5
+ * that drops sooner (a gateway closing right after the handshake) counts as a failed attempt, so
6
+ * backoff and `maxAttempts` still apply. It never resends a request: requests in flight when the connection drops
7
+ * reject as `disconnected`, and requests made while it is down reject at once. Whether and how to
8
+ * retry belongs to the caller, who knows which methods are safe to repeat.
9
+ */
10
+ /**
11
+ * @typedef {import('./client.js').AlpTransport} AlpTransport
12
+ * @typedef {import('./client.js').AlpRpcClientOptions} AlpRpcClientOptions
13
+ * @typedef {import('./client.js').RequestOptions} RequestOptions
14
+ * @typedef {import('./client.js').AbortSignalLike} AbortSignalLike
15
+ * @typedef {'connecting' | 'open' | 'waiting' | 'stopped'} ReconnectState
16
+ * @typedef {{ initialMs?: number, maxMs?: number, factor?: number, jitter?: number }} Backoff
17
+ * @typedef {{
18
+ * connect(context: { attempt: number, signal: AbortSignalLike }): Promise<AlpTransport>,
19
+ * onConnect?(client: AlpRpcClient, context: { attempt: number, reconnect: boolean, signal: AbortSignalLike }): unknown,
20
+ * onDisconnect?(error: Error, context: { reconnect: boolean }): void,
21
+ * backoff?: Backoff,
22
+ * maxAttempts?: number,
23
+ * stableMs?: number,
24
+ * shouldRetry?(error: Error): boolean,
25
+ * client?: AlpRpcClientOptions,
26
+ * random?: () => number,
27
+ * }} ReconnectOptions
28
+ * @typedef {{
29
+ * readonly state: ReconnectState,
30
+ * readonly client: AlpRpcClient | undefined,
31
+ * readonly attempt: number,
32
+ * ready(options?: { signal?: AbortSignalLike }): Promise<AlpRpcClient>,
33
+ * request<T = any>(method: string, params?: unknown, options?: RequestOptions): Promise<T>,
34
+ * onEvent(listener: (envelope: any) => void): () => boolean,
35
+ * onNotification(listener: (method: string, params: any) => void): () => boolean,
36
+ * onState(listener: (state: ReconnectState, error?: Error) => void): () => boolean,
37
+ * stop(): void,
38
+ * }} ReconnectingClient
39
+ */
40
+ /** The delay before attempt `attempt` (1-based), with ±jitter. @param {number} attempt @param {Backoff} backoff @param {() => number} random */
41
+ export function backoffDelay(attempt: number, { initialMs, maxMs, factor, jitter }?: Backoff, random?: () => number): number;
42
+ /**
43
+ * Whether a failed connect or onConnect is worth another attempt: everything but alpd refusing the
44
+ * handshake (ERROR.protocol, an unsupported protocol version), which retrying cannot fix.
45
+ * @param {Error} error
46
+ */
47
+ export function retryable(error: Error): boolean;
48
+ /**
49
+ * @param {ReconnectOptions} options
50
+ * @returns {ReconnectingClient}
51
+ */
52
+ export function createReconnectingClient({ connect, onConnect, onDisconnect, backoff, maxAttempts, stableMs, shouldRetry, client: clientOptions, random }: ReconnectOptions): ReconnectingClient;
53
+ export type AlpTransport = import("./client.js").AlpTransport;
54
+ export type AlpRpcClientOptions = import("./client.js").AlpRpcClientOptions;
55
+ export type RequestOptions = import("./client.js").RequestOptions;
56
+ export type AbortSignalLike = import("./client.js").AbortSignalLike;
57
+ export type ReconnectState = "connecting" | "open" | "waiting" | "stopped";
58
+ export type Backoff = {
59
+ initialMs?: number;
60
+ maxMs?: number;
61
+ factor?: number;
62
+ jitter?: number;
63
+ };
64
+ export type ReconnectOptions = {
65
+ connect(context: {
66
+ attempt: number;
67
+ signal: AbortSignalLike;
68
+ }): Promise<AlpTransport>;
69
+ onConnect?(client: AlpRpcClient, context: {
70
+ attempt: number;
71
+ reconnect: boolean;
72
+ signal: AbortSignalLike;
73
+ }): unknown;
74
+ onDisconnect?(error: Error, context: {
75
+ reconnect: boolean;
76
+ }): void;
77
+ backoff?: Backoff;
78
+ maxAttempts?: number;
79
+ stableMs?: number;
80
+ shouldRetry?(error: Error): boolean;
81
+ client?: AlpRpcClientOptions;
82
+ random?: () => number;
83
+ };
84
+ export type ReconnectingClient = {
85
+ readonly state: ReconnectState;
86
+ readonly client: AlpRpcClient | undefined;
87
+ readonly attempt: number;
88
+ ready(options?: {
89
+ signal?: AbortSignalLike;
90
+ }): Promise<AlpRpcClient>;
91
+ request<T = any>(method: string, params?: unknown, options?: RequestOptions): Promise<T>;
92
+ onEvent(listener: (envelope: any) => void): () => boolean;
93
+ onNotification(listener: (method: string, params: any) => void): () => boolean;
94
+ onState(listener: (state: ReconnectState, error?: Error) => void): () => boolean;
95
+ stop(): void;
96
+ };
97
+ import { AlpRpcClient } from './client.js';
@@ -0,0 +1,227 @@
1
+ // packages/client/src/rpc/reconnect.js
2
+ import { AlpRpcError, ERROR } from "@anhlp/alp-protocol";
3
+ import { AlpRpcClient } from "./client.js";
4
+ import { AlpClientError } from "./errors.js";
5
+ function backoffDelay(attempt, { initialMs = 250, maxMs = 1e4, factor = 2, jitter = 0.2 } = {}, random = Math.random) {
6
+ const base = Math.min(maxMs, initialMs * factor ** Math.max(0, attempt - 1));
7
+ return Math.max(0, Math.round(base * (1 + jitter * (2 * random() - 1))));
8
+ }
9
+ function retryable(error) {
10
+ return !(error instanceof AlpRpcError && error.code === ERROR.protocol);
11
+ }
12
+ function controller() {
13
+ if (typeof globalThis.AbortController === "function") {
14
+ const native = new globalThis.AbortController();
15
+ return {
16
+ signal: (
17
+ /** @type {AbortSignalLike} */
18
+ native.signal
19
+ ),
20
+ /** @param {unknown} reason */
21
+ abort: (reason) => native.abort(reason)
22
+ };
23
+ }
24
+ const listeners = /* @__PURE__ */ new Set();
25
+ const signal = {
26
+ aborted: false,
27
+ /** @type {unknown} */
28
+ reason: void 0,
29
+ /** @param {'abort'} _type @param {() => void} listener */
30
+ addEventListener(_type, listener) {
31
+ listeners.add(listener);
32
+ },
33
+ /** @param {'abort'} _type @param {() => void} listener */
34
+ removeEventListener(_type, listener) {
35
+ listeners.delete(listener);
36
+ }
37
+ };
38
+ return {
39
+ signal,
40
+ /** @param {unknown} reason */
41
+ abort(reason) {
42
+ if (signal.aborted) return;
43
+ signal.aborted = true;
44
+ signal.reason = reason;
45
+ for (const listener of [...listeners]) listener();
46
+ listeners.clear();
47
+ }
48
+ };
49
+ }
50
+ function createReconnectingClient({ connect, onConnect, onDisconnect, backoff = {}, maxAttempts = Infinity, stableMs = 5e3, shouldRetry = retryable, client: clientOptions, random = Math.random }) {
51
+ if (typeof connect !== "function") throw new TypeError("createReconnectingClient needs connect()");
52
+ let state = "connecting";
53
+ let current;
54
+ let attempt = 0;
55
+ let connectedBefore = false;
56
+ let timer;
57
+ const run = controller();
58
+ const events = /* @__PURE__ */ new Set();
59
+ const notifications = /* @__PURE__ */ new Set();
60
+ const states = /* @__PURE__ */ new Set();
61
+ const waiters = /* @__PURE__ */ new Set();
62
+ const onListenerError = clientOptions?.onListenerError ?? ((error) => {
63
+ setTimeout(() => {
64
+ throw error;
65
+ }, 0);
66
+ });
67
+ const dispatch = (listener, ...args) => {
68
+ try {
69
+ listener(...args);
70
+ } catch (error) {
71
+ onListenerError(error);
72
+ }
73
+ };
74
+ const enter = (next, error) => {
75
+ state = next;
76
+ for (const listener of [...states]) {
77
+ dispatch(listener, next, error);
78
+ if (state !== next) return;
79
+ }
80
+ if (next === "open" && current) for (const waiter of [...waiters]) {
81
+ waiters.delete(waiter);
82
+ waiter.resolve(current);
83
+ }
84
+ if (next === "stopped") for (const waiter of [...waiters]) {
85
+ waiters.delete(waiter);
86
+ waiter.reject(error ?? new AlpClientError("ALP connection stopped", { reason: "closed" }));
87
+ }
88
+ };
89
+ const retry = (error) => {
90
+ if (state === "stopped") return;
91
+ if (attempt >= maxAttempts || !shouldRetry(error)) return enter("stopped", error);
92
+ enter("waiting", error);
93
+ if (state !== "waiting") return;
94
+ timer = setTimeout(() => {
95
+ timer = void 0;
96
+ void open();
97
+ }, backoffDelay(attempt, backoff, random));
98
+ };
99
+ const open = async () => {
100
+ if (state === "stopped") return;
101
+ attempt++;
102
+ enter("connecting");
103
+ const { signal } = run;
104
+ if (state !== "connecting" || signal.aborted) return;
105
+ let candidate;
106
+ const closeCandidate = () => candidate?.close();
107
+ signal.addEventListener("abort", closeCandidate, { once: true });
108
+ try {
109
+ const transport = await connect({ attempt, signal });
110
+ if (signal.aborted) {
111
+ try {
112
+ transport.close();
113
+ } catch {
114
+ }
115
+ return;
116
+ }
117
+ candidate = new AlpRpcClient(transport, clientOptions);
118
+ candidate.onEvent((envelope) => {
119
+ for (const listener of [...events]) dispatch(listener, envelope);
120
+ });
121
+ candidate.onNotification((method, params) => {
122
+ for (const listener of [...notifications]) dispatch(listener, method, params);
123
+ });
124
+ if (onConnect) await onConnect(candidate, { attempt, reconnect: connectedBefore, signal });
125
+ if (signal.aborted) {
126
+ candidate.close();
127
+ return;
128
+ }
129
+ if (candidate.closed) throw new AlpClientError("ALP connection closed during onConnect", { reason: "disconnected" });
130
+ } catch (error) {
131
+ candidate?.close();
132
+ if (signal.aborted) return;
133
+ return retry(error instanceof Error ? error : new Error(String(error)));
134
+ } finally {
135
+ signal.removeEventListener("abort", closeCandidate);
136
+ }
137
+ const connected = (
138
+ /** @type {AlpRpcClient} */
139
+ candidate
140
+ );
141
+ current = connected;
142
+ const reconnect = connectedBefore;
143
+ connectedBefore = true;
144
+ const openedAt = Date.now();
145
+ connected.onClose((error) => {
146
+ if (current !== connected) return;
147
+ current = void 0;
148
+ if (Date.now() - openedAt >= stableMs) attempt = 0;
149
+ dispatch(() => onDisconnect?.(error, { reconnect }));
150
+ retry(error);
151
+ });
152
+ enter("open");
153
+ };
154
+ void open();
155
+ return {
156
+ get state() {
157
+ return state;
158
+ },
159
+ get client() {
160
+ return current;
161
+ },
162
+ get attempt() {
163
+ return attempt;
164
+ },
165
+ ready({ signal } = {}) {
166
+ if (state === "open" && current) return Promise.resolve(current);
167
+ if (state === "stopped") return Promise.reject(new AlpClientError("ALP connection stopped", { reason: "closed" }));
168
+ return new Promise((resolve, reject) => {
169
+ const waiter = {
170
+ /** @param {AlpRpcClient} client */
171
+ resolve: (client) => {
172
+ signal?.removeEventListener("abort", onAbort);
173
+ resolve(client);
174
+ },
175
+ /** @param {Error} error */
176
+ reject: (error) => {
177
+ signal?.removeEventListener("abort", onAbort);
178
+ reject(error);
179
+ }
180
+ };
181
+ const onAbort = () => {
182
+ waiters.delete(waiter);
183
+ reject(new AlpClientError("Waiting for the ALP connection was aborted", { reason: "aborted", cause: signal?.reason }));
184
+ };
185
+ if (signal?.aborted) return onAbort();
186
+ waiters.add(waiter);
187
+ signal?.addEventListener("abort", onAbort, { once: true });
188
+ });
189
+ },
190
+ request(method, params, options) {
191
+ if (state !== "open" || !current) return Promise.reject(new AlpClientError("ALP daemon connection closed", { reason: state === "stopped" ? "closed" : "disconnected" }));
192
+ return current.request(method, params, options);
193
+ },
194
+ onEvent(listener) {
195
+ events.add(listener);
196
+ return () => events.delete(listener);
197
+ },
198
+ onNotification(listener) {
199
+ notifications.add(listener);
200
+ return () => notifications.delete(listener);
201
+ },
202
+ onState(listener) {
203
+ states.add(listener);
204
+ return () => states.delete(listener);
205
+ },
206
+ stop() {
207
+ if (state === "stopped") return;
208
+ if (timer !== void 0) {
209
+ clearTimeout(timer);
210
+ timer = void 0;
211
+ }
212
+ run.abort(new AlpClientError("ALP connection stopped", { reason: "closed" }));
213
+ const last = current;
214
+ current = void 0;
215
+ enter("stopped");
216
+ last?.close();
217
+ events.clear();
218
+ notifications.clear();
219
+ states.clear();
220
+ }
221
+ };
222
+ }
223
+ export {
224
+ backoffDelay,
225
+ createReconnectingClient,
226
+ retryable
227
+ };
@@ -0,0 +1,211 @@
1
+ /** Typed registry methods for portable clients. Mutations are sent once: callers retain requestId
2
+ * and ifRevision to reconcile a timeout by repeating the identical request. @param {{request<T = any>(method: string, params?: unknown): Promise<T>}} client */
3
+ export function workspaceRegistry(client: {
4
+ request<T = any>(method: string, params?: unknown): Promise<T>;
5
+ }): {
6
+ /** @param {string} workspaceId @returns {Promise<import('@anhlp/alp-protocol').RpcMethodMap['workspace.worktree.options']['result']>} */
7
+ worktreeOptions: (workspaceId: string) => Promise<import("@anhlp/alp-protocol").RpcMethodMap["workspace.worktree.options"]["result"]>;
8
+ /** @param {import('@anhlp/alp-protocol').RpcMethodMap['workspace.worktree.create']['params']} params @returns {Promise<import('@anhlp/alp-protocol').RpcMethodMap['workspace.worktree.create']['result']>} */
9
+ createWorktree: (params: import("@anhlp/alp-protocol").RpcMethodMap["workspace.worktree.create"]["params"]) => Promise<import("@anhlp/alp-protocol").RpcMethodMap["workspace.worktree.create"]["result"]>;
10
+ /** @param {import('@anhlp/alp-protocol').RpcMethodMap['workspace.worktree.remove']['params']} params @returns {Promise<import('@anhlp/alp-protocol').RpcMethodMap['workspace.worktree.remove']['result']>} */
11
+ removeWorktree: (params: import("@anhlp/alp-protocol").RpcMethodMap["workspace.worktree.remove"]["params"]) => Promise<import("@anhlp/alp-protocol").RpcMethodMap["workspace.worktree.remove"]["result"]>;
12
+ /** Read-only Git status of a registry workspace (capability `workspace.git`, §12.5). @param {string} workspaceId @returns {Promise<import('@anhlp/alp-protocol').RpcMethodMap['workspace.git.status']['result']>} */
13
+ gitStatus: (workspaceId: string) => Promise<import("@anhlp/alp-protocol").RpcMethodMap["workspace.git.status"]["result"]>;
14
+ /** One page of a read-only Git diff; pass `nextCursor` back as `cursor` with the same comparison and path. @param {import('@anhlp/alp-protocol').RpcMethodMap['workspace.git.diff']['params']} params @returns {Promise<import('@anhlp/alp-protocol').RpcMethodMap['workspace.git.diff']['result']>} */
15
+ gitDiff: (params: import("@anhlp/alp-protocol").RpcMethodMap["workspace.git.diff"]["params"]) => Promise<import("@anhlp/alp-protocol").RpcMethodMap["workspace.git.diff"]["result"]>;
16
+ /** @param {import('@anhlp/alp-protocol').RpcMethodMap['project.list']['params']} [params] @returns {Promise<import('@anhlp/alp-protocol').RpcMethodMap['project.list']['result']>} */
17
+ projects: (params?: import("@anhlp/alp-protocol").RpcMethodMap["project.list"]["params"]) => Promise<import("@anhlp/alp-protocol").RpcMethodMap["project.list"]["result"]>;
18
+ /** @param {import('@anhlp/alp-protocol').RpcMethodMap['workspace.list']['params']} [params] @returns {Promise<import('@anhlp/alp-protocol').RpcMethodMap['workspace.list']['result']>} */
19
+ workspaces: (params?: import("@anhlp/alp-protocol").RpcMethodMap["workspace.list"]["params"]) => Promise<import("@anhlp/alp-protocol").RpcMethodMap["workspace.list"]["result"]>;
20
+ /** @param {string} workspaceId @returns {Promise<import('@anhlp/alp-protocol').RpcMethodMap['workspace.get']['result']>} */
21
+ get: (workspaceId: string) => Promise<import("@anhlp/alp-protocol").RpcMethodMap["workspace.get"]["result"]>;
22
+ /** @param {import('@anhlp/alp-protocol').RpcMethodMap['project.register']['params']} params @returns {Promise<import('@anhlp/alp-protocol').RpcMethodMap['project.register']['result']>} */
23
+ register: (params: import("@anhlp/alp-protocol").RpcMethodMap["project.register"]["params"]) => Promise<import("@anhlp/alp-protocol").RpcMethodMap["project.register"]["result"]>;
24
+ /** @param {import('@anhlp/alp-protocol').RpcMethodMap['project.remove']['params']} params @returns {Promise<import('@anhlp/alp-protocol').RpcMethodMap['project.remove']['result']>} */
25
+ remove: (params: import("@anhlp/alp-protocol").RpcMethodMap["project.remove"]["params"]) => Promise<import("@anhlp/alp-protocol").RpcMethodMap["project.remove"]["result"]>;
26
+ /** @param {import('@anhlp/alp-protocol').RpcMethodMap['workspace.update']['params']} params @returns {Promise<import('@anhlp/alp-protocol').RpcMethodMap['workspace.update']['result']>} */
27
+ update: (params: import("@anhlp/alp-protocol").RpcMethodMap["workspace.update"]["params"]) => Promise<import("@anhlp/alp-protocol").RpcMethodMap["workspace.update"]["result"]>;
28
+ };
29
+ /** Canonical base64 of `bytes`, without Buffer or btoa, so it runs in browsers and Node alike. @param {Uint8Array} bytes */
30
+ export function base64Encode(bytes: Uint8Array): string;
31
+ /** Lowercase hex SHA-256 of `bytes` through WebCrypto (browsers' secure contexts, Node 19+). @param {Uint8Array} bytes */
32
+ export function sha256Hex(bytes: Uint8Array): Promise<string>;
33
+ /**
34
+ * Typed attachment methods (capability `attachments`, docs/protocol/alp-json-rpc-v1.md §12.6) and a
35
+ * chunked upload that keeps to the limits the daemon (or gateway) advertised. Mutations are sent
36
+ * once: `upload` resumes an interrupted upload by repeating its begin with the same requestId and
37
+ * continues from what the daemon holds.
38
+ * @param {{request<T = any>(method: string, params?: unknown): Promise<T>}} client
39
+ */
40
+ export function attachments(client: {
41
+ request<T = any>(method: string, params?: unknown): Promise<T>;
42
+ }): {
43
+ /** @param {Map['attachment.begin']['params']} params @returns {Promise<Map['attachment.begin']['result']>} */
44
+ begin: (params: import("@anhlp/alp-protocol").PrincipalContext & {
45
+ sessionId: string;
46
+ name: string;
47
+ size: number;
48
+ mimeType: string;
49
+ sha256?: string;
50
+ requestId: string;
51
+ }) => Promise<{
52
+ uploadId: string;
53
+ state: "uploading" | "committed";
54
+ received: number;
55
+ size: number;
56
+ maxChunkBytes: number;
57
+ expiresAt?: string;
58
+ attachmentId?: string;
59
+ }>;
60
+ /** @param {Map['attachment.chunk']['params']} params @returns {Promise<Map['attachment.chunk']['result']>} */
61
+ chunk: (params: import("@anhlp/alp-protocol").PrincipalContext & {
62
+ uploadId: string;
63
+ offset: number;
64
+ data: string;
65
+ }) => Promise<{
66
+ received: number;
67
+ size: number;
68
+ }>;
69
+ /** @param {Map['attachment.commit']['params']} params @returns {Promise<Map['attachment.commit']['result']>} */
70
+ commit: (params: import("@anhlp/alp-protocol").PrincipalContext & {
71
+ uploadId: string;
72
+ sha256: string;
73
+ }) => Promise<{
74
+ attachment: import("@anhlp/alp-protocol").AttachmentInfo;
75
+ }>;
76
+ /** @param {string} uploadId @returns {Promise<Map['attachment.abort']['result']>} */
77
+ abort: (uploadId: string) => Promise<{
78
+ aborted: true;
79
+ }>;
80
+ /** Metadata only (no bytes, no path). @param {string} attachmentId @returns {Promise<Map['attachment.get']['result']>} */
81
+ get: (attachmentId: string) => Promise<{
82
+ attachment: import("@anhlp/alp-protocol").AttachmentInfo;
83
+ }>;
84
+ /** Refused (1004 attachment_referenced) while a prompt references it. @param {string} attachmentId @returns {Promise<Map['attachment.delete']['result']>} */
85
+ delete: (attachmentId: string) => Promise<{
86
+ deleted: true;
87
+ }>;
88
+ /**
89
+ * Uploads `data` and commits it: begin (or resume, with the same requestId), chunks of at most
90
+ * the smaller of `maxChunkBytes` and what begin answered, commit with its SHA-256. A chunk
91
+ * refused `offset_mismatch` continues from the daemon's `received`. Returns the attachment.
92
+ * @param {{ sessionId: string, name: string, mimeType: string, data: Uint8Array, requestId: string, sha256?: string, maxChunkBytes?: number, onProgress?: (received: number, size: number) => void }} options
93
+ */
94
+ upload({ sessionId, name, mimeType, data, requestId, sha256, maxChunkBytes, onProgress }: {
95
+ sessionId: string;
96
+ name: string;
97
+ mimeType: string;
98
+ data: Uint8Array;
99
+ requestId: string;
100
+ sha256?: string;
101
+ maxChunkBytes?: number;
102
+ onProgress?: (received: number, size: number) => void;
103
+ }): Promise<import("@anhlp/alp-protocol").AttachmentInfo>;
104
+ };
105
+ /**
106
+ * Typed conversation methods (capabilities `session.fork` and `session.rewind`, docs/protocol/
107
+ * alp-json-rpc-v1.md §12.7; `session.reload` and `session.resumeCommand`, §12.8). Both change the conversation only, never a file. Mutations are sent
108
+ * once: keep `requestId` (and `ifGeneration`) to repeat the identical request after a timeout, which
109
+ * answers the first result with `duplicate: true`. After a rewind, attached clients get a
110
+ * `history.reset` event: drop cached history, fetch a new tail and attach with its generation.
111
+ * @param {{request<T = any>(method: string, params?: unknown): Promise<T>}} client
112
+ */
113
+ export function conversation(client: {
114
+ request<T = any>(method: string, params?: unknown): Promise<T>;
115
+ }): {
116
+ /**
117
+ * A new closed root whose conversation is the source root's through or before a turn (`boundary`),
118
+ * or a branch a rewind set aside (`branchId`); resume it with session.create { sessionId, resume: true }.
119
+ * Principal fields are set by a trusted gateway on its own connection, never by a remote client.
120
+ * @param {Map['session.fork']['params']} params @returns {Promise<Map['session.fork']['result']>}
121
+ */
122
+ fork: (params: import("@anhlp/alp-protocol").PrincipalContext & {
123
+ sessionId: string;
124
+ requestId: string;
125
+ ifGeneration: string;
126
+ boundary?: import("@anhlp/alp-protocol").ForkBoundary;
127
+ branchId?: string;
128
+ workspaceId?: string;
129
+ newSessionId?: string;
130
+ title?: string;
131
+ }) => Promise<{
132
+ session: import("@anhlp/alp-protocol").SessionSummary;
133
+ forkedFrom: import("@anhlp/alp-protocol").ForkProvenance;
134
+ duplicate?: true;
135
+ }>;
136
+ /**
137
+ * Continues the root's conversation from before `beforeTurnId` under the same session id; the
138
+ * later turns stay a branch (`branch.branchId`) that `fork({ branchId })` recovers.
139
+ * @param {Map['session.rewind']['params']} params @returns {Promise<Map['session.rewind']['result']>}
140
+ */
141
+ rewind: (params: {
142
+ sessionId: string;
143
+ requestId: string;
144
+ ifGeneration: string;
145
+ beforeTurnId: string;
146
+ }) => Promise<{
147
+ session: import("@anhlp/alp-protocol").SessionSummary;
148
+ generation: string;
149
+ branch: import("@anhlp/alp-protocol").ConversationBranch;
150
+ duplicate?: true;
151
+ }>;
152
+ /**
153
+ * Restarts a live root's native process on its own conversation and configuration at the idle
154
+ * barrier (capability `session.reload`, §12.8); id, history and receipts stay. Sent once: repeat
155
+ * the same `requestId` after a lost answer.
156
+ * @param {Map['session.reload']['params']} params @returns {Promise<Map['session.reload']['result']>}
157
+ */
158
+ reload: (params: {
159
+ sessionId: string;
160
+ requestId: string;
161
+ ifGeneration: string;
162
+ }) => Promise<{
163
+ session: import("@anhlp/alp-protocol").SessionSummary;
164
+ duplicate?: true;
165
+ }>;
166
+ /**
167
+ * The ALP CLI command that follows the session in a terminal (capability `session.resumeCommand`,
168
+ * §12.8), built by alpd: for the clipboard only, never to run.
169
+ * @param {Map['session.resumeCommand']['params']} params @returns {Promise<Map['session.resumeCommand']['result']>}
170
+ */
171
+ resumeCommand: (params: {
172
+ sessionId: string;
173
+ }) => Promise<import("@anhlp/alp-protocol").ResumeCommand>;
174
+ /**
175
+ * Changes a live root's model, effort or team (capability `session.configure.live`, §12.9) at the
176
+ * idle barrier, through a restart of its native process on the same conversation; `configured` says
177
+ * what it runs with now. Sent once: repeat the same `requestId` after a lost answer. A change to
178
+ * another runtime is refused (1005 runtime_mismatch, `data.action: 'session.create'`).
179
+ * @param {Map['session.configure']['params'] & { requestId: string; ifGeneration: string }} params @returns {Promise<Map['session.configure']['result']>}
180
+ */
181
+ configure: (params: {
182
+ sessionId: string;
183
+ mode?: string;
184
+ model?: string;
185
+ thinking?: string;
186
+ team?: string;
187
+ requestId?: string;
188
+ ifGeneration?: string;
189
+ } & {
190
+ requestId: string;
191
+ ifGeneration: string;
192
+ }) => Promise<{
193
+ session: import("@anhlp/alp-protocol").SessionSnapshot | import("@anhlp/alp-protocol").SessionSummary;
194
+ configured?: import("@anhlp/alp-protocol").ConfigureOutcome;
195
+ duplicate?: true;
196
+ }>;
197
+ /**
198
+ * What a change of model, effort, team or mode would run a live root with, and whether it would be
199
+ * taken now (capability `session.configure.live`, §12.9). Reads only.
200
+ * @param {Map['session.configure.preview']['params']} params @returns {Promise<Map['session.configure.preview']['result']>}
201
+ */
202
+ configurePreview: (params: {
203
+ sessionId: string;
204
+ mode?: string;
205
+ model?: string;
206
+ thinking?: string;
207
+ team?: string;
208
+ }) => Promise<{
209
+ preview: import("@anhlp/alp-protocol").ConfigurePreview;
210
+ }>;
211
+ };