@linkshell/host 0.4.0 → 0.4.1
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.
- package/dist/host/src/drivers/claude/transcript.d.ts +68 -68
- package/dist/host/src/drivers/codex/app-server.d.ts +31 -6
- package/dist/host/src/drivers/codex/app-server.js +99 -24
- package/dist/host/src/drivers/codex/app-server.js.map +1 -1
- package/dist/host/src/drivers/codex/desktop-ipc.d.ts +8 -0
- package/dist/host/src/drivers/codex/desktop-ipc.js +124 -0
- package/dist/host/src/drivers/codex/desktop-ipc.js.map +1 -0
- package/dist/host/src/drivers/codex/driver.d.ts +111 -2
- package/dist/host/src/drivers/codex/driver.js +629 -63
- package/dist/host/src/drivers/codex/driver.js.map +1 -1
- package/dist/host/src/drivers/codex/mapper.d.ts +21 -0
- package/dist/host/src/drivers/codex/mapper.js +45 -6
- package/dist/host/src/drivers/codex/mapper.js.map +1 -1
- package/dist/host/src/drivers/types.d.ts +6 -0
- package/dist/host/src/fs.d.ts +3 -3
- package/dist/host/src/hub.js +8 -1
- package/dist/host/src/hub.js.map +1 -1
- package/dist/host/src/input.d.ts +4 -4
- package/dist/host/src/terminals.d.ts +1 -1
- package/dist/host/tsconfig.tsbuildinfo +1 -1
- package/dist/wire/src/model.d.ts +130 -130
- package/dist/wire/src/rpc.d.ts +1977 -1977
- package/dist/wire/src/updates.d.ts +545 -545
- package/package.json +2 -2
- package/dist/host/src/input-helper.d.ts +0 -1
- package/dist/host/src/input-helper.js +0 -564
- package/dist/host/src/input-helper.js.map +0 -1
|
@@ -33,7 +33,6 @@ export declare function transcriptLine(raw: string, options?: {
|
|
|
33
33
|
/** Parses a whole transcript file. */
|
|
34
34
|
/** When each transcript update happened, for history import. */
|
|
35
35
|
export declare const transcriptTimes: WeakMap<{
|
|
36
|
-
sessionUpdate: "user_message_chunk";
|
|
37
36
|
content: {
|
|
38
37
|
type: "text";
|
|
39
38
|
text: string;
|
|
@@ -44,14 +43,13 @@ export declare const transcriptTimes: WeakMap<{
|
|
|
44
43
|
uri?: string | undefined;
|
|
45
44
|
} | {
|
|
46
45
|
type: "resource_link";
|
|
47
|
-
uri: string;
|
|
48
46
|
name: string;
|
|
49
|
-
|
|
47
|
+
uri: string;
|
|
48
|
+
kind?: "agent" | "file" | "skill" | undefined;
|
|
50
49
|
};
|
|
50
|
+
sessionUpdate: "user_message_chunk";
|
|
51
51
|
messageId?: string | undefined;
|
|
52
52
|
} | {
|
|
53
|
-
sessionUpdate: "agent_message_chunk";
|
|
54
|
-
messageId: string;
|
|
55
53
|
content: {
|
|
56
54
|
type: "text";
|
|
57
55
|
text: string;
|
|
@@ -62,14 +60,14 @@ export declare const transcriptTimes: WeakMap<{
|
|
|
62
60
|
uri?: string | undefined;
|
|
63
61
|
} | {
|
|
64
62
|
type: "resource_link";
|
|
65
|
-
uri: string;
|
|
66
63
|
name: string;
|
|
67
|
-
|
|
64
|
+
uri: string;
|
|
65
|
+
kind?: "agent" | "file" | "skill" | undefined;
|
|
68
66
|
};
|
|
67
|
+
sessionUpdate: "agent_message_chunk";
|
|
68
|
+
messageId: string;
|
|
69
69
|
parentToolCallId?: string | undefined;
|
|
70
70
|
} | {
|
|
71
|
-
sessionUpdate: "agent_thought_chunk";
|
|
72
|
-
messageId: string;
|
|
73
71
|
content: {
|
|
74
72
|
type: "text";
|
|
75
73
|
text: string;
|
|
@@ -80,17 +78,49 @@ export declare const transcriptTimes: WeakMap<{
|
|
|
80
78
|
uri?: string | undefined;
|
|
81
79
|
} | {
|
|
82
80
|
type: "resource_link";
|
|
83
|
-
uri: string;
|
|
84
81
|
name: string;
|
|
85
|
-
|
|
82
|
+
uri: string;
|
|
83
|
+
kind?: "agent" | "file" | "skill" | undefined;
|
|
86
84
|
};
|
|
85
|
+
sessionUpdate: "agent_thought_chunk";
|
|
86
|
+
messageId: string;
|
|
87
87
|
parentToolCallId?: string | undefined;
|
|
88
88
|
} | {
|
|
89
|
-
|
|
90
|
-
status: "pending" | "in_progress" | "completed" | "failed";
|
|
89
|
+
status: "failed" | "pending" | "in_progress" | "completed";
|
|
91
90
|
kind: "read" | "edit" | "delete" | "move" | "search" | "execute" | "think" | "fetch" | "other";
|
|
92
|
-
toolCallId: string;
|
|
93
91
|
title: string;
|
|
92
|
+
toolCallId: string;
|
|
93
|
+
sessionUpdate: "tool_call";
|
|
94
|
+
detail?: {
|
|
95
|
+
type: "mcp";
|
|
96
|
+
tool: string;
|
|
97
|
+
server: string;
|
|
98
|
+
} | {
|
|
99
|
+
type: "subagent";
|
|
100
|
+
action: "message" | "spawn" | "wait" | "stop" | "resume" | "list";
|
|
101
|
+
task?: string | undefined;
|
|
102
|
+
agentType?: string | undefined;
|
|
103
|
+
model?: string | undefined;
|
|
104
|
+
} | {
|
|
105
|
+
type: "skill";
|
|
106
|
+
name: string;
|
|
107
|
+
description?: string | undefined;
|
|
108
|
+
} | {
|
|
109
|
+
type: "web_search";
|
|
110
|
+
query?: string | undefined;
|
|
111
|
+
} | {
|
|
112
|
+
type: "image_generation";
|
|
113
|
+
prompt?: string | undefined;
|
|
114
|
+
} | {
|
|
115
|
+
type: "compaction";
|
|
116
|
+
} | {
|
|
117
|
+
type: "review";
|
|
118
|
+
phase: "started" | "finished";
|
|
119
|
+
} | {
|
|
120
|
+
type: "question";
|
|
121
|
+
text: string;
|
|
122
|
+
more?: number | undefined;
|
|
123
|
+
} | undefined;
|
|
94
124
|
content?: ({
|
|
95
125
|
type: "content";
|
|
96
126
|
content: {
|
|
@@ -103,9 +133,9 @@ export declare const transcriptTimes: WeakMap<{
|
|
|
103
133
|
uri?: string | undefined;
|
|
104
134
|
} | {
|
|
105
135
|
type: "resource_link";
|
|
106
|
-
uri: string;
|
|
107
136
|
name: string;
|
|
108
|
-
|
|
137
|
+
uri: string;
|
|
138
|
+
kind?: "agent" | "file" | "skill" | undefined;
|
|
109
139
|
};
|
|
110
140
|
} | {
|
|
111
141
|
path: string;
|
|
@@ -128,10 +158,15 @@ export declare const transcriptTimes: WeakMap<{
|
|
|
128
158
|
path: string;
|
|
129
159
|
line?: number | undefined;
|
|
130
160
|
}[] | undefined;
|
|
161
|
+
} | {
|
|
162
|
+
toolCallId: string;
|
|
163
|
+
sessionUpdate: "tool_call_update";
|
|
164
|
+
status?: "failed" | "pending" | "in_progress" | "completed" | undefined;
|
|
165
|
+
title?: string | undefined;
|
|
131
166
|
detail?: {
|
|
132
167
|
type: "mcp";
|
|
133
|
-
server: string;
|
|
134
168
|
tool: string;
|
|
169
|
+
server: string;
|
|
135
170
|
} | {
|
|
136
171
|
type: "subagent";
|
|
137
172
|
action: "message" | "spawn" | "wait" | "stop" | "resume" | "list";
|
|
@@ -158,10 +193,6 @@ export declare const transcriptTimes: WeakMap<{
|
|
|
158
193
|
text: string;
|
|
159
194
|
more?: number | undefined;
|
|
160
195
|
} | undefined;
|
|
161
|
-
} | {
|
|
162
|
-
sessionUpdate: "tool_call_update";
|
|
163
|
-
toolCallId: string;
|
|
164
|
-
status?: "pending" | "in_progress" | "completed" | "failed" | undefined;
|
|
165
196
|
content?: ({
|
|
166
197
|
type: "content";
|
|
167
198
|
content: {
|
|
@@ -174,9 +205,9 @@ export declare const transcriptTimes: WeakMap<{
|
|
|
174
205
|
uri?: string | undefined;
|
|
175
206
|
} | {
|
|
176
207
|
type: "resource_link";
|
|
177
|
-
uri: string;
|
|
178
208
|
name: string;
|
|
179
|
-
|
|
209
|
+
uri: string;
|
|
210
|
+
kind?: "agent" | "file" | "skill" | undefined;
|
|
180
211
|
};
|
|
181
212
|
} | {
|
|
182
213
|
path: string;
|
|
@@ -194,46 +225,15 @@ export declare const transcriptTimes: WeakMap<{
|
|
|
194
225
|
terminalId: string;
|
|
195
226
|
})[] | undefined;
|
|
196
227
|
parentToolCallId?: string | undefined;
|
|
197
|
-
title?: string | undefined;
|
|
198
|
-
detail?: {
|
|
199
|
-
type: "mcp";
|
|
200
|
-
server: string;
|
|
201
|
-
tool: string;
|
|
202
|
-
} | {
|
|
203
|
-
type: "subagent";
|
|
204
|
-
action: "message" | "spawn" | "wait" | "stop" | "resume" | "list";
|
|
205
|
-
task?: string | undefined;
|
|
206
|
-
agentType?: string | undefined;
|
|
207
|
-
model?: string | undefined;
|
|
208
|
-
} | {
|
|
209
|
-
type: "skill";
|
|
210
|
-
name: string;
|
|
211
|
-
description?: string | undefined;
|
|
212
|
-
} | {
|
|
213
|
-
type: "web_search";
|
|
214
|
-
query?: string | undefined;
|
|
215
|
-
} | {
|
|
216
|
-
type: "image_generation";
|
|
217
|
-
prompt?: string | undefined;
|
|
218
|
-
} | {
|
|
219
|
-
type: "compaction";
|
|
220
|
-
} | {
|
|
221
|
-
type: "review";
|
|
222
|
-
phase: "started" | "finished";
|
|
223
|
-
} | {
|
|
224
|
-
type: "question";
|
|
225
|
-
text: string;
|
|
226
|
-
more?: number | undefined;
|
|
227
|
-
} | undefined;
|
|
228
228
|
appendOutput?: string | undefined;
|
|
229
229
|
rawOutput?: unknown;
|
|
230
230
|
} | {
|
|
231
|
-
sessionUpdate: "plan";
|
|
232
231
|
entries: {
|
|
233
232
|
status: "pending" | "in_progress" | "completed";
|
|
234
233
|
content: string;
|
|
235
234
|
priority: "high" | "medium" | "low";
|
|
236
235
|
}[];
|
|
236
|
+
sessionUpdate: "plan";
|
|
237
237
|
} | {
|
|
238
238
|
sessionUpdate: "available_commands_update";
|
|
239
239
|
availableCommands: {
|
|
@@ -258,44 +258,44 @@ export declare const transcriptTimes: WeakMap<{
|
|
|
258
258
|
role: "agent" | "thought" | "user";
|
|
259
259
|
parentToolCallId?: string | undefined;
|
|
260
260
|
} | {
|
|
261
|
-
sessionUpdate: "ls_config";
|
|
262
261
|
options: {
|
|
263
262
|
values: {
|
|
264
263
|
value: string;
|
|
265
264
|
name: string;
|
|
266
265
|
description?: string | undefined;
|
|
267
266
|
}[];
|
|
268
|
-
name: string;
|
|
269
267
|
id: string;
|
|
268
|
+
name: string;
|
|
270
269
|
category: "other" | "model" | "mode" | "effort";
|
|
271
270
|
current: string;
|
|
272
271
|
}[];
|
|
272
|
+
sessionUpdate: "ls_config";
|
|
273
273
|
} | {
|
|
274
|
-
sessionUpdate: "ls_notice";
|
|
275
274
|
title: string;
|
|
275
|
+
sessionUpdate: "ls_notice";
|
|
276
276
|
level: "info" | "warning";
|
|
277
277
|
detail?: string | undefined;
|
|
278
278
|
} | {
|
|
279
|
-
sessionUpdate: "ls_turn";
|
|
280
279
|
state: "started" | "ended";
|
|
280
|
+
sessionUpdate: "ls_turn";
|
|
281
281
|
parentToolCallId?: string | undefined;
|
|
282
282
|
turnId?: string | undefined;
|
|
283
|
-
stopReason?: "
|
|
283
|
+
stopReason?: "error" | "end_turn" | "cancelled" | "max_tokens" | "refusal" | undefined;
|
|
284
284
|
} | {
|
|
285
|
-
sessionUpdate: "ls_permission";
|
|
286
285
|
options: {
|
|
286
|
+
optionId: string;
|
|
287
287
|
name: string;
|
|
288
288
|
kind: "allow_once" | "allow_always" | "reject_once" | "reject_always";
|
|
289
|
-
optionId: string;
|
|
290
289
|
}[];
|
|
291
290
|
title: string;
|
|
292
291
|
requestId: string;
|
|
292
|
+
sessionUpdate: "ls_permission";
|
|
293
293
|
toolCallId?: string | undefined;
|
|
294
294
|
detail?: string | undefined;
|
|
295
295
|
questions?: {
|
|
296
|
-
text: string;
|
|
297
|
-
kind: "text" | "choice" | "choices";
|
|
298
296
|
id: string;
|
|
297
|
+
kind: "text" | "choice" | "choices";
|
|
298
|
+
text: string;
|
|
299
299
|
options?: {
|
|
300
300
|
value: string;
|
|
301
301
|
label: string;
|
|
@@ -307,8 +307,8 @@ export declare const transcriptTimes: WeakMap<{
|
|
|
307
307
|
required?: boolean | undefined;
|
|
308
308
|
}[] | undefined;
|
|
309
309
|
} | {
|
|
310
|
-
sessionUpdate: "ls_permission_resolved";
|
|
311
310
|
requestId: string;
|
|
311
|
+
sessionUpdate: "ls_permission_resolved";
|
|
312
312
|
optionId?: string | undefined;
|
|
313
313
|
answers?: {
|
|
314
314
|
values: string[];
|
|
@@ -316,16 +316,16 @@ export declare const transcriptTimes: WeakMap<{
|
|
|
316
316
|
other?: string | undefined;
|
|
317
317
|
}[] | undefined;
|
|
318
318
|
} | {
|
|
319
|
+
driver: "remote" | "desktop" | "none";
|
|
319
320
|
sessionUpdate: "ls_driver";
|
|
320
|
-
driver: "desktop" | "remote" | "none";
|
|
321
321
|
deviceId?: string | undefined;
|
|
322
322
|
} | {
|
|
323
|
+
state: "idle" | "running" | "waiting" | "error" | "offline";
|
|
323
324
|
sessionUpdate: "ls_status";
|
|
324
|
-
state: "error" | "idle" | "running" | "waiting" | "offline";
|
|
325
325
|
} | {
|
|
326
|
-
sessionUpdate: "ls_error";
|
|
327
326
|
code: string;
|
|
328
327
|
message: string;
|
|
328
|
+
sessionUpdate: "ls_error";
|
|
329
329
|
hint?: string | undefined;
|
|
330
330
|
}, number>;
|
|
331
331
|
/**
|
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
import { type RpcId } from "@linkshell/wire";
|
|
2
|
-
export interface
|
|
2
|
+
export interface CodexConnectionOptions {
|
|
3
3
|
/** Unix socket path; must stay under the ~104-byte sun_path limit. */
|
|
4
4
|
socketPath: string;
|
|
5
|
-
command?: string;
|
|
6
|
-
env?: NodeJS.ProcessEnv;
|
|
7
5
|
clientVersion: string;
|
|
8
|
-
log: (message: string) => void;
|
|
9
6
|
onNotification: (method: string, params: unknown) => void;
|
|
10
7
|
/** Server → client requests (approvals). Return ABANDON to leave one to other clients. */
|
|
11
8
|
onRequest: (method: string, params: unknown, id: RpcId) => unknown;
|
|
12
9
|
/** Called when the app-server process or its socket goes away unexpectedly. */
|
|
13
10
|
onDown: (reason: string) => void;
|
|
14
11
|
}
|
|
12
|
+
export interface CodexAppServerOptions extends CodexConnectionOptions {
|
|
13
|
+
command?: string;
|
|
14
|
+
env?: NodeJS.ProcessEnv;
|
|
15
|
+
log: (message: string) => void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Where Codex's own background server listens: the one a plain `codex` in a
|
|
19
|
+
* terminal starts and talks to (`codex app-server daemon`), unless it is run
|
|
20
|
+
* with `--no-daemon` or with configuration overrides.
|
|
21
|
+
*/
|
|
22
|
+
export declare function sharedServerSocket(env?: NodeJS.ProcessEnv): string;
|
|
15
23
|
export declare function detectCodex(command?: string, env?: NodeJS.ProcessEnv): Promise<string | undefined>;
|
|
16
24
|
/** The `--enable` arguments for the wanted features this Codex knows and hasn't removed. */
|
|
17
25
|
export declare function featureArgs(command?: string, env?: NodeJS.ProcessEnv): Promise<string[]>;
|
|
@@ -21,8 +29,6 @@ export declare function enableArgs(listing: string): string[];
|
|
|
21
29
|
* Owns one `codex app-server --listen unix://…` process and a client connection
|
|
22
30
|
* to it. The same socket is what `codex --remote` attaches the desktop TUI to,
|
|
23
31
|
* so the TUI and LinkShell are peers on the same threads.
|
|
24
|
-
*
|
|
25
|
-
* The socket speaks WebSocket; permessage-deflate must be off or the upgrade is reset.
|
|
26
32
|
*/
|
|
27
33
|
export declare class CodexAppServer {
|
|
28
34
|
private readonly options;
|
|
@@ -38,3 +44,22 @@ export declare class CodexAppServer {
|
|
|
38
44
|
private connectWhenReady;
|
|
39
45
|
private connect;
|
|
40
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* A client connection to Codex's own background server, which this host
|
|
49
|
+
* neither starts nor stops. A thread that lives there (opened by a plain
|
|
50
|
+
* `codex`) can't be loaded anywhere else, but any number of clients can be on
|
|
51
|
+
* it there: joining it is how the phone and that terminal share a thread.
|
|
52
|
+
*/
|
|
53
|
+
export declare class CodexSharedServer {
|
|
54
|
+
private readonly options;
|
|
55
|
+
private socket?;
|
|
56
|
+
private peer?;
|
|
57
|
+
private connecting?;
|
|
58
|
+
private closing;
|
|
59
|
+
constructor(options: CodexConnectionOptions);
|
|
60
|
+
/** Connects if that server is running; false when it isn't (it is never started from here). */
|
|
61
|
+
connect(): Promise<boolean>;
|
|
62
|
+
request<T>(method: string, params: unknown, timeoutMs?: number): Promise<T>;
|
|
63
|
+
close(): void;
|
|
64
|
+
private dropped;
|
|
65
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { spawn, execFile } from "node:child_process";
|
|
2
2
|
import { existsSync, rmSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
3
5
|
import { promisify } from "node:util";
|
|
4
6
|
import WebSocket from "ws";
|
|
5
7
|
import { ABANDON, RpcPeer } from "@linkshell/wire";
|
|
@@ -20,6 +22,51 @@ const OPT_OUT_NOTIFICATIONS = [
|
|
|
20
22
|
"thread/goal/cleared",
|
|
21
23
|
"thread/goal/updated",
|
|
22
24
|
];
|
|
25
|
+
/**
|
|
26
|
+
* Where Codex's own background server listens: the one a plain `codex` in a
|
|
27
|
+
* terminal starts and talks to (`codex app-server daemon`), unless it is run
|
|
28
|
+
* with `--no-daemon` or with configuration overrides.
|
|
29
|
+
*/
|
|
30
|
+
export function sharedServerSocket(env = process.env) {
|
|
31
|
+
return join(env.CODEX_HOME || join(homedir(), ".codex"), "app-server-control", "app-server-control.sock");
|
|
32
|
+
}
|
|
33
|
+
/** A client connection to an app-server socket. The socket speaks WebSocket; permessage-deflate must be off or the upgrade is reset. */
|
|
34
|
+
async function openConnection(options, closing) {
|
|
35
|
+
const socket = new WebSocket(`ws+unix://${options.socketPath}:/`, { perMessageDeflate: false, handshakeTimeout: 10_000 });
|
|
36
|
+
await new Promise((resolve, reject) => {
|
|
37
|
+
socket.once("open", () => resolve());
|
|
38
|
+
socket.once("error", reject);
|
|
39
|
+
});
|
|
40
|
+
const peer = new RpcPeer({
|
|
41
|
+
send: (text) => socket.send(text),
|
|
42
|
+
onNotification: (method, params) => options.onNotification(method, params),
|
|
43
|
+
onRequest: (method, params, id) => options.onRequest(method, params, id) ?? ABANDON,
|
|
44
|
+
requestTimeoutMs: 60_000,
|
|
45
|
+
});
|
|
46
|
+
socket.on("message", (data) => peer.receive(data.toString()));
|
|
47
|
+
let ready = false;
|
|
48
|
+
socket.on("close", () => {
|
|
49
|
+
peer.close("codex app-server connection closed");
|
|
50
|
+
if (ready && !closing())
|
|
51
|
+
options.onDown("codex app-server connection closed");
|
|
52
|
+
});
|
|
53
|
+
// A failure after the handshake arrives as "close" too; this only keeps it from being thrown.
|
|
54
|
+
socket.on("error", () => { });
|
|
55
|
+
try {
|
|
56
|
+
await peer.request("initialize", {
|
|
57
|
+
clientInfo: { name: "linkshell", title: "LinkShell", version: options.clientVersion },
|
|
58
|
+
// experimentalApi: plan mode (`collaborationMode`) and the questions Codex asks (`item/tool/requestUserInput`) need it.
|
|
59
|
+
capabilities: { experimentalApi: true, requestAttestation: false, optOutNotificationMethods: OPT_OUT_NOTIFICATIONS },
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
socket.close();
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
peer.notify("initialized", {});
|
|
67
|
+
ready = true;
|
|
68
|
+
return { socket, peer };
|
|
69
|
+
}
|
|
23
70
|
export async function detectCodex(command = "codex", env) {
|
|
24
71
|
try {
|
|
25
72
|
const { stdout } = await execFileAsync(command, ["--version"], { env, timeout: 10_000 });
|
|
@@ -58,8 +105,6 @@ export function enableArgs(listing) {
|
|
|
58
105
|
* Owns one `codex app-server --listen unix://…` process and a client connection
|
|
59
106
|
* to it. The same socket is what `codex --remote` attaches the desktop TUI to,
|
|
60
107
|
* so the TUI and LinkShell are peers on the same threads.
|
|
61
|
-
*
|
|
62
|
-
* The socket speaks WebSocket; permessage-deflate must be off or the upgrade is reset.
|
|
63
108
|
*/
|
|
64
109
|
export class CodexAppServer {
|
|
65
110
|
options;
|
|
@@ -140,31 +185,61 @@ export class CodexAppServer {
|
|
|
140
185
|
throw new Error(`codex app-server did not accept connections: ${lastError instanceof Error ? lastError.message : "timeout"}`);
|
|
141
186
|
}
|
|
142
187
|
async connect() {
|
|
143
|
-
const socket =
|
|
144
|
-
await new Promise((resolve, reject) => {
|
|
145
|
-
socket.once("open", () => resolve());
|
|
146
|
-
socket.once("error", reject);
|
|
147
|
-
});
|
|
148
|
-
const peer = new RpcPeer({
|
|
149
|
-
send: (text) => socket.send(text),
|
|
150
|
-
onNotification: (method, params) => this.options.onNotification(method, params),
|
|
151
|
-
onRequest: (method, params, id) => this.options.onRequest(method, params, id) ?? ABANDON,
|
|
152
|
-
requestTimeoutMs: 60_000,
|
|
153
|
-
});
|
|
154
|
-
socket.on("message", (data) => peer.receive(data.toString()));
|
|
155
|
-
socket.on("close", () => {
|
|
156
|
-
peer.close("codex app-server connection closed");
|
|
157
|
-
if (!this.stopping)
|
|
158
|
-
this.options.onDown("codex app-server connection closed");
|
|
159
|
-
});
|
|
188
|
+
const { socket, peer } = await openConnection(this.options, () => this.stopping);
|
|
160
189
|
this.socket = socket;
|
|
161
190
|
this.peer = peer;
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* A client connection to Codex's own background server, which this host
|
|
195
|
+
* neither starts nor stops. A thread that lives there (opened by a plain
|
|
196
|
+
* `codex`) can't be loaded anywhere else, but any number of clients can be on
|
|
197
|
+
* it there: joining it is how the phone and that terminal share a thread.
|
|
198
|
+
*/
|
|
199
|
+
export class CodexSharedServer {
|
|
200
|
+
options;
|
|
201
|
+
socket;
|
|
202
|
+
peer;
|
|
203
|
+
connecting;
|
|
204
|
+
closing = false;
|
|
205
|
+
constructor(options) {
|
|
206
|
+
this.options = options;
|
|
207
|
+
}
|
|
208
|
+
/** Connects if that server is running; false when it isn't (it is never started from here). */
|
|
209
|
+
connect() {
|
|
210
|
+
if (this.peer)
|
|
211
|
+
return Promise.resolve(true);
|
|
212
|
+
if (!existsSync(this.options.socketPath))
|
|
213
|
+
return Promise.resolve(false);
|
|
214
|
+
this.closing = false;
|
|
215
|
+
this.connecting ??= openConnection({ ...this.options, onDown: (reason) => this.dropped(reason) }, () => this.closing)
|
|
216
|
+
.then(({ socket, peer }) => {
|
|
217
|
+
this.socket = socket;
|
|
218
|
+
this.peer = peer;
|
|
219
|
+
return true;
|
|
220
|
+
})
|
|
221
|
+
.catch(() => false)
|
|
222
|
+
.finally(() => {
|
|
223
|
+
this.connecting = undefined;
|
|
166
224
|
});
|
|
167
|
-
|
|
225
|
+
return this.connecting;
|
|
226
|
+
}
|
|
227
|
+
request(method, params, timeoutMs) {
|
|
228
|
+
if (!this.peer)
|
|
229
|
+
return Promise.reject(new Error("Codex's background server is not connected"));
|
|
230
|
+
return this.peer.request(method, params, timeoutMs);
|
|
231
|
+
}
|
|
232
|
+
close() {
|
|
233
|
+
this.closing = true;
|
|
234
|
+
this.peer?.close("stopping");
|
|
235
|
+
this.socket?.close();
|
|
236
|
+
this.peer = undefined;
|
|
237
|
+
this.socket = undefined;
|
|
238
|
+
}
|
|
239
|
+
dropped(reason) {
|
|
240
|
+
this.peer = undefined;
|
|
241
|
+
this.socket = undefined;
|
|
242
|
+
this.options.onDown(reason);
|
|
168
243
|
}
|
|
169
244
|
}
|
|
170
245
|
//# sourceMappingURL=app-server.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-server.js","sourceRoot":"","sources":["../../../../../src/drivers/codex/app-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAqB,MAAM,oBAAoB,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,SAAS,MAAM,IAAI,CAAC;AAC3B,OAAO,EAAE,OAAO,EAAE,OAAO,EAAc,MAAM,iBAAiB,CAAC;AAE/D,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C,4EAA4E;AAC5E,MAAM,qBAAqB,GAAG;IAC5B,4BAA4B;IAC5B,iBAAiB;IACjB,kBAAkB;IAClB,YAAY;IACZ,cAAc;IACd,gBAAgB;IAChB,iCAAiC;IACjC,uBAAuB;IACvB,2BAA2B;IAC3B,8BAA8B;IAC9B,gBAAgB;IAChB,qBAAqB;IACrB,qBAAqB;CACtB,CAAC;
|
|
1
|
+
{"version":3,"file":"app-server.js","sourceRoot":"","sources":["../../../../../src/drivers/codex/app-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAqB,MAAM,oBAAoB,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,SAAS,MAAM,IAAI,CAAC;AAC3B,OAAO,EAAE,OAAO,EAAE,OAAO,EAAc,MAAM,iBAAiB,CAAC;AAE/D,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C,4EAA4E;AAC5E,MAAM,qBAAqB,GAAG;IAC5B,4BAA4B;IAC5B,iBAAiB;IACjB,kBAAkB;IAClB,YAAY;IACZ,cAAc;IACd,gBAAgB;IAChB,iCAAiC;IACjC,uBAAuB;IACvB,2BAA2B;IAC3B,8BAA8B;IAC9B,gBAAgB;IAChB,qBAAqB;IACrB,qBAAqB;CACtB,CAAC;AAmBF;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAyB,OAAO,CAAC,GAAG;IACrE,OAAO,IAAI,CAAC,GAAG,CAAC,UAAU,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,EAAE,oBAAoB,EAAE,yBAAyB,CAAC,CAAC;AAC5G,CAAC;AAED,wIAAwI;AACxI,KAAK,UAAU,cAAc,CAAC,OAA+B,EAAE,OAAsB;IACnF,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,aAAa,OAAO,CAAC,UAAU,IAAI,EAAE,EAAE,iBAAiB,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,CAAC,CAAC;IAC1H,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;QACrC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,IAAI,OAAO,CAAC;QACvB,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;QACjC,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC;QAC1E,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,IAAI,OAAO;QACnF,gBAAgB,EAAE,MAAM;KACzB,CAAC,CAAC;IACH,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IAC9D,IAAI,KAAK,GAAG,KAAK,CAAC;IAClB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;QACtB,IAAI,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;QACjD,IAAI,KAAK,IAAI,CAAC,OAAO,EAAE;YAAE,OAAO,CAAC,MAAM,CAAC,oCAAoC,CAAC,CAAC;IAChF,CAAC,CAAC,CAAC;IACH,8FAA8F;IAC9F,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAC7B,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE;YAC/B,UAAU,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,CAAC,aAAa,EAAE;YACrF,wHAAwH;YACxH,YAAY,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE,kBAAkB,EAAE,KAAK,EAAE,yBAAyB,EAAE,qBAAqB,EAAE;SACrH,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,MAAM,KAAK,CAAC;IACd,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;IAC/B,KAAK,GAAG,IAAI,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AAC1B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAO,GAAG,OAAO,EAAE,GAAuB;IAC1E,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,OAAO,EAAE,CAAC,WAAW,CAAC,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QACzF,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,IAAI,SAAS,CAAC;IACjE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,eAAe,GAAG,CAAC,iCAAiC,CAAC,CAAC;AAE5D,4FAA4F;AAC5F,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAO,GAAG,OAAO,EAAE,GAAuB;IAC1E,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,OAAO,EAAE,CAAC,UAAU,EAAE,MAAM,CAAC,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QAChG,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,UAAU,CAAC,OAAe;IACxC,MAAM,MAAM,GAAG,IAAI,GAAG,CACpB,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAsB,EAAE;QACvD,MAAM,KAAK,GAAG,mCAAmC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QACpE,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAE,EAAE,KAAK,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACtD,CAAC,CAAC,CACH,CAAC;IACF,OAAO,eAAe,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC;AACpI,CAAC;AAED;;;;GAIG;AACH,MAAM,OAAO,cAAc;IAMI;IALrB,KAAK,CAAgB;IACrB,MAAM,CAAa;IACnB,IAAI,CAAW;IACf,QAAQ,GAAG,KAAK,CAAC;IAEzB,YAA6B,OAA8B;QAA9B,YAAO,GAAP,OAAO,CAAuB;IAAG,CAAC;IAE/D,IAAI,UAAU;QACZ,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC;IACjC,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACtB,IAAI,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1F,MAAM,KAAK,GAAG,KAAK,CACjB,IAAI,CAAC,OAAO,CAAC,OAAO,IAAI,OAAO,EAC/B,CAAC,YAAY,EAAE,GAAG,CAAC,MAAM,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,EAC/H,EAAE,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAC7D,CAAC;QACF,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,UAAU,GAAG,EAAE,CAAC;QACpB,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YACzC,UAAU,GAAG,CAAC,UAAU,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC;QAC5D,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACnC,MAAM,MAAM,GAAG,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE;YAC9C,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE;gBAClC,MAAM,MAAM,GAAG,4BAA4B,MAAM,IAAI,IAAI,IAAI,UAAU,CAAC,CAAC,CAAC,KAAK,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;gBAC1G,IAAI,CAAC,IAAI,CAAC,QAAQ;oBAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBAChD,MAAM,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;YAC5B,CAAC,CAAC,CAAC;YACH,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAChD,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACvB,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;IACxD,CAAC;IAED,KAAK,CAAC,IAAI;QACR,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;QAC7B,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC;QACrB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;QACvB,IAAI,KAAK,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;YACrC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACtB,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;gBAClC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;oBAC5B,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;oBACtB,OAAO,EAAE,CAAC;gBACZ,CAAC,EAAE,IAAI,CAAC,CAAC;gBACT,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE;oBACtB,YAAY,CAAC,KAAK,CAAC,CAAC;oBACpB,OAAO,EAAE,CAAC;gBACZ,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;QACL,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,CAAC;IAED,OAAO,CAAI,MAAc,EAAE,MAAe,EAAE,SAAkB;QAC5D,IAAI,CAAC,IAAI,CAAC,IAAI;YAAE,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC,CAAC;QACtF,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAI,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;IACzD,CAAC;IAEO,KAAK,CAAC,gBAAgB;QAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC;QACrC,IAAI,SAAkB,CAAC;QACvB,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;YAC7B,IAAI,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;gBACxC,IAAI,CAAC;oBACH,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;oBACrB,OAAO;gBACT,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,SAAS,GAAG,KAAK,CAAC;gBACpB,CAAC;YACH,CAAC;YACD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;QAC3D,CAAC;QACD,MAAM,IAAI,KAAK,CACb,gDAAgD,SAAS,YAAY,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,EAAE,CAC7G,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,OAAO;QACnB,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACjF,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,OAAO,iBAAiB;IAMC;IALrB,MAAM,CAAa;IACnB,IAAI,CAAW;IACf,UAAU,CAAoB;IAC9B,OAAO,GAAG,KAAK,CAAC;IAExB,YAA6B,OAA+B;QAA/B,YAAO,GAAP,OAAO,CAAwB;IAAG,CAAC;IAEhE,+FAA+F;IAC/F,OAAO;QACL,IAAI,IAAI,CAAC,IAAI;YAAE,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC5C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC;YAAE,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACxE,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACrB,IAAI,CAAC,UAAU,KAAK,cAAc,CAAC,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC;aAClH,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE;YACzB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;YACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;YACjB,OAAO,IAAI,CAAC;QACd,CAAC,CAAC;aACD,KAAK,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC;aAClB,OAAO,CAAC,GAAG,EAAE;YACZ,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAC9B,CAAC,CAAC,CAAC;QACL,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED,OAAO,CAAI,MAAc,EAAE,MAAe,EAAE,SAAkB;QAC5D,IAAI,CAAC,IAAI,CAAC,IAAI;YAAE,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC,CAAC;QAC/F,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAI,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;IACzD,CAAC;IAED,KAAK;QACH,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;QAC7B,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,SAAS,CAAC;QACtB,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;IAC1B,CAAC;IAEO,OAAO,CAAC,MAAc;QAC5B,IAAI,CAAC,IAAI,GAAG,SAAS,CAAC;QACtB,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;QACxB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC;CACF"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Where the desktop app's bus listens (the first Codex window to start serves it). */
|
|
2
|
+
export declare function desktopBusSocket(env?: NodeJS.ProcessEnv): string;
|
|
3
|
+
/**
|
|
4
|
+
* Asks whichever Codex window runs `threadId` to stop its turn, as its own
|
|
5
|
+
* Stop button does. Resolves when that window says it has; rejects when no
|
|
6
|
+
* window runs the thread, none answers in time, or the app isn't there.
|
|
7
|
+
*/
|
|
8
|
+
export declare function interruptThroughDesktop(socketPath: string, threadId: string, turnId: string | undefined, timeoutMs?: number): Promise<void>;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { connect } from "node:net";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
// The Codex desktop app and its IDE extension keep the threads they run in
|
|
7
|
+
// app-servers of their own, which take no other clients. What they do share
|
|
8
|
+
// is a message bus between their windows, on a socket in Codex's home: a
|
|
9
|
+
// window that doesn't run a thread asks the one that does to act on it
|
|
10
|
+
// ("thread-follower-…" requests). That is the only way to stop a turn the
|
|
11
|
+
// desktop app is running without stopping the app.
|
|
12
|
+
//
|
|
13
|
+
// The bus is the app's own, not a published interface: each method carries a
|
|
14
|
+
// version the receiver checks, and a request it doesn't recognise is refused.
|
|
15
|
+
// So everything here is one question with a yes or a no; a no is reported as
|
|
16
|
+
// "can't be stopped from here", never retried in another shape.
|
|
17
|
+
/** Where the desktop app's bus listens (the first Codex window to start serves it). */
|
|
18
|
+
export function desktopBusSocket(env = process.env) {
|
|
19
|
+
return join(env.CODEX_HOME || join(homedir(), ".codex"), "ipc", "ipc.sock");
|
|
20
|
+
}
|
|
21
|
+
/** The version of the interrupt request the installed apps accept: with the turn it is meant for, or (older) without. */
|
|
22
|
+
const INTERRUPT_VERSION = { withTurn: 4, anyTurn: 3 };
|
|
23
|
+
function frame(message) {
|
|
24
|
+
const body = Buffer.from(JSON.stringify(message), "utf8");
|
|
25
|
+
const head = Buffer.alloc(4);
|
|
26
|
+
head.writeUInt32LE(body.length, 0);
|
|
27
|
+
return Buffer.concat([head, body]);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Asks whichever Codex window runs `threadId` to stop its turn, as its own
|
|
31
|
+
* Stop button does. Resolves when that window says it has; rejects when no
|
|
32
|
+
* window runs the thread, none answers in time, or the app isn't there.
|
|
33
|
+
*/
|
|
34
|
+
export async function interruptThroughDesktop(socketPath, threadId, turnId, timeoutMs = 12_000) {
|
|
35
|
+
if (!existsSync(socketPath))
|
|
36
|
+
throw new Error("the Codex desktop app isn't running");
|
|
37
|
+
const socket = connect(socketPath);
|
|
38
|
+
try {
|
|
39
|
+
await new Promise((resolve, reject) => {
|
|
40
|
+
socket.once("connect", resolve);
|
|
41
|
+
socket.once("error", reject);
|
|
42
|
+
});
|
|
43
|
+
const bus = busClient(socket, timeoutMs);
|
|
44
|
+
const registered = await bus.request("initialize", { clientType: "linkshell" }, 0);
|
|
45
|
+
bus.clientId = String(registered.clientId ?? "");
|
|
46
|
+
await bus.request("thread-follower-interrupt-turn", { conversationId: threadId, mode: "user-stop", ...(turnId ? { expectedTurnId: turnId } : {}) }, turnId ? INTERRUPT_VERSION.withTurn : INTERRUPT_VERSION.anyTurn);
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
socket.destroy();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** One connection to the bus: length-prefixed JSON, requests answered by `requestId`. */
|
|
53
|
+
function busClient(socket, timeoutMs) {
|
|
54
|
+
const pending = new Map();
|
|
55
|
+
let buffered = Buffer.alloc(0);
|
|
56
|
+
const failAll = (error) => {
|
|
57
|
+
for (const waiting of pending.values())
|
|
58
|
+
waiting.reject(error);
|
|
59
|
+
pending.clear();
|
|
60
|
+
};
|
|
61
|
+
socket.on("data", (chunk) => {
|
|
62
|
+
buffered = Buffer.concat([buffered, chunk]);
|
|
63
|
+
while (buffered.length >= 4) {
|
|
64
|
+
const length = buffered.readUInt32LE(0);
|
|
65
|
+
if (buffered.length < 4 + length)
|
|
66
|
+
return;
|
|
67
|
+
let message;
|
|
68
|
+
try {
|
|
69
|
+
message = JSON.parse(buffered.subarray(4, 4 + length).toString("utf8"));
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
failAll(new Error("the Codex desktop app sent something unreadable"));
|
|
73
|
+
socket.destroy();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
buffered = buffered.subarray(4 + length);
|
|
77
|
+
if (message.type === "response") {
|
|
78
|
+
const waiting = pending.get(String(message.requestId));
|
|
79
|
+
if (!waiting)
|
|
80
|
+
continue;
|
|
81
|
+
pending.delete(String(message.requestId));
|
|
82
|
+
if (message.resultType === "success")
|
|
83
|
+
waiting.resolve(message.result);
|
|
84
|
+
else
|
|
85
|
+
waiting.reject(new Error(String(message.error ?? "refused")));
|
|
86
|
+
}
|
|
87
|
+
else if (message.type === "client-discovery-request") {
|
|
88
|
+
// The bus asks every client whether it runs the thread a request is about. Left
|
|
89
|
+
// unanswered, the asker waits ten seconds for this client; it never does.
|
|
90
|
+
socket.write(frame({ type: "client-discovery-response", requestId: message.requestId, response: { canHandle: false } }));
|
|
91
|
+
}
|
|
92
|
+
else if (message.type === "request") {
|
|
93
|
+
socket.write(frame({ type: "response", requestId: message.requestId, resultType: "error", error: "no-handler-for-request" }));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
socket.on("close", () => failAll(new Error("the Codex desktop app closed the connection")));
|
|
98
|
+
socket.on("error", () => { });
|
|
99
|
+
const client = {
|
|
100
|
+
clientId: "initializing-client",
|
|
101
|
+
request(method, params, version) {
|
|
102
|
+
const requestId = randomUUID();
|
|
103
|
+
return new Promise((resolve, reject) => {
|
|
104
|
+
const timer = setTimeout(() => {
|
|
105
|
+
pending.delete(requestId);
|
|
106
|
+
reject(new Error("timeout"));
|
|
107
|
+
}, timeoutMs);
|
|
108
|
+
pending.set(requestId, {
|
|
109
|
+
resolve: (result) => {
|
|
110
|
+
clearTimeout(timer);
|
|
111
|
+
resolve(result);
|
|
112
|
+
},
|
|
113
|
+
reject: (error) => {
|
|
114
|
+
clearTimeout(timer);
|
|
115
|
+
reject(error);
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
socket.write(frame({ type: "request", requestId, sourceClientId: client.clientId, version, method, params, timeoutMs }));
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
return client;
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=desktop-ipc.js.map
|