@oh-my-pi/pi-coding-agent 14.9.5 → 14.9.8
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/CHANGELOG.md +69 -0
- package/package.json +7 -7
- package/scripts/generate-template.ts +4 -3
- package/src/cli/setup-cli.ts +14 -161
- package/src/cli/stats-cli.ts +56 -2
- package/src/cli.ts +0 -1
- package/src/config/settings-schema.ts +0 -10
- package/src/eval/eval.lark +30 -10
- package/src/eval/js/context-manager.ts +334 -564
- package/src/eval/js/shared/helpers.ts +237 -0
- package/src/eval/js/shared/indirect-eval.ts +30 -0
- package/src/eval/js/shared/rewrite-imports.ts +211 -0
- package/src/eval/js/shared/runtime.ts +168 -0
- package/src/eval/js/shared/types.ts +18 -0
- package/src/eval/js/tool-bridge.ts +2 -4
- package/src/eval/js/worker-core.ts +146 -0
- package/src/eval/js/worker-entry.ts +24 -0
- package/src/eval/js/worker-protocol.ts +41 -0
- package/src/eval/parse.ts +218 -49
- package/src/eval/py/display.ts +71 -0
- package/src/eval/py/executor.ts +74 -89
- package/src/eval/py/index.ts +1 -2
- package/src/eval/py/kernel.ts +472 -900
- package/src/eval/py/prelude.py +95 -7
- package/src/eval/py/runner.py +879 -0
- package/src/eval/py/runtime.ts +3 -16
- package/src/eval/py/tool-bridge.ts +137 -0
- package/src/export/html/index.ts +5 -2
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +93 -5
- package/src/export/html/template.macro.ts +4 -3
- package/src/internal-urls/docs-index.generated.ts +3 -3
- package/src/modes/components/read-tool-group.ts +9 -0
- package/src/modes/controllers/command-controller.ts +0 -23
- package/src/prompts/tools/eval.md +14 -27
- package/src/prompts/tools/read.md +1 -0
- package/src/session/agent-session.ts +0 -1
- package/src/session/history-storage.ts +77 -19
- package/src/tools/browser/tab-protocol.ts +4 -0
- package/src/tools/browser/tab-supervisor.ts +86 -5
- package/src/tools/browser/tab-worker.ts +104 -58
- package/src/tools/conflict-detect.ts +661 -0
- package/src/tools/eval.ts +1 -1
- package/src/tools/index.ts +6 -0
- package/src/tools/path-utils.ts +1 -1
- package/src/tools/read.ts +130 -0
- package/src/tools/write.ts +204 -0
- package/src/web/search/index.ts +6 -4
- package/src/cli/jupyter-cli.ts +0 -106
- package/src/commands/jupyter.ts +0 -32
- package/src/eval/py/cancellation.ts +0 -28
- package/src/eval/py/gateway-coordinator.ts +0 -424
- /package/src/eval/js/{prelude.ts → shared/prelude.ts} +0 -0
- /package/src/eval/js/{prelude.txt → shared/prelude.txt} +0 -0
|
@@ -1,424 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import { createServer } from "node:net";
|
|
3
|
-
import * as path from "node:path";
|
|
4
|
-
import { Process } from "@oh-my-pi/pi-natives";
|
|
5
|
-
import { getPythonGatewayDir, isEnoent, logger, procmgr } from "@oh-my-pi/pi-utils";
|
|
6
|
-
import type { Subprocess } from "bun";
|
|
7
|
-
import { Settings } from "../../config/settings";
|
|
8
|
-
import { getOrCreateSnapshot } from "../../utils/shell-snapshot";
|
|
9
|
-
import { filterEnv, resolvePythonRuntime } from "./runtime";
|
|
10
|
-
|
|
11
|
-
const GATEWAY_INFO_FILE = "gateway.json";
|
|
12
|
-
const GATEWAY_LOCK_FILE = "gateway.lock";
|
|
13
|
-
const GATEWAY_STARTUP_TIMEOUT_MS = 30000;
|
|
14
|
-
const GATEWAY_LOCK_TIMEOUT_MS = GATEWAY_STARTUP_TIMEOUT_MS + 5000;
|
|
15
|
-
const GATEWAY_LOCK_RETRY_MS = 50;
|
|
16
|
-
const GATEWAY_LOCK_STALE_MS = GATEWAY_STARTUP_TIMEOUT_MS * 2;
|
|
17
|
-
const GATEWAY_LOCK_HEARTBEAT_MS = 5000;
|
|
18
|
-
const HEALTH_CHECK_TIMEOUT_MS = 3000;
|
|
19
|
-
|
|
20
|
-
export interface GatewayInfo {
|
|
21
|
-
url: string;
|
|
22
|
-
pid: number;
|
|
23
|
-
startedAt: number;
|
|
24
|
-
pythonPath?: string;
|
|
25
|
-
venvPath?: string | null;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
interface GatewayLockInfo {
|
|
29
|
-
pid: number;
|
|
30
|
-
startedAt: number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
interface AcquireResult {
|
|
34
|
-
url: string;
|
|
35
|
-
isShared: boolean;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
let localGatewayProcess: Subprocess | null = null;
|
|
39
|
-
let localGatewayUrl: string | null = null;
|
|
40
|
-
let isCoordinatorInitialized = false;
|
|
41
|
-
|
|
42
|
-
async function allocatePort(): Promise<number> {
|
|
43
|
-
const { promise, resolve, reject } = Promise.withResolvers<number>();
|
|
44
|
-
const server = createServer();
|
|
45
|
-
server.unref();
|
|
46
|
-
server.on("error", reject);
|
|
47
|
-
server.listen(0, "127.0.0.1", () => {
|
|
48
|
-
const address = server.address();
|
|
49
|
-
if (address && typeof address === "object") {
|
|
50
|
-
const port = address.port;
|
|
51
|
-
server.close((err: Error | null | undefined) => {
|
|
52
|
-
if (err) {
|
|
53
|
-
reject(err);
|
|
54
|
-
} else {
|
|
55
|
-
resolve(port);
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
} else {
|
|
59
|
-
server.close();
|
|
60
|
-
reject(new Error("Failed to allocate port"));
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
return promise;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function getGatewayDir(): string {
|
|
68
|
-
return getPythonGatewayDir();
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function getGatewayInfoPath(): string {
|
|
72
|
-
return path.join(getGatewayDir(), GATEWAY_INFO_FILE);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function getGatewayLockPath(): string {
|
|
76
|
-
return path.join(getGatewayDir(), GATEWAY_LOCK_FILE);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async function writeLockInfo(lockPath: string): Promise<void> {
|
|
80
|
-
const payload: GatewayLockInfo = { pid: process.pid, startedAt: Date.now() };
|
|
81
|
-
try {
|
|
82
|
-
await Bun.write(lockPath, JSON.stringify(payload));
|
|
83
|
-
} catch {
|
|
84
|
-
// Ignore lock write failures
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async function readLockInfo(lockPath: string): Promise<GatewayLockInfo | null> {
|
|
89
|
-
try {
|
|
90
|
-
const raw = await Bun.file(lockPath).text();
|
|
91
|
-
const parsed = JSON.parse(raw) as Partial<GatewayLockInfo>;
|
|
92
|
-
if (typeof parsed.pid === "number" && Number.isFinite(parsed.pid)) {
|
|
93
|
-
return { pid: parsed.pid, startedAt: typeof parsed.startedAt === "number" ? parsed.startedAt : 0 };
|
|
94
|
-
}
|
|
95
|
-
} catch {
|
|
96
|
-
// Ignore parse errors
|
|
97
|
-
}
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
async function ensureGatewayDir(): Promise<void> {
|
|
102
|
-
const dir = getGatewayDir();
|
|
103
|
-
await fs.promises.mkdir(dir, { recursive: true });
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async function withGatewayLock<T>(handler: () => Promise<T>): Promise<T> {
|
|
107
|
-
await ensureGatewayDir();
|
|
108
|
-
const lockPath = getGatewayLockPath();
|
|
109
|
-
const start = Date.now();
|
|
110
|
-
while (true) {
|
|
111
|
-
let fd: fs.promises.FileHandle | undefined;
|
|
112
|
-
try {
|
|
113
|
-
fd = await fs.promises.open(lockPath, "wx");
|
|
114
|
-
let heartbeatRunning = true;
|
|
115
|
-
const heartbeat = (async () => {
|
|
116
|
-
while (heartbeatRunning) {
|
|
117
|
-
await Bun.sleep(GATEWAY_LOCK_HEARTBEAT_MS);
|
|
118
|
-
if (!heartbeatRunning) break;
|
|
119
|
-
try {
|
|
120
|
-
const now = new Date();
|
|
121
|
-
await fs.promises.utimes(lockPath, now, now);
|
|
122
|
-
} catch {
|
|
123
|
-
// Ignore heartbeat errors
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
})();
|
|
127
|
-
try {
|
|
128
|
-
await writeLockInfo(lockPath);
|
|
129
|
-
return await handler();
|
|
130
|
-
} finally {
|
|
131
|
-
heartbeatRunning = false;
|
|
132
|
-
void heartbeat.catch(() => {}); // Don't await - let it die naturally
|
|
133
|
-
try {
|
|
134
|
-
await fd.close();
|
|
135
|
-
await fs.promises.unlink(lockPath);
|
|
136
|
-
} catch {
|
|
137
|
-
// Ignore lock cleanup errors
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
} catch (err) {
|
|
141
|
-
const error = err as NodeJS.ErrnoException;
|
|
142
|
-
if (error.code === "EEXIST") {
|
|
143
|
-
let removedStale = false;
|
|
144
|
-
try {
|
|
145
|
-
const lockStat = await fs.promises.stat(lockPath);
|
|
146
|
-
const lockInfo = await readLockInfo(lockPath);
|
|
147
|
-
const lockPid = lockInfo?.pid;
|
|
148
|
-
const lockAgeMs = lockInfo?.startedAt ? Date.now() - lockInfo.startedAt : Date.now() - lockStat.mtimeMs;
|
|
149
|
-
const staleByTime = lockAgeMs > GATEWAY_LOCK_STALE_MS;
|
|
150
|
-
const staleByPid = lockPid !== undefined && !procmgr.isPidRunning(lockPid);
|
|
151
|
-
const staleByMissingPid = lockPid === undefined && staleByTime;
|
|
152
|
-
if (staleByPid || staleByMissingPid) {
|
|
153
|
-
await fs.promises.unlink(lockPath);
|
|
154
|
-
removedStale = true;
|
|
155
|
-
logger.warn("Removed stale shared gateway lock", { path: lockPath, pid: lockPid });
|
|
156
|
-
}
|
|
157
|
-
} catch {
|
|
158
|
-
// Ignore stat errors; keep waiting
|
|
159
|
-
}
|
|
160
|
-
if (!removedStale) {
|
|
161
|
-
if (Date.now() - start > GATEWAY_LOCK_TIMEOUT_MS) {
|
|
162
|
-
throw new Error("Timed out waiting for shared gateway lock");
|
|
163
|
-
}
|
|
164
|
-
await Bun.sleep(GATEWAY_LOCK_RETRY_MS);
|
|
165
|
-
}
|
|
166
|
-
continue;
|
|
167
|
-
}
|
|
168
|
-
throw err;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
async function readGatewayInfo(): Promise<GatewayInfo | null> {
|
|
174
|
-
const infoPath = getGatewayInfoPath();
|
|
175
|
-
try {
|
|
176
|
-
const content = await Bun.file(infoPath).text();
|
|
177
|
-
const parsed = JSON.parse(content) as Partial<GatewayInfo>;
|
|
178
|
-
|
|
179
|
-
if (typeof parsed.url !== "string" || typeof parsed.pid !== "number" || typeof parsed.startedAt !== "number") {
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
182
|
-
return {
|
|
183
|
-
url: parsed.url,
|
|
184
|
-
pid: parsed.pid,
|
|
185
|
-
startedAt: parsed.startedAt,
|
|
186
|
-
pythonPath: typeof parsed.pythonPath === "string" ? parsed.pythonPath : undefined,
|
|
187
|
-
venvPath: typeof parsed.venvPath === "string" || parsed.venvPath === null ? parsed.venvPath : undefined,
|
|
188
|
-
};
|
|
189
|
-
} catch (err) {
|
|
190
|
-
if (isEnoent(err)) return null;
|
|
191
|
-
return null;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
async function writeGatewayInfo(info: GatewayInfo): Promise<void> {
|
|
196
|
-
const infoPath = getGatewayInfoPath();
|
|
197
|
-
const tempPath = `${infoPath}.tmp`;
|
|
198
|
-
await Bun.write(tempPath, JSON.stringify(info, null, 2));
|
|
199
|
-
await fs.promises.rename(tempPath, infoPath);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
async function clearGatewayInfo(): Promise<void> {
|
|
203
|
-
const infoPath = getGatewayInfoPath();
|
|
204
|
-
try {
|
|
205
|
-
await fs.promises.unlink(infoPath);
|
|
206
|
-
} catch {
|
|
207
|
-
// Ignore errors on cleanup (file may not exist)
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
async function isGatewayHealthy(url: string): Promise<boolean> {
|
|
212
|
-
try {
|
|
213
|
-
const response = await fetch(`${url}/api/kernelspecs`, {
|
|
214
|
-
signal: AbortSignal.timeout(HEALTH_CHECK_TIMEOUT_MS),
|
|
215
|
-
});
|
|
216
|
-
return response.ok;
|
|
217
|
-
} catch {
|
|
218
|
-
return false;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
async function isGatewayAlive(info: GatewayInfo): Promise<boolean> {
|
|
223
|
-
if (!procmgr.isPidRunning(info.pid)) return false;
|
|
224
|
-
return await isGatewayHealthy(info.url);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
async function startGatewayProcess(
|
|
228
|
-
cwd: string,
|
|
229
|
-
): Promise<{ url: string; pid: number; pythonPath: string; venvPath: string | null }> {
|
|
230
|
-
const settings = await Settings.init();
|
|
231
|
-
const { shell, env } = settings.getShellConfig();
|
|
232
|
-
const filteredEnv = filterEnv(env);
|
|
233
|
-
const runtime = resolvePythonRuntime(cwd, filteredEnv);
|
|
234
|
-
const snapshotPath = await getOrCreateSnapshot(shell, env).catch((err: unknown) => {
|
|
235
|
-
logger.warn("Failed to resolve shell snapshot for shared Python gateway", {
|
|
236
|
-
error: err instanceof Error ? err.message : String(err),
|
|
237
|
-
});
|
|
238
|
-
return null;
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
const kernelEnv: Record<string, string | undefined> = {
|
|
242
|
-
...runtime.env,
|
|
243
|
-
PYTHONUNBUFFERED: "1",
|
|
244
|
-
PI_SHELL_SNAPSHOT: snapshotPath ?? undefined,
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
const gatewayPort = await allocatePort();
|
|
248
|
-
const gatewayUrl = `http://127.0.0.1:${gatewayPort}`;
|
|
249
|
-
|
|
250
|
-
const gatewayProcess = Bun.spawn(
|
|
251
|
-
[
|
|
252
|
-
runtime.pythonPath,
|
|
253
|
-
"-m",
|
|
254
|
-
"kernel_gateway",
|
|
255
|
-
"--KernelGatewayApp.ip=127.0.0.1",
|
|
256
|
-
`--KernelGatewayApp.port=${gatewayPort}`,
|
|
257
|
-
"--KernelGatewayApp.port_retries=0",
|
|
258
|
-
"--KernelGatewayApp.allow_origin=*",
|
|
259
|
-
"--JupyterApp.answer_yes=true",
|
|
260
|
-
],
|
|
261
|
-
{
|
|
262
|
-
cwd,
|
|
263
|
-
stdin: "ignore",
|
|
264
|
-
stdout: "pipe",
|
|
265
|
-
stderr: "pipe",
|
|
266
|
-
windowsHide: true,
|
|
267
|
-
detached: true,
|
|
268
|
-
env: kernelEnv,
|
|
269
|
-
},
|
|
270
|
-
);
|
|
271
|
-
|
|
272
|
-
let exited = false;
|
|
273
|
-
gatewayProcess.exited
|
|
274
|
-
.catch(() => {})
|
|
275
|
-
.then(() => {
|
|
276
|
-
exited = true;
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
const startTime = Date.now();
|
|
280
|
-
while (Date.now() - startTime < GATEWAY_STARTUP_TIMEOUT_MS) {
|
|
281
|
-
if (exited) {
|
|
282
|
-
throw new Error("Gateway process exited during startup");
|
|
283
|
-
}
|
|
284
|
-
if (await isGatewayHealthy(gatewayUrl)) {
|
|
285
|
-
localGatewayProcess = gatewayProcess;
|
|
286
|
-
localGatewayUrl = gatewayUrl;
|
|
287
|
-
return {
|
|
288
|
-
url: gatewayUrl,
|
|
289
|
-
pid: gatewayProcess.pid,
|
|
290
|
-
pythonPath: runtime.pythonPath,
|
|
291
|
-
venvPath: runtime.venvPath ?? null,
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
await Bun.sleep(100);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
gatewayProcess.kill();
|
|
298
|
-
throw new Error("Gateway startup timeout");
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
async function killGateway(pid: number, context: string): Promise<void> {
|
|
302
|
-
try {
|
|
303
|
-
await Process.fromPid(pid)?.terminate();
|
|
304
|
-
} catch (err) {
|
|
305
|
-
logger.warn("Failed to kill shared gateway process", {
|
|
306
|
-
error: err instanceof Error ? err.message : String(err),
|
|
307
|
-
pid,
|
|
308
|
-
context,
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
export async function acquireSharedGateway(cwd: string): Promise<AcquireResult | null> {
|
|
314
|
-
try {
|
|
315
|
-
return await withGatewayLock(async () => {
|
|
316
|
-
const existingInfo = await logger.time("acquireSharedGateway:readInfo", readGatewayInfo);
|
|
317
|
-
if (existingInfo) {
|
|
318
|
-
if (await logger.time("acquireSharedGateway:isAlive", isGatewayAlive, existingInfo)) {
|
|
319
|
-
localGatewayUrl = existingInfo.url;
|
|
320
|
-
isCoordinatorInitialized = true;
|
|
321
|
-
logger.debug("Reusing global Python gateway", { url: existingInfo.url });
|
|
322
|
-
return { url: existingInfo.url, isShared: true };
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
logger.debug("Cleaning up stale gateway info", { pid: existingInfo.pid });
|
|
326
|
-
if (procmgr.isPidRunning(existingInfo.pid)) {
|
|
327
|
-
await killGateway(existingInfo.pid, "stale");
|
|
328
|
-
}
|
|
329
|
-
await clearGatewayInfo();
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
const { url, pid, pythonPath, venvPath } = await logger.time(
|
|
333
|
-
"acquireSharedGateway:startGateway",
|
|
334
|
-
startGatewayProcess,
|
|
335
|
-
cwd,
|
|
336
|
-
);
|
|
337
|
-
const info: GatewayInfo = {
|
|
338
|
-
url,
|
|
339
|
-
pid,
|
|
340
|
-
startedAt: Date.now(),
|
|
341
|
-
pythonPath,
|
|
342
|
-
venvPath,
|
|
343
|
-
};
|
|
344
|
-
await writeGatewayInfo(info);
|
|
345
|
-
isCoordinatorInitialized = true;
|
|
346
|
-
logger.debug("Started global Python gateway", { url, pid });
|
|
347
|
-
return { url, isShared: true };
|
|
348
|
-
});
|
|
349
|
-
} catch (err) {
|
|
350
|
-
logger.warn("Failed to acquire shared gateway, falling back to local", {
|
|
351
|
-
error: err instanceof Error ? err.message : String(err),
|
|
352
|
-
});
|
|
353
|
-
return null;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
export async function releaseSharedGateway(): Promise<void> {
|
|
358
|
-
if (!isCoordinatorInitialized) return;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
export async function getSharedGatewayUrl(): Promise<string | null> {
|
|
362
|
-
if (localGatewayUrl) return localGatewayUrl;
|
|
363
|
-
return (await readGatewayInfo())?.url ?? null;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
export async function isSharedGatewayActive(): Promise<boolean> {
|
|
367
|
-
return (await getGatewayStatus()).active;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
export interface GatewayStatus {
|
|
371
|
-
active: boolean;
|
|
372
|
-
url: string | null;
|
|
373
|
-
pid: number | null;
|
|
374
|
-
uptime: number | null;
|
|
375
|
-
pythonPath: string | null;
|
|
376
|
-
venvPath: string | null;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
export async function getGatewayStatus(): Promise<GatewayStatus> {
|
|
380
|
-
const info = await readGatewayInfo();
|
|
381
|
-
if (!info) {
|
|
382
|
-
return {
|
|
383
|
-
active: false,
|
|
384
|
-
url: null,
|
|
385
|
-
pid: null,
|
|
386
|
-
uptime: null,
|
|
387
|
-
pythonPath: null,
|
|
388
|
-
venvPath: null,
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
|
-
const active = procmgr.isPidRunning(info.pid);
|
|
392
|
-
return {
|
|
393
|
-
active,
|
|
394
|
-
url: info.url,
|
|
395
|
-
pid: info.pid,
|
|
396
|
-
uptime: active ? Date.now() - info.startedAt : null,
|
|
397
|
-
pythonPath: info.pythonPath ?? null,
|
|
398
|
-
venvPath: info.venvPath ?? null,
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
export async function shutdownSharedGateway(): Promise<void> {
|
|
403
|
-
try {
|
|
404
|
-
await withGatewayLock(async () => {
|
|
405
|
-
const info = await readGatewayInfo();
|
|
406
|
-
if (!info) return;
|
|
407
|
-
if (procmgr.isPidRunning(info.pid)) {
|
|
408
|
-
await killGateway(info.pid, "shutdown");
|
|
409
|
-
}
|
|
410
|
-
await clearGatewayInfo();
|
|
411
|
-
});
|
|
412
|
-
} catch (err) {
|
|
413
|
-
logger.warn("Failed to shutdown shared gateway", {
|
|
414
|
-
error: err instanceof Error ? err.message : String(err),
|
|
415
|
-
});
|
|
416
|
-
} finally {
|
|
417
|
-
if (localGatewayProcess) {
|
|
418
|
-
await killGateway(localGatewayProcess.pid, "shutdown-local");
|
|
419
|
-
}
|
|
420
|
-
localGatewayProcess = null;
|
|
421
|
-
localGatewayUrl = null;
|
|
422
|
-
isCoordinatorInitialized = false;
|
|
423
|
-
}
|
|
424
|
-
}
|
|
File without changes
|
|
File without changes
|