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.
- package/dist/cli/src/commands/doctor.js +9 -3
- package/dist/cli/src/commands/doctor.js.map +1 -1
- package/dist/cli/src/runtime/acp/agent-workspace.d.ts +5 -0
- package/dist/cli/src/runtime/acp/agent-workspace.js +318 -119
- package/dist/cli/src/runtime/acp/agent-workspace.js.map +1 -1
- package/dist/cli/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/commands/doctor.ts +12 -3
- package/src/runtime/acp/agent-workspace.ts +311 -116
- package/web/assets/{FileBrowser-Daeji5i-.js → FileBrowser-wOAfY-xj.js} +1 -1
- package/web/assets/{PortPreview-6-Cj-cKd.js → PortPreview-Cbvv7yzA.js} +1 -1
- package/web/assets/{TerminalPanel-C3hvv9KU.js → TerminalPanel-CTSklkct.js} +1 -1
- package/web/assets/{UsageDashboard-D9Xlpl3d.js → UsageDashboard-CJ1AsNmd.js} +1 -1
- package/web/assets/index-q85psYZ5.js +36 -0
- package/web/index.html +1 -1
- package/web/assets/index-BahdI_WD.js +0 -36
|
@@ -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
|
-
|
|
49
|
+
// Resolve against this installation, not the user's current project.
|
|
50
|
+
requireFromCli("node-pty");
|
|
48
51
|
return "loaded";
|
|
49
52
|
}
|
|
50
|
-
catch {
|
|
51
|
-
|
|
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":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;
|
|
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
|
|
1982
|
-
//
|
|
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:
|
|
2639
|
-
//
|
|
2640
|
-
//
|
|
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
|
-
|
|
2700
|
-
|
|
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
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
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
|
-
|
|
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}: ${
|
|
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
|
|
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 =
|
|
3382
|
+
const supportsImages = Boolean(client) && protocolSupportsImages(protocol, advertised);
|
|
3213
3383
|
const isClaudeFallback = protocol === "claude-stream-json";
|
|
3214
|
-
const supportsPermission =
|
|
3215
|
-
const supportsReasoningEffort =
|
|
3216
|
-
const
|
|
3217
|
-
const
|
|
3218
|
-
|
|
3219
|
-
|
|
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 =
|
|
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
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
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.
|
|
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,
|