linkshell-cli 0.2.114 → 0.2.115

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.
@@ -1207,6 +1207,7 @@ export class AgentWorkspaceProxy {
1207
1207
  private error: string | undefined;
1208
1208
  private activeConversationId: string | undefined;
1209
1209
  private currentTurnIds = new Map<string, string>();
1210
+ private turnConversationIds = new Map<string, string>();
1210
1211
  private conversations = new Map<string, AgentConversation>();
1211
1212
  private conversationByAgentSessionId = new Map<string, string>();
1212
1213
  private timelines = new Map<string, AgentTimelineItem[]>();
@@ -1216,6 +1217,7 @@ export class AgentWorkspaceProxy {
1216
1217
  private permissionSources = new Map<string, string>();
1217
1218
  private pendingStructuredInputs = new Map<string, { conversationId: string; input: AgentStructuredInput }>();
1218
1219
  private structuredInputWaiters = new Map<string, PendingStructuredInputWaiter>();
1220
+ private itemConversationIds = new Map<string, string>();
1219
1221
  private toolConversationIds = new Map<string, string>();
1220
1222
 
1221
1223
  constructor(
@@ -1277,7 +1279,7 @@ export class AgentWorkspaceProxy {
1277
1279
  sessionId: conversation?.agentSessionId,
1278
1280
  turnId: this.currentTurnIds.get(payload.conversationId),
1279
1281
  });
1280
- this.currentTurnIds.delete(payload.conversationId);
1282
+ this.forgetCurrentTurn(payload.conversationId);
1281
1283
  this.updateConversationStatus(payload.conversationId, "idle");
1282
1284
  this.emitStatus(payload.conversationId, "idle", "已停止");
1283
1285
  break;
@@ -1769,7 +1771,7 @@ export class AgentWorkspaceProxy {
1769
1771
  this.conversationByAgentSessionId.set(nextAgentSessionId, conversation.id);
1770
1772
  }
1771
1773
  const turnId = this.extractTurnId(result);
1772
- if (turnId) this.currentTurnIds.set(conversation.id, turnId);
1774
+ if (turnId) this.rememberTurnConversationId(conversation.id, turnId);
1773
1775
  if (conversation.status === "running" && protocol !== "codex-app-server") {
1774
1776
  this.updateConversationStatus(conversation.id, "idle");
1775
1777
  }
@@ -2007,7 +2009,7 @@ export class AgentWorkspaceProxy {
2007
2009
  process.stderr.write(`[agent:v2] ${method} ${stringify(params).slice(0, 500)}\n`);
2008
2010
  }
2009
2011
  if (method === "initialized") {
2010
- const conversationId = this.conversationIdFromParams(params) ?? this.activeConversationId;
2012
+ const conversationId = this.conversationIdFromParams(params) ?? this.fallbackConversationId();
2011
2013
  const provider = conversationId ? this.conversations.get(conversationId)?.provider : this.input.availableProviders[0];
2012
2014
  if (provider) {
2013
2015
  const commands = runtimeCommands(provider, params);
@@ -2033,7 +2035,7 @@ export class AgentWorkspaceProxy {
2033
2035
  return;
2034
2036
  }
2035
2037
 
2036
- const conversationId = this.conversationIdFromParams(params) ?? this.activeConversationId;
2038
+ const conversationId = this.conversationIdFromParams(params) ?? this.fallbackConversationId();
2037
2039
  if (method === "item/tool/requestUserInput" || method === "tool/requestUserInput") {
2038
2040
  this.handleStructuredInput(params);
2039
2041
  return;
@@ -2058,14 +2060,14 @@ export class AgentWorkspaceProxy {
2058
2060
  if (method === "turn/started") {
2059
2061
  if (conversationId) {
2060
2062
  const turnId = this.extractTurnId(params);
2061
- if (turnId) this.currentTurnIds.set(conversationId, turnId);
2063
+ if (turnId) this.rememberTurnConversationId(conversationId, turnId);
2062
2064
  this.updateConversationStatus(conversationId, "running");
2063
2065
  }
2064
2066
  return;
2065
2067
  }
2066
2068
  if (method === "turn/completed") {
2067
2069
  if (conversationId) {
2068
- this.currentTurnIds.delete(conversationId);
2070
+ this.forgetCurrentTurn(conversationId, this.extractTurnId(params));
2069
2071
  this.updateConversationStatus(conversationId, "idle");
2070
2072
  }
2071
2073
  return;
@@ -2124,7 +2126,7 @@ export class AgentWorkspaceProxy {
2124
2126
  private handleAgentMessageDelta(params: unknown): void {
2125
2127
  const raw = asRecord(params);
2126
2128
  if (!raw) return;
2127
- const conversationId = this.conversationIdFromParams(raw) ?? this.activeConversationId;
2129
+ const conversationId = this.conversationIdFromParams(raw) ?? this.fallbackConversationId();
2128
2130
  if (!conversationId) return;
2129
2131
  const itemId = firstString(raw, ["itemId", "id", "messageId"]) ?? id("msg");
2130
2132
  const delta = firstString(raw, ["delta", "text", "content"]);
@@ -2148,7 +2150,7 @@ export class AgentWorkspaceProxy {
2148
2150
 
2149
2151
  private handlePlanUpdated(params: unknown): void {
2150
2152
  const raw = asRecord(params);
2151
- const conversationId = this.conversationIdFromParams(raw) ?? this.activeConversationId;
2153
+ const conversationId = this.conversationIdFromParams(raw) ?? this.fallbackConversationId();
2152
2154
  if (!conversationId) return;
2153
2155
  const plan = Array.isArray(raw?.plan) ? raw.plan : [];
2154
2156
  const steps = plan
@@ -2177,7 +2179,7 @@ export class AgentWorkspaceProxy {
2177
2179
  private handlePlanDelta(params: unknown): void {
2178
2180
  const raw = asRecord(params);
2179
2181
  if (!raw) return;
2180
- const conversationId = this.conversationIdFromParams(raw) ?? this.activeConversationId;
2182
+ const conversationId = this.conversationIdFromParams(raw) ?? this.fallbackConversationId();
2181
2183
  if (!conversationId) return;
2182
2184
  const itemId = firstString(raw, ["itemId", "id"]) ?? "plan";
2183
2185
  const delta = firstString(raw, ["delta", "text"]);
@@ -2199,23 +2201,25 @@ export class AgentWorkspaceProxy {
2199
2201
  private handleItemStarted(params: unknown): void {
2200
2202
  const item = extractItem(params);
2201
2203
  if (!item) return;
2202
- const itemType = firstString(item, ["type"]);
2204
+ const sourceConversationId = this.conversationIdFromParams(params);
2205
+ const routedItem = sourceConversationId ? { ...item, conversationId: sourceConversationId } : item;
2206
+ const itemType = firstString(routedItem, ["type"]);
2203
2207
  const normalizedItemType = normalizedIdentifier(itemType);
2204
2208
  if (normalizedItemType === "agentmessage" || normalizedItemType === "assistantmessage") {
2205
- this.handleCompletedMessageItem(item, true);
2209
+ this.handleCompletedMessageItem(routedItem, true);
2206
2210
  return;
2207
2211
  }
2208
2212
  if (normalizedItemType === "plan") {
2209
- this.handlePlanUpdated({ plan: [item] });
2213
+ this.handlePlanUpdated({ plan: [routedItem], conversationId: sourceConversationId });
2210
2214
  return;
2211
2215
  }
2212
2216
  if (isSubagentItemType(itemType)) {
2213
- this.handleSubagentItem(item, "running", true);
2217
+ this.handleSubagentItem(routedItem, "running", true);
2214
2218
  return;
2215
2219
  }
2216
- if (this.handleSemanticSystemItem(item, "running", true)) return;
2217
- const conversationId = this.conversationIdFromParams(item) ?? this.activeConversationId;
2218
- const toolCall = this.toolCallFromItem(item, "running");
2220
+ if (this.handleSemanticSystemItem(routedItem, "running", true)) return;
2221
+ const conversationId = this.conversationIdFromParams(routedItem) ?? this.fallbackConversationId();
2222
+ const toolCall = this.toolCallFromItem(routedItem, "running");
2219
2223
  if (!conversationId || !toolCall) return;
2220
2224
  this.toolConversationIds.set(toolCall.id, conversationId);
2221
2225
  this.upsertTool(conversationId, toolCall);
@@ -2224,23 +2228,25 @@ export class AgentWorkspaceProxy {
2224
2228
  private handleItemCompleted(params: unknown): void {
2225
2229
  const item = extractItem(params);
2226
2230
  if (!item) return;
2227
- const itemType = firstString(item, ["type"]);
2231
+ const sourceConversationId = this.conversationIdFromParams(params);
2232
+ const routedItem = sourceConversationId ? { ...item, conversationId: sourceConversationId } : item;
2233
+ const itemType = firstString(routedItem, ["type"]);
2228
2234
  const normalizedItemType = normalizedIdentifier(itemType);
2229
2235
  if (normalizedItemType === "agentmessage" || normalizedItemType === "assistantmessage") {
2230
- this.handleCompletedMessageItem(item, false);
2236
+ this.handleCompletedMessageItem(routedItem, false);
2231
2237
  return;
2232
2238
  }
2233
2239
  if (normalizedItemType === "plan") {
2234
- this.handlePlanDelta({ ...item, delta: firstString(item, ["text", "content", "message"]) });
2240
+ this.handlePlanDelta({ ...routedItem, delta: firstString(routedItem, ["text", "content", "message"]) });
2235
2241
  return;
2236
2242
  }
2237
2243
  if (isSubagentItemType(itemType)) {
2238
- this.handleSubagentItem(item, normalizeToolStatus(item.status, true), false);
2244
+ this.handleSubagentItem(routedItem, normalizeToolStatus(routedItem.status, true), false);
2239
2245
  return;
2240
2246
  }
2241
- if (this.handleSemanticSystemItem(item, normalizeToolStatus(item.status, true), false)) return;
2242
- const conversationId = this.conversationIdFromParams(item) ?? this.activeConversationId;
2243
- const toolCall = this.toolCallFromItem(item, normalizeToolStatus(item.status, true));
2247
+ if (this.handleSemanticSystemItem(routedItem, normalizeToolStatus(routedItem.status, true), false)) return;
2248
+ const conversationId = this.conversationIdFromParams(routedItem) ?? this.fallbackConversationId();
2249
+ const toolCall = this.toolCallFromItem(routedItem, normalizeToolStatus(routedItem.status, true));
2244
2250
  if (!conversationId || !toolCall) return;
2245
2251
  const bufferedOutput = this.toolOutputBuffers.get(toolCall.id);
2246
2252
  if (bufferedOutput && !toolCall.output) toolCall.output = bufferedOutput;
@@ -2256,7 +2262,8 @@ export class AgentWorkspaceProxy {
2256
2262
  const conversationId =
2257
2263
  this.conversationIdFromParams(raw) ??
2258
2264
  this.toolConversationIds.get(itemId) ??
2259
- this.activeConversationId;
2265
+ this.itemConversationIds.get(itemId) ??
2266
+ this.fallbackConversationId();
2260
2267
  if (!conversationId) return;
2261
2268
  const output = appendCapped(this.toolOutputBuffers.get(itemId), delta, 6000);
2262
2269
  this.toolOutputBuffers.set(itemId, output);
@@ -2278,7 +2285,8 @@ export class AgentWorkspaceProxy {
2278
2285
  const conversationId =
2279
2286
  this.conversationIdFromParams(raw) ??
2280
2287
  this.toolConversationIds.get(itemId) ??
2281
- this.activeConversationId;
2288
+ this.itemConversationIds.get(itemId) ??
2289
+ this.fallbackConversationId();
2282
2290
  if (!conversationId) return;
2283
2291
  const output =
2284
2292
  extractDiffText(raw) ??
@@ -2297,7 +2305,7 @@ export class AgentWorkspaceProxy {
2297
2305
  private handleTurnDiffUpdated(params: unknown): void {
2298
2306
  const raw = asRecord(params);
2299
2307
  if (!raw) return;
2300
- const conversationId = this.conversationIdFromParams(raw) ?? this.activeConversationId;
2308
+ const conversationId = this.conversationIdFromParams(raw) ?? this.fallbackConversationId();
2301
2309
  if (!conversationId) return;
2302
2310
  const diff = extractDiffText(raw);
2303
2311
  if (!diff) return;
@@ -2327,7 +2335,8 @@ export class AgentWorkspaceProxy {
2327
2335
  const conversationId =
2328
2336
  this.conversationIdFromParams(raw) ??
2329
2337
  this.toolConversationIds.get(processId) ??
2330
- this.activeConversationId;
2338
+ this.itemConversationIds.get(processId) ??
2339
+ this.fallbackConversationId();
2331
2340
  if (!conversationId) return;
2332
2341
  const output = appendCapped(this.toolOutputBuffers.get(processId), delta, 6000);
2333
2342
  this.toolOutputBuffers.set(processId, output);
@@ -2344,7 +2353,7 @@ export class AgentWorkspaceProxy {
2344
2353
 
2345
2354
  private handleAutoApprovalReview(params: unknown, streaming: boolean): void {
2346
2355
  const raw = asRecord(params) ?? {};
2347
- const conversationId = this.conversationIdFromParams(raw) ?? this.activeConversationId;
2356
+ const conversationId = this.conversationIdFromParams(raw) ?? this.fallbackConversationId();
2348
2357
  if (!conversationId) return;
2349
2358
  const itemId = firstString(raw, ["itemId", "id", "reviewId"]) ?? "auto-approval-review";
2350
2359
  const existing = this.findItem(conversationId, itemId);
@@ -2374,7 +2383,7 @@ export class AgentWorkspaceProxy {
2374
2383
  }
2375
2384
 
2376
2385
  private handleCompletedMessageItem(item: Record<string, unknown>, streaming: boolean): void {
2377
- const conversationId = this.conversationIdFromParams(item) ?? this.activeConversationId;
2386
+ const conversationId = this.conversationIdFromParams(item) ?? this.fallbackConversationId();
2378
2387
  if (!conversationId) return;
2379
2388
  const itemId = firstString(item, ["id"]) ?? id("msg");
2380
2389
  const existing = this.findItem(conversationId, itemId);
@@ -2401,7 +2410,7 @@ export class AgentWorkspaceProxy {
2401
2410
  firstString(raw, ["delta", "text", "content", "message"]) ??
2402
2411
  firstString(nested, ["delta", "text", "content", "message"]);
2403
2412
  if (!text) return;
2404
- const conversationId = this.conversationIdFromParams(raw) ?? this.activeConversationId;
2413
+ const conversationId = this.conversationIdFromParams(raw) ?? this.fallbackConversationId();
2405
2414
  if (!conversationId) return;
2406
2415
  if (firstString(raw, ["toolName", "tool", "name"])) {
2407
2416
  this.upsertTool(conversationId, {
@@ -2438,7 +2447,7 @@ export class AgentWorkspaceProxy {
2438
2447
  ): boolean {
2439
2448
  const itemType = firstString(item, ["type"]);
2440
2449
  const normalized = normalizedIdentifier(itemType);
2441
- const conversationId = this.conversationIdFromParams(item) ?? this.activeConversationId;
2450
+ const conversationId = this.conversationIdFromParams(item) ?? this.fallbackConversationId();
2442
2451
  if (!conversationId) return false;
2443
2452
  const itemId = firstString(item, ["id", "itemId"]) ?? id("item");
2444
2453
  const existing = this.findItem(conversationId, itemId);
@@ -2493,7 +2502,7 @@ export class AgentWorkspaceProxy {
2493
2502
  status: AgentToolCall["status"],
2494
2503
  streaming: boolean,
2495
2504
  ): void {
2496
- const conversationId = this.conversationIdFromParams(item) ?? this.activeConversationId;
2505
+ const conversationId = this.conversationIdFromParams(item) ?? this.fallbackConversationId();
2497
2506
  if (!conversationId) return;
2498
2507
  const subagent = decodeSubagentAction(item, status);
2499
2508
  if (!subagent) return;
@@ -2519,7 +2528,7 @@ export class AgentWorkspaceProxy {
2519
2528
 
2520
2529
  private handleStructuredInput(params: unknown, waitForResponse = false): Promise<unknown> | void {
2521
2530
  const raw = asRecord(params) ?? {};
2522
- const conversationId = this.conversationIdFromParams(raw) ?? this.activeConversationId;
2531
+ const conversationId = this.conversationIdFromParams(raw) ?? this.fallbackConversationId();
2523
2532
  const source = firstString(raw, ["method", "source", "requestMethod"]);
2524
2533
  const formatResponse = source === "mcpServer/elicitation/request"
2525
2534
  ? formatMcpElicitationResponse
@@ -2590,7 +2599,7 @@ export class AgentWorkspaceProxy {
2590
2599
  source?: string,
2591
2600
  ): Promise<unknown> | void {
2592
2601
  const raw = asRecord(params) ?? {};
2593
- const conversationId = this.conversationIdFromParams(raw) ?? this.activeConversationId;
2602
+ const conversationId = this.conversationIdFromParams(raw) ?? this.fallbackConversationId();
2594
2603
  if (!conversationId) return waitForResponse ? Promise.resolve({ outcome: { outcome: "cancelled" } }) : undefined;
2595
2604
  const requestId = firstString(raw, ["requestId", "id", "permissionId"]) ?? id("perm");
2596
2605
  const rawToolCall = asRecord(raw.toolCall) ?? raw;
@@ -2726,6 +2735,7 @@ export class AgentWorkspaceProxy {
2726
2735
  }
2727
2736
 
2728
2737
  private addItem(conversationId: string, item: AgentTimelineItem): void {
2738
+ this.rememberItemConversationId(conversationId, item);
2729
2739
  const timeline = this.timelines.get(conversationId) ?? [];
2730
2740
  timeline.push(item);
2731
2741
  timeline.sort((a, b) => a.createdAt - b.createdAt);
@@ -2737,6 +2747,7 @@ export class AgentWorkspaceProxy {
2737
2747
  }
2738
2748
 
2739
2749
  private upsertItem(conversationId: string, item: AgentTimelineItem): void {
2750
+ this.rememberItemConversationId(conversationId, item);
2740
2751
  const timeline = this.timelines.get(conversationId) ?? [];
2741
2752
  const index = timeline.findIndex((entry) => entry.id === item.id);
2742
2753
  if (index >= 0) timeline[index] = item;
@@ -2764,6 +2775,8 @@ export class AgentWorkspaceProxy {
2764
2775
  };
2765
2776
  this.toolConversationIds.set(toolCall.id, conversationId);
2766
2777
  this.toolConversationIds.set(nextToolCall.id, conversationId);
2778
+ this.itemConversationIds.set(toolCall.id, conversationId);
2779
+ this.itemConversationIds.set(nextToolCall.id, conversationId);
2767
2780
  const kind: AgentTimelineKind = nextToolCall.name.includes("文件")
2768
2781
  ? "file_change"
2769
2782
  : nextToolCall.name.includes("命令")
@@ -2863,6 +2876,22 @@ export class AgentWorkspaceProxy {
2863
2876
  this.toolConversationIds.set(toolId, newId);
2864
2877
  }
2865
2878
  }
2879
+ for (const [turnId, conversationId] of this.turnConversationIds) {
2880
+ if (conversationId === oldId) {
2881
+ this.turnConversationIds.set(turnId, newId);
2882
+ }
2883
+ }
2884
+ const currentTurnId = this.currentTurnIds.get(oldId);
2885
+ if (currentTurnId) {
2886
+ this.currentTurnIds.delete(oldId);
2887
+ this.currentTurnIds.set(newId, currentTurnId);
2888
+ this.turnConversationIds.set(currentTurnId, newId);
2889
+ }
2890
+ for (const [itemId, conversationId] of this.itemConversationIds) {
2891
+ if (conversationId === oldId) {
2892
+ this.itemConversationIds.set(itemId, newId);
2893
+ }
2894
+ }
2866
2895
  if (this.activeConversationId === oldId) {
2867
2896
  this.activeConversationId = newId;
2868
2897
  }
@@ -2941,13 +2970,86 @@ export class AgentWorkspaceProxy {
2941
2970
 
2942
2971
  private conversationIdFromParams(params: unknown): string | undefined {
2943
2972
  const raw = asRecord(params);
2944
- const agentSessionId = this.extractSessionId(raw);
2945
- if (agentSessionId) return this.conversationByAgentSessionId.get(agentSessionId);
2973
+ if (!raw) return undefined;
2974
+ const directConversationId = firstString(raw, ["conversationId"]);
2975
+ if (directConversationId && this.conversations.has(directConversationId)) {
2976
+ return directConversationId;
2977
+ }
2946
2978
  const threadId = firstString(raw, ["threadId", "sessionId", "agentSessionId"]);
2947
- if (threadId) return this.conversationByAgentSessionId.get(threadId);
2979
+ if (threadId) {
2980
+ const conversationId = this.conversationByAgentSessionId.get(threadId);
2981
+ if (conversationId) return conversationId;
2982
+ }
2983
+ const agentSessionId = this.extractSessionId(raw);
2984
+ if (agentSessionId) {
2985
+ const conversationId = this.conversationByAgentSessionId.get(agentSessionId);
2986
+ if (conversationId) return conversationId;
2987
+ }
2988
+ const turnId = this.extractTurnId(raw) ?? firstString(raw, ["turnId"]);
2989
+ if (turnId) {
2990
+ const conversationId = this.turnConversationIds.get(turnId);
2991
+ if (conversationId) return conversationId;
2992
+ }
2993
+ const itemId = firstString(raw, [
2994
+ "itemId",
2995
+ "messageId",
2996
+ "toolCallId",
2997
+ "processId",
2998
+ "callId",
2999
+ "requestId",
3000
+ "permissionId",
3001
+ "id",
3002
+ ]);
3003
+ if (itemId) {
3004
+ const conversationId =
3005
+ this.itemConversationIds.get(itemId) ??
3006
+ this.toolConversationIds.get(itemId);
3007
+ if (conversationId) return conversationId;
3008
+ }
3009
+ for (const nested of [raw.params, raw.item, raw.message, raw.toolCall, raw.command, raw.event]) {
3010
+ const nestedRecord = asRecord(nested);
3011
+ if (!nestedRecord || nestedRecord === raw) continue;
3012
+ const conversationId = this.conversationIdFromParams(nestedRecord);
3013
+ if (conversationId) return conversationId;
3014
+ }
2948
3015
  return undefined;
2949
3016
  }
2950
3017
 
3018
+ private fallbackConversationId(): string | undefined {
3019
+ const liveConversations = [...this.conversations.values()].filter((conversation) =>
3020
+ conversation.status === "running" || conversation.status === "waiting_permission",
3021
+ );
3022
+ return liveConversations.length === 1 ? liveConversations[0]?.id : undefined;
3023
+ }
3024
+
3025
+ private rememberTurnConversationId(conversationId: string, turnId: string): void {
3026
+ this.currentTurnIds.set(conversationId, turnId);
3027
+ this.turnConversationIds.set(turnId, conversationId);
3028
+ }
3029
+
3030
+ private forgetCurrentTurn(conversationId: string, turnId?: string): void {
3031
+ const currentTurnId = this.currentTurnIds.get(conversationId);
3032
+ this.currentTurnIds.delete(conversationId);
3033
+ if (turnId) this.turnConversationIds.delete(turnId);
3034
+ if (currentTurnId && currentTurnId !== turnId) this.turnConversationIds.delete(currentTurnId);
3035
+ }
3036
+
3037
+ private rememberItemConversationId(conversationId: string, item: AgentTimelineItem): void {
3038
+ const keys = [
3039
+ item.id,
3040
+ item.itemId,
3041
+ item.toolCall?.id,
3042
+ item.permission?.requestId,
3043
+ item.structuredInput?.requestId,
3044
+ ].filter((key): key is string => Boolean(key));
3045
+ for (const key of keys) {
3046
+ this.itemConversationIds.set(key, conversationId);
3047
+ }
3048
+ if (item.turnId) {
3049
+ this.turnConversationIds.set(item.turnId, conversationId);
3050
+ }
3051
+ }
3052
+
2951
3053
  private handleProviderExit(provider: AgentProvider, message: string): void {
2952
3054
  this.clients.delete(provider);
2953
3055
  this.agentProtocols.delete(provider);