linkshell-cli 0.5.1 → 0.5.2

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 (46) hide show
  1. package/dist/cli/src/index.js +1 -1
  2. package/dist/cli/src/index.js.map +1 -1
  3. package/dist/cli/src/runtime/acp/acp-client.d.ts +16 -0
  4. package/dist/cli/src/runtime/acp/acp-client.js +40 -2
  5. package/dist/cli/src/runtime/acp/acp-client.js.map +1 -1
  6. package/dist/cli/src/runtime/acp/agent-workspace-git.d.ts +63 -0
  7. package/dist/cli/src/runtime/acp/agent-workspace-git.js +297 -0
  8. package/dist/cli/src/runtime/acp/agent-workspace-git.js.map +1 -0
  9. package/dist/cli/src/runtime/acp/agent-workspace.d.ts +6 -0
  10. package/dist/cli/src/runtime/acp/agent-workspace.js +690 -81
  11. package/dist/cli/src/runtime/acp/agent-workspace.js.map +1 -1
  12. package/dist/cli/src/runtime/acp/claude-sdk-client.d.ts +17 -1
  13. package/dist/cli/src/runtime/acp/claude-sdk-client.js +331 -19
  14. package/dist/cli/src/runtime/acp/claude-sdk-client.js.map +1 -1
  15. package/dist/cli/src/runtime/acp/claude-stream-json-client.js +69 -14
  16. package/dist/cli/src/runtime/acp/claude-stream-json-client.js.map +1 -1
  17. package/dist/cli/src/runtime/acp/json-rpc.d.ts +1 -0
  18. package/dist/cli/src/runtime/acp/json-rpc.js +6 -0
  19. package/dist/cli/src/runtime/acp/json-rpc.js.map +1 -1
  20. package/dist/cli/src/runtime/acp-relay.d.ts +23 -0
  21. package/dist/cli/src/runtime/acp-relay.js +73 -0
  22. package/dist/cli/src/runtime/acp-relay.js.map +1 -0
  23. package/dist/cli/src/runtime/bridge-session.d.ts +5 -0
  24. package/dist/cli/src/runtime/bridge-session.js +104 -57
  25. package/dist/cli/src/runtime/bridge-session.js.map +1 -1
  26. package/dist/cli/tsconfig.tsbuildinfo +1 -1
  27. package/dist/shared-protocol/src/index.d.ts +2505 -2479
  28. package/dist/shared-protocol/src/index.js +46 -0
  29. package/dist/shared-protocol/src/index.js.map +1 -1
  30. package/package.json +3 -3
  31. package/src/index.ts +1 -1
  32. package/src/runtime/acp/acp-client.ts +53 -2
  33. package/src/runtime/acp/agent-workspace.ts +752 -76
  34. package/src/runtime/acp/claude-sdk-client.ts +352 -31
  35. package/src/runtime/acp/claude-stream-json-client.ts +66 -16
  36. package/src/runtime/acp/json-rpc.ts +5 -0
  37. package/src/runtime/bridge-session.ts +114 -61
  38. package/dist/cli/src/runtime/acp/claude-sessions.d.ts +0 -57
  39. package/dist/cli/src/runtime/acp/claude-sessions.js +0 -506
  40. package/dist/cli/src/runtime/acp/claude-sessions.js.map +0 -1
  41. package/dist/cli/src/runtime/acp/codex-rpc-bridge.d.ts +0 -34
  42. package/dist/cli/src/runtime/acp/codex-rpc-bridge.js +0 -204
  43. package/dist/cli/src/runtime/acp/codex-rpc-bridge.js.map +0 -1
  44. package/dist/cli/src/runtime/acp/codex-sessions.d.ts +0 -60
  45. package/dist/cli/src/runtime/acp/codex-sessions.js +0 -731
  46. package/dist/cli/src/runtime/acp/codex-sessions.js.map +0 -1
