linkshell-cli 0.9.0 → 0.10.0
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/README.md +11 -6
- package/dist/cli/src/commands/gateway.d.ts +2 -0
- package/dist/cli/src/commands/gateway.js +29 -0
- package/dist/cli/src/commands/gateway.js.map +1 -0
- package/dist/cli/src/commands/host.d.ts +2 -0
- package/dist/cli/src/commands/host.js +12 -2
- package/dist/cli/src/commands/host.js.map +1 -1
- package/dist/cli/src/commands/setup.js +4 -2
- package/dist/cli/src/commands/setup.js.map +1 -1
- package/dist/cli/src/index.js +12 -334
- package/dist/cli/src/index.js.map +1 -1
- package/dist/cli/src/utils/daemon.d.ts +0 -7
- package/dist/cli/src/utils/daemon.js +1 -18
- package/dist/cli/src/utils/daemon.js.map +1 -1
- package/dist/cli/tsconfig.tsbuildinfo +1 -1
- package/dist/gateway/src/accounts.d.ts +6 -0
- package/dist/gateway/src/accounts.js +32 -0
- package/dist/gateway/src/accounts.js.map +1 -0
- package/dist/gateway/src/index.d.ts +4 -0
- package/dist/gateway/src/index.js +5 -0
- package/dist/gateway/src/index.js.map +1 -0
- package/dist/gateway/src/main.d.ts +1 -0
- package/dist/gateway/src/main.js +57 -0
- package/dist/gateway/src/main.js.map +1 -0
- package/dist/gateway/src/rate-limit.d.ts +22 -0
- package/dist/gateway/src/rate-limit.js +66 -0
- package/dist/gateway/src/rate-limit.js.map +1 -0
- package/dist/gateway/src/relay.d.ts +84 -0
- package/dist/gateway/src/relay.js +434 -0
- package/dist/gateway/src/relay.js.map +1 -0
- package/dist/gateway/src/serve.d.ts +34 -0
- package/dist/gateway/src/serve.js +102 -0
- package/dist/gateway/src/serve.js.map +1 -0
- package/dist/gateway/src/store.d.ts +35 -0
- package/dist/gateway/src/store.js +109 -0
- package/dist/gateway/src/store.js.map +1 -0
- package/dist/gateway/src/subscription.d.ts +18 -0
- package/dist/gateway/src/subscription.js +46 -0
- package/dist/gateway/src/subscription.js.map +1 -0
- package/dist/host/src/drivers/claude/transcript.d.ts +23 -23
- package/dist/host/src/fs.d.ts +3 -3
- package/dist/host/src/input.d.ts +4 -4
- package/dist/host/src/terminals.d.ts +1 -1
- package/dist/wire/src/model.d.ts +6 -6
- package/dist/wire/src/relay.d.ts +8 -8
- package/dist/wire/src/rpc.d.ts +818 -818
- package/dist/wire/src/updates.d.ts +18 -18
- package/package.json +4 -13
- package/src/commands/gateway.ts +31 -0
- package/src/commands/host.ts +13 -2
- package/src/commands/setup.ts +4 -2
- package/src/index.ts +14 -429
- package/src/utils/daemon.ts +2 -27
- package/dist/cli/src/config.d.ts +0 -14
- package/dist/cli/src/config.js +0 -23
- package/dist/cli/src/config.js.map +0 -1
- package/dist/cli/src/machine-id.d.ts +0 -6
- package/dist/cli/src/machine-id.js +0 -50
- package/dist/cli/src/machine-id.js.map +0 -1
- package/dist/cli/src/providers.d.ts +0 -13
- package/dist/cli/src/providers.js +0 -77
- package/dist/cli/src/providers.js.map +0 -1
- package/dist/cli/src/runtime/acp/acp-client.d.ts +0 -128
- package/dist/cli/src/runtime/acp/acp-client.js +0 -430
- package/dist/cli/src/runtime/acp/acp-client.js.map +0 -1
- package/dist/cli/src/runtime/acp/agent-process-discovery.d.ts +0 -48
- package/dist/cli/src/runtime/acp/agent-process-discovery.js +0 -278
- package/dist/cli/src/runtime/acp/agent-process-discovery.js.map +0 -1
- package/dist/cli/src/runtime/acp/agent-session.d.ts +0 -62
- package/dist/cli/src/runtime/acp/agent-session.js +0 -1081
- package/dist/cli/src/runtime/acp/agent-session.js.map +0 -1
- package/dist/cli/src/runtime/acp/agent-workspace.d.ts +0 -311
- package/dist/cli/src/runtime/acp/agent-workspace.js +0 -6083
- package/dist/cli/src/runtime/acp/agent-workspace.js.map +0 -1
- package/dist/cli/src/runtime/acp/claude-sdk-client.d.ts +0 -121
- package/dist/cli/src/runtime/acp/claude-sdk-client.js +0 -1147
- package/dist/cli/src/runtime/acp/claude-sdk-client.js.map +0 -1
- package/dist/cli/src/runtime/acp/claude-stream-json-client.d.ts +0 -53
- package/dist/cli/src/runtime/acp/claude-stream-json-client.js +0 -552
- package/dist/cli/src/runtime/acp/claude-stream-json-client.js.map +0 -1
- package/dist/cli/src/runtime/acp/json-rpc.d.ts +0 -22
- package/dist/cli/src/runtime/acp/json-rpc.js +0 -160
- package/dist/cli/src/runtime/acp/json-rpc.js.map +0 -1
- package/dist/cli/src/runtime/acp/provider-resolver.d.ts +0 -16
- package/dist/cli/src/runtime/acp/provider-resolver.js +0 -132
- package/dist/cli/src/runtime/acp/provider-resolver.js.map +0 -1
- package/dist/cli/src/runtime/bridge-session.d.ts +0 -97
- package/dist/cli/src/runtime/bridge-session.js +0 -1364
- package/dist/cli/src/runtime/bridge-session.js.map +0 -1
- package/dist/cli/src/runtime/hook-command.d.ts +0 -9
- package/dist/cli/src/runtime/hook-command.js +0 -80
- package/dist/cli/src/runtime/hook-command.js.map +0 -1
- package/dist/cli/src/runtime/screen-fallback.d.ts +0 -22
- package/dist/cli/src/runtime/screen-fallback.js +0 -202
- package/dist/cli/src/runtime/screen-fallback.js.map +0 -1
- package/dist/cli/src/runtime/screen-share.d.ts +0 -34
- package/dist/cli/src/runtime/screen-share.js +0 -376
- package/dist/cli/src/runtime/screen-share.js.map +0 -1
- package/dist/cli/src/runtime/scrollback.d.ts +0 -11
- package/dist/cli/src/runtime/scrollback.js +0 -33
- package/dist/cli/src/runtime/scrollback.js.map +0 -1
- package/dist/cli/src/runtime/usage-report.d.ts +0 -34
- package/dist/cli/src/runtime/usage-report.js +0 -332
- package/dist/cli/src/runtime/usage-report.js.map +0 -1
- package/dist/cli/src/utils/keep-awake.d.ts +0 -6
- package/dist/cli/src/utils/keep-awake.js +0 -48
- package/dist/cli/src/utils/keep-awake.js.map +0 -1
- package/dist/cli/src/utils/lan-ip.d.ts +0 -9
- package/dist/cli/src/utils/lan-ip.js +0 -61
- package/dist/cli/src/utils/lan-ip.js.map +0 -1
- package/dist/host/src/input-helper.d.ts +0 -1
- package/dist/host/src/input-helper.js +0 -564
- package/dist/host/src/input-helper.js.map +0 -1
- package/dist/shared-protocol/src/index.d.ts +0 -17029
- package/dist/shared-protocol/src/index.js +0 -1064
- package/dist/shared-protocol/src/index.js.map +0 -1
- package/scripts/copy-web.mjs +0 -34
- package/src/config.ts +0 -36
- package/src/machine-id.ts +0 -61
- package/src/providers.ts +0 -97
- package/src/runtime/acp/acp-client.ts +0 -521
- package/src/runtime/acp/agent-process-discovery.ts +0 -308
- package/src/runtime/acp/agent-session.ts +0 -1186
- package/src/runtime/acp/agent-workspace.ts +0 -6590
- package/src/runtime/acp/claude-sdk-client.ts +0 -1221
- package/src/runtime/acp/claude-stream-json-client.ts +0 -633
- package/src/runtime/acp/json-rpc.ts +0 -184
- package/src/runtime/acp/provider-resolver.ts +0 -154
- package/src/runtime/bridge-session.ts +0 -1549
- package/src/runtime/hook-command.ts +0 -73
- package/src/runtime/screen-fallback.ts +0 -235
- package/src/runtime/screen-share.ts +0 -426
- package/src/runtime/scrollback.ts +0 -43
- package/src/runtime/usage-report.ts +0 -342
- package/src/utils/keep-awake.ts +0 -61
- package/src/utils/lan-ip.ts +0 -61
- package/web/assets/FileBrowser-wOAfY-xj.js +0 -2
- package/web/assets/PortPreview-Cbvv7yzA.js +0 -2
- package/web/assets/TerminalPanel-CTSklkct.js +0 -1
- package/web/assets/UsageDashboard-CJ1AsNmd.js +0 -1
- package/web/assets/index-BsnDaeCt.css +0 -33
- package/web/assets/index-q85psYZ5.js +0 -36
- package/web/assets/vendor-highlight-NXWAAyIv.js +0 -5
- package/web/assets/vendor-react-M7xG3qiw.js +0 -49
- package/web/assets/vendor-xterm-D15THcI4.js +0 -9
- package/web/index.html +0 -37
- package/web/logos/claude.png +0 -0
- package/web/logos/codex.png +0 -0
- package/web/logos/copilot.png +0 -0
- package/web/logos/gemini.png +0 -0
- package/web/logos/linkshell-1024.png +0 -0
- package/web/logos/linkshell-splash.png +0 -0
- package/web/logos/linkshell.png +0 -0
|
@@ -1,308 +0,0 @@
|
|
|
1
|
-
import { execFile, execFileSync } from "node:child_process";
|
|
2
|
-
import { promisify } from "node:util";
|
|
3
|
-
|
|
4
|
-
const execFileAsync = promisify(execFile);
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Host-side agent process discovery, modeled on Open Island's
|
|
8
|
-
* ActiveAgentProcessDiscovery: `ps` is the authoritative "is it alive?"
|
|
9
|
-
* signal. We classify well-known CLIs (Claude, Codex, Gemini, Copilot,
|
|
10
|
-
* OpenCode, Cursor Agent, Kimi) and ignore LinkShell's own control-plane
|
|
11
|
-
* subprocesses (codex app-server, Claude stream-json / SDK).
|
|
12
|
-
*
|
|
13
|
-
* This does not invent a remote conversation protocol for those agents.
|
|
14
|
-
* Live processes become visible conversations; prompting still requires
|
|
15
|
-
* an official adapter (Codex app-server / Claude stream-json or SDK).
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
export const KNOWN_AGENT_PROVIDERS = [
|
|
19
|
-
"codex",
|
|
20
|
-
"claude",
|
|
21
|
-
"gemini",
|
|
22
|
-
"copilot",
|
|
23
|
-
"opencode",
|
|
24
|
-
"cursor",
|
|
25
|
-
"grok",
|
|
26
|
-
"kimi",
|
|
27
|
-
"custom",
|
|
28
|
-
] as const;
|
|
29
|
-
|
|
30
|
-
export type KnownAgentProvider = (typeof KNOWN_AGENT_PROVIDERS)[number];
|
|
31
|
-
|
|
32
|
-
export const PROTOCOL_AGENT_PROVIDERS = new Set<string>(["codex", "claude", "gemini", "cursor", "grok"]);
|
|
33
|
-
|
|
34
|
-
export interface AgentProcessSnapshot {
|
|
35
|
-
provider: string;
|
|
36
|
-
pid: string;
|
|
37
|
-
command: string;
|
|
38
|
-
tty?: string;
|
|
39
|
-
cwd?: string;
|
|
40
|
-
sessionId?: string;
|
|
41
|
-
parentPid?: string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface RunningProcess {
|
|
45
|
-
pid: string;
|
|
46
|
-
parentPid: string;
|
|
47
|
-
tty?: string;
|
|
48
|
-
command: string;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export type ProcessCommandRunner = (executable: string, args: string[]) => string | undefined;
|
|
52
|
-
|
|
53
|
-
const UUID_RE = /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/;
|
|
54
|
-
|
|
55
|
-
export function agentProviderLabel(provider: string): string {
|
|
56
|
-
switch (provider) {
|
|
57
|
-
case "codex":
|
|
58
|
-
return "Codex";
|
|
59
|
-
case "claude":
|
|
60
|
-
return "Claude";
|
|
61
|
-
case "gemini":
|
|
62
|
-
return "Gemini";
|
|
63
|
-
case "copilot":
|
|
64
|
-
return "Copilot";
|
|
65
|
-
case "opencode":
|
|
66
|
-
return "OpenCode";
|
|
67
|
-
case "cursor":
|
|
68
|
-
return "Cursor";
|
|
69
|
-
case "grok":
|
|
70
|
-
return "Grok";
|
|
71
|
-
case "kimi":
|
|
72
|
-
return "Kimi";
|
|
73
|
-
case "custom":
|
|
74
|
-
return "Custom";
|
|
75
|
-
default:
|
|
76
|
-
return provider;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export function isProtocolAgentProvider(provider: string): boolean {
|
|
81
|
-
return PROTOCOL_AGENT_PROVIDERS.has(provider);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export function firstCommandToken(command: string): string {
|
|
85
|
-
return command.trim().split(/\s+/, 1)[0] ?? "";
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export function commandBinaryName(command: string): string {
|
|
89
|
-
const token = firstCommandToken(command);
|
|
90
|
-
const parts = token.split(/[/\\]/);
|
|
91
|
-
return (parts[parts.length - 1] ?? token).toLowerCase();
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** LinkShell-owned control planes must not appear as user sessions. */
|
|
95
|
-
export function isControlPlaneCommand(command: string): boolean {
|
|
96
|
-
const lowered = command.toLowerCase();
|
|
97
|
-
if (/\bapp-server\b/.test(lowered)) return true;
|
|
98
|
-
if (lowered.includes("claude-agent-sdk")) return true;
|
|
99
|
-
if (lowered.includes("stream-json") && (lowered.includes("--print") || lowered.includes("--output-format"))) {
|
|
100
|
-
return true;
|
|
101
|
-
}
|
|
102
|
-
if (/\b--acp\b/.test(lowered)) return true;
|
|
103
|
-
if (commandBinaryName(command) === "cursor-agent" && /\sacp(\s|$)/.test(lowered)) return true;
|
|
104
|
-
if (commandBinaryName(command) === "grok" && /\bagent\b/.test(lowered) && /\bstdio\b/.test(lowered)) return true;
|
|
105
|
-
return false;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export function classifyAgentCommand(command: string): string | undefined {
|
|
109
|
-
if (!command.trim() || isControlPlaneCommand(command)) return undefined;
|
|
110
|
-
const lowered = command.toLowerCase();
|
|
111
|
-
const first = firstCommandToken(lowered);
|
|
112
|
-
const binary = commandBinaryName(command);
|
|
113
|
-
|
|
114
|
-
if (binary === "codex" || first.endsWith("/codex") || lowered.includes("/codex/codex")) return "codex";
|
|
115
|
-
if (binary === "claude" || first.endsWith("/claude") || lowered.includes("/.local/bin/claude")) return "claude";
|
|
116
|
-
if (binary === "cursor-agent" || first.endsWith("/cursor-agent")) return "cursor";
|
|
117
|
-
if (isOpenCodeCommand(lowered, first)) return "opencode";
|
|
118
|
-
if (
|
|
119
|
-
binary === "gemini" ||
|
|
120
|
-
first.endsWith("/gemini") ||
|
|
121
|
-
lowered.includes("/bin/gemini") ||
|
|
122
|
-
lowered.includes("/google/gemini-cli") ||
|
|
123
|
-
lowered.includes("@google/gemini-cli")
|
|
124
|
-
) {
|
|
125
|
-
return "gemini";
|
|
126
|
-
}
|
|
127
|
-
if (binary === "kimi" && (first === "kimi" || first.endsWith("/kimi"))) return "kimi";
|
|
128
|
-
if (binary === "copilot" || first.endsWith("/copilot")) return "copilot";
|
|
129
|
-
if (binary === "grok" || first.endsWith("/grok")) return "grok";
|
|
130
|
-
return undefined;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function isOpenCodeCommand(lowered: string, first: string): boolean {
|
|
134
|
-
if (
|
|
135
|
-
first === "opencode" ||
|
|
136
|
-
first === "opencode-ai" ||
|
|
137
|
-
first.endsWith("/opencode") ||
|
|
138
|
-
first.endsWith("/opencode-ai")
|
|
139
|
-
) {
|
|
140
|
-
return true;
|
|
141
|
-
}
|
|
142
|
-
if (!lowered.includes("opencode")) return false;
|
|
143
|
-
|
|
144
|
-
const isPackageRunner =
|
|
145
|
-
first === "npx" || first.endsWith("/npx") ||
|
|
146
|
-
first === "pnpx" || first.endsWith("/pnpx") ||
|
|
147
|
-
first === "bunx" || first.endsWith("/bunx") ||
|
|
148
|
-
((first === "npm" || first.endsWith("/npm")) && (lowered.includes(" exec ") || lowered.includes(" run "))) ||
|
|
149
|
-
((first === "pnpm" || first.endsWith("/pnpm")) && (lowered.includes(" dlx ") || lowered.includes(" exec ") || lowered.includes(" run "))) ||
|
|
150
|
-
first === "yarn" || first.endsWith("/yarn");
|
|
151
|
-
|
|
152
|
-
if (isPackageRunner) {
|
|
153
|
-
const tokens = lowered.split(/\s+/);
|
|
154
|
-
const packageIndex = tokens.findIndex((token) => {
|
|
155
|
-
if (token.startsWith("@opencode-ai/")) return true;
|
|
156
|
-
const base = token.startsWith("@") ? token : (token.split("@")[0] ?? token);
|
|
157
|
-
return base === "opencode" || base === "opencode-ai";
|
|
158
|
-
});
|
|
159
|
-
if (packageIndex >= 0) {
|
|
160
|
-
const installLike = new Set(["install", "i", "add", "remove", "rm", "uninstall", "update", "upgrade", "up", "unlink"]);
|
|
161
|
-
const isInstall = tokens.slice(0, packageIndex).some((token) => installLike.has(token));
|
|
162
|
-
if (!isInstall) return true;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const isNode = first === "node" || first.endsWith("/node") || first === "bun" || first.endsWith("/bun");
|
|
167
|
-
if (
|
|
168
|
-
isNode && (
|
|
169
|
-
lowered.includes("/opencode-ai/") ||
|
|
170
|
-
lowered.includes("/@opencode-ai/") ||
|
|
171
|
-
lowered.includes("/node_modules/opencode/") ||
|
|
172
|
-
lowered.includes("/node_modules/opencode-ai/") ||
|
|
173
|
-
lowered.includes("/node_modules/@opencode-ai/") ||
|
|
174
|
-
/\/\.bin\/opencode(-ai)?(\s|$)/.test(lowered)
|
|
175
|
-
)
|
|
176
|
-
) {
|
|
177
|
-
return true;
|
|
178
|
-
}
|
|
179
|
-
return false;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
export function parsePsOutput(output: string): RunningProcess[] {
|
|
183
|
-
const processes: RunningProcess[] = [];
|
|
184
|
-
for (const rawLine of output.split(/\r?\n/)) {
|
|
185
|
-
const line = rawLine.trim();
|
|
186
|
-
if (!line) continue;
|
|
187
|
-
const parts = line.split(/\s+/);
|
|
188
|
-
if (parts.length < 4) continue;
|
|
189
|
-
const pid = parts[0]!;
|
|
190
|
-
const parentPid = parts[1]!;
|
|
191
|
-
const ttyRaw = parts[2]!;
|
|
192
|
-
const command = parts.slice(3).join(" ").trim();
|
|
193
|
-
if (!command) continue;
|
|
194
|
-
processes.push({
|
|
195
|
-
pid,
|
|
196
|
-
parentPid,
|
|
197
|
-
tty: normalizeTty(ttyRaw),
|
|
198
|
-
command,
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
return processes;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function normalizeTty(raw: string): string | undefined {
|
|
205
|
-
const trimmed = raw.trim();
|
|
206
|
-
if (!trimmed || trimmed === "??") return undefined;
|
|
207
|
-
return trimmed.startsWith("/dev/") ? trimmed : `/dev/${trimmed}`;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
export function sessionIdFromCommand(command: string): string | undefined {
|
|
211
|
-
const tokens = command.split(/\s+/);
|
|
212
|
-
for (let index = 0; index < tokens.length; index += 1) {
|
|
213
|
-
const token = tokens[index]!;
|
|
214
|
-
if (token === "--resume" || token === "-r" || token === "--session-id") {
|
|
215
|
-
const next = tokens[index + 1];
|
|
216
|
-
const match = next?.match(UUID_RE);
|
|
217
|
-
if (match) return match[0].toLowerCase();
|
|
218
|
-
}
|
|
219
|
-
if (token.startsWith("--resume=") || token.startsWith("--session-id=")) {
|
|
220
|
-
const match = token.split("=", 2)[1]?.match(UUID_RE);
|
|
221
|
-
if (match) return match[0].toLowerCase();
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
return undefined;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
export function liveConversationId(snapshot: Pick<AgentProcessSnapshot, "provider" | "pid" | "sessionId">): string {
|
|
228
|
-
if (snapshot.sessionId) return `agent-live-${snapshot.provider}-${snapshot.sessionId}`;
|
|
229
|
-
return `agent-live-${snapshot.provider}-${snapshot.pid}`;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
export function isLiveConversationId(conversationId: string): boolean {
|
|
233
|
-
return conversationId.startsWith("agent-live-");
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
export function discoverAgentProcesses(input?: {
|
|
237
|
-
run?: ProcessCommandRunner;
|
|
238
|
-
processes?: RunningProcess[];
|
|
239
|
-
includeCwd?: boolean;
|
|
240
|
-
}): AgentProcessSnapshot[] {
|
|
241
|
-
const run = input?.run ?? defaultCommandRunner;
|
|
242
|
-
const processes = input?.processes ?? parsePsOutput(run("/bin/ps", ["-Ao", "pid=,ppid=,tty=,command="]) ?? "");
|
|
243
|
-
const snapshots: AgentProcessSnapshot[] = [];
|
|
244
|
-
const claimed = new Set<string>();
|
|
245
|
-
|
|
246
|
-
for (const process of processes) {
|
|
247
|
-
const provider = classifyAgentCommand(process.command);
|
|
248
|
-
if (!provider) continue;
|
|
249
|
-
// Open Island lets OpenCode through without a TTY (IDE terminals).
|
|
250
|
-
if (!process.tty && provider !== "opencode") continue;
|
|
251
|
-
|
|
252
|
-
const sessionId = sessionIdFromCommand(process.command);
|
|
253
|
-
const claimKey = sessionId ? `${provider}:${sessionId}` : `${provider}:${process.tty ?? process.pid}`;
|
|
254
|
-
if (claimed.has(claimKey)) continue;
|
|
255
|
-
claimed.add(claimKey);
|
|
256
|
-
|
|
257
|
-
snapshots.push({
|
|
258
|
-
provider,
|
|
259
|
-
pid: process.pid,
|
|
260
|
-
command: process.command,
|
|
261
|
-
tty: process.tty,
|
|
262
|
-
// lsof is useful but too expensive for the 3s live poll — it stalls the
|
|
263
|
-
// PTY/WebSocket loop. Callers that need cwd pass includeCwd.
|
|
264
|
-
cwd: input?.includeCwd ? cwdFromLsof(run, process.pid) : undefined,
|
|
265
|
-
sessionId,
|
|
266
|
-
parentPid: process.parentPid,
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
return snapshots;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/** Non-blocking `ps` scan for the live poll. Never calls lsof. */
|
|
273
|
-
export async function discoverAgentProcessesAsync(): Promise<AgentProcessSnapshot[]> {
|
|
274
|
-
try {
|
|
275
|
-
const { stdout } = await execFileAsync("/bin/ps", ["-Ao", "pid=,ppid=,tty=,command="], {
|
|
276
|
-
encoding: "utf8",
|
|
277
|
-
timeout: 800,
|
|
278
|
-
maxBuffer: 2 * 1024 * 1024,
|
|
279
|
-
});
|
|
280
|
-
return discoverAgentProcesses({ processes: parsePsOutput(stdout) });
|
|
281
|
-
} catch {
|
|
282
|
-
return [];
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function cwdFromLsof(run: ProcessCommandRunner, pid: string): string | undefined {
|
|
287
|
-
const output = run("/usr/sbin/lsof", ["-a", "-p", pid, "-Fn"]);
|
|
288
|
-
if (!output) return undefined;
|
|
289
|
-
const lines = output.split(/\r?\n/);
|
|
290
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
291
|
-
if (lines[index] !== "fcwd") continue;
|
|
292
|
-
const next = lines[index + 1] ?? "";
|
|
293
|
-
if (next.startsWith("n/") ) return next.slice(1);
|
|
294
|
-
}
|
|
295
|
-
return undefined;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
function defaultCommandRunner(executable: string, args: string[]): string | undefined {
|
|
299
|
-
try {
|
|
300
|
-
return execFileSync(executable, args, {
|
|
301
|
-
encoding: "utf8",
|
|
302
|
-
timeout: 800,
|
|
303
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
304
|
-
});
|
|
305
|
-
} catch {
|
|
306
|
-
return undefined;
|
|
307
|
-
}
|
|
308
|
-
}
|