linkshell-cli 0.5.34 → 0.5.36

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 (40) hide show
  1. package/dist/cli/src/runtime/acp/acp-client.d.ts +38 -0
  2. package/dist/cli/src/runtime/acp/acp-client.js +134 -20
  3. package/dist/cli/src/runtime/acp/acp-client.js.map +1 -1
  4. package/dist/cli/src/runtime/acp/agent-process-discovery.d.ts +48 -0
  5. package/dist/cli/src/runtime/acp/agent-process-discovery.js +267 -0
  6. package/dist/cli/src/runtime/acp/agent-process-discovery.js.map +1 -0
  7. package/dist/cli/src/runtime/acp/agent-workspace.d.ts +23 -0
  8. package/dist/cli/src/runtime/acp/agent-workspace.js +834 -90
  9. package/dist/cli/src/runtime/acp/agent-workspace.js.map +1 -1
  10. package/dist/cli/src/runtime/acp/provider-resolver.d.ts +3 -1
  11. package/dist/cli/src/runtime/acp/provider-resolver.js +13 -4
  12. package/dist/cli/src/runtime/acp/provider-resolver.js.map +1 -1
  13. package/dist/cli/src/runtime/bridge-session.d.ts +1 -0
  14. package/dist/cli/src/runtime/bridge-session.js +49 -18
  15. package/dist/cli/src/runtime/bridge-session.js.map +1 -1
  16. package/dist/cli/src/runtime/hook-command.d.ts +19 -0
  17. package/dist/cli/src/runtime/hook-command.js +31 -0
  18. package/dist/cli/src/runtime/hook-command.js.map +1 -0
  19. package/dist/cli/tsconfig.tsbuildinfo +1 -1
  20. package/dist/shared-protocol/src/index.d.ts +1161 -939
  21. package/dist/shared-protocol/src/index.js +43 -1
  22. package/dist/shared-protocol/src/index.js.map +1 -1
  23. package/package.json +3 -3
  24. package/src/runtime/acp/acp-client.ts +153 -22
  25. package/src/runtime/acp/agent-process-discovery.ts +301 -0
  26. package/src/runtime/acp/agent-workspace.ts +844 -96
  27. package/src/runtime/acp/provider-resolver.ts +18 -3
  28. package/src/runtime/bridge-session.ts +46 -18
  29. package/src/runtime/hook-command.ts +35 -0
  30. package/web/assets/{FileBrowser-9wRS6a0E.js → FileBrowser-Coy7Szjx.js} +1 -1
  31. package/web/assets/{PortPreview-yvXMn63g.js → PortPreview-D-XovbV1.js} +1 -1
  32. package/web/assets/{TerminalPanel-xtGQI_fv.js → TerminalPanel-Cah56AtC.js} +1 -1
  33. package/web/assets/{UsageDashboard-ByQ6mehF.js → UsageDashboard-DfkjJagC.js} +1 -1
  34. package/web/assets/index-BsnDaeCt.css +33 -0
  35. package/web/assets/index-ClZzQ4ba.js +35 -0
  36. package/web/assets/{vendor-highlight-Cus7zBI4.js → vendor-highlight-NXWAAyIv.js} +1 -1
  37. package/web/assets/{vendor-react-0zU5oKmd.js → vendor-react-M7xG3qiw.js} +8 -8
  38. package/web/index.html +4 -4
  39. package/web/assets/index-Vtyd7s5T.css +0 -33
  40. package/web/assets/index-pqslvKcS.js +0 -35
@@ -1,4 +1,6 @@
1
- export type AgentProvider = "codex" | "claude" | "custom";
1
+ import type { KnownAgentProvider } from "./agent-process-discovery.js";
2
+ export type AgentProvider = string;
3
+ export type { KnownAgentProvider };
2
4
  export type AgentProtocol = "acp" | "codex-app-server" | "claude-agent-sdk" | "claude-stream-json";
3
5
  export type AgentFraming = "content-length" | "newline";
4
6
  export interface AgentCommandConfig {
@@ -86,12 +86,21 @@ function resolveBinary(bin) {
86
86
  }
87
87
  return null;
88
88
  }
