pi-ui-extend 0.1.21 → 0.1.24
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 +1 -10
- package/bin/pix.mjs +11 -154
- package/dist/app/app.d.ts +1 -0
- package/dist/app/app.js +34 -9
- package/dist/app/cli/startup-info.d.ts +0 -1
- package/dist/app/cli/startup-info.js +0 -3
- package/dist/app/commands/command-session-actions.js +3 -0
- package/dist/app/popup/popup-menu-controller.js +7 -1
- package/dist/app/rendering/conversation-entry-renderer.js +29 -40
- package/dist/app/rendering/render-text.d.ts +6 -0
- package/dist/app/rendering/render-text.js +9 -0
- package/dist/app/rendering/tab-line-renderer.js +1 -5
- package/dist/app/rendering/tool-block-renderer.js +7 -1
- package/dist/app/screen/mouse-controller.js +14 -6
- package/dist/app/session/session-event-controller.js +5 -4
- package/dist/app/session/session-lifecycle-controller.js +0 -4
- package/dist/app/session/tabs-controller.d.ts +5 -1
- package/dist/app/session/tabs-controller.js +111 -23
- package/dist/app/types.d.ts +5 -0
- package/dist/app/workspace/workspace-actions-controller.d.ts +3 -0
- package/dist/app/workspace/workspace-actions-controller.js +71 -16
- package/dist/app/workspace/workspace-undo.js +41 -6
- package/dist/markdown-format.d.ts +4 -0
- package/dist/markdown-format.js +6 -1
- package/dist/theme.js +18 -18
- package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +1 -0
- package/external/pi-tools-suite/src/telegram-mirror/README.md +81 -46
- package/external/pi-tools-suite/src/telegram-mirror/bot.ts +81 -10
- package/external/pi-tools-suite/src/telegram-mirror/events.ts +6 -38
- package/external/pi-tools-suite/src/telegram-mirror/index.ts +246 -40
- package/external/pi-tools-suite/src/telegram-mirror/ipc.ts +20 -0
- package/external/pi-tools-suite/src/telegram-mirror/multiplexer.ts +247 -17
- package/external/pi-tools-suite/src/telegram-mirror/renderer.ts +75 -78
- package/external/pi-tools-suite/src/todo/index.ts +7 -6
- package/external/pi-tools-suite/src/todo/tool/response-envelope.ts +1 -1
- package/external/pi-tools-suite/src/web-search/index.ts +139 -2
- package/package.json +7 -7
|
@@ -44,7 +44,9 @@ import {
|
|
|
44
44
|
IpcSocket,
|
|
45
45
|
buildInstanceId,
|
|
46
46
|
tryAcquireLeadership,
|
|
47
|
+
updateInstanceInfo,
|
|
47
48
|
type IpcMessage,
|
|
49
|
+
type InstanceInfo,
|
|
48
50
|
} from "./ipc.js";
|
|
49
51
|
import { Multiplexer, type LocalDispatch } from "./multiplexer.js";
|
|
50
52
|
|
|
@@ -55,19 +57,34 @@ interface MirrorContext {
|
|
|
55
57
|
isIdle(): boolean;
|
|
56
58
|
hasPendingMessages(): boolean;
|
|
57
59
|
compact(): void;
|
|
60
|
+
currentDialog(): string | undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface SessionSnapshot {
|
|
64
|
+
cwd: string;
|
|
65
|
+
sessionId?: string;
|
|
66
|
+
sessionFile?: string;
|
|
67
|
+
sessionName?: string;
|
|
58
68
|
}
|
|
59
69
|
|
|
60
70
|
const RECONNECT_DELAY_MS = 2_000;
|
|
71
|
+
const COMMAND_NAME = "telegram-mirror";
|
|
72
|
+
const COMMAND_ALIAS = "tg";
|
|
73
|
+
const COMMAND_OFF = "tg-off";
|
|
61
74
|
|
|
62
75
|
export default function telegramMirror(pi: ExtensionAPI): void {
|
|
63
76
|
const cfg = loadTelegramMirrorConfig();
|
|
64
|
-
if (!cfg
|
|
65
|
-
// Soft-disable: no logging at startup, the user
|
|
77
|
+
if (!cfg) {
|
|
78
|
+
// Soft-disable: no logging at startup, the user hasn't configured
|
|
79
|
+
// botToken/chatId yet. When the block exists, even with
|
|
80
|
+
// enabled:false, still register /telegram-mirror and /tg so the
|
|
81
|
+
// user can activate the bot explicitly by slash command.
|
|
66
82
|
return;
|
|
67
83
|
}
|
|
68
84
|
|
|
69
85
|
const { botToken: token, chatId } = cfg;
|
|
70
|
-
const { id: selfId, info:
|
|
86
|
+
const { id: selfId, info: baseSelfInfo } = buildInstanceId();
|
|
87
|
+
let selfInfo: InstanceInfo = baseSelfInfo;
|
|
71
88
|
|
|
72
89
|
let role: Role = "starting";
|
|
73
90
|
let bot: TelegramBot | undefined;
|
|
@@ -80,6 +97,7 @@ export default function telegramMirror(pi: ExtensionAPI): void {
|
|
|
80
97
|
let reconnectTimer: NodeJS.Timeout | undefined;
|
|
81
98
|
let shutdown = false;
|
|
82
99
|
let disabled = false;
|
|
100
|
+
let activationRequested = false;
|
|
83
101
|
|
|
84
102
|
const log = (message: string): void => {
|
|
85
103
|
// eslint-disable-next-line no-console
|
|
@@ -87,13 +105,16 @@ export default function telegramMirror(pi: ExtensionAPI): void {
|
|
|
87
105
|
};
|
|
88
106
|
|
|
89
107
|
// Dispatch the leader uses to execute commands on its own pi session.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
108
|
+
const localDispatch: LocalDispatch = {
|
|
109
|
+
sendUserMessage(text) {
|
|
110
|
+
pi.sendUserMessage(text);
|
|
111
|
+
},
|
|
112
|
+
currentDialog() {
|
|
113
|
+
return mirrorCtx?.currentDialog();
|
|
114
|
+
},
|
|
115
|
+
abort() {
|
|
116
|
+
mirrorCtx?.abort();
|
|
117
|
+
},
|
|
97
118
|
compact() {
|
|
98
119
|
mirrorCtx?.compact();
|
|
99
120
|
},
|
|
@@ -118,12 +139,18 @@ export default function telegramMirror(pi: ExtensionAPI): void {
|
|
|
118
139
|
|
|
119
140
|
const hooks: PixMirrorHooks = {
|
|
120
141
|
getRenderer: () => eventSink,
|
|
142
|
+
describeInstance: (ctx) => describeInstance(ctx),
|
|
121
143
|
notifyAgentEnd: () => undefined,
|
|
122
144
|
};
|
|
123
145
|
|
|
124
146
|
registerPixEventHandlers(pi, hooks);
|
|
147
|
+
registerActivationCommand(COMMAND_NAME);
|
|
148
|
+
registerActivationCommand(COMMAND_ALIAS);
|
|
149
|
+
registerOffCommand();
|
|
125
150
|
|
|
126
151
|
pi.on("session_start", async (_event, ctx) => {
|
|
152
|
+
refreshCtx(ctx as ExtensionContext | undefined);
|
|
153
|
+
refreshSelfInfo(ctx as ExtensionContext | undefined);
|
|
127
154
|
if (!captureHooksInstalled) {
|
|
128
155
|
captureHooksInstalled = true;
|
|
129
156
|
captureAbortableContext(ctx as ExtensionContext | undefined, {
|
|
@@ -145,11 +172,17 @@ export default function telegramMirror(pi: ExtensionAPI): void {
|
|
|
145
172
|
},
|
|
146
173
|
});
|
|
147
174
|
}
|
|
148
|
-
await start();
|
|
175
|
+
if (activationRequested) await start();
|
|
149
176
|
});
|
|
150
177
|
|
|
151
|
-
pi.on("agent_start", (_e, ctx) =>
|
|
152
|
-
|
|
178
|
+
pi.on("agent_start", (_e, ctx) => {
|
|
179
|
+
refreshCtx(ctx as ExtensionContext | undefined);
|
|
180
|
+
refreshSelfInfo(ctx as ExtensionContext | undefined);
|
|
181
|
+
});
|
|
182
|
+
pi.on("before_agent_start", (_e, ctx) => {
|
|
183
|
+
refreshCtx(ctx as ExtensionContext | undefined);
|
|
184
|
+
refreshSelfInfo(ctx as ExtensionContext | undefined);
|
|
185
|
+
});
|
|
153
186
|
|
|
154
187
|
pi.on("session_shutdown", async (event) => {
|
|
155
188
|
// On reload/fork the module will be reloaded in the same process —
|
|
@@ -160,6 +193,7 @@ export default function telegramMirror(pi: ExtensionAPI): void {
|
|
|
160
193
|
});
|
|
161
194
|
|
|
162
195
|
async function start(): Promise<void> {
|
|
196
|
+
activationRequested = true;
|
|
163
197
|
if (shutdown) return;
|
|
164
198
|
if (disabled) return;
|
|
165
199
|
if (role !== "starting") return;
|
|
@@ -211,6 +245,17 @@ export default function telegramMirror(pi: ExtensionAPI): void {
|
|
|
211
245
|
await stepDown();
|
|
212
246
|
return;
|
|
213
247
|
}
|
|
248
|
+
await created.setMyCommands([
|
|
249
|
+
{ command: "menu", description: "Choose project/session" },
|
|
250
|
+
{ command: "list", description: "List pi sessions" },
|
|
251
|
+
{ command: "use", description: "Follow a session by number" },
|
|
252
|
+
{ command: "status", description: "Show followed session status" },
|
|
253
|
+
{ command: "clear", description: "Clear known bot messages" },
|
|
254
|
+
{ command: "abort", description: "Abort followed session" },
|
|
255
|
+
{ command: "compact", description: "Compact followed session" },
|
|
256
|
+
{ command: "disconnect", description: "Stop Telegram mirror" },
|
|
257
|
+
{ command: "help", description: "Show help" },
|
|
258
|
+
]).catch((error) => log(`setMyCommands failed: ${errorMessage(error)}`));
|
|
214
259
|
bot = created;
|
|
215
260
|
log(`connected as @${me.result.username} (leader) [${selfInfo.label}]`);
|
|
216
261
|
} catch (error) {
|
|
@@ -231,15 +276,16 @@ export default function telegramMirror(pi: ExtensionAPI): void {
|
|
|
231
276
|
standDown: clusterStandDown,
|
|
232
277
|
log,
|
|
233
278
|
});
|
|
234
|
-
|
|
279
|
+
multiplexer.init();
|
|
235
280
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
281
|
+
bot.startPolling(async (update) => {
|
|
282
|
+
await multiplexer?.handleTelegramUpdate(update);
|
|
283
|
+
});
|
|
284
|
+
await bot.sendMessage(`✅ Telegram mirror active: ${selfInfo.label}`, {
|
|
285
|
+
replyMarkup: { inline_keyboard: [[{ text: "🧭 Choose project/session", callback_data: "tg:list" }]] },
|
|
286
|
+
}).catch((error) => log(`startup message failed: ${errorMessage(error)}`));
|
|
287
|
+
await multiplexer.showActiveDialog();
|
|
288
|
+
}
|
|
243
289
|
|
|
244
290
|
function becomeFollower(socket: IpcSocket): void {
|
|
245
291
|
role = "follower";
|
|
@@ -309,15 +355,53 @@ export default function telegramMirror(pi: ExtensionAPI): void {
|
|
|
309
355
|
socket.send({ type: "command_ack", reqId, ok, error });
|
|
310
356
|
}
|
|
311
357
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
358
|
+
async function handleFollowerQuery(socket: IpcSocket, reqId: string, query: string): Promise<void> {
|
|
359
|
+
if (query === "status") {
|
|
360
|
+
const result = mirrorCtx
|
|
361
|
+
? { idle: mirrorCtx.isIdle(), hasPending: mirrorCtx.hasPendingMessages() }
|
|
362
|
+
: { idle: true, hasPending: false };
|
|
363
|
+
socket.send({ type: "query_reply", reqId, ok: true, result });
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
if (query === "dialog") {
|
|
367
|
+
socket.send({ type: "query_reply", reqId, ok: true, result: { text: mirrorCtx?.currentDialog() ?? "" } });
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
socket.send({ type: "query_reply", reqId, ok: false, error: `unknown query: ${query}` });
|
|
319
371
|
}
|
|
320
|
-
|
|
372
|
+
|
|
373
|
+
function registerActivationCommand(name: string): void {
|
|
374
|
+
pi.registerCommand(name, {
|
|
375
|
+
description: name === COMMAND_NAME ? "Start Telegram mirror and show connection status" : "Alias for /telegram-mirror",
|
|
376
|
+
handler: async (args, ctx) => {
|
|
377
|
+
refreshCtx(ctx as ExtensionContext | undefined);
|
|
378
|
+
refreshSelfInfo(ctx as ExtensionContext | undefined);
|
|
379
|
+
const trimmed = args.trim();
|
|
380
|
+
if (trimmed === "status") {
|
|
381
|
+
notify(ctx, localStatusText(), "info");
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
if (trimmed === "stop" || trimmed === "disconnect") {
|
|
385
|
+
await clusterStandDown();
|
|
386
|
+
notify(ctx, "Telegram mirror stopped. Run /telegram-mirror to start again.", "info");
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
disabled = false;
|
|
390
|
+
activationRequested = true;
|
|
391
|
+
await start();
|
|
392
|
+
notify(ctx, localStatusText(), "info");
|
|
393
|
+
},
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function registerOffCommand(): void {
|
|
398
|
+
pi.registerCommand(COMMAND_OFF, {
|
|
399
|
+
description: "Stop Telegram mirror cluster",
|
|
400
|
+
handler: async (_args, ctx) => {
|
|
401
|
+
await clusterStandDown();
|
|
402
|
+
notify(ctx, "Telegram mirror stopped. Run /tg or /telegram-mirror to start again.", "info");
|
|
403
|
+
},
|
|
404
|
+
});
|
|
321
405
|
}
|
|
322
406
|
|
|
323
407
|
async function stepDown(): Promise<void> {
|
|
@@ -351,6 +435,7 @@ export default function telegramMirror(pi: ExtensionAPI): void {
|
|
|
351
435
|
async function clusterStandDown(): Promise<void> {
|
|
352
436
|
if (disabled) return;
|
|
353
437
|
disabled = true;
|
|
438
|
+
activationRequested = false;
|
|
354
439
|
if (server && role === "leader") {
|
|
355
440
|
try {
|
|
356
441
|
server.broadcast({ type: "stand_down" });
|
|
@@ -392,19 +477,46 @@ export default function telegramMirror(pi: ExtensionAPI): void {
|
|
|
392
477
|
function refreshCtx(ctx: ExtensionContext | undefined): void {
|
|
393
478
|
if (!ctx) return;
|
|
394
479
|
if (!mirrorCtx) {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
480
|
+
mirrorCtx = makeCtx({
|
|
481
|
+
abort: () => ctx.abort(),
|
|
482
|
+
isIdle: () => ctx.isIdle(),
|
|
483
|
+
hasPendingMessages: () => ctx.hasPendingMessages(),
|
|
484
|
+
compact: () => ctx.compact(),
|
|
485
|
+
currentDialog: () => currentDialogFromContext(ctx),
|
|
486
|
+
});
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
403
489
|
const m = mirrorCtx as Mutable<MirrorContext>;
|
|
404
490
|
m.abort = () => ctx.abort();
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
491
|
+
m.isIdle = () => ctx.isIdle();
|
|
492
|
+
m.hasPendingMessages = () => ctx.hasPendingMessages();
|
|
493
|
+
m.compact = () => ctx.compact();
|
|
494
|
+
m.currentDialog = () => currentDialogFromContext(ctx);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function refreshSelfInfo(ctx: ExtensionContext | undefined): void {
|
|
498
|
+
const snapshot = sessionSnapshot(ctx);
|
|
499
|
+
if (!snapshot) return;
|
|
500
|
+
selfInfo = updateInstanceInfo(selfInfo, snapshot);
|
|
501
|
+
multiplexer?.updateSelfInfo(selfInfo);
|
|
502
|
+
if (role === "follower" && clientSocket && !clientSocket.isClosed) {
|
|
503
|
+
clientSocket.send({ type: "instance_update", info: selfInfo });
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function describeInstance(ctx: ExtensionContext | undefined) {
|
|
508
|
+
refreshSelfInfo(ctx);
|
|
509
|
+
return {
|
|
510
|
+
label: selfInfo.label,
|
|
511
|
+
cwd: selfInfo.cwd,
|
|
512
|
+
...(selfInfo.sessionId ? { sessionId: selfInfo.sessionId } : {}),
|
|
513
|
+
...(selfInfo.sessionName ? { sessionName: selfInfo.sessionName } : {}),
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function localStatusText(): string {
|
|
518
|
+
const status = role === "leader" ? "leader/polling" : role === "follower" ? "follower/connected" : "not connected";
|
|
519
|
+
return `Telegram mirror: ${status}\n${selfInfo.label}${selfInfo.sessionName ? ` · ${selfInfo.sessionName}` : ""}`;
|
|
408
520
|
}
|
|
409
521
|
}
|
|
410
522
|
|
|
@@ -416,9 +528,103 @@ function makeCtx(seed: Partial<MirrorContext>): MirrorContext {
|
|
|
416
528
|
isIdle: seed.isIdle ?? (() => true),
|
|
417
529
|
hasPendingMessages: seed.hasPendingMessages ?? (() => false),
|
|
418
530
|
compact: seed.compact ?? (() => undefined),
|
|
531
|
+
currentDialog: seed.currentDialog ?? (() => undefined),
|
|
419
532
|
};
|
|
420
533
|
}
|
|
421
534
|
|
|
535
|
+
const TRANSCRIPT_MAX_MESSAGES = 40;
|
|
536
|
+
const TRANSCRIPT_MAX_CHARS = 28_000;
|
|
537
|
+
|
|
538
|
+
function currentDialogFromContext(ctx: ExtensionContext | undefined): string | undefined {
|
|
539
|
+
if (!ctx) return undefined;
|
|
540
|
+
let branch: unknown[];
|
|
541
|
+
try {
|
|
542
|
+
const maybeBranch = ctx.sessionManager.getBranch?.();
|
|
543
|
+
branch = Array.isArray(maybeBranch) ? maybeBranch : [...(maybeBranch ?? [])];
|
|
544
|
+
} catch {
|
|
545
|
+
return undefined;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const messages: { role: "user" | "assistant"; text: string }[] = [];
|
|
549
|
+
for (const entry of branch) {
|
|
550
|
+
if (!isRecord(entry) || entry.type !== "message" || !isRecord(entry.message)) continue;
|
|
551
|
+
const role = entry.message.role === "user" || entry.message.role === "assistant" ? entry.message.role : undefined;
|
|
552
|
+
if (!role) continue;
|
|
553
|
+
const text = stripDcpMarkers(visibleMessageText(entry.message.content, role)).trim();
|
|
554
|
+
if (!text) continue;
|
|
555
|
+
messages.push({ role, text });
|
|
556
|
+
}
|
|
557
|
+
if (messages.length === 0) return undefined;
|
|
558
|
+
|
|
559
|
+
const selected: string[] = [];
|
|
560
|
+
let used = 0;
|
|
561
|
+
let omitted = 0;
|
|
562
|
+
for (let idx = messages.length - 1; idx >= 0; idx -= 1) {
|
|
563
|
+
const formatted = formatDialogMessage(messages[idx]);
|
|
564
|
+
if (selected.length >= TRANSCRIPT_MAX_MESSAGES || (used + formatted.length > TRANSCRIPT_MAX_CHARS && selected.length > 0)) {
|
|
565
|
+
omitted = idx + 1;
|
|
566
|
+
break;
|
|
567
|
+
}
|
|
568
|
+
selected.unshift(formatted);
|
|
569
|
+
used += formatted.length;
|
|
570
|
+
}
|
|
571
|
+
return `${omitted > 0 ? `… ${omitted} earlier message(s) omitted …\n\n` : ""}${selected.join("\n\n")}`;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function formatDialogMessage(message: { role: "user" | "assistant"; text: string }): string {
|
|
575
|
+
return `${message.role === "user" ? "👤 You" : "🤖 Pi"}\n${message.text}`;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function stripDcpMarkers(text: string): string {
|
|
579
|
+
return text
|
|
580
|
+
.split(/\r?\n/u)
|
|
581
|
+
.map((line) => line.replace(DCP_MARKER_RE, "").trimEnd())
|
|
582
|
+
.join("\n")
|
|
583
|
+
.replace(/[ \t]+\n/gu, "\n")
|
|
584
|
+
.replace(/\n{3,}/gu, "\n\n")
|
|
585
|
+
.trim();
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const DCP_MARKER_RE = /\[dcp(?:-[\w-]+)?\]:\s*#\s*\([^)]*\)/giu;
|
|
589
|
+
|
|
590
|
+
function visibleMessageText(value: unknown, role: "user" | "assistant"): string {
|
|
591
|
+
if (typeof value === "string") return value;
|
|
592
|
+
if (Array.isArray(value)) return value.map((item) => visibleContentBlockText(item, role)).filter(Boolean).join("\n");
|
|
593
|
+
return visibleContentBlockText(value, role);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function visibleContentBlockText(value: unknown, role: "user" | "assistant"): string {
|
|
597
|
+
if (typeof value === "string") return value;
|
|
598
|
+
if (!isRecord(value)) return "";
|
|
599
|
+
const type = typeof value.type === "string" ? value.type.toLowerCase() : "";
|
|
600
|
+
if (type.includes("tool") || type.includes("thinking")) return "";
|
|
601
|
+
if (typeof value.text === "string") return value.text;
|
|
602
|
+
if (typeof value.content === "string") return value.content;
|
|
603
|
+
if (Array.isArray(value.content)) return value.content.map((item) => visibleContentBlockText(item, role)).filter(Boolean).join("\n");
|
|
604
|
+
if (role === "user" && (type.includes("image") || type.includes("file"))) return `[${type || "attachment"}]`;
|
|
605
|
+
return "";
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
609
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function sessionSnapshot(ctx: ExtensionContext | undefined): SessionSnapshot | undefined {
|
|
613
|
+
if (!ctx) return undefined;
|
|
614
|
+
const manager = ctx.sessionManager;
|
|
615
|
+
return {
|
|
616
|
+
cwd: manager.getCwd?.() ?? ctx.cwd,
|
|
617
|
+
...(manager.getSessionId?.() ? { sessionId: manager.getSessionId() } : {}),
|
|
618
|
+
...(manager.getSessionFile?.() ? { sessionFile: manager.getSessionFile() } : {}),
|
|
619
|
+
...(manager.getSessionName?.() ? { sessionName: manager.getSessionName() } : {}),
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function notify(ctx: { hasUI?: boolean; ui?: { notify?: (message: string, type?: "info" | "warning" | "error") => void } }, message: string, type: "info" | "warning" | "error" = "info"): void {
|
|
624
|
+
if (ctx.hasUI) ctx.ui?.notify?.(message, type);
|
|
625
|
+
else console.error(`[telegram-mirror] ${message}`);
|
|
626
|
+
}
|
|
627
|
+
|
|
422
628
|
function errorMessage(error: unknown): string {
|
|
423
629
|
return error instanceof Error ? error.message : String(error);
|
|
424
630
|
}
|
|
@@ -57,12 +57,19 @@ export interface InstanceInfo {
|
|
|
57
57
|
cwd: string;
|
|
58
58
|
/** Human-friendly label, e.g. basename of cwd + short pid suffix. */
|
|
59
59
|
label: string;
|
|
60
|
+
/** Current pi session id, when available. */
|
|
61
|
+
sessionId?: string;
|
|
62
|
+
/** Current pi session file, when available. */
|
|
63
|
+
sessionFile?: string;
|
|
64
|
+
/** Human-friendly pi session name, when set. */
|
|
65
|
+
sessionName?: string;
|
|
60
66
|
/** ms-since-epoch when this instance started. */
|
|
61
67
|
started: number;
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
export type IpcMessage =
|
|
65
71
|
| { type: "register"; info: InstanceInfo }
|
|
72
|
+
| { type: "instance_update"; info: InstanceInfo }
|
|
66
73
|
| { type: "registered"; leader: InstanceInfo; activeId: string | null }
|
|
67
74
|
| { type: "ping"; t: number }
|
|
68
75
|
| { type: "pong"; t: number }
|
|
@@ -414,6 +421,19 @@ export function buildInstanceId(): { id: string; info: InstanceInfo } {
|
|
|
414
421
|
};
|
|
415
422
|
}
|
|
416
423
|
|
|
424
|
+
export function updateInstanceInfo(base: InstanceInfo, patch: Partial<Pick<InstanceInfo, "cwd" | "sessionId" | "sessionFile" | "sessionName">>): InstanceInfo {
|
|
425
|
+
const cwd = patch.cwd ?? base.cwd;
|
|
426
|
+
const cwdBase = path.basename(cwd) || cwd;
|
|
427
|
+
return {
|
|
428
|
+
...base,
|
|
429
|
+
cwd,
|
|
430
|
+
label: `${cwdBase} (#${base.pid})`,
|
|
431
|
+
...(patch.sessionId !== undefined ? { sessionId: patch.sessionId } : base.sessionId ? { sessionId: base.sessionId } : {}),
|
|
432
|
+
...(patch.sessionFile !== undefined ? { sessionFile: patch.sessionFile } : base.sessionFile ? { sessionFile: base.sessionFile } : {}),
|
|
433
|
+
...(patch.sessionName !== undefined ? { sessionName: patch.sessionName } : base.sessionName ? { sessionName: base.sessionName } : {}),
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
417
437
|
/** Generate a unique request id for command/query correlation. */
|
|
418
438
|
export function generateReqId(): string {
|
|
419
439
|
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|