linkshell-cli 0.3.24 → 0.3.25
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/cli/src/index.js +0 -0
- package/dist/cli/src/runtime/acp/agent-workspace.js +1 -0
- package/dist/cli/src/runtime/acp/agent-workspace.js.map +1 -1
- package/dist/cli/src/runtime/acp/codex-rpc-bridge.d.ts +5 -0
- package/dist/cli/src/runtime/acp/codex-rpc-bridge.js +54 -1
- package/dist/cli/src/runtime/acp/codex-rpc-bridge.js.map +1 -1
- package/dist/cli/src/runtime/acp/json-rpc.d.ts +1 -0
- package/dist/cli/src/runtime/acp/json-rpc.js +3 -1
- package/dist/cli/src/runtime/acp/json-rpc.js.map +1 -1
- package/dist/cli/tsconfig.tsbuildinfo +1 -1
- package/package.json +12 -12
- package/src/runtime/acp/agent-workspace.ts +1 -0
- package/src/runtime/acp/codex-rpc-bridge.ts +59 -1
- package/src/runtime/acp/json-rpc.ts +5 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "linkshell-cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.25",
|
|
4
4
|
"description": "Remote terminal bridge for Claude Code / Codex / Gemini / Copilot — control from your phone",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/cli/src/index.js",
|
|
@@ -29,14 +29,21 @@
|
|
|
29
29
|
"type": "git",
|
|
30
30
|
"url": "git+https://github.com/LiuTianjie/LinkShell.git"
|
|
31
31
|
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc -p tsconfig.json",
|
|
34
|
+
"postinstall": "node scripts/postinstall.mjs",
|
|
35
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
36
|
+
"dev": "tsx src/index.ts",
|
|
37
|
+
"test": "vitest run"
|
|
38
|
+
},
|
|
32
39
|
"dependencies": {
|
|
40
|
+
"@linkshell/gateway": "workspace:*",
|
|
41
|
+
"@linkshell/protocol": "workspace:*",
|
|
33
42
|
"commander": "^13.1.0",
|
|
34
43
|
"node-pty": "^1.0.0",
|
|
35
44
|
"qrcode-terminal": "^0.12.0",
|
|
36
45
|
"ws": "^8.18.1",
|
|
37
|
-
"zod": "^4.4.3"
|
|
38
|
-
"@linkshell/gateway": "0.3.9",
|
|
39
|
-
"@linkshell/protocol": "0.3.11"
|
|
46
|
+
"zod": "^4.4.3"
|
|
40
47
|
},
|
|
41
48
|
"optionalDependencies": {
|
|
42
49
|
"@anthropic-ai/claude-agent-sdk": "^0.2.128",
|
|
@@ -46,12 +53,5 @@
|
|
|
46
53
|
"@types/ws": "^8.5.14",
|
|
47
54
|
"tsx": "^4.19.3",
|
|
48
55
|
"vitest": "^3.2.1"
|
|
49
|
-
},
|
|
50
|
-
"scripts": {
|
|
51
|
-
"build": "tsc -p tsconfig.json",
|
|
52
|
-
"postinstall": "node scripts/postinstall.mjs",
|
|
53
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
54
|
-
"dev": "tsx src/index.ts",
|
|
55
|
-
"test": "vitest run"
|
|
56
56
|
}
|
|
57
|
-
}
|
|
57
|
+
}
|
|
@@ -2497,6 +2497,7 @@ export class AgentWorkspaceProxy {
|
|
|
2497
2497
|
if (
|
|
2498
2498
|
method.startsWith("account/") ||
|
|
2499
2499
|
method.startsWith("mcpServer/startupStatus/") ||
|
|
2500
|
+
method.startsWith("remoteControl/") ||
|
|
2500
2501
|
method === "thread/status/changed" ||
|
|
2501
2502
|
method === "thread/tokenUsage/updated" ||
|
|
2502
2503
|
method === "serverRequest/resolved" ||
|
|
@@ -5,6 +5,8 @@ import { resolveCodexAppServerCommands, type AgentCommandConfig } from "./provid
|
|
|
5
5
|
|
|
6
6
|
const CODEX_PROXY_STARTUP_GRACE_MS = 250;
|
|
7
7
|
|
|
8
|
+
type JsonRpcId = string | number;
|
|
9
|
+
|
|
8
10
|
export class CodexRpcBridge {
|
|
9
11
|
private transport: JsonRpcStdioTransport | undefined;
|
|
10
12
|
private transportReady = false;
|
|
@@ -13,6 +15,7 @@ export class CodexRpcBridge {
|
|
|
13
15
|
private candidateIndex = 0;
|
|
14
16
|
private startupTimer: ReturnType<typeof setTimeout> | undefined;
|
|
15
17
|
private queuedMessages: JsonRpcMessage[] = [];
|
|
18
|
+
private inFlightRequests = new Map<JsonRpcId, JsonRpcMessage>();
|
|
16
19
|
|
|
17
20
|
constructor(
|
|
18
21
|
private readonly input: {
|
|
@@ -32,6 +35,7 @@ export class CodexRpcBridge {
|
|
|
32
35
|
this.queuedMessages.push(jsonMessage);
|
|
33
36
|
return;
|
|
34
37
|
}
|
|
38
|
+
this.rememberRequest(jsonMessage);
|
|
35
39
|
this.transport?.send(jsonMessage);
|
|
36
40
|
}
|
|
37
41
|
|
|
@@ -46,6 +50,7 @@ export class CodexRpcBridge {
|
|
|
46
50
|
this.transportReady = false;
|
|
47
51
|
this.starting = false;
|
|
48
52
|
this.queuedMessages = [];
|
|
53
|
+
this.inFlightRequests.clear();
|
|
49
54
|
this.stopping = false;
|
|
50
55
|
}
|
|
51
56
|
|
|
@@ -80,6 +85,7 @@ export class CodexRpcBridge {
|
|
|
80
85
|
() => ({}),
|
|
81
86
|
(message) => this.handleTransportExit(message),
|
|
82
87
|
(message) => {
|
|
88
|
+
this.forgetRequest(message);
|
|
83
89
|
this.input.send(createEnvelope({
|
|
84
90
|
type: "agent.codex.rpc",
|
|
85
91
|
hostDeviceId: this.input.hostDeviceId,
|
|
@@ -87,7 +93,11 @@ export class CodexRpcBridge {
|
|
|
87
93
|
}));
|
|
88
94
|
return true;
|
|
89
95
|
},
|
|
90
|
-
{
|
|
96
|
+
{
|
|
97
|
+
includeJsonrpc: false,
|
|
98
|
+
verbose: this.input.verbose,
|
|
99
|
+
onStderrData: (message) => this.handleTransportStderr(message),
|
|
100
|
+
},
|
|
91
101
|
);
|
|
92
102
|
this.transport.start(this.input.cwd);
|
|
93
103
|
if (this.input.verbose) {
|
|
@@ -120,12 +130,14 @@ export class CodexRpcBridge {
|
|
|
120
130
|
return;
|
|
121
131
|
}
|
|
122
132
|
this.failQueued(message);
|
|
133
|
+
this.failInFlight(message);
|
|
123
134
|
this.emitError(message);
|
|
124
135
|
}
|
|
125
136
|
|
|
126
137
|
private flushQueued(): void {
|
|
127
138
|
const messages = this.queuedMessages.splice(0);
|
|
128
139
|
for (const message of messages) {
|
|
140
|
+
this.rememberRequest(message);
|
|
129
141
|
this.transport?.send(message);
|
|
130
142
|
}
|
|
131
143
|
}
|
|
@@ -160,4 +172,50 @@ export class CodexRpcBridge {
|
|
|
160
172
|
},
|
|
161
173
|
}));
|
|
162
174
|
}
|
|
175
|
+
|
|
176
|
+
private handleTransportStderr(message: string): void {
|
|
177
|
+
if (this.input.verbose) {
|
|
178
|
+
process.stderr.write(`[agent:codex-rpc] stderr ignored while app-server is running: ${message}\n`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private rememberRequest(message: JsonRpcMessage): void {
|
|
183
|
+
if (!isJsonRpcRequest(message)) return;
|
|
184
|
+
this.inFlightRequests.set(message.id, message);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private forgetRequest(message: JsonRpcMessage): void {
|
|
188
|
+
if (!isJsonRpcResponse(message)) return;
|
|
189
|
+
this.inFlightRequests.delete(message.id);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private failInFlight(message: string): void {
|
|
193
|
+
const requests = [...this.inFlightRequests.values()];
|
|
194
|
+
this.inFlightRequests.clear();
|
|
195
|
+
for (const item of requests) {
|
|
196
|
+
if (!isJsonRpcRequest(item)) continue;
|
|
197
|
+
this.input.send(createEnvelope({
|
|
198
|
+
type: "agent.codex.rpc",
|
|
199
|
+
hostDeviceId: this.input.hostDeviceId,
|
|
200
|
+
payload: {
|
|
201
|
+
jsonrpc: "2.0",
|
|
202
|
+
id: item.id,
|
|
203
|
+
error: { code: -32000, message },
|
|
204
|
+
},
|
|
205
|
+
}));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function isJsonRpcRequest(message: JsonRpcMessage): message is JsonRpcMessage & { id: JsonRpcId; method: string } {
|
|
211
|
+
return "id" in message &&
|
|
212
|
+
"method" in message &&
|
|
213
|
+
(typeof message.id === "string" || typeof message.id === "number") &&
|
|
214
|
+
typeof message.method === "string";
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function isJsonRpcResponse(message: JsonRpcMessage): message is JsonRpcMessage & { id: JsonRpcId } {
|
|
218
|
+
return "id" in message &&
|
|
219
|
+
(typeof message.id === "string" || typeof message.id === "number") &&
|
|
220
|
+
("result" in message || "error" in message);
|
|
163
221
|
}
|
|
@@ -26,6 +26,7 @@ export type JsonRpcMessage = JsonRpcRequest | JsonRpcResponse | JsonRpcNotificat
|
|
|
26
26
|
interface JsonRpcTransportOptions {
|
|
27
27
|
includeJsonrpc?: boolean;
|
|
28
28
|
verbose?: boolean;
|
|
29
|
+
onStderrData?: (message: string) => void;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
export class JsonRpcStdioTransport {
|
|
@@ -64,7 +65,10 @@ export class JsonRpcStdioTransport {
|
|
|
64
65
|
this.child.stdout.on("data", (chunk: string) => this.read(chunk));
|
|
65
66
|
this.child.stderr.on("data", (chunk: string) => {
|
|
66
67
|
const trimmed = chunk.trim();
|
|
67
|
-
if (trimmed)
|
|
68
|
+
if (trimmed) {
|
|
69
|
+
process.stderr.write(`[agent:stderr] ${trimmed}\n`);
|
|
70
|
+
this.options.onStderrData?.(trimmed);
|
|
71
|
+
}
|
|
68
72
|
});
|
|
69
73
|
this.child.on("error", (error) => this.failAll(error.message));
|
|
70
74
|
this.child.on("exit", (code, signal) => {
|