89
+ const INSTALL_BINARIES = [
90
+ { provider: "claude", binaries: ["claude"] },
91
+ { provider: "codex", binaries: ["codex"] },
92
+ { provider: "gemini", binaries: ["gemini"] },
93
+ { provider: "copilot", binaries: ["copilot"] },
94
+ { provider: "opencode", binaries: ["opencode", "opencode-ai"] },
95
+ { provider: "cursor", binaries: ["cursor-agent"] },
96
+ { provider: "kimi", binaries: ["kimi"] },
97
+ ];
89
98
  export function detectAvailableProviders() {
90
99
  const available = [];
91
- if (resolveBinary("claude"))
92
- available.push("claude");
93
- if (resolveBinary("codex"))
94
- available.push("codex");
100
+ for (const entry of INSTALL_BINARIES) {
101
+ if (entry.binaries.some((bin) => resolveBinary(bin)))
102
+ available.push(entry.provider);
103
+ }
95
104
  return available;
96
105
  }
97
106
  //# sourceMappingURL=provider-resolver.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"provider-resolver.js","sourceRoot":"","sources":["../../../../../src/runtime/acp/provider-resolver.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"provider-resolver.js","sourceRoot":"","sources":["../../../../../src/runtime/acp/provider-resolver.ts"],"names":[],"mappings":"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"}
@@ -57,6 +57,7 @@ export declare class BridgeSession {
57
57
  private keepAwake;
58
58
  private agentSession;
59
59
  private agentWorkspace;
60
+ private outboundAgentQueue;
60
61
  private machineIdentity;
61
62
  private readonly defaultCwd;
62
63
  private readonly fileRoot;
@@ -16,6 +16,7 @@ import { getValidToken, refreshAccessToken } from "../auth.js";
16
16
  import { AgentSessionProxy } from "./acp/agent-session.js";
17
17
  import { AgentWorkspaceProxy, makeAgentV2RemoteConversationId } from "./acp/agent-workspace.js";
18
18
  import { detectAvailableProviders } from "./acp/provider-resolver.js";
19
+ import { buildLinkShellHookCommand, shouldWriteProjectHooksJson } from "./hook-command.js";
19
20
  const HEARTBEAT_INTERVAL = 15_000;
20
21
  // Transport-layer WS keepalive: ping the gateway periodically; if a pong
21
22
  // doesn't arrive before the next ping, the socket is half-open (network
@@ -41,6 +42,7 @@ const EXITED_TERMINAL_GRACE_MS = 30_000;
41
42
  // allowlist is configured. Covers the common dev servers the app tunnels to.
42
43
  const DEFAULT_TUNNEL_PORTS = [3000, 3001, 4321, 5173, 5174, 8080, 8000, 8081];
43
44
  const PERMISSION_REQUEST_TIMEOUT_MS = Number(process.env.LINKSHELL_PERMISSION_TIMEOUT_MS ?? 5 * 60_000);
45
+ const HOOK_COMMAND_TIMEOUT_SEC = Math.ceil((PERMISSION_REQUEST_TIMEOUT_MS + 30_000) / 1000);
44
46
  const LINKSHELL_PERMISSION_GUARD_MARKER = "LINKSHELL_PERMISSION_GUARD";
45
47
  function isLinkShellHookEntry(entry, marker) {
46
48
  let raw = "";
@@ -220,9 +222,8 @@ export function resolvePairingGateway(gatewayHttpUrl, pairingGateway) {
220
222
  }
221
223
  }
222
224
  function normalizeAgentProvider(provider) {
223
- if (provider === "claude" || provider === "custom") {
224
- return provider;
225
- }
225
+ if (typeof provider === "string" && provider.trim())
226
+ return provider.trim();
226
227
  return "codex";
227
228
  }
228
229
  export function resolveAgentWorkspaceProviders(options) {
@@ -270,6 +271,7 @@ export class BridgeSession {
270
271
  keepAwake;
271
272
  agentSession;
272
273
  agentWorkspace;
274
+ outboundAgentQueue = [];
273
275
  machineIdentity;
274
276
  // Default host workspace shown to clients and used by the initial PTY.
275
277
  defaultCwd;
@@ -504,6 +506,11 @@ export class BridgeSession {
504
506
  projectName: basename(this.defaultCwd),
505
507
  },
506
508
  }));