@@ -6,6 +6,7 @@ import { writeFileSync, readFileSync, readdirSync, statSync, unlinkSync, mkdirSy
6
6
  import { tmpdir } from "node:os";
7
7
  import { join, basename, resolve } from "node:path";
8
8
  import {
9
+ agentV2MessageRoute,
9
10
  createEnvelope,
10
11
  parseEnvelope,
11
12
  parseTypedPayload,
@@ -211,9 +212,23 @@ function hookPermissionOptions(suggestions: unknown[]): Array<{
211
212
  ];
212
213
  }
213
214
 
215
+ function stripGatewayBase(url: URL): string {
216
+ url.hash = "";
217
+ url.search = "";
218
+ url.pathname = "";
219
+ return url.toString().replace(/\/+$/, "");
220
+ }
221
+
222
+ function isHttpGatewayUrl(url: URL): boolean {
223
+ return url.protocol === "http:" || url.protocol === "https:";
224
+ }
225
+
214
226
  function getPairingGatewayParam(gatewayHttpUrl: string): string | undefined {
215
227
  try {
216
228
  const url = new URL(gatewayHttpUrl);
229
+ if (!isHttpGatewayUrl(url)) {
230
+ return undefined;
231
+ }
217
232
  const hostname = url.hostname.trim().toLowerCase();
218
233
  if (
219
234
  hostname === "localhost" ||
@@ -227,15 +242,15 @@ function getPairingGatewayParam(gatewayHttpUrl: string): string | undefined {
227
242
  return undefined; // No LAN interface found, can't help
228
243
  }
229
244
  url.hostname = lanIp;
230
- return url.toString().replace(/\/+$/, "");
245
+ return stripGatewayBase(url);
231
246
  }
232
- return url.toString().replace(/\/+$/, "");
247
+ return stripGatewayBase(url);
233
248
  } catch {
234
249
  return gatewayHttpUrl.replace(/\/+$/, "") || undefined;
235
250
  }
236
251
  }
237
252
 
238
- function resolvePairingGateway(
253
+ export function resolvePairingGateway(
239
254
  gatewayHttpUrl: string,
240
255
  pairingGateway?: string,
241
256
  ): string | undefined {
@@ -246,24 +261,36 @@ function resolvePairingGateway(
246
261
 
247
262
  try {
248
263
  const absoluteUrl = new URL(override);
249
- return absoluteUrl.toString().replace(/\/+$/, "");
264
+ if (isHttpGatewayUrl(absoluteUrl)) {
265
+ return stripGatewayBase(absoluteUrl);
266
+ }
250
267
  } catch {
251
- try {
252
- const baseUrl = new URL(gatewayHttpUrl);
253
- const normalizedHost = override
254
- .replace(/^https?:\/\//i, "")
255
- .replace(/\/.*$/, "")
256
- .trim();
257
-
258
- if (!normalizedHost) {
259
- return getPairingGatewayParam(gatewayHttpUrl);
260
- }
268
+ // Fall through and treat the override as a host[:port] value.
269
+ }
261
270
 
262
- baseUrl.hostname = normalizedHost;
263
- return baseUrl.toString().replace(/\/+$/, "");
264
- } catch {
271
+ try {
272
+ const baseUrl = new URL(gatewayHttpUrl);
273
+ if (!isHttpGatewayUrl(baseUrl)) {
265
274
  return override.replace(/\/+$/, "") || undefined;
266
275
  }
276
+ const normalizedHost = override
277
+ .replace(/^https?:\/\//i, "")
278
+ .replace(/\/.*$/, "")
279
+ .trim();
280
+
281
+ if (!normalizedHost) {
282
+ return getPairingGatewayParam(gatewayHttpUrl);
283
+ }
284
+
285
+ baseUrl.host = normalizedHost;
286
+ return stripGatewayBase(baseUrl);
287
+ } catch {
288
+ try {
289
+ const prefixed = new URL(`http://${override.replace(/^\/+/, "")}`);
290
+ return stripGatewayBase(prefixed);
291
+ } catch {
292
+ return undefined;
293
+ }
267
294
  }
268
295
  }
269
296
 
@@ -274,6 +301,28 @@ function normalizeAgentProvider(provider: unknown): AgentProvider {
274
301
  return "codex";
275
302
  }
276
303
 
304
+ export function resolveAgentWorkspaceProviders(options: {
305
+ agentProvider?: AgentProvider;
306
+ agentCommand?: string;
307
+ }): AgentProvider[] {
308
+ if (options.agentCommand?.trim()) {
309
+ return [normalizeAgentProvider(options.agentProvider ?? "custom")];
310
+ }
311
+ const defaultProviders: AgentProvider[] = ["codex", "claude"];
312
+ const detected = detectAvailableProviders();
313
+ const requested = options.agentProvider ? normalizeAgentProvider(options.agentProvider) : undefined;
314
+ const ordered = [
315
+ ...(requested ? [requested] : []),
316
+ ...detected,
317
+ ...defaultProviders,
318
+ ];
319
+ const unique: AgentProvider[] = [];
320
+ for (const provider of ordered) {
321
+ if (!unique.includes(provider)) unique.push(provider);
322
+ }
323
+ return unique;
324
+ }
325
+
277
326
  export class BridgeSession {
278
327
  private readonly options: BridgeSessionOptions;
279
328
  private socket: WebSocket | undefined;
@@ -334,9 +383,7 @@ export class BridgeSession {
334
383
  }
335
384
  if (this.options.agentUi) {
336
385
  process.env.LINKSHELL_ID = this.terminalHookMarker(DEFAULT_TERMINAL_ID);
337
- const availableProviders = this.options.agentProvider
338
- ? [normalizeAgentProvider(this.options.agentProvider)]
339
- : detectAvailableProviders();
386
+ const availableProviders = resolveAgentWorkspaceProviders(this.options);
340
387
  const agentOptions = {
341
388
  sessionId: this.sessionId,
342
389
  cwd: process.cwd(),
@@ -516,6 +563,52 @@ export class BridgeSession {
516
563
 
517
564
  private async handleMessage(envelope: Envelope): Promise<void> {
518
565
  const tid = envelope.terminalId ?? DEFAULT_TERMINAL_ID;
566
+ const agentV2Route = agentV2MessageRoute(envelope.type);
567
+ if (agentV2Route === "client_write" || agentV2Route === "client_read") {
568
+ if (!this.agentWorkspace) {
569
+ this.send(
570
+ createEnvelope({
571
+ type: "agent.v2.capabilities",
572
+ sessionId: this.sessionId,
573
+ payload: {
574
+ enabled: false,
575
+ provider: normalizeAgentProvider(
576
+ this.options.agentProvider ?? "codex",
577
+ ),
578
+ machineId: this.machineIdentity?.machineId,
579
+ workspaceProtocolVersion: 2,
580
+ error: "Agent Workspace is not enabled. Start CLI with --agent-ui.",
581
+ supportsSessionList: false,
582
+ supportsSessionLoad: false,
583
+ supportsImages: false,
584
+ supportsAudio: false,
585
+ supportsPermission: false,
586
+ supportsPlan: false,
587
+ supportsCancel: false,
588
+ },
589
+ }),
590
+ );
591
+ return;
592
+ }
593
+ if (envelope.type === "agent.v2.prompt" || envelope.type === "agent.v2.command.execute") {
594
+ this.refreshAgentPermissionHooks();
595
+ }
596
+ try {
597
+ await this.agentWorkspace.handleEnvelope(envelope);
598
+ } catch (error) {
599
+ const message = error instanceof Error ? error.message : String(error);
600
+ this.log(`agent.v2 invalid message: ${message}`);
601
+ this.send(createEnvelope({
602
+ type: "session.error",
603
+ sessionId: this.sessionId,
604
+ payload: {
605
+ code: "invalid_message",
606
+ message: `Invalid Agent Workspace message: ${message}`,
607
+ },
608
+ }));
609
+ }
610
+ return;
611
+ }
519
612
  switch (envelope.type) {
520
613
  case "terminal.input": {
521
614
  const p = parseTypedPayload("terminal.input", envelope.payload);
@@ -842,44 +935,6 @@ export class BridgeSession {
842
935
  await this.agentSession.handleEnvelope(envelope);
843
936
  break;
844
937
  }
845
- case "agent.v2.capabilities.request":
846
- case "agent.v2.conversation.open":
847
- case "agent.v2.conversation.list":
848
- case "agent.v2.prompt":
849
- case "agent.v2.command.execute":
850
- case "agent.v2.cancel":
851
- case "agent.v2.permission.respond":
852
- case "agent.v2.structured_input.respond":
853
- case "agent.v2.snapshot.request": {
854
- if (!this.agentWorkspace) {
855
- this.send(
856
- createEnvelope({
857
- type: "agent.v2.capabilities",
858
- sessionId: this.sessionId,
859
- payload: {
860
- enabled: false,
861
- provider: normalizeAgentProvider(
862
- this.options.agentProvider ?? "codex",
863
- ),
864
- machineId: this.machineIdentity?.machineId,
865
- workspaceProtocolVersion: 2,
866
- error: "Agent Workspace is not enabled. Start CLI with --agent-ui.",
867
- supportsSessionList: false,
868
- supportsSessionLoad: false,
869
- supportsImages: false,
870
- supportsAudio: false,
871
- supportsPermission: false,
872
- supportsPlan: false,
873
- supportsCancel: false,
874
- },
875
- }),
876
- );
877
- break;
878
- }
879
- if (envelope.type === "agent.v2.prompt" || envelope.type === "agent.v2.command.execute") this.refreshAgentPermissionHooks();
880
- await this.agentWorkspace.handleEnvelope(envelope);
881
- break;
882
- }
883
938
  case "file.upload": {
884
939
  const p = parseTypedPayload("file.upload", envelope.payload);
885
940
  const ext = p.filename.split(".").pop() || "png";
@@ -1397,9 +1452,7 @@ export class BridgeSession {
1397
1452
  if (!term?.hookPort) return;
1398
1453
  const marker = term.hookMarker;
1399
1454
  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`;
1400
- const providers = this.options.agentProvider
1401
- ? [normalizeAgentProvider(this.options.agentProvider)]
1402
- : detectAvailableProviders();
1455
+ const providers = resolveAgentWorkspaceProviders(this.options);
1403
1456
  try {
1404
1457
  for (const provider of providers) {
1405
1458
  if (provider === "codex") {
@@ -1,57 +0,0 @@
1
- export interface ClaudeStoredSession {
2
- id: string;
3
- cwd: string;
4
- title?: string;
5
- createdAt?: number;
6
- lastModified: number;
7
- }
8
- export interface StoredAgentTimelineItem {
9
- id: string;
10
- conversationId: string;
11
- type: "message" | "tool_call";
12
- kind?: "tool_activity" | "command_execution" | "file_change";
13
- itemId?: string;
14
- role?: "user" | "assistant" | "system";
15
- content?: Array<{
16
- type: "text";
17
- text: string;
18
- }>;
19
- text?: string;
20
- toolCall?: {
21
- id: string;
22
- name: string;
23
- input?: string;
24
- output?: string;
25
- createdAt?: number;
26
- status: "pending" | "running" | "completed" | "failed";
27
- };
28
- commandExecution?: {
29
- command?: string;
30
- cwd?: string;
31
- output?: string;
32
- exitCode?: number | null;
33
- status?: "pending" | "running" | "completed" | "failed";
34
- };
35
- fileChange?: {
36
- entries: Array<{
37
- path: string;
38
- kind?: string;
39
- added?: number;
40
- removed?: number;
41
- }>;
42
- diff?: string;
43
- summary?: string;
44
- status?: "pending" | "running" | "completed" | "failed";
45
- };
46
- createdAt: number;
47
- updatedAt?: number;
48
- metadata?: Record<string, unknown>;
49
- }
50
- export declare function listClaudeStoredSessions(inputCwd: string): {
51
- sessions: ClaudeStoredSession[];
52
- };
53
- export declare function loadClaudeStoredTimeline(sessionId: string, conversationId: string, options?: {
54
- maxItems?: number;
55
- }): {
56
- items: StoredAgentTimelineItem[];
57
- };