linkshell-cli 0.5.40 → 0.5.42

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.
@@ -1,5 +1,7 @@
1
1
  import { execSync } from "node:child_process";
2
+ import { createRequire } from "node:module";
2
3
  import { loadConfig, getConfigPath } from "../config.js";
4
+ const requireFromCli = createRequire(import.meta.url);
3
5
  function check(name, fn) {
4
6
  try {
5
7
  return { name, ok: true, detail: fn() };
@@ -44,11 +46,15 @@ export async function runDoctor(gatewayUrl) {
44
46
  }));
45
47
  results.push(check("node-pty", () => {
46
48
  try {
47
- execSync("node -e \"require('node-pty')\"", { timeout: 5000, stdio: "pipe" });
49
+ // Resolve against this installation, not the user's current project.
50
+ requireFromCli("node-pty");
48
51
  return "loaded";
49
52
  }
50
- catch {
51
- throw new Error("native module not built — run: pnpm approve-builds && pnpm install --force");
53
+ catch (error) {
54
+ const reason = error instanceof Error ? error.message : String(error);
55
+ throw new Error(`could not load node-pty from this LinkShell installation: ${reason}. ` +
56
+ "Reinstall linkshell-cli with the package manager you used to install it. " +
57
+ "For a pnpm source checkout, approve node-pty build scripts and reinstall.");
52
58
  }
53
59
  }));
54
60
  // Optional CLI hints — not required, since LinkShell now bridges a plain shell.
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
@@ -189,6 +189,8 @@ export declare class AgentWorkspaceProxy {
189
189
  private static readonly FS_SYNC_THROTTLE_MS;
190
190
  private static readonly EXTERNAL_HYDRATE_THROTTLE_MS;
191
191
  private static readonly LIST_IDLE_CAP;
192
+ private static readonly LIST_HARD_CAP;
193
+ private static readonly CLIENT_START_TIMEOUT_MS;
192
194
  private startSessionWatchers;
193
195
  private scheduleSessionSync;
194
196
  private syncAndPushSessions;
@@ -225,6 +227,9 @@ export declare class AgentWorkspaceProxy {
225
227
  private refreshProviderCapabilities;
226
228
  private listProviderMcpServers;
227
229
  private loadProviderSkillCommands;
230
+ private listDiskSessions;
231
+ private listClientSessions;
232
+ private upsertRemoteSession;
228
233
  private syncProviderSessions;
229
234
  private hydrateConversationFromProvider;
230
235
  /** Tail a Codex desktop/TUI rollout. `emit` sends agent.v2.event for new lines
@@ -1502,6 +1502,147 @@ function findCodexRolloutPath(sessionId, root = join(homedir(), ".codex", "sessi
1502
1502
  }
1503
1503
  return undefined;
1504
1504
  }
1505
+ function readJsonlPrefix(path, maxBytes = 8192) {
1506
+ let fd;
1507
+ try {
1508
+ const size = statSync(path).size;
1509
+ if (size <= 0)
1510
+ return "";
1511
+ fd = openSync(path, "r");
1512
+ const buf = Buffer.alloc(Math.min(maxBytes, size));
1513
+ readSync(fd, buf, 0, buf.length, 0);
1514
+ return buf.toString("utf8");
1515
+ }
1516
+ catch {
1517
+ return undefined;
1518
+ }
1519
+ finally {
1520
+ if (fd !== undefined) {
1521
+ try {
1522
+ closeSync(fd);
1523
+ }
1524
+ catch { /* ignore */ }
1525
+ }
1526
+ }
1527
+ }
1528
+ /** Newest Codex rollouts as catalog metadata. Reads the first line only. */
1529
+ function listCodexSessionsFromDisk(limit = 80) {
1530
+ const files = [];
1531
+ walkCodexRollouts(join(homedir(), ".codex", "sessions"), (path, filenameId, mt) => {
1532
+ files.push({ path, filenameId, mt });
1533
+ });
1534
+ files.sort((a, b) => b.mt - a.mt);
1535
+ const out = [];
1536
+ const seen = new Set();
1537
+ for (const file of files) {
1538
+ if (out.length >= limit)
1539
+ break;
1540
+ const prefix = readJsonlPrefix(file.path);
1541
+ const first = prefix
1542
+ ? prefix.slice(0, prefix.indexOf("\n") >= 0 ? prefix.indexOf("\n") : prefix.length)
1543
+ : "";
1544
+ let sessionId = file.filenameId;
1545
+ let cwd;
1546
+ let createdAt;
1547
+ if (first) {
1548
+ try {
1549
+ const payload = asRecord(asRecord(JSON.parse(first))?.payload) ?? asRecord(JSON.parse(first));
1550
+ sessionId = firstString(payload, ["session_id", "id"]) ?? file.filenameId;
1551
+ cwd = firstString(payload, ["cwd"]);
1552
+ createdAt = parseTimestamp(payload?.timestamp ?? asRecord(JSON.parse(first))?.timestamp);
1553
+ }
1554
+ catch {
1555
+ sessionId = file.filenameId;
1556
+ }
1557
+ }
1558
+ if (seen.has(sessionId))
1559
+ continue;
1560
+ seen.add(sessionId);
1561
+ out.push({
1562
+ id: sessionId,
1563
+ cwd,
1564
+ title: cwd ? titleFromCwd(cwd) : undefined,
1565
+ createdAt,
1566
+ lastActivityAt: file.mt,
1567
+ });
1568
+ }
1569
+ return out;
1570
+ }
1571
+ function listClaudeSessionsFromDisk(limit = 80) {
1572
+ const root = join(homedir(), ".claude", "projects");
1573
+ if (!existsSync(root))
1574
+ return [];
1575
+ const files = [];
1576
+ let projectDirs = [];
1577
+ try {
1578
+ projectDirs = readdirSync(root).map((name) => join(root, name));
1579
+ }
1580
+ catch {
1581
+ return [];
1582
+ }
1583
+ for (const dir of projectDirs) {
1584
+ let stat;
1585
+ try {
1586
+ stat = statSync(dir);
1587
+ }
1588
+ catch {
1589
+ continue;
1590
+ }
1591
+ if (!stat.isDirectory())
1592
+ continue;
1593
+ const dirName = basename(dir);
1594
+ const cwd = dirName.startsWith("-") ? `/${dirName.slice(1).replace(/-/g, "/")}` : undefined;
1595
+ let entries;
1596
+ try {
1597
+ entries = readdirSync(dir);
1598
+ }
1599
+ catch {
1600
+ continue;
1601
+ }
1602
+ for (const name of entries) {
1603
+ if (!name.endsWith(".jsonl"))
1604
+ continue;
1605
+ const path = join(dir, name);
1606
+ let fileStat;
1607
+ try {
1608
+ fileStat = statSync(path);
1609
+ }
1610
+ catch {
1611
+ continue;
1612
+ }
1613
+ if (!fileStat.isFile())
1614
+ continue;
1615
+ files.push({
1616
+ id: name.replace(/\.jsonl$/, ""),
1617
+ mt: fileStat.mtimeMs,
1618
+ cwd,
1619
+ });
1620
+ }
1621
+ }
1622
+ files.sort((a, b) => b.mt - a.mt);
1623
+ const out = [];
1624
+ const seen = new Set();
1625
+ for (const file of files) {
1626
+ if (out.length >= limit)
1627
+ break;
1628
+ if (seen.has(file.id))
1629
+ continue;
1630
+ seen.add(file.id);
1631
+ out.push({
1632
+ id: file.id,
1633
+ cwd: file.cwd,
1634
+ title: file.cwd ? titleFromCwd(file.cwd) : undefined,
1635
+ lastActivityAt: file.mt,
1636
+ });
1637
+ }
1638
+ return out;
1639
+ }
1640
+ function withTimeout(promise, ms, message) {
1641
+ return new Promise((resolve, reject) => {
1642
+ const timer = setTimeout(() => reject(new Error(message)), ms);
1643
+ promise.then((value) => { clearTimeout(timer); resolve(value); }, (error) => { clearTimeout(timer); reject(error); });
1644
+ });
1645
+ }
1505
1646
  function findLatestCodexRolloutForCwd(cwd, root = join(homedir(), ".codex", "sessions")) {
1506
1647
  const wanted = cwd.replace(/\/+$/, "");
1507
1648
  const candidates = [];
@@ -1978,8 +2119,8 @@ function previewFromTimelineItem(item) {
1978
2119
  export class AgentWorkspaceProxy {
1979
2120
  input;
1980
2121
  clients = new Map();
1981
- // In-flight client starts, so concurrent ensureProviderClient calls (eager
1982
- // initialize() racing conversation.open) share one subprocess spawn.
2122
+ // In-flight client starts, so concurrent ensureProviderClient calls
2123
+ // (prompt racing conversation.open) share one subprocess spawn.
1983
2124
  clientStartPromises = new Map();
1984
2125
  agentProtocols = new Map();
1985
2126
  providerCapabilities = new Map();
@@ -2022,7 +2163,6 @@ export class AgentWorkspaceProxy {
2022
2163
  case "agent.v2.capabilities.request":
2023
2164
  await this.initialize();
2024
2165
  this.sendCapabilities();
2025
- this.sendUsageReport();
2026
2166
  break;
2027
2167
  case "agent.v2.conversation.open": {
2028
2168
  const payload = parseTypedPayload("agent.v2.conversation.open", envelope.payload);
@@ -2254,6 +2394,8 @@ export class AgentWorkspaceProxy {
2254
2394
  static FS_SYNC_THROTTLE_MS = 1500;
2255
2395
  static EXTERNAL_HYDRATE_THROTTLE_MS = 400;
2256
2396
  static LIST_IDLE_CAP = 40;
2397
+ static LIST_HARD_CAP = 80;
2398
+ static CLIENT_START_TIMEOUT_MS = 12_000;
2257
2399
  startSessionWatchers() {
2258
2400
  const roots = [
2259
2401
  join(homedir(), ".claude", "projects"),
@@ -2372,6 +2514,8 @@ export class AgentWorkspaceProxy {
2372
2514
  out.push(conversation);
2373
2515
  };
2374
2516
  for (const conversation of ranked) {
2517
+ if (out.length >= AgentWorkspaceProxy.LIST_HARD_CAP)
2518
+ break;
2375
2519
  if (conversation.status === "running" ||
2376
2520
  conversation.status === "waiting_permission" ||
2377
2521
  conversation.control === "owned" ||
@@ -2456,14 +2600,11 @@ export class AgentWorkspaceProxy {
2456
2600
  if (this.initialized)
2457
2601
  return;
2458
2602
  this.initialized = true;
2459
- // Eagerly start all detected providers so capabilities report real status
2460
- const startPromises = this.input.availableProviders.map((p) => this.ensureProviderClient(p));
2461
- await Promise.allSettled(startPromises);
2462
2603
  this.status = "idle";
2463
2604
  this.error = undefined;
2605
+ // Presence only. Spawning every detected CLI here (especially
2606
+ // `grok agent stdio`) blocked the sequential control plane on connect.
2464
2607
  this.sendCapabilities();
2465
- // Start watching the on-disk session stores so externally-started sessions
2466
- // and status changes surface live, not just at connect time.
2467
2608
  this.startSessionWatchers();
2468
2609
  this.startProcessDiscovery();
2469
2610
  }
@@ -2635,10 +2776,9 @@ export class AgentWorkspaceProxy {
2635
2776
  const existing = this.clients.get(provider);
2636
2777
  if (existing)
2637
2778
  return existing;
2638
- // Dedupe concurrent starts: eager initialize() and conversation.open can
2639
- // race here before this.clients is populated (it's only set AFTER the
2640
- // client finishes initializing), which would spawn the provider subprocess
2641
- // twice. Memoize the in-flight promise so both callers share one spawn;
2779
+ // Dedupe concurrent starts: conversation.open and prompt can race here
2780
+ // before this.clients is populated (it's only set AFTER initialize).
2781
+ // Memoize the in-flight promise so both callers share one spawn;
2642
2782
  // drop it once settled so a failed start can be retried.
2643
2783
  const inflight = this.clientStartPromises.get(provider);
2644
2784
  if (inflight)
@@ -2696,8 +2836,17 @@ export class AgentWorkspaceProxy {
2696
2836
  onRequest: (method, params) => this.handleRequest(method, params),
2697
2837
  onExit: (message) => this.handleProviderExit(provider, message),
2698
2838
  });
2699
- await client.initialize();
2700
- return client;
2839
+ try {
2840
+ await withTimeout(client.initialize(), AgentWorkspaceProxy.CLIENT_START_TIMEOUT_MS, `${providerLabel(provider)} 启动超时`);
2841
+ return client;
2842
+ }
2843
+ catch (error) {
2844
+ try {
2845
+ client.stop();
2846
+ }
2847
+ catch { /* ignore */ }
2848
+ throw error;
2849
+ }
2701
2850
  };
2702
2851
  try {
2703
2852
  const client = await tryCreateClient(resolved);
@@ -2865,96 +3014,112 @@ export class AgentWorkspaceProxy {
2865
3014
  return [];
2866
3015
  }
2867
3016
  }
2868
- async syncProviderSessions() {
2869
- await this.initialize();
2870
- for (const [provider, client] of this.clients) {
2871
- try {
2872
- let activeClient = client;
2873
- let result;
2874
- try {
2875
- result = await activeClient.listSessions();
2876
- }
2877
- catch (error) {
2878
- const recovered = await this.recycleProviderClient(provider, error);
2879
- if (!recovered)
2880
- throw error;
2881
- activeClient = recovered;
2882
- result = await activeClient.listSessions();
2883
- }
2884
- // Codex's thread/list carries no live status and only knows threads
2885
- // THIS app-server owns — so an externally-run `codex` shows idle. Read
2886
- // running state off disk once per sync (CPU-frugal: stats all rollouts,
2887
- // reads only the fresh few). Claude reports status via parseRemoteSessions.
2888
- const codexRunning = provider === "codex" ? runningCodexSessionIds(Date.now()) : undefined;
2889
- for (const remote of this.pickListedRemotes(parseRemoteSessions(result), provider, codexRunning)) {
2890
- const agentSessionId = remote.id;
2891
- // Skip conversations the user explicitly forgot — don't resurrect them.
2892
- if (this.deletedAgentSessionIds.has(agentSessionId))
2893
- continue;
2894
- const existingId = this.conversationByAgentSessionId.get(agentSessionId);
2895
- const now = Date.now();
2896
- const conversationId = existingId ?? makeAgentV2RemoteConversationId(provider, agentSessionId);
2897
- const existing = this.conversations.get(conversationId);
2898
- const cwd = remote.cwd ?? existing?.cwd ?? this.input.cwd;
2899
- const model = remote.model ?? existing?.model;
2900
- // Usage read straight from the on-disk transcript (decoupled from any
2901
- // live LinkShell turn) — so history + externally-started sessions all
2902
- // carry a context meter. Falls back to any existing live-turn usage.
2903
- const usageFields = normalizeUsageFields(remote.usage, model);
2904
- const usage = usageFields
2905
- ? { ...usageFields, updatedAt: remote.lastActivityAt ?? now }
2906
- : existing?.usage;
2907
- // Status precedence. While LinkShell is driving a turn for this
2908
- // conversation, the in-memory status is authoritative — the transcript
2909
- // lags (the SDK hasn't flushed the in-flight turn to disk), so trusting
2910
- // it would flicker a live "running" turn back to idle. Otherwise the
2911
- // transcript-derived running signal is authoritative (Claude reports it
2912
- // via remote.status; Codex via the on-disk rollout set): surface
2913
- // "running" while an external process drives it, and let it clear once
2914
- // the external turn finishes. We only fall back to the existing status
2915
- // to keep error/waiting_permission sticky across a re-sync — the
2916
- // transcript signals never emit those, so they live only in `existing`;
2917
- // defaulting everything else to idle avoids a purely-external session
2918
- // getting stuck at "running" forever.
2919
- const remoteRunning = remote.status === "running" || (codexRunning?.has(agentSessionId) ?? false);
2920
- const linkshellDriving = this.currentTurnIds.has(conversationId);
2921
- const status = linkshellDriving
2922
- ? existing?.status ?? "running"
2923
- : remoteRunning
2924
- ? "running"
2925
- : remote.status === "error"
2926
- ? "error"
2927
- : existing?.status === "error" || existing?.status === "waiting_permission"
2928
- ? existing.status
2929
- : "idle";
2930
- const conversation = {
2931
- id: conversationId,
2932
- agentSessionId,
2933
- provider,
2934
- cwd,
2935
- title: remote.title ?? existing?.title ?? titleFromCwd(cwd),
2936
- model,
2937
- reasoningEffort: existing?.reasoningEffort,
2938
- permissionMode: existing?.permissionMode,
2939
- collaborationMode: existing?.collaborationMode,
2940
- status,
2941
- archived: existing?.archived ?? false,
2942
- lastMessagePreview: existing?.lastMessagePreview,
2943
- lastActivityAt: remote.lastActivityAt ?? existing?.lastActivityAt ?? now,
2944
- createdAt: remote.createdAt ?? existing?.createdAt ?? now,
2945
- usage,
2946
- group: existing?.group ?? (provider === "claude" ? titleFromCwd(cwd) : existing?.group),
2947
- workspaceRoots: existing?.workspaceRoots,
2948
- };
2949
- this.conversations.set(conversation.id, conversation);
2950
- this.conversationByAgentSessionId.set(agentSessionId, conversation.id);
2951
- this.timelines.set(conversation.id, this.timelines.get(conversation.id) ?? []);
3017
+ listDiskSessions(provider) {
3018
+ if (provider === "codex")
3019
+ return listCodexSessionsFromDisk(AgentWorkspaceProxy.LIST_HARD_CAP);
3020
+ if (provider === "claude")
3021
+ return listClaudeSessionsFromDisk(AgentWorkspaceProxy.LIST_HARD_CAP);
3022
+ return [];
3023
+ }
3024
+ async listClientSessions(provider) {
3025
+ const client = this.clients.get(provider);
3026
+ if (!client)
3027
+ return [];
3028
+ try {
3029
+ return parseRemoteSessions(await client.listSessions());
3030
+ }
3031
+ catch (error) {
3032
+ const recovered = await this.recycleProviderClient(provider, error);
3033
+ if (!recovered) {
3034
+ if (this.input.verbose) {
3035
+ process.stderr.write(`[agent:v2] session list failed for ${provider}: ${error instanceof Error ? error.message : String(error)}\n`);
2952
3036
  }
3037
+ return [];
2953
3038
  }
2954
- catch (error) {
3039
+ try {
3040
+ return parseRemoteSessions(await recovered.listSessions());
3041
+ }
3042
+ catch (retryError) {
2955
3043
  if (this.input.verbose) {
2956
- process.stderr.write(`[agent:v2] session list failed for ${provider}: ${error instanceof Error ? error.message : String(error)}\n`);
3044
+ process.stderr.write(`[agent:v2] session list failed for ${provider}: ${retryError instanceof Error ? retryError.message : String(retryError)}\n`);
2957
3045
  }
3046
+ return [];
3047
+ }
3048
+ }
3049
+ }
3050
+ upsertRemoteSession(provider, remote, now, runningIds) {
3051
+ const agentSessionId = remote.id;
3052
+ if (this.deletedAgentSessionIds.has(agentSessionId))
3053
+ return;
3054
+ const existingId = this.conversationByAgentSessionId.get(agentSessionId);
3055
+ const conversationId = existingId ?? makeAgentV2RemoteConversationId(provider, agentSessionId);
3056
+ const existing = this.conversations.get(conversationId);
3057
+ const cwd = remote.cwd ?? existing?.cwd ?? this.input.cwd;
3058
+ const model = remote.model ?? existing?.model;
3059
+ const usageFields = normalizeUsageFields(remote.usage, model);
3060
+ const usage = usageFields
3061
+ ? { ...usageFields, updatedAt: remote.lastActivityAt ?? now }
3062
+ : existing?.usage;
3063
+ const remoteRunning = remote.status === "running" || (runningIds?.has(agentSessionId) ?? false);
3064
+ const linkshellDriving = this.currentTurnIds.has(conversationId);
3065
+ const owned = Boolean(agentSessionId && this.ownedCodexSessionIds.has(agentSessionId));
3066
+ const attached = !owned && (existing?.control === "attached" ||
3067
+ this.attachedExternalCodex.has(conversationId) ||
3068
+ remoteRunning);
3069
+ if (attached)
3070
+ this.attachedExternalCodex.add(conversationId);
3071
+ const status = linkshellDriving
3072
+ ? existing?.status ?? "running"
3073
+ : remoteRunning
3074
+ ? "running"
3075
+ : remote.status === "error"
3076
+ ? "error"
3077
+ : existing?.status === "error" || existing?.status === "waiting_permission"
3078
+ ? existing.status
3079
+ : "idle";
3080
+ const conversation = {
3081
+ id: conversationId,
3082
+ agentSessionId,
3083
+ provider,
3084
+ cwd,
3085
+ title: remote.title ?? existing?.title ?? titleFromCwd(cwd),
3086
+ model,
3087
+ reasoningEffort: existing?.reasoningEffort,
3088
+ permissionMode: existing?.permissionMode,
3089
+ collaborationMode: existing?.collaborationMode,
3090
+ control: owned ? "owned" : attached ? "attached" : existing?.control,
3091
+ status,
3092
+ archived: existing?.archived ?? false,
3093
+ lastMessagePreview: existing?.lastMessagePreview,
3094
+ lastActivityAt: remote.lastActivityAt ?? existing?.lastActivityAt ?? now,
3095
+ createdAt: remote.createdAt ?? existing?.createdAt ?? now,
3096
+ usage,
3097
+ group: existing?.group ?? (provider === "claude" ? titleFromCwd(cwd) : existing?.group),
3098
+ workspaceRoots: existing?.workspaceRoots,
3099
+ };
3100
+ this.conversations.set(conversation.id, conversation);
3101
+ this.conversationByAgentSessionId.set(agentSessionId, conversation.id);
3102
+ this.timelines.set(conversation.id, this.timelines.get(conversation.id) ?? []);
3103
+ }
3104
+ async syncProviderSessions() {
3105
+ await this.initialize();
3106
+ const now = Date.now();
3107
+ const codexRunning = runningCodexSessionIds(now);
3108
+ const providers = new Set([
3109
+ ...this.input.availableProviders,
3110
+ ...this.clients.keys(),
3111
+ ]);
3112
+ for (const provider of providers) {
3113
+ const byId = new Map();
3114
+ for (const remote of this.listDiskSessions(provider))
3115
+ byId.set(remote.id, remote);
3116
+ for (const remote of await this.listClientSessions(provider)) {
3117
+ const prev = byId.get(remote.id);
3118
+ byId.set(remote.id, prev ? { ...prev, ...remote } : remote);
3119
+ }
3120
+ const runningIds = provider === "codex" ? codexRunning : undefined;
3121
+ for (const remote of this.pickListedRemotes([...byId.values()], provider, runningIds)) {
3122
+ this.upsertRemoteSession(provider, remote, now, runningIds);
2958
3123
  }
2959
3124
  }
2960
3125
  }
@@ -3202,26 +3367,38 @@ export class AgentWorkspaceProxy {
3202
3367
  ...this.lastDiscoveredProviders,
3203
3368
  ])];
3204
3369
  const providers = catalog.map((provider) => {
3370
+ const resolved = resolveAgentCommand({
3371
+ provider,
3372
+ command: this.input.command,
3373
+ });
3205
3374
  const client = this.clients.get(provider);
3206
- const protocol = this.agentProtocols.get(provider);
3375
+ const protocol = this.agentProtocols.get(provider) ?? resolved?.protocol;
3207
3376
  const runtimeCapabilities = this.providerCapabilities.get(provider);
3208
3377
  const advertised = runtimeCapabilities?.advertised
3209
3378
  ?? (client instanceof AcpClient ? client.advertised : undefined);
3210
- const enabled = Boolean(client);
3379
+ const canStart = Boolean(resolved) && (this.input.availableProviders.includes(provider) || Boolean(this.input.command?.trim()));
3380
+ const enabled = Boolean(client) || canStart;
3211
3381
  const isAcp = protocol === "acp";
3212
- const supportsImages = enabled && protocolSupportsImages(protocol, advertised);
3382
+ const supportsImages = Boolean(client) && protocolSupportsImages(protocol, advertised);
3213
3383
  const isClaudeFallback = protocol === "claude-stream-json";
3214
- const supportsPermission = enabled && !isClaudeFallback;
3215
- const supportsReasoningEffort = enabled && !isClaudeFallback;
3216
- const supportsSessionList = enabled && (isAcp ? Boolean(advertised?.listSession) : !isClaudeFallback);
3217
- const supportsSessionLoad = enabled && (isAcp ? Boolean(advertised?.loadSession) : !isClaudeFallback);
3218
- const supportsCancel = enabled && (isAcp ? Boolean(advertised?.cancel) : true);
3219
- const supportsFork = enabled && (isAcp
3384
+ const supportsPermission = Boolean(client) && !isClaudeFallback;
3385
+ const supportsReasoningEffort = (Boolean(client) || (canStart && protocol === "codex-app-server")) && !isClaudeFallback;
3386
+ const diskListable = provider === "codex" || provider === "claude";
3387
+ const supportsSessionList = Boolean(client)
3388
+ ? (isAcp ? Boolean(advertised?.listSession) : !isClaudeFallback)
3389
+ : canStart && diskListable;
3390
+ const supportsSessionLoad = Boolean(client)
3391
+ ? (isAcp ? Boolean(advertised?.loadSession) : !isClaudeFallback)
3392
+ : canStart;
3393
+ const supportsCancel = Boolean(client)
3394
+ ? (isAcp ? Boolean(advertised?.cancel) : true)
3395
+ : canStart && !isAcp;
3396
+ const supportsFork = Boolean(client) && (isAcp
3220
3397
  ? Boolean(advertised?.forkSession)
3221
3398
  : protocol === "codex-app-server" || protocol === "claude-agent-sdk");
3222
- const supportsSetModel = enabled && (isAcp
3223
- ? Boolean(advertised?.setModel)
3224
- : protocol === "codex-app-server");
3399
+ const supportsSetModel = Boolean(client)
3400
+ ? (isAcp ? Boolean(advertised?.setModel) : protocol === "codex-app-server")
3401
+ : canStart && protocol === "codex-app-server";
3225
3402
  const protocolBacked = isProtocolAgentProvider(provider);
3226
3403
  const commands = mergeCommands(defaultProviderCommands(provider, this.input.cwd, enabled), runtimeCapabilities?.commands);
3227
3404
  const currentMode = [...this.conversations.values()].find((conversation) => conversation.provider === provider)?.collaborationMode;
@@ -3238,8 +3415,8 @@ export class AgentWorkspaceProxy {
3238
3415
  supportsPermission,
3239
3416
  supportsPlan: enabled && (isAcp ? Boolean(advertised?.embeddedContext) : true),
3240
3417
  supportsCancel,
3241
- models: runtimeCapabilities?.models ?? [{ id: "default", label: "默认模型" }],
3242
- defaultModel: runtimeCapabilities?.defaultModel ?? "default",
3418
+ models: runtimeCapabilities?.models ?? (provider === "codex" ? [...CODEX_FALLBACK_MODELS] : [{ id: "default", label: "默认模型" }]),
3419
+ defaultModel: runtimeCapabilities?.defaultModel ?? (provider === "codex" ? CODEX_FALLBACK_DEFAULT_MODEL : "default"),
3243
3420
  reasoningEfforts: supportsReasoningEffort
3244
3421
  ? runtimeCapabilities?.reasoningEfforts ?? [...ALL_REASONING_EFFORTS]
3245
3422
  : [],
@@ -3292,9 +3469,19 @@ export class AgentWorkspaceProxy {
3292
3469
  }));
3293
3470
  }
3294
3471
  shouldOpenAsAttachedView(conversation) {
3295
- return conversation.control === "attached"
3296
- || this.attachedExternalCodex.has(conversation.id)
3297
- || isLiveConversationId(conversation.id);
3472
+ if (conversation.control === "attached")
3473
+ return true;
3474
+ if (this.attachedExternalCodex.has(conversation.id))
3475
+ return true;
3476
+ if (isLiveConversationId(conversation.id))
3477
+ return true;
3478
+ if (conversation.provider === "codex" &&
3479
+ conversation.agentSessionId &&
3480
+ !this.ownedCodexSessionIds.has(conversation.agentSessionId) &&
3481
+ conversation.status === "running") {
3482
+ return true;
3483
+ }
3484
+ return false;
3298
3485
  }
3299
3486
  openAttachedView(conversation, requestedConversationId) {
3300
3487
  conversation.control = "attached";
@@ -3316,6 +3503,7 @@ export class AgentWorkspaceProxy {
3316
3503
  return conversation;
3317
3504
  }
3318
3505
  async openConversation(payload) {
3506
+ await this.initialize();
3319
3507
  const provider = payload.provider ?? this.input.availableProviders[0];
3320
3508
  if (!provider) {
3321
3509
  return this.openFailure(payload, "没有可用的 Agent provider。");
@@ -3353,6 +3541,12 @@ export class AgentWorkspaceProxy {
3353
3541
  if (existingConversation && this.shouldOpenAsAttachedView(existingConversation)) {
3354
3542
  return this.openAttachedView(existingConversation, payload.conversationId);
3355
3543
  }
3544
+ if (existingConversation?.provider === "codex" &&
3545
+ existingConversation.agentSessionId &&
3546
+ !this.ownedCodexSessionIds.has(existingConversation.agentSessionId) &&
3547
+ runningCodexSessionIds(Date.now()).has(existingConversation.agentSessionId)) {
3548
+ return this.openAttachedView(existingConversation, payload.conversationId);
3549
+ }
3356
3550
  let client = await this.ensureProviderClient(provider);
3357
3551
  if (!client) {
3358
3552
  const message = this.providerErrors.get(provider) ?? `${providerLabel(provider)} 启动失败。请确认 CLI 已安装并可用。`;
@@ -5386,10 +5580,15 @@ export class AgentWorkspaceProxy {
5386
5580
  this.emitConversation(conversation);
5387
5581
  }
5388
5582
  sendSnapshot(conversationId) {
5389
- const conversations = [...this.conversations.values()];
5583
+ const conversations = this.listedConversations([...this.conversations.values()].filter((conversation) => !conversation.archived));
5584
+ if (conversationId && !conversations.some((conversation) => conversation.id === conversationId)) {
5585
+ const extra = this.conversations.get(conversationId);
5586
+ if (extra)
5587
+ conversations.push(extra);
5588
+ }
5390
5589
  const items = conversationId
5391
5590
  ? this.timelines.get(conversationId) ?? []
5392
- : [...this.timelines.values()].flat();
5591
+ : [...this.watchedConversationIds].flatMap((id) => this.timelines.get(id) ?? []);
5393
5592
  this.input.send(createEnvelope({
5394
5593
  type: "agent.v2.snapshot",
5395
5594
  sessionId: this.input.sessionId,