@termlink/cli 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 (147) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +84 -0
  3. package/dist/auth/login.d.ts +55 -0
  4. package/dist/auth/login.d.ts.map +1 -0
  5. package/dist/auth/login.js +238 -0
  6. package/dist/auth/login.js.map +1 -0
  7. package/dist/auth/oauth-defaults.d.ts +9 -0
  8. package/dist/auth/oauth-defaults.d.ts.map +1 -0
  9. package/dist/auth/oauth-defaults.js +9 -0
  10. package/dist/auth/oauth-defaults.js.map +1 -0
  11. package/dist/cli.d.ts +3 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +319 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/errors.d.ts +8 -0
  16. package/dist/errors.d.ts.map +1 -0
  17. package/dist/errors.js +13 -0
  18. package/dist/errors.js.map +1 -0
  19. package/dist/index.d.ts +20 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +19 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/providers/claude/history.d.ts +15 -0
  24. package/dist/providers/claude/history.d.ts.map +1 -0
  25. package/dist/providers/claude/history.js +71 -0
  26. package/dist/providers/claude/history.js.map +1 -0
  27. package/dist/providers/claude/input-queue.d.ts +8 -0
  28. package/dist/providers/claude/input-queue.d.ts.map +1 -0
  29. package/dist/providers/claude/input-queue.js +34 -0
  30. package/dist/providers/claude/input-queue.js.map +1 -0
  31. package/dist/providers/claude/mapper.d.ts +21 -0
  32. package/dist/providers/claude/mapper.d.ts.map +1 -0
  33. package/dist/providers/claude/mapper.js +136 -0
  34. package/dist/providers/claude/mapper.js.map +1 -0
  35. package/dist/providers/claude/provider.d.ts +46 -0
  36. package/dist/providers/claude/provider.d.ts.map +1 -0
  37. package/dist/providers/claude/provider.js +148 -0
  38. package/dist/providers/claude/provider.js.map +1 -0
  39. package/dist/providers/claude/session.d.ts +27 -0
  40. package/dist/providers/claude/session.d.ts.map +1 -0
  41. package/dist/providers/claude/session.js +353 -0
  42. package/dist/providers/claude/session.js.map +1 -0
  43. package/dist/providers/codex/app-server.d.ts +24 -0
  44. package/dist/providers/codex/app-server.d.ts.map +1 -0
  45. package/dist/providers/codex/app-server.js +79 -0
  46. package/dist/providers/codex/app-server.js.map +1 -0
  47. package/dist/providers/codex/command.d.ts +12 -0
  48. package/dist/providers/codex/command.d.ts.map +1 -0
  49. package/dist/providers/codex/command.js +60 -0
  50. package/dist/providers/codex/command.js.map +1 -0
  51. package/dist/providers/codex/history.d.ts +5 -0
  52. package/dist/providers/codex/history.d.ts.map +1 -0
  53. package/dist/providers/codex/history.js +33 -0
  54. package/dist/providers/codex/history.js.map +1 -0
  55. package/dist/providers/codex/mapper.d.ts +13 -0
  56. package/dist/providers/codex/mapper.d.ts.map +1 -0
  57. package/dist/providers/codex/mapper.js +83 -0
  58. package/dist/providers/codex/mapper.js.map +1 -0
  59. package/dist/providers/codex/protocol.d.ts +223 -0
  60. package/dist/providers/codex/protocol.d.ts.map +1 -0
  61. package/dist/providers/codex/protocol.js +11 -0
  62. package/dist/providers/codex/protocol.js.map +1 -0
  63. package/dist/providers/codex/provider.d.ts +28 -0
  64. package/dist/providers/codex/provider.d.ts.map +1 -0
  65. package/dist/providers/codex/provider.js +116 -0
  66. package/dist/providers/codex/provider.js.map +1 -0
  67. package/dist/providers/codex/rpc.d.ts +23 -0
  68. package/dist/providers/codex/rpc.d.ts.map +1 -0
  69. package/dist/providers/codex/rpc.js +104 -0
  70. package/dist/providers/codex/rpc.js.map +1 -0
  71. package/dist/providers/codex/session.d.ts +16 -0
  72. package/dist/providers/codex/session.d.ts.map +1 -0
  73. package/dist/providers/codex/session.js +262 -0
  74. package/dist/providers/codex/session.js.map +1 -0
  75. package/dist/providers/display.d.ts +9 -0
  76. package/dist/providers/display.d.ts.map +1 -0
  77. package/dist/providers/display.js +21 -0
  78. package/dist/providers/display.js.map +1 -0
  79. package/dist/providers/terminal.d.ts +24 -0
  80. package/dist/providers/terminal.d.ts.map +1 -0
  81. package/dist/providers/terminal.js +33 -0
  82. package/dist/providers/terminal.js.map +1 -0
  83. package/dist/providers/types.d.ts +72 -0
  84. package/dist/providers/types.d.ts.map +1 -0
  85. package/dist/providers/types.js +2 -0
  86. package/dist/providers/types.js.map +1 -0
  87. package/dist/relay/api.d.ts +13 -0
  88. package/dist/relay/api.d.ts.map +1 -0
  89. package/dist/relay/api.js +75 -0
  90. package/dist/relay/api.js.map +1 -0
  91. package/dist/relay/device.d.ts +26 -0
  92. package/dist/relay/device.d.ts.map +1 -0
  93. package/dist/relay/device.js +83 -0
  94. package/dist/relay/device.js.map +1 -0
  95. package/dist/relay/relay-host.d.ts +41 -0
  96. package/dist/relay/relay-host.d.ts.map +1 -0
  97. package/dist/relay/relay-host.js +238 -0
  98. package/dist/relay/relay-host.js.map +1 -0
  99. package/dist/server/coalesce.d.ts +15 -0
  100. package/dist/server/coalesce.d.ts.map +1 -0
  101. package/dist/server/coalesce.js +50 -0
  102. package/dist/server/coalesce.js.map +1 -0
  103. package/dist/server/connection.d.ts +30 -0
  104. package/dist/server/connection.d.ts.map +1 -0
  105. package/dist/server/connection.js +182 -0
  106. package/dist/server/connection.js.map +1 -0
  107. package/dist/server/local-server.d.ts +27 -0
  108. package/dist/server/local-server.d.ts.map +1 -0
  109. package/dist/server/local-server.js +108 -0
  110. package/dist/server/local-server.js.map +1 -0
  111. package/dist/session/event-log.d.ts +17 -0
  112. package/dist/session/event-log.d.ts.map +1 -0
  113. package/dist/session/event-log.js +21 -0
  114. package/dist/session/event-log.js.map +1 -0
  115. package/dist/session/manager.d.ts +62 -0
  116. package/dist/session/manager.d.ts.map +1 -0
  117. package/dist/session/manager.js +282 -0
  118. package/dist/session/manager.js.map +1 -0
  119. package/dist/session/session.d.ts +43 -0
  120. package/dist/session/session.d.ts.map +1 -0
  121. package/dist/session/session.js +310 -0
  122. package/dist/session/session.js.map +1 -0
  123. package/dist/session/store.d.ts +22 -0
  124. package/dist/session/store.d.ts.map +1 -0
  125. package/dist/session/store.js +44 -0
  126. package/dist/session/store.js.map +1 -0
  127. package/dist/session/terminal-session.d.ts +52 -0
  128. package/dist/session/terminal-session.d.ts.map +1 -0
  129. package/dist/session/terminal-session.js +210 -0
  130. package/dist/session/terminal-session.js.map +1 -0
  131. package/dist/util/id.d.ts +2 -0
  132. package/dist/util/id.d.ts.map +1 -0
  133. package/dist/util/id.js +5 -0
  134. package/dist/util/id.js.map +1 -0
  135. package/dist/util/lock.d.ts +12 -0
  136. package/dist/util/lock.d.ts.map +1 -0
  137. package/dist/util/lock.js +52 -0
  138. package/dist/util/lock.js.map +1 -0
  139. package/dist/util/which.d.ts +3 -0
  140. package/dist/util/which.d.ts.map +1 -0
  141. package/dist/util/which.js +12 -0
  142. package/dist/util/which.js.map +1 -0
  143. package/dist/version.d.ts +2 -0
  144. package/dist/version.d.ts.map +1 -0
  145. package/dist/version.js +5 -0
  146. package/dist/version.js.map +1 -0
  147. package/package.json +56 -0