509
+ if (this.outboundAgentQueue.length > 0) {
510
+ const queued = this.outboundAgentQueue.splice(0);
511
+ for (const envelope of queued)
512
+ this.send(envelope);
513
+ }
507
514
  this.startHeartbeat();
508
515
  this.startWsPing();
509
516
  });
@@ -1240,7 +1247,7 @@ export class BridgeSession {
1240
1247
  hookPort = result.port;
1241
1248
  hookConfigPaths.push(result.configPath);
1242
1249
  // Also set up hooks for other providers (curlCmd already has marker from setupHookServer)
1243
- const curlCmd = `curl -s --connect-timeout 1 --max-time ${Math.ceil((PERMISSION_REQUEST_TIMEOUT_MS + 30_000) / 1000)} -X POST "http://127.0.0.1:${result.port}/hook?m=${hookMarker}&lid=$LINKSHELL_ID" -H 'Content-Type: application/json' --data-binary @- || true`;
1250
+ const curlCmd = buildLinkShellHookCommand(result.port, hookMarker, HOOK_COMMAND_TIMEOUT_SEC);
1244
1251
  hookConfigPaths.push(this.setupCodexHooks(terminalId, curlCmd, hookMarker));
1245
1252
  hookConfigPaths.push(this.setupGeminiHooks(terminalId, curlCmd, hookMarker));
1246
1253
  hookConfigPaths.push(this.setupCopilotHooks(terminalId, curlCmd, hookMarker));
@@ -1346,8 +1353,8 @@ export class BridgeSession {
1346
1353
  const reqLid = reqUrl.searchParams.get("lid") ?? "";
1347
1354
  if (reqMarker !== marker || (reqLid !== "" && reqLid !== marker)) {
1348
1355
  this.log(`ignoring hook event: m=${reqMarker} lid=${reqLid} (expected ${marker})`);
1349
- res.writeHead(200);
1350
- res.end("ok");
1356
+ res.writeHead(200, { "Content-Type": "application/json" });
1357
+ res.end("{}");
1351
1358
  return;
1352
1359
  }
1353
1360
  let body = "";
@@ -1358,8 +1365,8 @@ export class BridgeSession {
1358
1365
  body += chunk.toString();
1359
1366
  if (Buffer.byteLength(body, "utf8") > HOOK_BODY_LIMIT) {
1360
1367
  bodyTooLarge = true;
1361
- res.writeHead(413);
1362
- res.end("payload too large");
1368
+ res.writeHead(413, { "Content-Type": "application/json" });
1369
+ res.end("{}");
1363
1370
  req.destroy();
1364
1371
  }
1365
1372
  });
@@ -1403,15 +1410,16 @@ export class BridgeSession {
1403
1410
  this.sendHookPermissionRequest(terminalId, event, requestId, provider);
1404
1411
  }
1405
1412
  else {
1406
- // All other hooks: respond immediately
1407
- res.writeHead(200);
1408
- res.end("ok");
1413
+ // All other hooks: respond immediately with valid JSON so clients
1414
+ // that parse stdout (Cursor) fail-open instead of blocking the tool.
1415
+ res.writeHead(200, { "Content-Type": "application/json" });
1416
+ res.end("{}");
1409
1417
  this.handleHookEvent(terminalId, event, provider);
1410
1418
  }
1411
1419
  }
1412
1420
  catch (e) {
1413
- res.writeHead(200);
1414
- res.end("ok");
1421
+ res.writeHead(200, { "Content-Type": "application/json" });
1422
+ res.end("{}");
1415
1423
  this.log(`hook parse error: ${e}`);
1416
1424
  }
1417
1425
  });
@@ -1425,7 +1433,7 @@ export class BridgeSession {
1425
1433
  server.on("error", reject);
1426
1434
  });
1427
1435
  this.log(`hook server for ${terminalId} (${provider}) listening on port ${port}, marker=${marker}`);
1428
- const curlCmd = `curl -s --connect-timeout 1 --max-time ${Math.ceil((PERMISSION_REQUEST_TIMEOUT_MS + 30_000) / 1000)} -X POST "http://127.0.0.1:${port}/hook?m=${marker}&lid=$LINKSHELL_ID" -H 'Content-Type: application/json' --data-binary @- || true`;
1436
+ const curlCmd = buildLinkShellHookCommand(port, marker, HOOK_COMMAND_TIMEOUT_SEC);
1429
1437
  let configPath;
