linkshell-cli 0.2.64 → 0.2.66
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/README.md +3 -0
- package/dist/cli/src/index.js +28 -1
- package/dist/cli/src/index.js.map +1 -1
- package/dist/cli/src/runtime/acp/acp-client.d.ts +41 -0
- package/dist/cli/src/runtime/acp/acp-client.js +102 -0
- package/dist/cli/src/runtime/acp/acp-client.js.map +1 -0
- package/dist/cli/src/runtime/acp/agent-session.d.ts +42 -0
- package/dist/cli/src/runtime/acp/agent-session.js +492 -0
- package/dist/cli/src/runtime/acp/agent-session.js.map +1 -0
- package/dist/cli/src/runtime/acp/json-rpc.d.ts +21 -0
- package/dist/cli/src/runtime/acp/json-rpc.js +150 -0
- package/dist/cli/src/runtime/acp/json-rpc.js.map +1 -0
- package/dist/cli/src/runtime/acp/provider-resolver.d.ts +13 -0
- package/dist/cli/src/runtime/acp/provider-resolver.js +22 -0
- package/dist/cli/src/runtime/acp/provider-resolver.js.map +1 -0
- package/dist/cli/src/runtime/bridge-session.d.ts +9 -0
- package/dist/cli/src/runtime/bridge-session.js +163 -41
- package/dist/cli/src/runtime/bridge-session.js.map +1 -1
- package/dist/cli/src/utils/daemon.d.ts +6 -0
- package/dist/cli/src/utils/daemon.js +22 -0
- package/dist/cli/src/utils/daemon.js.map +1 -1
- package/dist/cli/src/utils/keep-awake.d.ts +6 -0
- package/dist/cli/src/utils/keep-awake.js +48 -0
- package/dist/cli/src/utils/keep-awake.js.map +1 -0
- package/dist/cli/tsconfig.tsbuildinfo +1 -1
- package/dist/shared-protocol/src/index.d.ts +1108 -54
- package/dist/shared-protocol/src/index.js +112 -1
- package/dist/shared-protocol/src/index.js.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +38 -1
- package/src/runtime/acp/acp-client.ts +133 -0
- package/src/runtime/acp/agent-session.ts +589 -0
- package/src/runtime/acp/json-rpc.ts +177 -0
- package/src/runtime/acp/provider-resolver.ts +37 -0
- package/src/runtime/bridge-session.ts +189 -41
- package/src/utils/daemon.ts +28 -0
- package/src/utils/keep-awake.ts +61 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
export class JsonRpcStdioTransport {
|
|
3
|
+
command;
|
|
4
|
+
framing;
|
|
5
|
+
onNotification;
|
|
6
|
+
onRequest;
|
|
7
|
+
onExit;
|
|
8
|
+
child;
|
|
9
|
+
nextId = 1;
|
|
10
|
+
pending = new Map();
|
|
11
|
+
buffer = "";
|
|
12
|
+
constructor(command, framing, onNotification, onRequest, onExit) {
|
|
13
|
+
this.command = command;
|
|
14
|
+
this.framing = framing;
|
|
15
|
+
this.onNotification = onNotification;
|
|
16
|
+
this.onRequest = onRequest;
|
|
17
|
+
this.onExit = onExit;
|
|
18
|
+
}
|
|
19
|
+
start(cwd) {
|
|
20
|
+
if (this.child)
|
|
21
|
+
return;
|
|
22
|
+
this.child = spawn(this.command, {
|
|
23
|
+
cwd,
|
|
24
|
+
shell: true,
|
|
25
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
26
|
+
env: process.env,
|
|
27
|
+
});
|
|
28
|
+
this.child.stdout.setEncoding("utf8");
|
|
29
|
+
this.child.stderr.setEncoding("utf8");
|
|
30
|
+
this.child.stdout.on("data", (chunk) => this.read(chunk));
|
|
31
|
+
this.child.stderr.on("data", (chunk) => {
|
|
32
|
+
const trimmed = chunk.trim();
|
|
33
|
+
if (trimmed)
|
|
34
|
+
process.stderr.write(`[agent:stderr] ${trimmed}\n`);
|
|
35
|
+
});
|
|
36
|
+
this.child.on("error", (error) => this.failAll(error.message));
|
|
37
|
+
this.child.on("exit", (code, signal) => {
|
|
38
|
+
this.failAll(`ACP agent exited (code=${code ?? "null"}, signal=${signal ?? "null"})`);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
request(method, params, timeoutMs = 30_000) {
|
|
42
|
+
if (!this.child || this.child.stdin.destroyed) {
|
|
43
|
+
return Promise.reject(new Error("ACP agent is not running"));
|
|
44
|
+
}
|
|
45
|
+
const id = this.nextId++;
|
|
46
|
+
const message = { jsonrpc: "2.0", id, method, params };
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
const timer = setTimeout(() => {
|
|
49
|
+
this.pending.delete(id);
|
|
50
|
+
reject(new Error(`ACP request timed out: ${method}`));
|
|
51
|
+
}, timeoutMs);
|
|
52
|
+
this.pending.set(id, { resolve, reject, timer });
|
|
53
|
+
this.write(message);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
notify(method, params) {
|
|
57
|
+
this.write({ jsonrpc: "2.0", method, params });
|
|
58
|
+
}
|
|
59
|
+
stop() {
|
|
60
|
+
const child = this.child;
|
|
61
|
+
this.child = undefined;
|
|
62
|
+
if (child && !child.killed)
|
|
63
|
+
child.kill("SIGTERM");
|
|
64
|
+
this.failAll("ACP transport stopped");
|
|
65
|
+
}
|
|
66
|
+
write(message) {
|
|
67
|
+
const raw = JSON.stringify(message);
|
|
68
|
+
if (this.framing === "newline") {
|
|
69
|
+
this.child?.stdin.write(`${raw}\n`);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
this.child?.stdin.write(`Content-Length: ${Buffer.byteLength(raw, "utf8")}\r\n\r\n${raw}`);
|
|
73
|
+
}
|
|
74
|
+
read(chunk) {
|
|
75
|
+
this.buffer += chunk;
|
|
76
|
+
while (this.buffer.length > 0) {
|
|
77
|
+
const contentLengthMatch = this.buffer.match(/^Content-Length:\s*(\d+)\r?\n/i);
|
|
78
|
+
if (contentLengthMatch) {
|
|
79
|
+
const headerEnd = this.buffer.indexOf("\r\n\r\n");
|
|
80
|
+
const altHeaderEnd = this.buffer.indexOf("\n\n");
|
|
81
|
+
const end = headerEnd >= 0 ? headerEnd + 4 : altHeaderEnd >= 0 ? altHeaderEnd + 2 : -1;
|
|
82
|
+
if (end < 0)
|
|
83
|
+
return;
|
|
84
|
+
const length = Number(contentLengthMatch[1]);
|
|
85
|
+
if (this.buffer.length < end + length)
|
|
86
|
+
return;
|
|
87
|
+
const raw = this.buffer.slice(end, end + length);
|
|
88
|
+
this.buffer = this.buffer.slice(end + length);
|
|
89
|
+
this.dispatch(raw);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const newline = this.buffer.indexOf("\n");
|
|
93
|
+
if (newline < 0)
|
|
94
|
+
return;
|
|
95
|
+
const raw = this.buffer.slice(0, newline).trim();
|
|
96
|
+
this.buffer = this.buffer.slice(newline + 1);
|
|
97
|
+
if (raw)
|
|
98
|
+
this.dispatch(raw);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
dispatch(raw) {
|
|
102
|
+
let message;
|
|
103
|
+
try {
|
|
104
|
+
message = JSON.parse(raw);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if ("id" in message && ("result" in message || "error" in message)) {
|
|
110
|
+
const pending = this.pending.get(message.id);
|
|
111
|
+
if (!pending)
|
|
112
|
+
return;
|
|
113
|
+
this.pending.delete(message.id);
|
|
114
|
+
clearTimeout(pending.timer);
|
|
115
|
+
const response = message;
|
|
116
|
+
if (response.error)
|
|
117
|
+
pending.reject(new Error(response.error.message));
|
|
118
|
+
else
|
|
119
|
+
pending.resolve(response.result);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if ("method" in message && "id" in message) {
|
|
123
|
+
Promise.resolve(this.onRequest(message.method, message.params))
|
|
124
|
+
.then((result) => this.write({ jsonrpc: "2.0", id: message.id, result }))
|
|
125
|
+
.catch((error) => {
|
|
126
|
+
this.write({
|
|
127
|
+
jsonrpc: "2.0",
|
|
128
|
+
id: message.id,
|
|
129
|
+
error: {
|
|
130
|
+
code: -32000,
|
|
131
|
+
message: error instanceof Error ? error.message : String(error),
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if ("method" in message) {
|
|
138
|
+
this.onNotification(message.method, message.params);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
failAll(message) {
|
|
142
|
+
this.onExit(message);
|
|
143
|
+
for (const [, pending] of this.pending) {
|
|
144
|
+
clearTimeout(pending.timer);
|
|
145
|
+
pending.reject(new Error(message));
|
|
146
|
+
}
|
|
147
|
+
this.pending.clear();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=json-rpc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-rpc.js","sourceRoot":"","sources":["../../../../../src/runtime/acp/json-rpc.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type AgentProvider = "codex" | "claude" | "custom";
|
|
2
|
+
export type AgentProtocol = "acp" | "codex-app-server";
|
|
3
|
+
export type AgentFraming = "content-length" | "newline";
|
|
4
|
+
export interface AgentCommandConfig {
|
|
5
|
+
command: string;
|
|
6
|
+
provider: AgentProvider;
|
|
7
|
+
protocol: AgentProtocol;
|
|
8
|
+
framing: AgentFraming;
|
|
9
|
+
}
|
|
10
|
+
export declare function resolveAgentCommand(input: {
|
|
11
|
+
provider: AgentProvider;
|
|
12
|
+
command?: string;
|
|
13
|
+
}): AgentCommandConfig | null;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function resolveAgentCommand(input) {
|
|
2
|
+
const explicit = input.command?.trim();
|
|
3
|
+
if (explicit) {
|
|
4
|
+
const isCodexAppServer = /\bcodex\b/.test(explicit) && /\bapp-server\b/.test(explicit);
|
|
5
|
+
return {
|
|
6
|
+
provider: input.provider,
|
|
7
|
+
command: explicit,
|
|
8
|
+
protocol: isCodexAppServer ? "codex-app-server" : "acp",
|
|
9
|
+
framing: isCodexAppServer ? "newline" : "content-length",
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
if (input.provider === "codex") {
|
|
13
|
+
return {
|
|
14
|
+
provider: "codex",
|
|
15
|
+
command: "codex app-server --listen stdio://",
|
|
16
|
+
protocol: "codex-app-server",
|
|
17
|
+
framing: "newline",
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=provider-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider-resolver.js","sourceRoot":"","sources":["../../../../../src/runtime/acp/provider-resolver.ts"],"names":[],"mappings":"AAWA,MAAM,UAAU,mBAAmB,CAAC,KAGnC;IACC,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC;IACvC,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,gBAAgB,GAAG,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACvF,OAAO;YACL,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,OAAO,EAAE,QAAQ;YACjB,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,KAAK;YACvD,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,gBAAgB;SACzD,CAAC;IACJ,CAAC;IAED,IAAI,KAAK,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAC/B,OAAO;YACL,QAAQ,EAAE,OAAO;YACjB,OAAO,EAAE,oCAAoC;YAC7C,QAAQ,EAAE,kBAAkB;YAC5B,OAAO,EAAE,SAAS;SACnB,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ProviderConfig } from "../providers.js";
|
|
2
|
+
import type { AgentProvider } from "./acp/provider-resolver.js";
|
|
2
3
|
export interface BridgeSessionOptions {
|
|
3
4
|
gatewayUrl: string;
|
|
4
5
|
gatewayHttpUrl: string;
|
|
@@ -12,6 +13,10 @@ export interface BridgeSessionOptions {
|
|
|
12
13
|
screen?: boolean;
|
|
13
14
|
providerConfig: ProviderConfig;
|
|
14
15
|
authToken?: string;
|
|
16
|
+
keepAwake?: boolean;
|
|
17
|
+
agentUi?: boolean;
|
|
18
|
+
agentProvider?: AgentProvider;
|
|
19
|
+
agentCommand?: string;
|
|
15
20
|
}
|
|
16
21
|
export declare class BridgeSession {
|
|
17
22
|
private readonly options;
|
|
@@ -30,6 +35,8 @@ export declare class BridgeSession {
|
|
|
30
35
|
private screenCapture;
|
|
31
36
|
private screenShare;
|
|
32
37
|
private tunnelSockets;
|
|
38
|
+
private keepAwake;
|
|
39
|
+
private agentSession;
|
|
33
40
|
constructor(options: BridgeSessionOptions);
|
|
34
41
|
private log;
|
|
35
42
|
start(): Promise<void>;
|
|
@@ -61,6 +68,8 @@ export declare class BridgeSession {
|
|
|
61
68
|
private autoResolvePending;
|
|
62
69
|
/** Drain all pending permissions for a terminal (session ended, stop, etc.) */
|
|
63
70
|
private drainPendingPermissions;
|
|
71
|
+
private resolvePendingPermission;
|
|
72
|
+
private sendPermissionSnapshot;
|
|
64
73
|
private cleanupHookServer;
|
|
65
74
|
/** Remove hook entries containing our marker from a JSON config file */
|
|
66
75
|
private removeHookEntries;
|
|
@@ -10,11 +10,19 @@ import { ScrollbackBuffer } from "./scrollback.js";
|
|
|
10
10
|
import { ScreenFallback } from "./screen-fallback.js";
|
|
11
11
|
import { ScreenShare } from "./screen-share.js";
|
|
12
12
|
import { getLanIp } from "../utils/lan-ip.js";
|
|
13
|
+
import { startKeepAwake } from "../utils/keep-awake.js";
|
|
14
|
+
import { AgentSessionProxy } from "./acp/agent-session.js";
|
|
13
15
|
const HEARTBEAT_INTERVAL = 15_000;
|
|
14
16
|
const RECONNECT_BASE_DELAY = 1_000;
|
|
15
17
|
const RECONNECT_MAX_DELAY = 30_000;
|
|
16
18
|
const RECONNECT_MAX_ATTEMPTS = 20;
|
|
17
19
|
const DEFAULT_TERMINAL_ID = "default";
|
|
20
|
+
const HOOK_BODY_LIMIT = 256 * 1024;
|
|
21
|
+
const PERMISSION_REQUEST_TIMEOUT_MS = Number(process.env.LINKSHELL_PERMISSION_TIMEOUT_MS ?? 5 * 60_000);
|
|
22
|
+
function isLinkShellHookEntry(entry) {
|
|
23
|
+
const raw = JSON.stringify(entry);
|
|
24
|
+
return /\/hook\?m=lsh-[^"'\s]+/.test(raw);
|
|
25
|
+
}
|
|
18
26
|
function getPairingGatewayParam(gatewayHttpUrl) {
|
|
19
27
|
try {
|
|
20
28
|
const url = new URL(gatewayHttpUrl);
|
|
@@ -64,6 +72,12 @@ function resolvePairingGateway(gatewayHttpUrl, pairingGateway) {
|
|
|
64
72
|
}
|
|
65
73
|
}
|
|
66
74
|
}
|
|
75
|
+
function normalizeAgentProvider(provider) {
|
|
76
|
+
if (provider === "claude" || provider === "custom") {
|
|
77
|
+
return provider;
|
|
78
|
+
}
|
|
79
|
+
return "codex";
|
|
80
|
+
}
|
|
67
81
|
export class BridgeSession {
|
|
68
82
|
options;
|
|
69
83
|
socket;
|
|
@@ -82,6 +96,8 @@ export class BridgeSession {
|
|
|
82
96
|
screenCapture;
|
|
83
97
|
screenShare;
|
|
84
98
|
tunnelSockets = new Map();
|
|
99
|
+
keepAwake;
|
|
100
|
+
agentSession;
|
|
85
101
|
constructor(options) {
|
|
86
102
|
this.options = options;
|
|
87
103
|
this.sessionId = options.sessionId ?? "";
|
|
@@ -96,6 +112,24 @@ export class BridgeSession {
|
|
|
96
112
|
if (!this.sessionId) {
|
|
97
113
|
await this.createPairing();
|
|
98
114
|
}
|
|
115
|
+
if (this.options.keepAwake) {
|
|
116
|
+
this.keepAwake = startKeepAwake();
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
process.stderr.write("[bridge] keep-awake disabled\n");
|
|
120
|
+
}
|
|
121
|
+
if (this.options.agentUi) {
|
|
122
|
+
const agentProvider = normalizeAgentProvider(this.options.agentProvider ?? this.options.providerConfig.provider);
|
|
123
|
+
this.agentSession = new AgentSessionProxy({
|
|
124
|
+
sessionId: this.sessionId,
|
|
125
|
+
cwd: process.cwd(),
|
|
126
|
+
provider: agentProvider,
|
|
127
|
+
command: this.options.agentCommand,
|
|
128
|
+
verbose: this.options.verbose,
|
|
129
|
+
send: (envelope) => this.send(envelope),
|
|
130
|
+
});
|
|
131
|
+
process.stderr.write("[bridge] agent GUI channel enabled\n");
|
|
132
|
+
}
|
|
99
133
|
await this.spawnTerminal(DEFAULT_TERMINAL_ID, process.cwd());
|
|
100
134
|
this.connectGateway();
|
|
101
135
|
}
|
|
@@ -180,7 +214,14 @@ export class BridgeSession {
|
|
|
180
214
|
this.startHeartbeat();
|
|
181
215
|
});
|
|
182
216
|
this.socket.on("message", (data) => {
|
|
183
|
-
|
|
217
|
+
let envelope;
|
|
218
|
+
try {
|
|
219
|
+
envelope = parseEnvelope(data.toString());
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
this.log(`invalid gateway message ignored: ${err}`);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
184
225
|
this.log(`recv ${envelope.type}${envelope.seq !== undefined ? ` seq=${envelope.seq}` : ""}`);
|
|
185
226
|
this.handleMessage(envelope).catch((err) => {
|
|
186
227
|
this.log(`handleMessage error: ${err}`);
|
|
@@ -374,7 +415,7 @@ export class BridgeSession {
|
|
|
374
415
|
const p = parseTypedPayload("session.resume", envelope.payload);
|
|
375
416
|
// Replay all terminals
|
|
376
417
|
for (const [termId, term] of this.terminals) {
|
|
377
|
-
this.replayFrom(termId, term, p.lastAckedSeq);
|
|
418
|
+
this.replayFrom(termId, term, p.lastAckedSeqByTerminal[termId] ?? p.lastAckedSeq);
|
|
378
419
|
}
|
|
379
420
|
// Also send terminal list so client knows what's available
|
|
380
421
|
this.sendTerminalList();
|
|
@@ -401,6 +442,35 @@ export class BridgeSession {
|
|
|
401
442
|
this.screenShare?.handleIceCandidate(p.candidate, p.sdpMid, p.sdpMLineIndex);
|
|
402
443
|
break;
|
|
403
444
|
}
|
|
445
|
+
case "agent.initialize":
|
|
446
|
+
case "agent.session.new":
|
|
447
|
+
case "agent.session.load":
|
|
448
|
+
case "agent.session.list":
|
|
449
|
+
case "agent.prompt":
|
|
450
|
+
case "agent.cancel":
|
|
451
|
+
case "agent.permission.response": {
|
|
452
|
+
if (!this.agentSession) {
|
|
453
|
+
this.send(createEnvelope({
|
|
454
|
+
type: "agent.capabilities",
|
|
455
|
+
sessionId: this.sessionId,
|
|
456
|
+
payload: {
|
|
457
|
+
enabled: false,
|
|
458
|
+
provider: normalizeAgentProvider(this.options.agentProvider ?? this.options.providerConfig.provider),
|
|
459
|
+
error: "Agent GUI is not enabled. Start CLI with --agent-ui.",
|
|
460
|
+
supportsSessionList: false,
|
|
461
|
+
supportsSessionLoad: false,
|
|
462
|
+
supportsImages: false,
|
|
463
|
+
supportsAudio: false,
|
|
464
|
+
supportsPermission: false,
|
|
465
|
+
supportsPlan: false,
|
|
466
|
+
supportsCancel: false,
|
|
467
|
+
},
|
|
468
|
+
}));
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
await this.agentSession.handleEnvelope(envelope);
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
404
474
|
case "file.upload": {
|
|
405
475
|
const p = parseTypedPayload("file.upload", envelope.payload);
|
|
406
476
|
const ext = p.filename.split(".").pop() || "png";
|
|
@@ -415,22 +485,8 @@ export class BridgeSession {
|
|
|
415
485
|
}
|
|
416
486
|
case "permission.decision": {
|
|
417
487
|
const p = envelope.payload;
|
|
418
|
-
|
|
419
|
-
if (resolve) {
|
|
420
|
-
this.pendingPermissions.delete(p.requestId);
|
|
421
|
-
resolve(p.decision);
|
|
488
|
+
if (this.resolvePendingPermission(p.requestId, p.decision)) {
|
|
422
489
|
this.log(`permission decision for ${p.requestId}: ${p.decision}`);
|
|
423
|
-
// Pop from permission stack
|
|
424
|
-
if (p.decision === "allow" || p.decision === "deny") {
|
|
425
|
-
const stack = this.permissionStacks.get(tid);
|
|
426
|
-
if (stack) {
|
|
427
|
-
const idx = stack.findIndex((s) => s.requestId === p.requestId);
|
|
428
|
-
if (idx >= 0)
|
|
429
|
-
stack.splice(idx, 1);
|
|
430
|
-
if (stack.length === 0)
|
|
431
|
-
this.permissionStacks.delete(tid);
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
490
|
}
|
|
435
491
|
else {
|
|
436
492
|
this.log(`no pending permission for ${p.requestId}`);
|
|
@@ -740,8 +796,21 @@ export class BridgeSession {
|
|
|
740
796
|
return;
|
|
741
797
|
}
|
|
742
798
|
let body = "";
|
|
743
|
-
|
|
799
|
+
let bodyTooLarge = false;
|
|
800
|
+
req.on("data", (chunk) => {
|
|
801
|
+
if (bodyTooLarge)
|
|
802
|
+
return;
|
|
803
|
+
body += chunk.toString();
|
|
804
|
+
if (Buffer.byteLength(body, "utf8") > HOOK_BODY_LIMIT) {
|
|
805
|
+
bodyTooLarge = true;
|
|
806
|
+
res.writeHead(413);
|
|
807
|
+
res.end("payload too large");
|
|
808
|
+
req.destroy();
|
|
809
|
+
}
|
|
810
|
+
});
|
|
744
811
|
req.on("end", () => {
|
|
812
|
+
if (bodyTooLarge || res.writableEnded)
|
|
813
|
+
return;
|
|
745
814
|
this.log(`hook body (${body.length} bytes): ${body.slice(0, 200)}`);
|
|
746
815
|
try {
|
|
747
816
|
const event = JSON.parse(body);
|
|
@@ -749,15 +818,27 @@ export class BridgeSession {
|
|
|
749
818
|
// PermissionRequest: hold connection, wait for user decision from mobile app
|
|
750
819
|
if (hookName === "PermissionRequest") {
|
|
751
820
|
const requestId = `pr-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
821
|
+
const timeout = setTimeout(() => {
|
|
822
|
+
if (this.resolvePendingPermission(requestId, "deny")) {
|
|
823
|
+
this.log(`permission request ${requestId} timed out`);
|
|
824
|
+
this.sendPermissionSnapshot(terminalId, "thinking", "permission timed out");
|
|
825
|
+
}
|
|
826
|
+
}, PERMISSION_REQUEST_TIMEOUT_MS);
|
|
827
|
+
this.pendingPermissions.set(requestId, {
|
|
828
|
+
terminalId,
|
|
829
|
+
timeout,
|
|
830
|
+
resolve: (decision) => {
|
|
831
|
+
if (res.writableEnded)
|
|
832
|
+
return;
|
|
833
|
+
const responseJson = JSON.stringify({
|
|
834
|
+
hookSpecificOutput: {
|
|
835
|
+
hookEventName: "PermissionRequest",
|
|
836
|
+
decision: { behavior: decision },
|
|
837
|
+
},
|
|
838
|
+
});
|
|
839
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
840
|
+
res.end(responseJson);
|
|
841
|
+
},
|
|
761
842
|
});
|
|
762
843
|
// Send status with requestId so app can route decision back
|
|
763
844
|
this.handleHookEvent(terminalId, event, provider, requestId);
|
|
@@ -814,7 +895,14 @@ export class BridgeSession {
|
|
|
814
895
|
}
|
|
815
896
|
catch { /* doesn't exist yet */ }
|
|
816
897
|
const hookEntry = { matcher: "", hooks: [{ type: "command", command: curlCmd, timeout: 5 }] };
|
|
817
|
-
const permissionEntry = {
|
|
898
|
+
const permissionEntry = {
|
|
899
|
+
matcher: "",
|
|
900
|
+
hooks: [{
|
|
901
|
+
type: "command",
|
|
902
|
+
command: curlCmd,
|
|
903
|
+
timeout: Math.ceil((PERMISSION_REQUEST_TIMEOUT_MS + 30_000) / 1000),
|
|
904
|
+
}],
|
|
905
|
+
};
|
|
818
906
|
const hookEvents = {
|
|
819
907
|
PreToolUse: hookEntry,
|
|
820
908
|
PostToolUse: hookEntry,
|
|
@@ -829,7 +917,7 @@ export class BridgeSession {
|
|
|
829
917
|
for (const [eventName, entry] of Object.entries(hookEvents)) {
|
|
830
918
|
let arr = Array.isArray(existingHooks[eventName]) ? existingHooks[eventName] : [];
|
|
831
919
|
// Remove any dead linkshell hook entries (from previous instances)
|
|
832
|
-
arr = arr.filter((e) => !
|
|
920
|
+
arr = arr.filter((e) => !isLinkShellHookEntry(e));
|
|
833
921
|
arr.push(entry);
|
|
834
922
|
existingHooks[eventName] = arr;
|
|
835
923
|
}
|
|
@@ -884,7 +972,7 @@ export class BridgeSession {
|
|
|
884
972
|
const existingHooks = existing.hooks ?? {};
|
|
885
973
|
for (const [eventName, entry] of Object.entries(hookEvents)) {
|
|
886
974
|
let arr = Array.isArray(existingHooks[eventName]) ? existingHooks[eventName] : [];
|
|
887
|
-
arr = arr.filter((e) => !
|
|
975
|
+
arr = arr.filter((e) => !isLinkShellHookEntry(e));
|
|
888
976
|
arr.push(entry);
|
|
889
977
|
existingHooks[eventName] = arr;
|
|
890
978
|
}
|
|
@@ -914,7 +1002,7 @@ export class BridgeSession {
|
|
|
914
1002
|
const existingHooks = (existing.hooks ?? {});
|
|
915
1003
|
for (const [eventName, entry] of Object.entries(hookEvents)) {
|
|
916
1004
|
let arr = Array.isArray(existingHooks[eventName]) ? existingHooks[eventName] : [];
|
|
917
|
-
arr = arr.filter((e) => !
|
|
1005
|
+
arr = arr.filter((e) => !isLinkShellHookEntry(e));
|
|
918
1006
|
arr.push(entry);
|
|
919
1007
|
existingHooks[eventName] = arr;
|
|
920
1008
|
}
|
|
@@ -949,7 +1037,7 @@ export class BridgeSession {
|
|
|
949
1037
|
const existingHooks = existing.hooks ?? {};
|
|
950
1038
|
for (const [eventName, entry] of Object.entries(hookEvents)) {
|
|
951
1039
|
let arr = Array.isArray(existingHooks[eventName]) ? existingHooks[eventName] : [];
|
|
952
|
-
arr = arr.filter((e) => !
|
|
1040
|
+
arr = arr.filter((e) => !isLinkShellHookEntry(e));
|
|
953
1041
|
arr.push(entry);
|
|
954
1042
|
existingHooks[eventName] = arr;
|
|
955
1043
|
}
|
|
@@ -1180,10 +1268,7 @@ export class BridgeSession {
|
|
|
1180
1268
|
}
|
|
1181
1269
|
/** Auto-resolve a single pending permission (user acted in terminal) */
|
|
1182
1270
|
autoResolvePending(requestId) {
|
|
1183
|
-
|
|
1184
|
-
if (resolve) {
|
|
1185
|
-
this.pendingPermissions.delete(requestId);
|
|
1186
|
-
resolve("allow");
|
|
1271
|
+
if (this.resolvePendingPermission(requestId, "allow")) {
|
|
1187
1272
|
this.log(`auto-resolved pending permission ${requestId} (user acted in terminal)`);
|
|
1188
1273
|
}
|
|
1189
1274
|
}
|
|
@@ -1192,15 +1277,48 @@ export class BridgeSession {
|
|
|
1192
1277
|
const stack = this.permissionStacks.get(terminalId);
|
|
1193
1278
|
if (!stack)
|
|
1194
1279
|
return;
|
|
1195
|
-
for (const entry of stack) {
|
|
1196
|
-
|
|
1197
|
-
if (resolve) {
|
|
1198
|
-
this.pendingPermissions.delete(entry.requestId);
|
|
1199
|
-
resolve("deny");
|
|
1280
|
+
for (const entry of [...stack]) {
|
|
1281
|
+
if (this.resolvePendingPermission(entry.requestId, "deny")) {
|
|
1200
1282
|
this.log(`drained pending permission ${entry.requestId}`);
|
|
1201
1283
|
}
|
|
1202
1284
|
}
|
|
1203
1285
|
}
|
|
1286
|
+
resolvePendingPermission(requestId, decision) {
|
|
1287
|
+
const pending = this.pendingPermissions.get(requestId);
|
|
1288
|
+
if (!pending)
|
|
1289
|
+
return false;
|
|
1290
|
+
this.pendingPermissions.delete(requestId);
|
|
1291
|
+
clearTimeout(pending.timeout);
|
|
1292
|
+
pending.resolve(decision);
|
|
1293
|
+
const stack = this.permissionStacks.get(pending.terminalId);
|
|
1294
|
+
if (stack) {
|
|
1295
|
+
const idx = stack.findIndex((entry) => entry.requestId === requestId);
|
|
1296
|
+
if (idx >= 0)
|
|
1297
|
+
stack.splice(idx, 1);
|
|
1298
|
+
if (stack.length === 0)
|
|
1299
|
+
this.permissionStacks.delete(pending.terminalId);
|
|
1300
|
+
}
|
|
1301
|
+
return true;
|
|
1302
|
+
}
|
|
1303
|
+
sendPermissionSnapshot(terminalId, phase, summary) {
|
|
1304
|
+
const stack = this.permissionStacks.get(terminalId);
|
|
1305
|
+
const topPermission = stack && stack.length > 0 ? stack[stack.length - 1] : undefined;
|
|
1306
|
+
const pendingPermissionCount = stack?.length ?? 0;
|
|
1307
|
+
const term = this.terminals.get(terminalId);
|
|
1308
|
+
const seq = term ? term.statusSeq++ : 0;
|
|
1309
|
+
this.send(createEnvelope({
|
|
1310
|
+
type: "terminal.status",
|
|
1311
|
+
sessionId: this.sessionId,
|
|
1312
|
+
terminalId,
|
|
1313
|
+
payload: {
|
|
1314
|
+
phase,
|
|
1315
|
+
seq,
|
|
1316
|
+
...(summary && { summary }),
|
|
1317
|
+
...(topPermission && { topPermission }),
|
|
1318
|
+
...(pendingPermissionCount > 0 && { pendingPermissionCount }),
|
|
1319
|
+
},
|
|
1320
|
+
}));
|
|
1321
|
+
}
|
|
1204
1322
|
cleanupHookServer(term) {
|
|
1205
1323
|
// Drain any pending permission requests for this terminal
|
|
1206
1324
|
this.drainPendingPermissions(term.id);
|
|
@@ -1374,6 +1492,10 @@ export class BridgeSession {
|
|
|
1374
1492
|
this.exited = true;
|
|
1375
1493
|
this.stopHeartbeat();
|
|
1376
1494
|
this.stopScreenCapture();
|
|
1495
|
+
this.agentSession?.stop();
|
|
1496
|
+
this.agentSession = undefined;
|
|
1497
|
+
this.keepAwake?.stop();
|
|
1498
|
+
this.keepAwake = undefined;
|
|
1377
1499
|
if (this.reconnectTimer) {
|
|
1378
1500
|
clearTimeout(this.reconnectTimer);
|
|
1379
1501
|
this.reconnectTimer = undefined;
|