@@ -0,0 +1,182 @@
1
+ import { Command, PROTOCOL_VERSION, TERMINAL_FRAME_INPUT, decodeTerminalFrame, } from "@termlink/protocol";
2
+ import { HostError, errorMessage } from "../errors.js";
3
+ import { TerminalSession } from "../session/terminal-session.js";
4
+ /**
5
+ * One client's view of the host, independent of transport. The local WebSocket
6
+ * server and the relay link both use it.
7
+ */
8
+ export class ClientConnection {
9
+ #manager;
10
+ #send;
11
+ #sendBinary;
12
+ #hostInfo;
13
+ #attachments = new Map();
14
+ #unsubscribeHost = null;
15
+ #disposed = false;
16
+ constructor(manager, transport, hostInfo) {
17
+ this.#manager = manager;
18
+ const t = typeof transport === "function" ? { send: transport } : transport;
19
+ this.#send = t.send;
20
+ this.#sendBinary = t.sendBinary ?? (() => { });
21
+ this.#hostInfo = hostInfo;
22
+ }
23
+ async open() {
24
+ this.#unsubscribeHost = this.#manager.onHostEvent((event) => {
25
+ if (event.type === "session.closed" && event.sessionId)
26
+ this.#attachments.delete(event.sessionId);
27
+ this.#send(event);
28
+ });
29
+ await this.announce();
30
+ }
31
+ /** Sends host.ready. The relay transport repeats it when a new client joins. */
32
+ async announce() {
33
+ const payload = await this.#hostInfo();
34
+ if (this.#disposed)
35
+ return;
36
+ this.#send({ v: PROTOCOL_VERSION, kind: "evt", type: "host.ready", ts: Date.now(), payload });
37
+ }
38
+ async handle(raw) {
39
+ let data;
40
+ try {
41
+ data = JSON.parse(raw);
42
+ }
43
+ catch {
44
+ this.#fail("", "bad_request", "message is not valid JSON");
45
+ return;
46
+ }
47
+ await this.handleMessage(data);
48
+ }
49
+ /** Handles an already parsed message, such as one reassembled from chunks. */
50
+ async handleMessage(data) {
51
+ const parsed = Command.safeParse(data);
52
+ if (!parsed.success) {
53
+ const issues = parsed.error.issues.slice(0, 3).map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`);
54
+ this.#fail(reqIdOf(data), "bad_request", issues.join("; "));
55
+ return;
56
+ }
57
+ try {
58
+ await this.#dispatch(parsed.data);
59
+ }
60
+ catch (err) {
61
+ if (err instanceof HostError)
62
+ this.#fail(parsed.data.reqId, err.code, err.message);
63
+ else
64
+ this.#fail(parsed.data.reqId, "internal", errorMessage(err));
65
+ }
66
+ }
67
+ /**
68
+ * Terminal input from a binary frame. Only for sessions this connection is attached
69
+ * to; anything else is dropped without a reply, since frames carry no reqId.
70
+ */
71
+ handleBinary(data) {
72
+ const frame = decodeTerminalFrame(data);
73
+ if (!frame || frame.kind !== TERMINAL_FRAME_INPUT || !this.#attachments.has(frame.sessionId))
74
+ return;
75
+ const session = this.#manager.get(frame.sessionId);
76
+ if (session instanceof TerminalSession && !session.closed)
77
+ session.write(frame.payload);
78
+ }
79
+ dispose() {
80
+ this.#disposed = true;
81
+ for (const detach of this.#attachments.values())
82
+ detach();
83
+ this.#attachments.clear();
84
+ this.#unsubscribeHost?.();
85
+ }
86
+ // Each case replies itself. Attaching, replying and replaying happen in one
87
+ // synchronous block so no live event can slip in between them.
88
+ async #dispatch(command) {
89
+ const { reqId } = command;
90
+ switch (command.type) {
91
+ case "host.info":
92
+ return this.#ok(reqId, command.type, await this.#hostInfo());
93
+ case "session.list":
94
+ return this.#ok(reqId, command.type, { sessions: this.#manager.list() });
95
+ case "session.create": {
96
+ const session = await this.#manager.create(command.payload);
97
+ if (session instanceof TerminalSession) {
98
+ const attachment = this.#subscribeTerminal(session, command.payload.clientId);
99
+ this.#ok(reqId, command.type, { session: session.info });
100
+ return attachment.catchUp();
101
+ }
102
+ const { replay } = this.#subscribeAgent(session, 0);
103
+ this.#ok(reqId, command.type, { session: session.info });
104
+ return replay.forEach(this.#send);
105
+ }
106
+ case "session.attach": {
107
+ const session = this.#manager.get(command.payload.sessionId);
108
+ if (session instanceof TerminalSession) {
109
+ const attachment = this.#subscribeTerminal(session, command.payload.clientId);
110
+ this.#ok(reqId, command.type, { session: session.info, gap: false, oldestSeq: 0, replayed: 0 });
111
+ return attachment.catchUp();
112
+ }
113
+ const { replay, gap, oldestSeq } = this.#subscribeAgent(session, command.payload.afterSeq ?? 0);
114
+ this.#ok(reqId, command.type, { session: session.info, gap, oldestSeq, replayed: replay.length });
115
+ return replay.forEach(this.#send);
116
+ }
117
+ case "session.detach":
118
+ this.#detach(command.payload.sessionId);
119
+ return this.#ok(reqId, command.type, {});
120
+ case "session.send":
121
+ await this.#manager.agent(command.payload.sessionId).send(command.payload.input);
122
+ return this.#ok(reqId, command.type, {});
123
+ case "session.interrupt":
124
+ await this.#manager.agent(command.payload.sessionId).interrupt();
125
+ return this.#ok(reqId, command.type, {});
126
+ case "session.close":
127
+ await this.#manager.close(command.payload.sessionId, "closed by client");
128
+ return this.#ok(reqId, command.type, {});
129
+ case "session.configure": {
130
+ const { sessionId, ...settings } = command.payload;
131
+ const session = this.#manager.agent(sessionId);
132
+ session.configure(settings);
133
+ return this.#ok(reqId, command.type, { session: session.info });
134
+ }
135
+ case "input.respond": {
136
+ const { sessionId, requestId, decisionId, answers } = command.payload;
137
+ this.#manager.agent(sessionId).respond(requestId, decisionId, answers);
138
+ return this.#ok(reqId, command.type, {});
139
+ }
140
+ case "terminal.resize": {
141
+ const session = this.#manager.terminal(command.payload.sessionId);
142
+ session.resize(command.payload.cols, command.payload.rows);
143
+ return this.#ok(reqId, command.type, { session: session.info });
144
+ }
145
+ case "terminal.ack":
146
+ this.#manager.terminal(command.payload.sessionId).ack(command.payload.clientId, command.payload.bytes);
147
+ return this.#ok(reqId, command.type, {});
148
+ case "terminal.kill":
149
+ this.#manager.terminal(command.payload.sessionId).kill();
150
+ return this.#ok(reqId, command.type, {});
151
+ }
152
+ }
153
+ #subscribeAgent(session, afterSeq) {
154
+ this.#detach(session.id);
155
+ const attachment = session.attach(this.#send, afterSeq);
156
+ this.#attachments.set(session.id, attachment.detach);
157
+ return attachment;
158
+ }
159
+ #subscribeTerminal(session, clientId) {
160
+ this.#detach(session.id);
161
+ const attachment = session.attach({ event: this.#send, bytes: this.#sendBinary }, clientId);
162
+ this.#attachments.set(session.id, attachment.detach);
163
+ return attachment;
164
+ }
165
+ #detach(sessionId) {
166
+ this.#attachments.get(sessionId)?.();
167
+ this.#attachments.delete(sessionId);
168
+ }
169
+ #ok(reqId, _type, result) {
170
+ this.#send({ v: PROTOCOL_VERSION, kind: "res", reqId, ok: true, result });
171
+ }
172
+ #fail(reqId, code, message) {
173
+ this.#send({ v: PROTOCOL_VERSION, kind: "res", reqId, ok: false, error: { code, message } });
174
+ }
175
+ }
176
+ function reqIdOf(data) {
177
+ if (typeof data === "object" && data !== null && "reqId" in data && typeof data.reqId === "string") {
178
+ return data.reqId.slice(0, 64);
179
+ }
180
+ return "";
181
+ }
182
+ //# sourceMappingURL=connection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/server/connection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import { type HostInfo } from "@termlink/protocol";
2
+ import { type RawData } from "ws";
3
+ import type { SessionManager } from "../session/manager.js";
4
+ export interface LocalServerOptions {
5
+ manager: SessionManager;
6
+ hostInfo: () => Promise<HostInfo>;
7
+ token: string;
8
+ host?: string;
9
+ /** 0 picks a free port. */
10
+ port?: number;
11
+ /** A client that falls this far behind is disconnected rather than buffered without bound. */
12
+ maxBufferedBytes?: number;
13
+ }
14
+ export interface LocalServer {
15
+ url: string;
16
+ port: number;
17
+ close(): Promise<void>;
18
+ }
19
+ /**
20
+ * Direct WebSocket endpoint for development and for clients on the same machine.
21
+ * Clients authenticate with ?token=. There is no TLS, so keep it on loopback.
22
+ * Accepts chunked messages like the relay transport, so one client works with both.
23
+ */
24
+ export declare function startLocalServer(options: LocalServerOptions): Promise<LocalServer>;
25
+ /** ws hands binary messages over as a Buffer, an array of them, or an ArrayBuffer. */
26
+ export declare function toBytes(data: RawData): Uint8Array;
27
+ //# sourceMappingURL=local-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-server.d.ts","sourceRoot":"","sources":["../../src/server/local-server.ts"],"names":[],"mappings":"AAGA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAmB,KAAK,OAAO,EAAE,MAAM,IAAI,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAG5D,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,cAAc,CAAC;IACxB,QAAQ,EAAE,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8FAA8F;IAC9F,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,WAAW,CAAC,CAiFxF;AAED,sFAAsF;AACtF,wBAAgB,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,UAAU,CAIjD"}
@@ -0,0 +1,108 @@
1
+ import { timingSafeEqual } from "node:crypto";
2
+ import { createServer } from "node:http";
3
+ import { FrameAssembler } from "@termlink/protocol";
4
+ import { WebSocketServer } from "ws";
5
+ import { ClientConnection } from "./connection.js";
6
+ /**
7
+ * Direct WebSocket endpoint for development and for clients on the same machine.
8
+ * Clients authenticate with ?token=. There is no TLS, so keep it on loopback.
9
+ * Accepts chunked messages like the relay transport, so one client works with both.
10
+ */
11
+ export async function startLocalServer(options) {
12
+ const host = options.host ?? "127.0.0.1";
13
+ const maxBufferedBytes = options.maxBufferedBytes ?? 8 * 1024 * 1024;
14
+ const http = createServer((req, res) => {
15
+ if (req.url === "/healthz") {
16
+ res.writeHead(200, { "content-type": "text/plain" }).end("ok");
17
+ return;
18
+ }
19
+ res.writeHead(404).end();
20
+ });
21
+ const wss = new WebSocketServer({ noServer: true, maxPayload: 1024 * 1024 });
22
+ http.on("upgrade", (req, socket, head) => {
23
+ const url = new URL(req.url ?? "/", "http://localhost");
24
+ if (url.pathname !== "/ws" || !tokenMatches(url.searchParams.get("token"), options.token)) {
25
+ socket.end("HTTP/1.1 401 Unauthorized\r\nConnection: close\r\nContent-Length: 0\r\n\r\n");
26
+ return;
27
+ }
28
+ wss.handleUpgrade(req, socket, head, (ws) => {
29
+ const guard = () => {
30
+ if (ws.readyState !== ws.OPEN)
31
+ return false;
32
+ if (ws.bufferedAmount > maxBufferedBytes) {
33
+ ws.close(1013, "client is not keeping up");
34
+ return false;
35
+ }
36
+ return true;
37
+ };
38
+ const connection = new ClientConnection(options.manager, {
39
+ send: (message) => {
40
+ if (guard())
41
+ ws.send(JSON.stringify(message));
42
+ },
43
+ sendBinary: (frame) => {
44
+ if (guard())
45
+ ws.send(frame, { binary: true });
46
+ },
47
+ }, options.hostInfo);
48
+ const assembler = new FrameAssembler();
49
+ ws.on("message", (data, isBinary) => {
50
+ if (isBinary) {
51
+ connection.handleBinary(toBytes(data));
52
+ return;
53
+ }
54
+ const text = data.toString();
55
+ let message;
56
+ try {
57
+ message = assembler.push(text);
58
+ }
59
+ catch (err) {
60
+ // Not JSON: let the connection answer bad_request. Too large: drop the client.
61
+ if (err instanceof SyntaxError)
62
+ void connection.handle(text);
63
+ else
64
+ ws.close(1009, "message too large");
65
+ return;
66
+ }
67
+ if (message !== undefined)
68
+ void connection.handleMessage(message);
69
+ });
70
+ ws.on("close", () => connection.dispose());
71
+ void connection.open();
72
+ });
73
+ });
74
+ await new Promise((resolve, reject) => {
75
+ http.once("error", reject);
76
+ http.listen(options.port ?? 7420, host, () => {
77
+ http.off("error", reject);
78
+ resolve();
79
+ });
80
+ });
81
+ const { port } = http.address();
82
+ return {
83
+ url: `ws://${host}:${port}/ws`,
84
+ port,
85
+ close: async () => {
86
+ for (const client of wss.clients)
87
+ client.terminate();
88
+ wss.close();
89
+ await new Promise((resolve) => http.close(() => resolve()));
90
+ },
91
+ };
92
+ }
93
+ /** ws hands binary messages over as a Buffer, an array of them, or an ArrayBuffer. */
94
+ export function toBytes(data) {
95
+ if (Array.isArray(data))
96
+ return Buffer.concat(data);
97
+ if (data instanceof ArrayBuffer)
98
+ return new Uint8Array(data);
99
+ return data;
100
+ }
101
+ function tokenMatches(given, expected) {
102
+ if (given === null)
103
+ return false;
104
+ const a = Buffer.from(given);
105
+ const b = Buffer.from(expected);
106
+ return a.length === b.length && timingSafeEqual(a, b);
107
+ }
108
+ //# sourceMappingURL=local-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-server.js","sourceRoot":"","sources":["../../src/server/local-server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { AgentEvent } from "@termlink/protocol";
2
+ export interface LogSlice {
3
+ events: AgentEvent[];
4
+ /** True when events after the requested seq have already been evicted. */
5
+ gap: boolean;
6
+ /** Seq of the oldest event still held, or 0 when the log is empty. */
7
+ oldestSeq: number;
8
+ }
9
+ /** Bounded buffer of one session's durable events, used to replay after a reconnect. */
10
+ export declare class EventLog {
11
+ #private;
12
+ readonly capacity: number;
13
+ constructor(capacity?: number);
14
+ append(event: AgentEvent): void;
15
+ since(afterSeq: number): LogSlice;
16
+ }
17
+ //# sourceMappingURL=event-log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-log.d.ts","sourceRoot":"","sources":["../../src/session/event-log.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,0EAA0E;IAC1E,GAAG,EAAE,OAAO,CAAC;IACb,sEAAsE;IACtE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wFAAwF;AACxF,qBAAa,QAAQ;;IACnB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAG1B,YAAY,QAAQ,SAAO,EAE1B;IAED,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAI9B;IAED,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAIhC;CACF"}
@@ -0,0 +1,21 @@
1
+ /** Bounded buffer of one session's durable events, used to replay after a reconnect. */
2
+ export class EventLog {
3
+ capacity;
4
+ #events = [];
5
+ constructor(capacity = 2000) {
6
+ this.capacity = capacity;
7
+ }
8
+ append(event) {
9
+ if (event.seq === undefined)
10
+ throw new Error(`event ${event.type} has no seq`);
11
+ this.#events.push(event);
12
+ if (this.#events.length > this.capacity)
13
+ this.#events.shift();
14
+ }
15
+ since(afterSeq) {
16
+ const oldestSeq = this.#events[0]?.seq ?? 0;
17
+ const events = this.#events.filter((e) => (e.seq ?? 0) > afterSeq);
18
+ return { events, gap: oldestSeq > 0 && afterSeq < oldestSeq - 1, oldestSeq };
19
+ }
20
+ }
21
+ //# sourceMappingURL=event-log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-log.js","sourceRoot":"","sources":["../../src/session/event-log.ts"],"names":[],"mappings":"AAUA,wFAAwF;AACxF,MAAM,OAAO,QAAQ;IACV,QAAQ,CAAS;IAC1B,OAAO,GAAiB,EAAE,CAAC;IAE3B,YAAY,QAAQ,GAAG,IAAI;QACzB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;IAED,MAAM,CAAC,KAAiB;QACtB,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,SAAS,KAAK,CAAC,IAAI,aAAa,CAAC,CAAC;QAC/E,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzB,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,QAAQ;YAAE,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IAChE,CAAC;IAED,KAAK,CAAC,QAAgB;QACpB,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;QAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC;QACnE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,GAAG,CAAC,IAAI,QAAQ,GAAG,SAAS,GAAG,CAAC,EAAE,SAAS,EAAE,CAAC;IAC/E,CAAC;CACF"}
@@ -0,0 +1,62 @@
1
+ import type { AutoApprove, ProviderId, ProviderStatus, SessionInfo } from "@termlink/protocol";
2
+ import type { HostProvider } from "../providers/types.js";
3
+ import { AgentSession, type Listener } from "./session.js";
4
+ import type { SessionStore } from "./store.js";
5
+ import { TerminalSession, type FlowControlOptions } from "./terminal-session.js";
6
+ export type HostSession = AgentSession | TerminalSession;
7
+ export interface CreateSessionOptions {
8
+ provider: ProviderId;
9
+ cwd: string;
10
+ title?: string | undefined;
11
+ /** Agent sessions. */
12
+ autoApprove?: AutoApprove | undefined;
13
+ /** Terminal sessions. */
14
+ cols?: number | undefined;
15
+ rows?: number | undefined;
16
+ }
17
+ export interface SessionManagerOptions {
18
+ providers: HostProvider[];
19
+ logCapacity?: number;
20
+ /** Folders sessions may be opened in. Unset means any folder. */
21
+ allowedRoots?: string[];
22
+ /** Remembers resumable agent sessions across host restarts. */
23
+ store?: SessionStore;
24
+ /** autoApprove for new agent sessions that do not ask for one. Defaults to off. */
25
+ autoApprove?: AutoApprove;
26
+ /** Flow control for terminal sessions. */
27
+ flow?: FlowControlOptions;
28
+ }
29
+ /** Owns all sessions on this host and fans host-channel events out to every connection. */
30
+ export declare class SessionManager {
31
+ #private;
32
+ constructor(options: SessionManagerOptions);
33
+ /** Folders sessions may be opened in, or undefined when unrestricted. */
34
+ get roots(): string[] | undefined;
35
+ onHostEvent(listener: Listener): () => void;
36
+ providerStatuses(): Promise<ProviderStatus[]>;
37
+ list(): SessionInfo[];
38
+ get(sessionId: string): HostSession;
39
+ /** The session, if it is an agent session; commands for turns and approvals need one. */
40
+ agent(sessionId: string): AgentSession;
41
+ terminal(sessionId: string): TerminalSession;
42
+ create(options: CreateSessionOptions): Promise<HostSession>;
43
+ /** create() for callers that need an agent session, such as tests. */
44
+ createAgent(options: CreateSessionOptions): Promise<AgentSession>;
45
+ /** create() for callers that need a terminal session. */
46
+ createTerminal(options: CreateSessionOptions): Promise<TerminalSession>;
47
+ /**
48
+ * Brings back the agent sessions the store remembers, with their recent history read
49
+ * from each provider's transcript. Providers resume on first use. Returns how many came back.
50
+ * Terminal sessions are never remembered: their shell died with the previous host.
51
+ */
52
+ restore(): Promise<number>;
53
+ /** Closed by a client: the session is forgotten and will not come back after a restart. */
54
+ close(sessionId: string, reason?: string | null): Promise<void>;
55
+ closeAll(reason?: string | null): Promise<void>;
56
+ /**
57
+ * Stops the host: saves resumable sessions, closes everything, then releases provider
58
+ * resources such as the Codex app-server. Unlike close(), agent sessions stay remembered.
59
+ */
60
+ shutdown(reason?: string | null): Promise<void>;
61
+ }
62
+ //# sourceMappingURL=manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/session/manager.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAc,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAE3G,OAAO,KAAK,EAAE,YAAY,EAAqC,MAAM,uBAAuB,CAAC;AAE7F,OAAO,EAAE,YAAY,EAAE,KAAK,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,KAAK,EAAiB,YAAY,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEjF,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,eAAe,CAAC;AAEzD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,UAAU,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,sBAAsB;IACtB,WAAW,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IACtC,yBAAyB;IACzB,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3B;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iEAAiE;IACjE,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,mFAAmF;IACnF,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,0CAA0C;IAC1C,IAAI,CAAC,EAAE,kBAAkB,CAAC;CAC3B;AAMD,2FAA2F;AAC3F,qBAAa,cAAc;;IAYzB,YAAY,OAAO,EAAE,qBAAqB,EAOzC;IAED,yEAAyE;IACzE,IAAI,KAAK,IAAI,MAAM,EAAE,GAAG,SAAS,CAEhC;IAED,WAAW,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,IAAI,CAG1C;IAEK,gBAAgB,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAWlD;IAED,IAAI,IAAI,WAAW,EAAE,CAEpB;IAED,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,CAIlC;IAED,yFAAyF;IACzF,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,CAIrC;IAED,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,eAAe,CAI3C;IAEK,MAAM,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,WAAW,CAAC,CAOhE;IAED,sEAAsE;IAChE,WAAW,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,YAAY,CAAC,CAOtE;IAED,yDAAyD;IACnD,cAAc,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,eAAe,CAAC,CAO5E;IAiED;;;;OAIG;IACG,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,CA0C/B;IAED,2FAA2F;IACrF,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,GAAG,IAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAK1E;IAEK,QAAQ,CAAC,MAAM,GAAE,MAAM,GAAG,IAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAE1D;IAED;;;OAGG;IACG,QAAQ,CAAC,MAAM,GAAE,MAAM,GAAG,IAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ1D;CAqDF"}