1430
1438
  if (provider === "codex") {
1431
1439
  configPath = this.setupCodexHooks(terminalId, curlCmd, marker);
@@ -1447,7 +1455,7 @@ export class BridgeSession {
1447
1455
  if (!term?.hookPort)
1448
1456
  return;
1449
1457
  const marker = term.hookMarker;
1450
- const curlCmd = `curl -s --connect-timeout 1 --max-time ${Math.ceil((PERMISSION_REQUEST_TIMEOUT_MS + 30_000) / 1000)} -X POST "http://127.0.0.1:${term.hookPort}/hook?m=${marker}&lid=$LINKSHELL_ID" -H 'Content-Type: application/json' --data-binary @- || true`;
1458
+ const curlCmd = buildLinkShellHookCommand(term.hookPort, marker, HOOK_COMMAND_TIMEOUT_SEC);
1451
1459
  const providers = resolveAgentWorkspaceProviders(this.options);
1452
1460
  try {
1453
1461
  for (const provider of providers) {
@@ -1481,7 +1489,7 @@ export class BridgeSession {
1481
1489
  hooks: [{
1482
1490
  type: "command",
1483
1491
  command: curlCmd,
1484
- timeout: Math.ceil((PERMISSION_REQUEST_TIMEOUT_MS + 30_000) / 1000),
1492
+ timeout: HOOK_COMMAND_TIMEOUT_SEC,
1485
1493
  }],
1486
1494
  };
1487
1495
  const hookEvents = {
@@ -1540,7 +1548,7 @@ export class BridgeSession {
1540
1548
  hooks: [{
1541
1549
  type: "command",
1542
1550
  command: curlCmd,
1543
- timeout: Math.ceil((PERMISSION_REQUEST_TIMEOUT_MS + 30_000) / 1000),
1551
+ timeout: HOOK_COMMAND_TIMEOUT_SEC,
1544
1552
  }],
1545
1553
  };
1546
1554
  const hookEvents = {
@@ -1596,9 +1604,26 @@ export class BridgeSession {
1596
1604
  return settingsPath;
1597
1605
  }
1598
1606
  setupCopilotHooks(terminalId, curlCmd, marker) {
1599
- // Copilot loads hooks from CWD as hooks.json
1607
+ // Copilot loads hooks from CWD as hooks.json. Cursor's coding agent also
1608
+ // reads that file when the folder is a Cursor project — writing it here
1609
+ // freezes every IDE tool call the moment the hook server is down or
1610
+ // returns non-JSON.
1600
1611
  const cwd = this.terminals.get(terminalId)?.cwd ?? this.defaultCwd;
1601
1612
  const hooksPath = join(cwd, "hooks.json");
1613
+ if (!shouldWriteProjectHooksJson(cwd)) {
1614
+ this.sweepLinkShellHookEntries(hooksPath);
1615
+ try {
1616
+ if (existsSync(hooksPath)) {
1617
+ const raw = JSON.parse(readFileSync(hooksPath, "utf8"));
1618
+ if (!raw.hooks || (typeof raw.hooks === "object" && Object.keys(raw.hooks).length === 0)) {
1619
+ unlinkSync(hooksPath);
1620
+ }
1621
+ }
1622
+ }
1623
+ catch { /* leave whatever is on disk */ }
1624
+ this.log(`skip writing ${hooksPath}: Cursor project would inherit it and freeze the IDE agent`);
1625
+ return hooksPath;
1626
+ }
1602
1627
  const mkHook = () => ({
1603
1628
  type: "command",
1604
1629
  bash: curlCmd,
@@ -2156,6 +2181,12 @@ export class BridgeSession {
2156
2181
  }
2157
2182
  send(message) {
2158
2183
  if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
2184
+ if (message.type.startsWith("agent.v2.")) {
2185
+ this.outboundAgentQueue.push(message);
2186
+ if (this.outboundAgentQueue.length > 40) {
2187
+ this.outboundAgentQueue.splice(0, this.outboundAgentQueue.length - 40);
2188
+ }
2189
+ }
2159
2190
  return;
2160
2191
  }
2161
2192
  const machineId = this.machineIdentity?.machineId;