pi-orchestrator-config 4.2.0 → 4.3.2
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 +76 -67
- package/agents/code-reviewer-docs.md +11 -1
- package/agents/code-reviewer-guidelines.md +11 -1
- package/agents/code-reviewer-quality.md +11 -1
- package/agents/code-reviewer-security.md +11 -1
- package/agents/code-reviewer-spec.md +11 -1
- package/agents/git-expert.md +42 -3
- package/agents/github-expert.md +11 -0
- package/extensions/acpx-provider/runtime-models.ts +18 -6
- package/extensions/cli-provider/agents/claude.ts +3 -2
- package/extensions/cli-provider/agents/cursor.ts +3 -5
- package/extensions/cli-provider/agents/gemini.ts +3 -2
- package/extensions/cli-provider/discover.ts +7 -7
- package/extensions/cli-provider/index.ts +5 -0
- package/extensions/cli-provider/runtime-models.ts +18 -6
- package/extensions/cli-provider/session-reaper.ts +5 -7
- package/extensions/cli-provider/sessions.ts +4 -0
- package/extensions/cli-provider/shared/discover-cache.ts +17 -176
- package/extensions/coms/coms-p2p.ts +1719 -423
- package/extensions/coms/coms-shared.ts +261 -39
- package/extensions/coms/coms-wrapper.ts +100 -62
- package/extensions/coms/index.ts +8 -4
- package/extensions/coms/mixed-turn.ts +26 -0
- package/extensions/coms/probe-socket.ts +42 -0
- package/extensions/coms/themeMap.ts +0 -1
- package/extensions/image-gen/image-gen.ts +19 -1
- package/extensions/orchestrator/async-agents.ts +563 -102
- package/extensions/orchestrator/async-capability.ts +8 -8
- package/extensions/orchestrator/cron.ts +15 -9
- package/extensions/orchestrator/dreaming.ts +32 -25
- package/extensions/orchestrator/enforcement-helpers.ts +459 -17
- package/extensions/orchestrator/enforcement-rules.ts +5 -4
- package/extensions/orchestrator/enforcement.ts +80 -27
- package/extensions/orchestrator/extended-autocomplete.ts +7 -0
- package/extensions/orchestrator/git-helpers.ts +2 -2
- package/extensions/orchestrator/index.ts +78 -10
- package/extensions/orchestrator/markdown-transformer.ts +313 -0
- package/extensions/orchestrator/memory-embeddings.ts +36 -34
- package/extensions/orchestrator/memory-provenance.ts +29 -25
- package/extensions/orchestrator/memory-scoring.ts +23 -18
- package/extensions/orchestrator/memory-tools.ts +10 -3
- package/extensions/orchestrator/overlay-dashboard.ts +17 -5
- package/extensions/orchestrator/parse-model-override.ts +29 -0
- package/extensions/orchestrator/pi-config-review-state.ts +57 -33
- package/extensions/orchestrator/project-settings.ts +333 -127
- package/extensions/orchestrator/resolve-agent-model.ts +24 -13
- package/extensions/orchestrator/review-porcelain.ts +4 -0
- package/extensions/orchestrator/review-ui.ts +63 -15
- package/extensions/orchestrator/rule-placeholders.ts +411 -2
- package/extensions/orchestrator/rules.ts +80 -132
- package/extensions/orchestrator/settings-tui-helpers.ts +380 -0
- package/extensions/orchestrator/settings-tui-submenus.ts +647 -0
- package/extensions/orchestrator/settings-tui.ts +636 -0
- package/extensions/orchestrator/state-jsonl.ts +431 -0
- package/extensions/orchestrator/subagent-tool.ts +157 -59
- package/extensions/orchestrator/sync-limit.ts +23 -0
- package/extensions/orchestrator/task-lifecycle.ts +77 -67
- package/extensions/orchestrator/utils.ts +8 -5
- package/extensions/pidash/pidash.ts +193 -156
- package/extensions/pidiff/pidiff.ts +96 -38
- package/extensions/pidiff/spawn-lock.ts +62 -0
- package/extensions/pitasks/index.ts +430 -0
- package/extensions/pitasks/reminders.ts +37 -0
- package/extensions/pitasks/task-store.ts +467 -0
- package/extensions/pitasks/task-tools.ts +375 -0
- package/extensions/pitasks/task-widget.ts +181 -0
- package/extensions/pitasks/types.ts +38 -0
- package/extensions/providers/acpx-driver.ts +12 -2
- package/extensions/providers/built-in-drivers.ts +6 -6
- package/extensions/providers/claude-driver.ts +1 -1
- package/extensions/providers/cursor-cli-driver.ts +1 -1
- package/extensions/providers/gemini-driver.ts +1 -1
- package/extensions/providers/index.ts +144 -26
- package/extensions/providers/initialized-guard.ts +27 -0
- package/extensions/providers/restore-default-model.ts +596 -0
- package/extensions/providers/session-shutdown.ts +39 -0
- package/extensions/shared/coms-active.ts +23 -0
- package/extensions/shared/create-runtime-provider.ts +2 -0
- package/extensions/shared/daemon-manager.ts +0 -8
- package/extensions/shared/file-logger.ts +132 -25
- package/extensions/shared/logger.ts +50 -0
- package/extensions/shared/models-dev.ts +421 -0
- package/extensions/shared/oneshot.ts +116 -0
- package/extensions/shared/resolve-binary.ts +191 -0
- package/extensions/shared/types.ts +3 -0
- package/package.json +9 -3
- package/prompts/coderabbit-rate-limit.md +2 -0
- package/rules/00-orchestrator-core.md +3 -0
- package/rules/10-agent-routing.md +12 -1
- package/rules/20-code-review-loop.md +52 -25
- package/rules/30-prompt-templates.md +4 -0
- package/rules/35-memory.md +3 -3
- package/rules/40-critical-rules.md +3 -0
- package/rules/55-coms-protocol.md +84 -18
- package/rules/60-task-tracking.md +2 -0
- package/settings-keys.json +563 -0
- package/templates/coms-feature-manager-prompt.md +4 -6
- package/extensions/coms/coms-net-server.ts +0 -1642
- package/extensions/coms/coms-net-wrapper.ts +0 -521
- package/extensions/coms/coms-net.ts +0 -1589
|
@@ -17,17 +17,21 @@ import { setupHeartbeat, setupReconnectPoller } from "../shared/ws-client.js";
|
|
|
17
17
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
18
18
|
import { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
19
19
|
import { hyperlink } from "@earendil-works/pi-tui";
|
|
20
|
-
|
|
21
|
-
const commandHandlerRegistry = new Map<string, (args: string, ctx: any) => Promise<void>>();
|
|
22
|
-
import { checkHealth, ensureUiBuilt, spawnDaemon as spawnDaemonGeneric, killDaemon, createLogger } from "../shared/daemon-manager.js";
|
|
20
|
+
import { checkHealth, ensureUiBuilt, spawnDaemon as spawnDaemonGeneric, killDaemon } from "../shared/daemon-manager.js";
|
|
23
21
|
import { getSetting } from "../orchestrator/project-settings.js";
|
|
22
|
+
import { shouldSkipOneshotRegister } from "../shared/oneshot.js";
|
|
23
|
+
import { createLogger } from "../shared/logger.js";
|
|
24
24
|
|
|
25
|
-
const
|
|
25
|
+
const log = createLogger("pidash");
|
|
26
|
+
|
|
27
|
+
// Module-level ref — survives closure replacement on /reload.
|
|
28
|
+
// raceWithBrowser captures `pi` from the closure, but after reload
|
|
29
|
+
// the old `pi` is stale (assertActive throws). This ref is updated
|
|
30
|
+
// on each extension factory call so event listeners always use the
|
|
31
|
+
// current runtime.
|
|
32
|
+
let currentPiEvents: any = null;
|
|
26
33
|
|
|
27
|
-
const
|
|
28
|
-
path.join(process.env.HOME || "/tmp", ".pi", "pidash-debug.log"),
|
|
29
|
-
"ext",
|
|
30
|
-
);
|
|
34
|
+
const RECONNECT_INTERVAL_MS = 5000;
|
|
31
35
|
|
|
32
36
|
// ── Helpers ──────────────────────────────────────────────────────────
|
|
33
37
|
|
|
@@ -36,12 +40,12 @@ function isDaemonRunning(port: number): Promise<boolean> {
|
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
function spawnDaemon(port: number): void {
|
|
39
|
-
ensureUiBuilt(import.meta.url, "pidash-ui",
|
|
43
|
+
ensureUiBuilt(import.meta.url, "pidash-ui", (msg) => log.debug(msg));
|
|
40
44
|
spawnDaemonGeneric({
|
|
41
45
|
serverScript: "pidash-server.ts",
|
|
42
46
|
logFile: path.join(process.env.HOME || "/tmp", ".pi", "pidash-server.log"),
|
|
43
47
|
env: { PI_PIDASH_PORT: String(port) },
|
|
44
|
-
log:
|
|
48
|
+
log: (msg) => log.debug(msg),
|
|
45
49
|
});
|
|
46
50
|
}
|
|
47
51
|
|
|
@@ -82,6 +86,8 @@ export function registerPidash(
|
|
|
82
86
|
pi: ExtensionAPI,
|
|
83
87
|
): void {
|
|
84
88
|
if (process.env.PI_SUBAGENT_CHILD === "1") return;
|
|
89
|
+
if (shouldSkipOneshotRegister(log)) return;
|
|
90
|
+
currentPiEvents = pi.events;
|
|
85
91
|
|
|
86
92
|
const projectCwd = process.cwd();
|
|
87
93
|
const pidashPort = getSetting(projectCwd, "pidash_port");
|
|
@@ -113,11 +119,50 @@ export function registerPidash(
|
|
|
113
119
|
let shuttingDown = false;
|
|
114
120
|
let spawning = false;
|
|
115
121
|
let lastCtx: any = null;
|
|
122
|
+
let lastCmdCtx: any = null; // Command context with switchSession — updated by /pidash handler
|
|
116
123
|
let cleanupHeartbeat: (() => void) | null = null;
|
|
124
|
+
let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
117
125
|
const sessionId = `${process.pid}:${process.cwd()}`;
|
|
118
126
|
const eventBuffer: string[] = []; // Buffer events for replay on daemon reconnect
|
|
119
127
|
let execCtx: any = null;
|
|
120
|
-
let
|
|
128
|
+
let comsIdentityName: string | undefined;
|
|
129
|
+
let comsIdentityPurpose: string | undefined;
|
|
130
|
+
let comsIdentityProject: string | undefined;
|
|
131
|
+
const commandHandlerRegistry = new Map<string, (args: string, ctx: any) => Promise<void> | void>();
|
|
132
|
+
|
|
133
|
+
// Listen for coms identity — coms ext emits this after boot
|
|
134
|
+
pi.events.on("pidash:coms-identity", (data: any) => {
|
|
135
|
+
comsIdentityName = data?.name;
|
|
136
|
+
comsIdentityPurpose = data?.purpose;
|
|
137
|
+
comsIdentityProject = data?.project;
|
|
138
|
+
log.debug("coms_identity_received", comsIdentityName);
|
|
139
|
+
if (ws && connected) {
|
|
140
|
+
try { ws.send(JSON.stringify({ type: "update_info", comsName: comsIdentityName, comsPurpose: comsIdentityPurpose, comsProject: comsIdentityProject })); } catch {}
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// ── Command handler bridge via pi.events ──────────────────────────
|
|
145
|
+
// TODO: Remove once pidash uses PiClient/RemoteSession (#732) — native
|
|
146
|
+
// command dispatch via RemoteSession.submit() eliminates this.
|
|
147
|
+
pi.events.on("pidash:register-command", (data: unknown) => {
|
|
148
|
+
const d = data as { name: string; handler: (args: string, ctx: any) => Promise<void> };
|
|
149
|
+
if (typeof d?.name === "string" && typeof d?.handler === "function") {
|
|
150
|
+
commandHandlerRegistry.set(d.name, d.handler);
|
|
151
|
+
log.debug("registered command handler", d.name);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Request existing commands (handles load order — orchestrator loaded first)
|
|
156
|
+
// TODO: Remove once pidash uses PiClient/RemoteSession (#732)
|
|
157
|
+
pi.events.emit("pidash:request-commands");
|
|
158
|
+
|
|
159
|
+
// Capture command context from orchestrator
|
|
160
|
+
// TODO: Remove once pidash uses PiClient/RemoteSession (#732)
|
|
161
|
+
pi.events.on("pidash:command-ctx", (ctx: unknown) => {
|
|
162
|
+
if (ctx && typeof (ctx as any)?.switchSession === "function") {
|
|
163
|
+
lastCmdCtx = ctx as any;
|
|
164
|
+
}
|
|
165
|
+
});
|
|
121
166
|
|
|
122
167
|
// ── Extracted inner functions ──────────────────────────────────────
|
|
123
168
|
|
|
@@ -207,9 +252,9 @@ export function registerPidash(
|
|
|
207
252
|
|
|
208
253
|
historyCount++;
|
|
209
254
|
}
|
|
210
|
-
if (historyCount > 0)
|
|
255
|
+
if (historyCount > 0) log.debug(`loaded ${historyCount} entries from session history`);
|
|
211
256
|
} catch (e: any) {
|
|
212
|
-
|
|
257
|
+
log.debug(`session history load error: ${e.message}`);
|
|
213
258
|
}
|
|
214
259
|
|
|
215
260
|
// Signal replay is complete so the server can stop suppressing notifications
|
|
@@ -221,10 +266,10 @@ export function registerPidash(
|
|
|
221
266
|
try {
|
|
222
267
|
const parsed = JSON.parse(data.toString());
|
|
223
268
|
if (parsed.type === "prompt" && (parsed.text || parsed.images)) {
|
|
224
|
-
|
|
269
|
+
log.debug(`received prompt from browser: ${(parsed.text || "").slice(0, 100)}${parsed.images ? ` [+${parsed.images.length} images]` : ""}`);
|
|
225
270
|
// Notify browser if prompt is queued during streaming
|
|
226
271
|
if (isStreaming && ws && connected) {
|
|
227
|
-
try { ws.send(JSON.stringify({ type: "prompt-queued" })); } catch (e: any) {
|
|
272
|
+
try { ws.send(JSON.stringify({ type: "prompt-queued" })); } catch (e: any) { log.debug(`prompt-queued send error: ${e.message}`); }
|
|
228
273
|
}
|
|
229
274
|
if (parsed.images && parsed.images.length > 0) {
|
|
230
275
|
// Build content array with text + images
|
|
@@ -241,18 +286,18 @@ export function registerPidash(
|
|
|
241
286
|
}
|
|
242
287
|
}
|
|
243
288
|
if (parsed.type === "extension_ui_response" && parsed.id) {
|
|
244
|
-
|
|
289
|
+
log.debug(`received UI response from browser: ${JSON.stringify(parsed).slice(0, 100)}`);
|
|
245
290
|
pi.events.emit("pidash:ui-response", parsed);
|
|
246
291
|
}
|
|
247
292
|
if (parsed.type === "pidash-command") {
|
|
248
|
-
|
|
293
|
+
log.debug(`received command from browser: ${parsed.command}`);
|
|
249
294
|
|
|
250
295
|
if (parsed.command === "list-sessions") {
|
|
251
296
|
try {
|
|
252
297
|
const sessions = await SessionManager.list(lastCtx?.cwd || process.cwd());
|
|
253
|
-
|
|
298
|
+
log.debug(`list-sessions: found ${sessions.length}`);
|
|
254
299
|
if (ws && connected) ws.send(JSON.stringify({ type: "sessions-list", sessions }));
|
|
255
|
-
} catch (e: any) {
|
|
300
|
+
} catch (e: any) { log.debug(`list-sessions error: ${e.message}`); }
|
|
256
301
|
}
|
|
257
302
|
|
|
258
303
|
if (parsed.command === "list-models") {
|
|
@@ -264,12 +309,12 @@ export function registerPidash(
|
|
|
264
309
|
name: m.name,
|
|
265
310
|
provider: typeof m.provider === "string" ? m.provider : m.provider?.name || "",
|
|
266
311
|
}));
|
|
267
|
-
|
|
312
|
+
log.debug(`models found: ${list.length}`);
|
|
268
313
|
if (ws && connected) ws.send(JSON.stringify({ type: "models-list", models: list }));
|
|
269
314
|
} else {
|
|
270
|
-
|
|
315
|
+
log.debug("list-models: no modelRegistry on ctx");
|
|
271
316
|
}
|
|
272
|
-
} catch (e: any) {
|
|
317
|
+
} catch (e: any) { log.debug(`list-models error: ${e.message}`); }
|
|
273
318
|
}
|
|
274
319
|
|
|
275
320
|
if (parsed.command === "set-model" && parsed.modelId) {
|
|
@@ -278,68 +323,85 @@ export function registerPidash(
|
|
|
278
323
|
m.id === parsed.modelId || m.name === parsed.modelId || m.id.includes(parsed.modelId) || m.name.includes(parsed.modelId));
|
|
279
324
|
if (model) {
|
|
280
325
|
await (pi as any).setModel(model);
|
|
281
|
-
|
|
326
|
+
log.debug(`model set to: ${model.name}`);
|
|
282
327
|
ws.send(JSON.stringify({ type: "update_info", model: model.name, contextWindow: model.contextWindow || 0 }));
|
|
283
328
|
}
|
|
284
|
-
} catch (e: any) {
|
|
329
|
+
} catch (e: any) { log.debug(`set-model error: ${e.message}`); }
|
|
285
330
|
}
|
|
286
331
|
|
|
287
332
|
if (parsed.command === "set-thinking" && parsed.level) {
|
|
288
333
|
try {
|
|
289
334
|
(pi as any).setThinkingLevel(parsed.level);
|
|
290
|
-
|
|
335
|
+
log.debug(`thinking set to: ${parsed.level}`);
|
|
291
336
|
ws.send(JSON.stringify({ type: "update_info", thinkingLevel: parsed.level }));
|
|
292
|
-
} catch (e: any) {
|
|
337
|
+
} catch (e: any) { log.debug(`set-thinking error: ${e.message}`); }
|
|
293
338
|
}
|
|
294
339
|
|
|
295
340
|
if (parsed.command === "switch-session" && parsed.sessionFile) {
|
|
296
|
-
|
|
297
|
-
const ctx =
|
|
341
|
+
log.debug(`switch-session: ${parsed.sessionFile}`);
|
|
342
|
+
const ctx = lastCmdCtx ?? lastCtx;
|
|
298
343
|
if (ctx?.switchSession) {
|
|
299
344
|
try {
|
|
300
345
|
await ctx.switchSession(parsed.sessionFile, {
|
|
301
346
|
withSession: async () => {
|
|
302
|
-
|
|
347
|
+
log.debug("switch-session: completed");
|
|
303
348
|
},
|
|
304
349
|
});
|
|
305
350
|
} catch (e: any) {
|
|
306
|
-
|
|
351
|
+
log.debug(`switch-session error: ${e.message}`);
|
|
307
352
|
}
|
|
308
353
|
} else {
|
|
309
|
-
|
|
354
|
+
log.debug("switch-session: no command context available");
|
|
310
355
|
}
|
|
311
356
|
}
|
|
312
357
|
|
|
313
358
|
if (parsed.command === "abort") {
|
|
314
359
|
if (lastCtx) {
|
|
315
|
-
try { lastCtx.abort();
|
|
360
|
+
try { lastCtx.abort(); log.debug("abort sent"); } catch {}
|
|
316
361
|
}
|
|
317
362
|
}
|
|
318
363
|
|
|
319
364
|
if (parsed.command === "async-kill" && parsed.target) {
|
|
320
|
-
|
|
365
|
+
log.debug(`async-kill from browser: ${parsed.target}`);
|
|
321
366
|
pi.events.emit("pidash:async-kill", parsed.target);
|
|
322
367
|
}
|
|
323
368
|
|
|
324
369
|
if (parsed.command === "cron-kill" && parsed.target) {
|
|
325
|
-
|
|
370
|
+
log.debug(`cron-kill from browser: ${parsed.target}`);
|
|
326
371
|
pi.events.emit("pidash:cron-kill", parsed.target);
|
|
327
372
|
}
|
|
328
373
|
|
|
374
|
+
if (parsed.command === "rename-session") {
|
|
375
|
+
log.debug("rename-session full parsed", JSON.stringify(parsed));
|
|
376
|
+
const newName = parsed.name ?? parsed.newName ?? "";
|
|
377
|
+
if (newName !== undefined) {
|
|
378
|
+
try {
|
|
379
|
+
(pi as any).setSessionName?.(newName);
|
|
380
|
+
sessionNamed = true;
|
|
381
|
+
log.debug(`session renamed: ${newName}`);
|
|
382
|
+
// Also send update_info to ensure sidebar updates immediately
|
|
383
|
+
if (ws && connected) {
|
|
384
|
+
try { ws.send(JSON.stringify({ type: "update_info", name: newName })); } catch {}
|
|
385
|
+
}
|
|
386
|
+
} catch (e: any) { log.debug(`rename-session error: ${e.message}`); }
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
329
390
|
if (parsed.command === "list-commands") {
|
|
330
391
|
try {
|
|
331
392
|
const cmds = (pi as any).getCommands?.() || [];
|
|
332
393
|
const list = cmds.map((c: any) => ({ name: c.name, description: c.description || "" }));
|
|
333
394
|
if (ws && connected) ws.send(JSON.stringify({ type: "commands-list", commands: list }));
|
|
334
|
-
} catch (e: any) {
|
|
395
|
+
} catch (e: any) { log.debug(`list-commands error: ${e.message}`); }
|
|
335
396
|
}
|
|
336
397
|
|
|
337
398
|
}
|
|
338
|
-
} catch (e: any) {
|
|
399
|
+
} catch (e: any) { log.debug(`message handler error: ${e.message}`); }
|
|
339
400
|
}
|
|
340
401
|
|
|
341
402
|
/** Handle /pidash command (start|stop|restart|status). */
|
|
342
403
|
async function handlePidashCommand(args: string, ctx: any): Promise<void> {
|
|
404
|
+
lastCmdCtx = ctx;
|
|
343
405
|
// Guard: pidash daemon connections only in TUI mode
|
|
344
406
|
if (ctx.mode !== "tui") {
|
|
345
407
|
if (ctx.hasUI) ctx.ui.notify("pidash is only available in TUI mode.", "info");
|
|
@@ -347,15 +409,13 @@ export function registerPidash(
|
|
|
347
409
|
}
|
|
348
410
|
|
|
349
411
|
execCtx = ctx;
|
|
350
|
-
pidashCommandCtx = ctx; // Real ExtensionCommandContext
|
|
351
|
-
debugLog("pidashCommandCtx captured from /pidash handler");
|
|
352
412
|
|
|
353
413
|
const cmd = (args || "").trim().toLowerCase();
|
|
354
414
|
|
|
355
415
|
if (cmd === "stop") {
|
|
356
416
|
if (ws) { try { ws.close(); } catch {} ws = null; }
|
|
357
417
|
connected = false;
|
|
358
|
-
killDaemon("pidash-server",
|
|
418
|
+
killDaemon("pidash-server", (msg) => log.debug(msg));
|
|
359
419
|
if (ctx.hasUI) {
|
|
360
420
|
ctx.ui.setStatus("9-pidash", undefined);
|
|
361
421
|
ctx.ui.notify("pidash server stopped", "info");
|
|
@@ -387,7 +447,7 @@ export function registerPidash(
|
|
|
387
447
|
if (cmd === "restart") {
|
|
388
448
|
if (ws) { try { ws.close(); } catch {} ws = null; }
|
|
389
449
|
connected = false;
|
|
390
|
-
killDaemon("pidash-server",
|
|
450
|
+
killDaemon("pidash-server", (msg) => log.debug(msg));
|
|
391
451
|
await new Promise(r => setTimeout(r, 1000));
|
|
392
452
|
spawnDaemon(pidashPort);
|
|
393
453
|
if (ctx.hasUI) ctx.ui.notify("Restarting pidash server...", "info");
|
|
@@ -420,31 +480,33 @@ export function registerPidash(
|
|
|
420
480
|
// ── connect() ──────────────────────────────────────────────────────
|
|
421
481
|
|
|
422
482
|
async function connect(ctx: any) {
|
|
423
|
-
|
|
483
|
+
log.debug(`connect() called, connected=${connected}, connecting=${connecting}, shuttingDown=${shuttingDown}, cwd=${ctx?.cwd}`);
|
|
424
484
|
if (connected || connecting || shuttingDown) return;
|
|
485
|
+
// Guard against stale ctx from surviving setTimeout after reload
|
|
486
|
+
try { void ctx?.ui?.theme; } catch { log.debug("connect() skipped — stale ctx"); return; }
|
|
425
487
|
connecting = true;
|
|
426
488
|
lastCtx = ctx;
|
|
427
489
|
|
|
428
490
|
const running = await isDaemonRunning(pidashPort);
|
|
429
|
-
|
|
491
|
+
log.debug(`daemon running: ${running}`);
|
|
430
492
|
if (!running) {
|
|
431
493
|
if (spawning) {
|
|
432
|
-
|
|
494
|
+
log.debug("daemon already spawning, waiting...");
|
|
433
495
|
} else {
|
|
434
496
|
spawning = true;
|
|
435
|
-
|
|
497
|
+
log.debug("spawning daemon...");
|
|
436
498
|
spawnDaemon(pidashPort);
|
|
437
499
|
}
|
|
438
500
|
// jiti cold compilation can take 30+ seconds on first run
|
|
439
501
|
for (let i = 0; i < 60; i++) {
|
|
440
502
|
await new Promise(r => setTimeout(r, 1000));
|
|
441
503
|
if (await isDaemonRunning(pidashPort)) {
|
|
442
|
-
|
|
504
|
+
log.debug(`daemon ready after ${i + 1}s`);
|
|
443
505
|
break;
|
|
444
506
|
}
|
|
445
507
|
}
|
|
446
508
|
if (!(await isDaemonRunning(pidashPort))) {
|
|
447
|
-
|
|
509
|
+
log.debug("daemon failed to start after 60s");
|
|
448
510
|
spawning = false;
|
|
449
511
|
connecting = false;
|
|
450
512
|
return;
|
|
@@ -455,11 +517,11 @@ export function registerPidash(
|
|
|
455
517
|
try {
|
|
456
518
|
const _require = createRequire(import.meta.url);
|
|
457
519
|
const WebSocket = _require("ws");
|
|
458
|
-
|
|
520
|
+
log.debug("creating WebSocket client...");
|
|
459
521
|
const wsClient = new WebSocket(`ws://127.0.0.1:${pidashPort}/ws/pi`);
|
|
460
522
|
|
|
461
523
|
wsClient.on("open", () => {
|
|
462
|
-
|
|
524
|
+
log.debug("WebSocket connected!");
|
|
463
525
|
ws = wsClient;
|
|
464
526
|
connected = true;
|
|
465
527
|
connecting = false;
|
|
@@ -488,8 +550,12 @@ export function registerPidash(
|
|
|
488
550
|
sessionFile: ctx.sessionManager?.getSessionFile?.() || ctx.sessionFile || "",
|
|
489
551
|
thinkingLevel: thinking,
|
|
490
552
|
diffPort,
|
|
553
|
+
name: (pi as any).getSessionName?.() || undefined,
|
|
554
|
+
comsName: comsIdentityName || (pi as any).getFlag?.("cname") || undefined,
|
|
555
|
+
comsPurpose: comsIdentityPurpose || (pi as any).getFlag?.("purpose") || undefined,
|
|
556
|
+
comsProject: comsIdentityProject || undefined,
|
|
491
557
|
});
|
|
492
|
-
|
|
558
|
+
log.debug(`sending register: ${reg}`);
|
|
493
559
|
wsClient.send(reg);
|
|
494
560
|
|
|
495
561
|
// Always load history from session file (source of truth)
|
|
@@ -503,18 +569,21 @@ export function registerPidash(
|
|
|
503
569
|
|
|
504
570
|
} catch (err) {
|
|
505
571
|
// ctx may be stale if session was replaced during WebSocket connect
|
|
506
|
-
|
|
572
|
+
log.debug(`open handler error (likely stale ctx): ${err}`);
|
|
507
573
|
}
|
|
508
574
|
|
|
509
575
|
// Keepalive + dead connection detection
|
|
510
576
|
if (cleanupHeartbeat) cleanupHeartbeat();
|
|
511
577
|
cleanupHeartbeat = setupHeartbeat({
|
|
512
578
|
ws: wsClient,
|
|
513
|
-
log:
|
|
579
|
+
log: (msg) => log.debug(msg),
|
|
514
580
|
onDead: () => {
|
|
515
581
|
connected = false;
|
|
516
582
|
ws = null;
|
|
517
|
-
if (!shuttingDown && lastCtx)
|
|
583
|
+
if (!shuttingDown && lastCtx) {
|
|
584
|
+
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
585
|
+
reconnectTimer = setTimeout(() => { reconnectTimer = null; if (lastCtx && !shuttingDown) connect(lastCtx); }, 1000);
|
|
586
|
+
}
|
|
518
587
|
},
|
|
519
588
|
});
|
|
520
589
|
|
|
@@ -532,21 +601,22 @@ export function registerPidash(
|
|
|
532
601
|
wsClient.on("message", (data: Buffer) => { handleWsMessage(data); });
|
|
533
602
|
|
|
534
603
|
wsClient.on("close", (code: number, reason: Buffer) => {
|
|
535
|
-
|
|
604
|
+
log.debug(`WebSocket closed: code=${code} reason=${reason?.toString() || 'none'}`);
|
|
536
605
|
connecting = false;
|
|
537
|
-
|
|
606
|
+
log.debug("WebSocket closed");
|
|
538
607
|
connected = false;
|
|
539
608
|
ws = null;
|
|
540
609
|
if (!shuttingDown) {
|
|
541
|
-
|
|
610
|
+
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
611
|
+
reconnectTimer = setTimeout(() => { reconnectTimer = null; if (lastCtx && !shuttingDown) connect(lastCtx); }, RECONNECT_INTERVAL_MS);
|
|
542
612
|
}
|
|
543
613
|
});
|
|
544
614
|
|
|
545
615
|
wsClient.on("error", (e: Error) => {
|
|
546
|
-
|
|
616
|
+
log.debug(`WebSocket error: ${e.message}`);
|
|
547
617
|
});
|
|
548
618
|
} catch (e: any) {
|
|
549
|
-
|
|
619
|
+
log.debug(`connect error: ${e.message}`);
|
|
550
620
|
connecting = false;
|
|
551
621
|
}
|
|
552
622
|
}
|
|
@@ -556,6 +626,7 @@ export function registerPidash(
|
|
|
556
626
|
// Forward events to daemon
|
|
557
627
|
function forward(type: string) {
|
|
558
628
|
pi.on(type as any, (event: any, ctx: any) => {
|
|
629
|
+
if (shuttingDown) return;
|
|
559
630
|
lastCtx = ctx;
|
|
560
631
|
let payload: any = { type, ...event, timestamp: Date.now() };
|
|
561
632
|
|
|
@@ -585,6 +656,7 @@ export function registerPidash(
|
|
|
585
656
|
// Keep only role and essential content
|
|
586
657
|
if (type === "message_end" && payload.message) {
|
|
587
658
|
const m = payload.message;
|
|
659
|
+
const isStreamed = ["user", "assistant", "toolResult"].includes(m.role);
|
|
588
660
|
payload = {
|
|
589
661
|
type,
|
|
590
662
|
message: {
|
|
@@ -594,6 +666,7 @@ export function registerPidash(
|
|
|
594
666
|
provider: m.provider,
|
|
595
667
|
customType: m.customType,
|
|
596
668
|
display: m.display,
|
|
669
|
+
...(isStreamed ? {} : { content: m.content }),
|
|
597
670
|
},
|
|
598
671
|
timestamp: payload.timestamp,
|
|
599
672
|
};
|
|
@@ -606,78 +679,15 @@ export function registerPidash(
|
|
|
606
679
|
while (eventBuffer.length > 10000) eventBuffer.shift();
|
|
607
680
|
}
|
|
608
681
|
if (ws && connected) {
|
|
609
|
-
try { ws.send(msg); } catch (e: any) {
|
|
682
|
+
try { ws.send(msg); } catch (e: any) { log.debug(`forward ${type} error: ${e.message}`); }
|
|
610
683
|
} else if (!connected) {
|
|
611
|
-
|
|
684
|
+
log.debug(`forward ${type}: ws not connected`);
|
|
612
685
|
}
|
|
613
686
|
});
|
|
614
687
|
}
|
|
615
688
|
|
|
616
689
|
// ── Setup functions ────────────────────────────────────────────────
|
|
617
690
|
|
|
618
|
-
/** Register wrapCtx on events that provide ctx, bridging TUI dialogs to browser. */
|
|
619
|
-
function setupCtxWrapping(): void {
|
|
620
|
-
// Wrap ALL ctx.ui dialog methods for pidash bridging
|
|
621
|
-
const wrapCtx = (_event: any, ctx: any) => {
|
|
622
|
-
if (!ctx?.ui || ctx.ui.__pidashWrapped) return;
|
|
623
|
-
ctx.ui.__pidashWrapped = true;
|
|
624
|
-
const origSelect = ctx.ui.select.bind(ctx.ui);
|
|
625
|
-
const origConfirm = ctx.ui.confirm.bind(ctx.ui);
|
|
626
|
-
|
|
627
|
-
function raceWithBrowser<T>(
|
|
628
|
-
askId: string,
|
|
629
|
-
payload: object,
|
|
630
|
-
origFn: (...args: any[]) => Promise<T>,
|
|
631
|
-
origArgs: any[],
|
|
632
|
-
extractBrowserValue: (r: any) => T,
|
|
633
|
-
opts?: any,
|
|
634
|
-
): Promise<T> {
|
|
635
|
-
if (ws && connected) ws.send(JSON.stringify({ id: askId, ...payload }));
|
|
636
|
-
const ac = new AbortController();
|
|
637
|
-
let browserResolve: ((v: T) => void) | null = null;
|
|
638
|
-
const unsub = pi.events.on("pidash:ui-response", (data: unknown) => {
|
|
639
|
-
const r = data as any;
|
|
640
|
-
if (r.id === askId && browserResolve) { browserResolve(extractBrowserValue(r)); ac.abort(); }
|
|
641
|
-
});
|
|
642
|
-
return Promise.race([
|
|
643
|
-
origFn(...origArgs, { ...opts, signal: opts?.signal || ac.signal }).then((v: T) => {
|
|
644
|
-
browserResolve = null;
|
|
645
|
-
pi.events.emit("pidash:ui-dismiss", { type: "ui-dismiss", id: askId });
|
|
646
|
-
return v;
|
|
647
|
-
}),
|
|
648
|
-
new Promise<T>((resolve) => { browserResolve = resolve; }),
|
|
649
|
-
]).finally(() => unsub());
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
ctx.ui.select = async (title: string, options: string[], opts?: any) => {
|
|
653
|
-
const askId = `dialog-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
654
|
-
return raceWithBrowser<string | undefined>(
|
|
655
|
-
askId,
|
|
656
|
-
{ type: "extension_ui_request", method: "select", title, options },
|
|
657
|
-
origSelect, [title, options],
|
|
658
|
-
(r) => r.cancelled ? undefined : r.value,
|
|
659
|
-
opts,
|
|
660
|
-
);
|
|
661
|
-
};
|
|
662
|
-
|
|
663
|
-
ctx.ui.confirm = async (title: string, message: string, opts?: any) => {
|
|
664
|
-
const askId = `dialog-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
665
|
-
return raceWithBrowser<boolean>(
|
|
666
|
-
askId,
|
|
667
|
-
{ type: "extension_ui_request", method: "confirm", title, message },
|
|
668
|
-
origConfirm, [title, message],
|
|
669
|
-
(r) => r.confirmed ?? false,
|
|
670
|
-
opts,
|
|
671
|
-
);
|
|
672
|
-
};
|
|
673
|
-
};
|
|
674
|
-
|
|
675
|
-
// Register wrapper on all events that provide ctx
|
|
676
|
-
for (const evt of ["tool_call", "tool_result", "agent_start", "turn_start"] as const) {
|
|
677
|
-
pi.on(evt as any, wrapCtx);
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
|
|
681
691
|
/** Register all forward() calls and individual pi.on() handlers that forward events to the daemon. */
|
|
682
692
|
function setupEventForwarding(): void {
|
|
683
693
|
forward("agent_start");
|
|
@@ -685,8 +695,8 @@ export function registerPidash(
|
|
|
685
695
|
forward("agent_settled");
|
|
686
696
|
|
|
687
697
|
// Track streaming state for prompt-queued feedback
|
|
688
|
-
pi.on("agent_start", () => { isStreaming = true; });
|
|
689
|
-
pi.on("agent_settled", () => { isStreaming = false; });
|
|
698
|
+
pi.on("agent_start", () => { if (shuttingDown) return; isStreaming = true; });
|
|
699
|
+
pi.on("agent_settled", () => { if (shuttingDown) return; isStreaming = false; });
|
|
690
700
|
forward("turn_start");
|
|
691
701
|
forward("turn_end");
|
|
692
702
|
forward("message_start");
|
|
@@ -700,6 +710,7 @@ export function registerPidash(
|
|
|
700
710
|
forward("session_info_changed");
|
|
701
711
|
|
|
702
712
|
pi.on("model_select", (event: any) => {
|
|
713
|
+
if (shuttingDown) return;
|
|
703
714
|
if (ws && connected) {
|
|
704
715
|
ws.send(JSON.stringify({
|
|
705
716
|
type: "update_info",
|
|
@@ -711,6 +722,7 @@ export function registerPidash(
|
|
|
711
722
|
|
|
712
723
|
// Sync thinking level on every turn
|
|
713
724
|
pi.on("turn_end", () => {
|
|
725
|
+
if (shuttingDown) return;
|
|
714
726
|
if (!ws || !connected) return;
|
|
715
727
|
try {
|
|
716
728
|
const level = (pi as any).getThinkingLevel?.();
|
|
@@ -730,6 +742,7 @@ export function registerPidash(
|
|
|
730
742
|
|
|
731
743
|
// Forward provider response info to pidash
|
|
732
744
|
pi.on("after_provider_response" as any, (event: any) => {
|
|
745
|
+
if (shuttingDown) return;
|
|
733
746
|
if (!ws || !connected) return;
|
|
734
747
|
try {
|
|
735
748
|
const info: any = { type: "provider_response" };
|
|
@@ -741,7 +754,7 @@ export function registerPidash(
|
|
|
741
754
|
if (event.headers["x-request-id"]) info.requestId = event.headers["x-request-id"];
|
|
742
755
|
}
|
|
743
756
|
ws.send(JSON.stringify(info));
|
|
744
|
-
} catch (e: any) {
|
|
757
|
+
} catch (e: any) { log.debug(`provider response forward error: ${e.message}`); }
|
|
745
758
|
});
|
|
746
759
|
}
|
|
747
760
|
|
|
@@ -775,28 +788,15 @@ export function registerPidash(
|
|
|
775
788
|
}
|
|
776
789
|
});
|
|
777
790
|
|
|
778
|
-
//
|
|
779
|
-
pi.events.on("pidash:
|
|
780
|
-
const d = data as {
|
|
781
|
-
if (
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
}
|
|
785
|
-
commandHandlerRegistry.set(d.name, d.handler);
|
|
786
|
-
debugLog(`registered command handler: ${d.name}`);
|
|
791
|
+
// Forward coms peer join/leave events to browser
|
|
792
|
+
pi.events.on("pidash:coms-peer-event", (data: unknown) => {
|
|
793
|
+
const d = data as { customType?: string; content?: string };
|
|
794
|
+
if (!d?.customType) return;
|
|
795
|
+
if (ws && connected) {
|
|
796
|
+
try { ws.send(JSON.stringify({ type: "coms_peer_event", customType: d.customType, content: d.content || "" })); } catch {}
|
|
787
797
|
}
|
|
788
798
|
});
|
|
789
799
|
|
|
790
|
-
// Request existing commands (handles load order — orchestrator may have loaded first)
|
|
791
|
-
pi.events.emit("pidash:request-commands");
|
|
792
|
-
|
|
793
|
-
// Capture command context from orchestrator for switch-session fallback
|
|
794
|
-
// Only accept real ExtensionCommandContext (has switchSession method)
|
|
795
|
-
pi.events.on("pidash:command-ctx", (ctx: unknown) => {
|
|
796
|
-
if (ctx && typeof (ctx as any)?.switchSession === "function") {
|
|
797
|
-
pidashCommandCtx = ctx as any;
|
|
798
|
-
}
|
|
799
|
-
});
|
|
800
800
|
}
|
|
801
801
|
|
|
802
802
|
/** Periodically send git status updates to the daemon. */
|
|
@@ -812,16 +812,18 @@ export function registerPidash(
|
|
|
812
812
|
gitDirty: git.dirty,
|
|
813
813
|
gitChanges: git.changes,
|
|
814
814
|
}));
|
|
815
|
-
} catch (e: any) {
|
|
815
|
+
} catch (e: any) { log.debug(`periodic status error: ${e?.message || e}`); }
|
|
816
816
|
}, 10000);
|
|
817
817
|
if (statusInterval.unref) statusInterval.unref();
|
|
818
818
|
}
|
|
819
819
|
|
|
820
820
|
/** Handle session_start event — connect or notify session switch. */
|
|
821
|
-
function handleSessionStart(
|
|
821
|
+
function handleSessionStart(event: any, ctx: any): void {
|
|
822
|
+
sessionNamed = false;
|
|
822
823
|
execCtx = ctx;
|
|
823
|
-
|
|
824
|
-
|
|
824
|
+
log.debug("execCtx created from session_start");
|
|
825
|
+
|
|
826
|
+
log.debug("session_start", (event as any)?.reason);
|
|
825
827
|
|
|
826
828
|
// Auto-capture command context: silently run /pidash status.
|
|
827
829
|
// pi.sendUserMessage won't trigger command dispatch (expandPromptTemplates: false),
|
|
@@ -842,7 +844,7 @@ export function registerPidash(
|
|
|
842
844
|
branch: getCurrentBranch(ctx.cwd),
|
|
843
845
|
sessionFile: ctx.sessionManager?.getSessionFile?.() || ctx.sessionFile || "",
|
|
844
846
|
}));
|
|
845
|
-
|
|
847
|
+
log.debug(`session_switch sent: cwd=${ctx.cwd}`);
|
|
846
848
|
}
|
|
847
849
|
}
|
|
848
850
|
|
|
@@ -850,7 +852,7 @@ export function registerPidash(
|
|
|
850
852
|
async function handleBrowserInput(event: any, _ctx: any): Promise<{ action: "handled" } | void> {
|
|
851
853
|
if ((event as any).streamingBehavior && ws && connected) {
|
|
852
854
|
try { ws.send(JSON.stringify({ type: "streaming-behavior", behavior: (event as any).streamingBehavior })); }
|
|
853
|
-
catch (e: any) {
|
|
855
|
+
catch (e: any) { log.debug(`streaming-behavior send error: ${e.message}`); }
|
|
854
856
|
}
|
|
855
857
|
|
|
856
858
|
// Intercept extension commands from browser
|
|
@@ -862,30 +864,40 @@ export function registerPidash(
|
|
|
862
864
|
const cmdName = (spaceIdx > 0 ? text.slice(1, spaceIdx) : text.slice(1)).toLowerCase();
|
|
863
865
|
const arg = spaceIdx > 0 ? text.slice(spaceIdx + 1).trim() : "";
|
|
864
866
|
|
|
865
|
-
|
|
867
|
+
log.debug(`browser command: /${cmdName} ${arg.slice(0, 80)}`);
|
|
866
868
|
|
|
867
869
|
const handler = commandHandlerRegistry.get(cmdName);
|
|
868
870
|
if (handler) {
|
|
869
871
|
try {
|
|
870
|
-
|
|
872
|
+
const rawCtx = lastCmdCtx ?? execCtx ?? lastCtx;
|
|
873
|
+
const ctx = rawCtx?.ui ? { ...rawCtx, ui: { ...rawCtx.ui, notify: (msg: string, level?: string) => {
|
|
874
|
+
if (ws && connected) { try { ws.send(JSON.stringify({ type: "notification", level: level || "info", message: String(msg) })); } catch {} }
|
|
875
|
+
return rawCtx.ui.notify(msg, level);
|
|
876
|
+
} } } : rawCtx;
|
|
877
|
+
await handler(arg, ctx);
|
|
871
878
|
} catch (e: any) {
|
|
872
|
-
|
|
879
|
+
log.error(`command /${cmdName} error:`, e);
|
|
873
880
|
}
|
|
874
881
|
return { action: "handled" as const };
|
|
875
882
|
}
|
|
883
|
+
// Unknown command from browser — send error notification
|
|
884
|
+
if (ws && connected) {
|
|
885
|
+
try { ws.send(JSON.stringify({ type: "notification", level: "warning", message: `Unknown command: /${cmdName}` })); } catch {}
|
|
886
|
+
}
|
|
887
|
+
return { action: "handled" as const };
|
|
876
888
|
}
|
|
877
889
|
|
|
878
890
|
// ── Wire everything up ─────────────────────────────────────────────
|
|
879
891
|
|
|
880
|
-
setupCtxWrapping();
|
|
881
892
|
setupEventForwarding();
|
|
882
893
|
setupPidashEventListeners();
|
|
883
894
|
setupPeriodicStatus();
|
|
884
895
|
|
|
885
|
-
pi.on("session_start", handleSessionStart);
|
|
896
|
+
pi.on("session_start", (event: any, ctx: any) => { if (shuttingDown) return; handleSessionStart(event, ctx); });
|
|
886
897
|
|
|
887
898
|
// Fallback for /reload — connect on first tool_result if not connected
|
|
888
899
|
pi.on("tool_result", (_event, ctx) => {
|
|
900
|
+
if (shuttingDown) return;
|
|
889
901
|
if (!connected && !shuttingDown && ctx.mode === "tui") connect(ctx);
|
|
890
902
|
});
|
|
891
903
|
|
|
@@ -918,8 +930,32 @@ export function registerPidash(
|
|
|
918
930
|
},
|
|
919
931
|
handler: (args, ctx) => handlePidashCommand(args, ctx),
|
|
920
932
|
});
|
|
933
|
+
commandHandlerRegistry.set("pidash", (args, ctx) => handlePidashCommand(args, ctx));
|
|
934
|
+
|
|
935
|
+
pi.on("input", async (event, _ctx) => { if (shuttingDown) return; return handleBrowserInput(event, _ctx); });
|
|
921
936
|
|
|
922
|
-
|
|
937
|
+
// Auto-name session from first user prompt if no name set
|
|
938
|
+
let sessionNamed = false;
|
|
939
|
+
pi.on("agent_end" as any, () => {
|
|
940
|
+
if (sessionNamed || shuttingDown) return;
|
|
941
|
+
const currentName = (pi as any).getSessionName?.();
|
|
942
|
+
if (currentName) { sessionNamed = true; return; }
|
|
943
|
+
try {
|
|
944
|
+
const entries = lastCtx?.sessionManager?.getEntries?.() || [];
|
|
945
|
+
const firstUser = entries.find((e: any) => e.type === "message" && e.message?.role === "user");
|
|
946
|
+
if (firstUser) {
|
|
947
|
+
const content = firstUser.message.content;
|
|
948
|
+
const text = typeof content === "string" ? content :
|
|
949
|
+
Array.isArray(content) ? content.filter((b: any) => b.type === "text").map((b: any) => b.text).join(" ") : "";
|
|
950
|
+
if (text) {
|
|
951
|
+
const name = text.slice(0, 60).replace(/\n/g, " ").trim();
|
|
952
|
+
(pi as any).setSessionName?.(name);
|
|
953
|
+
sessionNamed = true;
|
|
954
|
+
log.debug("auto_session_name", name);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
} catch (e: any) { log.debug("auto_session_name_error", e?.message); }
|
|
958
|
+
});
|
|
923
959
|
|
|
924
960
|
pi.on("session_shutdown", (event) => {
|
|
925
961
|
// Forward shutdown reason to pidash dashboard
|
|
@@ -934,6 +970,7 @@ export function registerPidash(
|
|
|
934
970
|
}
|
|
935
971
|
shuttingDown = true;
|
|
936
972
|
cleanupReconnect();
|
|
973
|
+
if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; }
|
|
937
974
|
if (cleanupHeartbeat) { cleanupHeartbeat(); cleanupHeartbeat = null; }
|
|
938
975
|
if (ws) { try { ws.close(); } catch {} ws = null; }
|
|
939
976
|
connected = false;
|