copilotoffice 1.1.1 → 2.0.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/dist/electron/main.js +114 -56
- package/dist/electron/terminal/preload.js +12 -0
- package/dist/electron/terminal/server.js +102 -28
- package/dist/game.bundle.js +1907 -391
- package/package.json +3 -3
package/dist/electron/main.js
CHANGED
|
@@ -23,9 +23,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
));
|
|
24
24
|
|
|
25
25
|
// electron/main.ts
|
|
26
|
-
var
|
|
27
|
-
var
|
|
28
|
-
var
|
|
26
|
+
var import_electron3 = require("electron");
|
|
27
|
+
var path3 = __toESM(require("path"));
|
|
28
|
+
var fs2 = __toESM(require("fs"));
|
|
29
29
|
var import_child_process2 = require("child_process");
|
|
30
30
|
|
|
31
31
|
// electron/terminal/ipc-relay.ts
|
|
@@ -313,6 +313,96 @@ var TerminalRelay = class _TerminalRelay {
|
|
|
313
313
|
}
|
|
314
314
|
};
|
|
315
315
|
|
|
316
|
+
// electron/officeFileStore.ts
|
|
317
|
+
var fs = __toESM(require("fs"));
|
|
318
|
+
var path2 = __toESM(require("path"));
|
|
319
|
+
var DEFAULT_DATA_SUBDIR = ".data";
|
|
320
|
+
var DEFAULT_FILE_NAME = "copilot-offices.json";
|
|
321
|
+
function createOfficeFileStore(options = {}) {
|
|
322
|
+
const cwd = options.cwd ?? process.cwd();
|
|
323
|
+
const dataSubdir = options.dataSubdir ?? DEFAULT_DATA_SUBDIR;
|
|
324
|
+
const fileName = options.fileName ?? DEFAULT_FILE_NAME;
|
|
325
|
+
const dataDir = path2.join(cwd, dataSubdir);
|
|
326
|
+
const filePath = path2.join(dataDir, fileName);
|
|
327
|
+
return {
|
|
328
|
+
filePath,
|
|
329
|
+
load() {
|
|
330
|
+
try {
|
|
331
|
+
if (!fs.existsSync(filePath)) return { success: true, data: null };
|
|
332
|
+
const data = fs.readFileSync(filePath, "utf8");
|
|
333
|
+
return { success: true, data };
|
|
334
|
+
} catch (e) {
|
|
335
|
+
return { success: false, data: null, error: String(e) };
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
save(data) {
|
|
339
|
+
try {
|
|
340
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
341
|
+
fs.writeFileSync(filePath, data, "utf8");
|
|
342
|
+
return { success: true };
|
|
343
|
+
} catch (e) {
|
|
344
|
+
return { success: false, error: String(e) };
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// electron/nonTerminalIpc.ts
|
|
351
|
+
var import_electron2 = require("electron");
|
|
352
|
+
function registerNonTerminalIpc(hooks) {
|
|
353
|
+
import_electron2.ipcMain.handle("request-hard-reload", () => {
|
|
354
|
+
console.log("[Main] Hard reload requested by renderer");
|
|
355
|
+
hooks.onHardReloadRequested();
|
|
356
|
+
return { success: true };
|
|
357
|
+
});
|
|
358
|
+
import_electron2.ipcMain.handle("show-native-notification", (_event, title, body) => {
|
|
359
|
+
if (!import_electron2.Notification.isSupported()) return { success: false };
|
|
360
|
+
const notification = new import_electron2.Notification({ title, body });
|
|
361
|
+
notification.on("click", () => {
|
|
362
|
+
const win = hooks.getMainWindow();
|
|
363
|
+
if (win) {
|
|
364
|
+
if (win.isMinimized()) win.restore();
|
|
365
|
+
win.focus();
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
notification.show();
|
|
369
|
+
return { success: true };
|
|
370
|
+
});
|
|
371
|
+
import_electron2.ipcMain.handle("save-offices", (_event, data) => {
|
|
372
|
+
return hooks.officeStore.save(data);
|
|
373
|
+
});
|
|
374
|
+
import_electron2.ipcMain.handle("load-offices", () => {
|
|
375
|
+
return hooks.officeStore.load();
|
|
376
|
+
});
|
|
377
|
+
import_electron2.ipcMain.handle("clipboard-write-text", (_event, text) => {
|
|
378
|
+
try {
|
|
379
|
+
if (typeof text !== "string") {
|
|
380
|
+
return { success: false, verified: false, error: "text must be a string" };
|
|
381
|
+
}
|
|
382
|
+
import_electron2.clipboard.writeText(text);
|
|
383
|
+
const verify = import_electron2.clipboard.readText();
|
|
384
|
+
const matched = verify === text;
|
|
385
|
+
if (!matched) {
|
|
386
|
+
console.warn(`[Main/Clipboard] writeText verify mismatch len=${text.length}`);
|
|
387
|
+
return { success: false, verified: false, error: "clipboard verification failed" };
|
|
388
|
+
}
|
|
389
|
+
return { success: true, verified: true };
|
|
390
|
+
} catch (e) {
|
|
391
|
+
console.warn("[Main/Clipboard] writeText threw", e);
|
|
392
|
+
return { success: false, verified: false, error: e?.message || String(e) };
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
import_electron2.ipcMain.handle("clipboard-read-text", () => {
|
|
396
|
+
try {
|
|
397
|
+
const text = import_electron2.clipboard.readText();
|
|
398
|
+
return { success: true, text };
|
|
399
|
+
} catch (e) {
|
|
400
|
+
console.warn("[Main/Clipboard] readText threw", e);
|
|
401
|
+
return { success: false, text: "", error: e?.message || String(e) };
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
316
406
|
// electron/main.ts
|
|
317
407
|
var OPEN_DEVTOOLS_ON_START = process.env.COPILOT_OFFICE_OPEN_DEVTOOLS !== "0";
|
|
318
408
|
var ENABLE_FILE_WATCHER = process.env.COPILOT_OFFICE_ENABLE_WATCHER !== "0";
|
|
@@ -357,9 +447,9 @@ var watcherProcess = null;
|
|
|
357
447
|
var relay = new TerminalRelay(() => mainWindow);
|
|
358
448
|
var pendingHardReload = false;
|
|
359
449
|
function startFileWatcher() {
|
|
360
|
-
const copilotOfficePath =
|
|
361
|
-
if (!
|
|
362
|
-
if (
|
|
450
|
+
const copilotOfficePath = path3.join(process.cwd(), "CopilotOffice");
|
|
451
|
+
if (!fs2.existsSync(path3.join(copilotOfficePath, "package.json"))) {
|
|
452
|
+
if (fs2.existsSync(path3.join(process.cwd(), "src", "main.ts"))) {
|
|
363
453
|
watcherProcess = (0, import_child_process2.spawn)("npx", ["esbuild", "src/main.ts", "--bundle", "--outfile=dist/game.bundle.js", "--platform=browser", "--format=iife", "--global-name=CopilotOffice", "--watch"], {
|
|
364
454
|
cwd: process.cwd(),
|
|
365
455
|
shell: true,
|
|
@@ -390,19 +480,19 @@ function startFileWatcher() {
|
|
|
390
480
|
}
|
|
391
481
|
}
|
|
392
482
|
function createWindow() {
|
|
393
|
-
mainWindow = new
|
|
483
|
+
mainWindow = new import_electron3.BrowserWindow({
|
|
394
484
|
width: 2560,
|
|
395
485
|
height: 1440,
|
|
396
486
|
title: "Copilot Office",
|
|
397
487
|
webPreferences: {
|
|
398
|
-
preload:
|
|
488
|
+
preload: path3.join(__dirname, "terminal", "preload.js"),
|
|
399
489
|
contextIsolation: true,
|
|
400
490
|
nodeIntegration: false,
|
|
401
491
|
backgroundThrottling: false
|
|
402
492
|
}
|
|
403
493
|
});
|
|
404
494
|
mainWindow.maximize();
|
|
405
|
-
mainWindow.loadFile(
|
|
495
|
+
mainWindow.loadFile(path3.join(__dirname, "../../src/index.html"));
|
|
406
496
|
if (OPEN_DEVTOOLS_ON_START) {
|
|
407
497
|
mainWindow.webContents.openDevTools({ mode: "detach" });
|
|
408
498
|
}
|
|
@@ -427,49 +517,17 @@ function createWindow() {
|
|
|
427
517
|
mainWindow = null;
|
|
428
518
|
});
|
|
429
519
|
}
|
|
430
|
-
|
|
431
|
-
|
|
520
|
+
import_electron3.app.whenReady().then(async () => {
|
|
521
|
+
import_electron3.Menu.setApplicationMenu(null);
|
|
432
522
|
killOrphanedProcesses();
|
|
433
523
|
relay.registerIpc();
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
const notification = new import_electron2.Notification({ title, body });
|
|
442
|
-
notification.on("click", () => {
|
|
443
|
-
if (mainWindow) {
|
|
444
|
-
if (mainWindow.isMinimized()) mainWindow.restore();
|
|
445
|
-
mainWindow.focus();
|
|
446
|
-
}
|
|
447
|
-
});
|
|
448
|
-
notification.show();
|
|
449
|
-
return { success: true };
|
|
450
|
-
});
|
|
451
|
-
const dataDir = path2.join(process.cwd(), ".data");
|
|
452
|
-
fs.mkdirSync(dataDir, { recursive: true });
|
|
453
|
-
const officesFilePath = path2.join(dataDir, "copilot-offices.json");
|
|
454
|
-
import_electron2.ipcMain.handle("save-offices", (_event, data) => {
|
|
455
|
-
try {
|
|
456
|
-
fs.mkdirSync(dataDir, { recursive: true });
|
|
457
|
-
fs.writeFileSync(officesFilePath, data, "utf8");
|
|
458
|
-
return { success: true };
|
|
459
|
-
} catch (e) {
|
|
460
|
-
console.warn("[Main] Failed to save offices:", e);
|
|
461
|
-
return { success: false, error: String(e) };
|
|
462
|
-
}
|
|
463
|
-
});
|
|
464
|
-
import_electron2.ipcMain.handle("load-offices", () => {
|
|
465
|
-
try {
|
|
466
|
-
if (!fs.existsSync(officesFilePath)) return { success: true, data: null };
|
|
467
|
-
const data = fs.readFileSync(officesFilePath, "utf8");
|
|
468
|
-
return { success: true, data };
|
|
469
|
-
} catch (e) {
|
|
470
|
-
console.warn("[Main] Failed to load offices:", e);
|
|
471
|
-
return { success: false, error: String(e), data: null };
|
|
472
|
-
}
|
|
524
|
+
const officeStore = createOfficeFileStore();
|
|
525
|
+
registerNonTerminalIpc({
|
|
526
|
+
getMainWindow: () => mainWindow,
|
|
527
|
+
onHardReloadRequested: () => {
|
|
528
|
+
pendingHardReload = true;
|
|
529
|
+
},
|
|
530
|
+
officeStore
|
|
473
531
|
});
|
|
474
532
|
await relay.spawnServer(__dirname);
|
|
475
533
|
if (ENABLE_FILE_WATCHER) {
|
|
@@ -478,22 +536,22 @@ import_electron2.app.whenReady().then(async () => {
|
|
|
478
536
|
console.log("[Main] File watcher disabled");
|
|
479
537
|
}
|
|
480
538
|
createWindow();
|
|
481
|
-
|
|
482
|
-
if (
|
|
539
|
+
import_electron3.app.on("activate", () => {
|
|
540
|
+
if (import_electron3.BrowserWindow.getAllWindows().length === 0) createWindow();
|
|
483
541
|
});
|
|
484
542
|
});
|
|
485
|
-
|
|
543
|
+
import_electron3.app.on("window-all-closed", () => {
|
|
486
544
|
if (watcherProcess) watcherProcess.kill();
|
|
487
|
-
if (process.platform !== "darwin")
|
|
545
|
+
if (process.platform !== "darwin") import_electron3.app.quit();
|
|
488
546
|
});
|
|
489
547
|
var isShuttingDown = false;
|
|
490
|
-
|
|
548
|
+
import_electron3.app.on("before-quit", (event) => {
|
|
491
549
|
if (isShuttingDown) return;
|
|
492
550
|
isShuttingDown = true;
|
|
493
551
|
event.preventDefault();
|
|
494
552
|
console.log("[Main] Awaiting relay shutdown before quit\u2026");
|
|
495
553
|
relay.shutdown().finally(() => {
|
|
496
554
|
console.log("[Main] Relay shutdown complete \u2014 quitting");
|
|
497
|
-
|
|
555
|
+
import_electron3.app.quit();
|
|
498
556
|
});
|
|
499
557
|
});
|
|
@@ -17,6 +17,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
17
17
|
var preload_exports = {};
|
|
18
18
|
module.exports = __toCommonJS(preload_exports);
|
|
19
19
|
var import_electron = require("electron");
|
|
20
|
+
import_electron.contextBridge.exposeInMainWorld("__copilotOfficeE2E", process.env.COPILOT_E2E === "1");
|
|
20
21
|
import_electron.contextBridge.exposeInMainWorld("copilotBridge", {
|
|
21
22
|
// Terminal management
|
|
22
23
|
terminalStart: (officeId, agentId, workingDir, cols, rows, preseededPrompt, launchMode) => {
|
|
@@ -148,5 +149,16 @@ import_electron.contextBridge.exposeInMainWorld("copilotBridge", {
|
|
|
148
149
|
// Native OS notifications
|
|
149
150
|
showNativeNotification: (title, body) => {
|
|
150
151
|
return import_electron.ipcRenderer.invoke("show-native-notification", title, body);
|
|
152
|
+
},
|
|
153
|
+
// Spec 003 follow-up: write to OS clipboard via Electron main process.
|
|
154
|
+
// Bypasses Permissions API + focus restrictions that make
|
|
155
|
+
// navigator.clipboard.writeText unreliable in xterm-focused contexts.
|
|
156
|
+
clipboardWriteText: (text) => {
|
|
157
|
+
return import_electron.ipcRenderer.invoke("clipboard-write-text", text);
|
|
158
|
+
},
|
|
159
|
+
// Spec 004: read OS clipboard via Electron main. Renderer pairs this with
|
|
160
|
+
// terminalWrite to implement Paste in the terminal context menu.
|
|
161
|
+
clipboardReadText: () => {
|
|
162
|
+
return import_electron.ipcRenderer.invoke("clipboard-read-text");
|
|
151
163
|
}
|
|
152
164
|
});
|
|
@@ -5070,7 +5070,7 @@ var init_dist = __esm({
|
|
|
5070
5070
|
var path3 = __toESM(require("path"));
|
|
5071
5071
|
var os3 = __toESM(require("os"));
|
|
5072
5072
|
var fs2 = __toESM(require("fs"));
|
|
5073
|
-
var
|
|
5073
|
+
var crypto2 = __toESM(require("crypto"));
|
|
5074
5074
|
var import_child_process2 = require("child_process");
|
|
5075
5075
|
|
|
5076
5076
|
// electron/terminal/events-watcher.ts
|
|
@@ -5595,10 +5595,66 @@ function createSdkCliLaunchConfig(cliPath) {
|
|
|
5595
5595
|
return { cliPath, cliArgs: [] };
|
|
5596
5596
|
}
|
|
5597
5597
|
|
|
5598
|
+
// electron/terminal/agent-viewers.ts
|
|
5599
|
+
function addAgentViewer(ck, maps) {
|
|
5600
|
+
maps.activeAgentViewers.add(ck);
|
|
5601
|
+
const termKey = maps.agentToTerminal.get(ck);
|
|
5602
|
+
if (termKey && termKey !== ck) {
|
|
5603
|
+
maps.activeAgentViewers.add(termKey);
|
|
5604
|
+
return { aliasKey: termKey };
|
|
5605
|
+
}
|
|
5606
|
+
return { aliasKey: null };
|
|
5607
|
+
}
|
|
5608
|
+
function removeAgentViewer(ck, maps) {
|
|
5609
|
+
maps.activeAgentViewers.delete(ck);
|
|
5610
|
+
const termKey = maps.agentToTerminal.get(ck);
|
|
5611
|
+
if (termKey && termKey !== ck) {
|
|
5612
|
+
maps.activeAgentViewers.delete(termKey);
|
|
5613
|
+
return { aliasKey: termKey };
|
|
5614
|
+
}
|
|
5615
|
+
return { aliasKey: null };
|
|
5616
|
+
}
|
|
5617
|
+
function hasActiveViewer(ck, maps) {
|
|
5618
|
+
if (maps.activeAgentViewers.has(ck)) return true;
|
|
5619
|
+
for (const [alias, termKey] of maps.agentToTerminal) {
|
|
5620
|
+
if (termKey === ck && maps.activeAgentViewers.has(alias)) return true;
|
|
5621
|
+
}
|
|
5622
|
+
return false;
|
|
5623
|
+
}
|
|
5624
|
+
|
|
5625
|
+
// electron/terminal/session-repair.ts
|
|
5626
|
+
var crypto = __toESM(require("crypto"));
|
|
5627
|
+
function repairDuplicateSessionIds(officeId, data, options = {}) {
|
|
5628
|
+
const logger = options.logger ?? { warn: (msg) => console.warn(msg) };
|
|
5629
|
+
const mintId = options.mintId ?? (() => crypto.randomUUID());
|
|
5630
|
+
const seen = /* @__PURE__ */ new Map();
|
|
5631
|
+
let repaired = false;
|
|
5632
|
+
for (const [agentId, sessionId] of data.sessionIds.entries()) {
|
|
5633
|
+
const firstAgent = seen.get(sessionId);
|
|
5634
|
+
if (firstAgent === void 0) {
|
|
5635
|
+
seen.set(sessionId, agentId);
|
|
5636
|
+
continue;
|
|
5637
|
+
}
|
|
5638
|
+
let fresh = mintId();
|
|
5639
|
+
while (seen.has(fresh) || data.sessionIds.get(agentId) === fresh) {
|
|
5640
|
+
fresh = mintId();
|
|
5641
|
+
}
|
|
5642
|
+
data.sessionIds.set(agentId, fresh);
|
|
5643
|
+
seen.set(fresh, agentId);
|
|
5644
|
+
logger.warn(
|
|
5645
|
+
`[TermServer] Repaired duplicate sessionId for officeId=${officeId} agentId=${agentId} from=${sessionId} to=${fresh}`
|
|
5646
|
+
);
|
|
5647
|
+
repaired = true;
|
|
5648
|
+
}
|
|
5649
|
+
return repaired;
|
|
5650
|
+
}
|
|
5651
|
+
|
|
5598
5652
|
// electron/terminal/server.ts
|
|
5599
5653
|
var ptyProcesses = /* @__PURE__ */ new Map();
|
|
5654
|
+
var DEBUG_COLD_START = process.env.COPILOT_OFFICE_DEBUG_COLD_START === "1";
|
|
5600
5655
|
var agentToTerminal = /* @__PURE__ */ new Map();
|
|
5601
5656
|
var activeAgentViewers = /* @__PURE__ */ new Set();
|
|
5657
|
+
var viewerMaps = { activeAgentViewers, agentToTerminal };
|
|
5602
5658
|
var agentWatchers = /* @__PURE__ */ new Map();
|
|
5603
5659
|
var terminalBackend = null;
|
|
5604
5660
|
var eventSourceFactory = new FileWatcherEventSourceFactory();
|
|
@@ -5626,12 +5682,8 @@ function getOfficeSession(officeId) {
|
|
|
5626
5682
|
function compositeKey(officeId, agentId) {
|
|
5627
5683
|
return `${officeId}:${agentId}`;
|
|
5628
5684
|
}
|
|
5629
|
-
function
|
|
5630
|
-
|
|
5631
|
-
for (const [alias, termKey] of agentToTerminal) {
|
|
5632
|
-
if (termKey === ck && activeAgentViewers.has(alias)) return true;
|
|
5633
|
-
}
|
|
5634
|
-
return false;
|
|
5685
|
+
function hasActiveViewer2(ck) {
|
|
5686
|
+
return hasActiveViewer(ck, viewerMaps);
|
|
5635
5687
|
}
|
|
5636
5688
|
function send(msg) {
|
|
5637
5689
|
if (process.send) {
|
|
@@ -5675,6 +5727,10 @@ async function loadOfficeSessionFile(officeId) {
|
|
|
5675
5727
|
data.sessionMeta = /* @__PURE__ */ new Map();
|
|
5676
5728
|
await saveOfficeSessionFile(officeId);
|
|
5677
5729
|
}
|
|
5730
|
+
const repaired = repairDuplicateSessionIds(officeId, data);
|
|
5731
|
+
if (repaired) {
|
|
5732
|
+
await saveOfficeSessionFile(officeId);
|
|
5733
|
+
}
|
|
5678
5734
|
console.log(`[TermServer] Loaded sessions for ${officeId}: ${data.sessionIds.size} current, ${data.sessionHistory.size} history`);
|
|
5679
5735
|
}
|
|
5680
5736
|
} catch (e) {
|
|
@@ -5790,6 +5846,9 @@ function killPtyProcess(proc) {
|
|
|
5790
5846
|
}
|
|
5791
5847
|
var pendingPreseededPrompts = /* @__PURE__ */ new Map();
|
|
5792
5848
|
async function startTerminalForAgent(officeId, agentId, workingDir, cols, rows, preseededPrompt, launchMode = "copilot") {
|
|
5849
|
+
if (process.env.COPILOT_E2E === "1") {
|
|
5850
|
+
launchMode = "shell";
|
|
5851
|
+
}
|
|
5793
5852
|
if (!terminalBackend || !terminalBackend.isAvailable()) {
|
|
5794
5853
|
return { success: false, error: "terminal backend not available" };
|
|
5795
5854
|
}
|
|
@@ -5806,7 +5865,7 @@ async function startTerminalForAgent(officeId, agentId, workingDir, cols, rows,
|
|
|
5806
5865
|
const officeData = getOfficeSession(officeId);
|
|
5807
5866
|
let sessionId = officeData.sessionIds.get(agentId);
|
|
5808
5867
|
if (!sessionId) {
|
|
5809
|
-
sessionId =
|
|
5868
|
+
sessionId = crypto2.randomUUID();
|
|
5810
5869
|
officeData.sessionIds.set(agentId, sessionId);
|
|
5811
5870
|
await saveOfficeSessionFile(officeId);
|
|
5812
5871
|
console.log(`[TermServer] New session GUID for ${ck}: ${sessionId}`);
|
|
@@ -5939,7 +5998,7 @@ async function startTerminalForAgent(officeId, agentId, workingDir, cols, rows,
|
|
|
5939
5998
|
console.log(`[TermServer] Subagent FAILED for ${ck}: ${d.agentName ?? "unknown"} (toolCallId: ${d.toolCallId ?? "?"}, error: ${d.error ?? "unknown"})`);
|
|
5940
5999
|
}
|
|
5941
6000
|
const isFleetCriticalEvent = event.type.startsWith("subagent.") || event.type === "system.notification" || event.type === "tool.execution_start" && event.data?.toolName === "task";
|
|
5942
|
-
if (isFleetCriticalEvent ||
|
|
6001
|
+
if (isFleetCriticalEvent || hasActiveViewer2(ck)) {
|
|
5943
6002
|
send({ type: "copilot-event", agentId, event });
|
|
5944
6003
|
} else {
|
|
5945
6004
|
console.warn(`[TermServer] Dropped copilot-event ${event.type} for ${ck} \u2014 no active viewer (viewers: [${[...activeAgentViewers].join(", ")}])`);
|
|
@@ -5952,18 +6011,22 @@ async function startTerminalForAgent(officeId, agentId, workingDir, cols, rows,
|
|
|
5952
6011
|
let flushTimer = null;
|
|
5953
6012
|
const flushData = () => {
|
|
5954
6013
|
flushTimer = null;
|
|
5955
|
-
if (pendingData &&
|
|
6014
|
+
if (pendingData && hasActiveViewer2(ck)) {
|
|
5956
6015
|
send({ type: "terminal-data", agentId, data: pendingData });
|
|
5957
6016
|
}
|
|
5958
6017
|
pendingData = "";
|
|
5959
6018
|
};
|
|
5960
6019
|
proc.onData((data) => {
|
|
5961
6020
|
appendToScrollback(ck, data);
|
|
5962
|
-
if (!shellOnlyMode && terminalBackend?.name === "node-pty" && !hasSignalledReady
|
|
5963
|
-
|
|
5964
|
-
|
|
6021
|
+
if (!shellOnlyMode && terminalBackend?.name === "node-pty" && !hasSignalledReady) {
|
|
6022
|
+
const hasLegacyReadyMarker = data.includes("Environment loaded");
|
|
6023
|
+
const hasInteractiveFooter = data.includes("/ commands") || data.includes("? help");
|
|
6024
|
+
if (hasLegacyReadyMarker || hasInteractiveFooter) {
|
|
6025
|
+
console.log(`[TermServer] Ready signal for ${ck}: PTY marker detected`);
|
|
6026
|
+
signalReady();
|
|
6027
|
+
}
|
|
5965
6028
|
}
|
|
5966
|
-
if (!
|
|
6029
|
+
if (!hasActiveViewer2(ck)) return;
|
|
5967
6030
|
pendingData += data;
|
|
5968
6031
|
if (pendingData.length >= MAX_PENDING_BYTES) {
|
|
5969
6032
|
if (flushTimer) clearTimeout(flushTimer);
|
|
@@ -5989,7 +6052,7 @@ async function startTerminalForAgent(officeId, agentId, workingDir, cols, rows,
|
|
|
5989
6052
|
if (!shellOnlyMode && terminalBackend.name === "node-pty") {
|
|
5990
6053
|
setTimeout(() => {
|
|
5991
6054
|
console.log(`[TermServer] Starting copilot --session-id for ${ck}: ${sessionId}`);
|
|
5992
|
-
proc.write(`copilot --session-id
|
|
6055
|
+
proc.write(`copilot --session-id=${sessionId}\r`);
|
|
5993
6056
|
}, 500);
|
|
5994
6057
|
}
|
|
5995
6058
|
return { success: true, pid: proc.pid, sessionId };
|
|
@@ -6058,11 +6121,9 @@ async function handleMessage(msg) {
|
|
|
6058
6121
|
case "attach": {
|
|
6059
6122
|
const ck = compositeKey(msg.officeId, msg.agentId);
|
|
6060
6123
|
console.log(`[TermServer] Attaching viewer for ${ck}`);
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
activeAgentViewers.add(termKey);
|
|
6065
|
-
console.log(`[TermServer] Also marking original key ${termKey} as active viewer (transferred session)`);
|
|
6124
|
+
const { aliasKey } = addAgentViewer(ck, viewerMaps);
|
|
6125
|
+
if (aliasKey) {
|
|
6126
|
+
console.log(`[TermServer] Also marking original key ${aliasKey} as active viewer (transferred session)`);
|
|
6066
6127
|
}
|
|
6067
6128
|
const chunks = agentScrollbackBuffers.get(ck) || [];
|
|
6068
6129
|
const rawScrollback = chunks.join("");
|
|
@@ -6072,11 +6133,7 @@ async function handleMessage(msg) {
|
|
|
6072
6133
|
case "detach": {
|
|
6073
6134
|
const ck = compositeKey(msg.officeId, msg.agentId);
|
|
6074
6135
|
console.log(`[TermServer] Detaching viewer for ${ck}`);
|
|
6075
|
-
|
|
6076
|
-
const termKey = agentToTerminal.get(ck);
|
|
6077
|
-
if (termKey && termKey !== ck) {
|
|
6078
|
-
activeAgentViewers.delete(termKey);
|
|
6079
|
-
}
|
|
6136
|
+
removeAgentViewer(ck, viewerMaps);
|
|
6080
6137
|
break;
|
|
6081
6138
|
}
|
|
6082
6139
|
case "exists": {
|
|
@@ -6093,6 +6150,15 @@ async function handleMessage(msg) {
|
|
|
6093
6150
|
const officeData = getOfficeSession(msg.officeId);
|
|
6094
6151
|
const current = officeData.sessionIds.get(msg.agentId);
|
|
6095
6152
|
const changed = !!normalized && current !== normalized;
|
|
6153
|
+
if (changed && normalized) {
|
|
6154
|
+
for (const [otherAgent, otherSid] of officeData.sessionIds) {
|
|
6155
|
+
if (otherAgent !== msg.agentId && otherSid === normalized) {
|
|
6156
|
+
console.warn(`[TermServer] Rejected set-session-id ${normalized} for ${compositeKey(msg.officeId, msg.agentId)} \u2014 already in use by ${otherAgent}`);
|
|
6157
|
+
send({ type: "response", requestId: msg.requestId, result: { success: false, error: "sessionId already in use by another agent in this office" } });
|
|
6158
|
+
return;
|
|
6159
|
+
}
|
|
6160
|
+
}
|
|
6161
|
+
}
|
|
6096
6162
|
if (changed) {
|
|
6097
6163
|
archiveSessionId(msg.officeId, msg.agentId);
|
|
6098
6164
|
officeData.sessionIds.set(msg.agentId, normalized);
|
|
@@ -6159,7 +6225,7 @@ async function handleMessage(msg) {
|
|
|
6159
6225
|
hasAutoTitled.delete(ck);
|
|
6160
6226
|
send({ type: "session-meta-updated", agentId: msg.agentId, meta: { title: "" } });
|
|
6161
6227
|
archiveSessionId(msg.officeId, msg.agentId);
|
|
6162
|
-
const newSessionId =
|
|
6228
|
+
const newSessionId = crypto2.randomUUID();
|
|
6163
6229
|
officeDataReset.sessionIds.set(msg.agentId, newSessionId);
|
|
6164
6230
|
await saveOfficeSessionFile(msg.officeId);
|
|
6165
6231
|
console.log(`[TermServer] Reset session for ${ck}: new GUID ${newSessionId}`);
|
|
@@ -6210,7 +6276,7 @@ async function handleMessage(msg) {
|
|
|
6210
6276
|
}
|
|
6211
6277
|
officeData.sessionMeta.clear();
|
|
6212
6278
|
for (const agentId of officeData.sessionIds.keys()) {
|
|
6213
|
-
officeData.sessionIds.set(agentId,
|
|
6279
|
+
officeData.sessionIds.set(agentId, crypto2.randomUUID());
|
|
6214
6280
|
}
|
|
6215
6281
|
await saveOfficeSessionFile(officeId);
|
|
6216
6282
|
console.log(`[TermServer] All sessions reset for ${officeId}`);
|
|
@@ -6266,7 +6332,15 @@ async function handleMessage(msg) {
|
|
|
6266
6332
|
}
|
|
6267
6333
|
case "get-all-session-meta": {
|
|
6268
6334
|
const officeData = getOfficeSession(msg.officeId);
|
|
6269
|
-
|
|
6335
|
+
const merged = {};
|
|
6336
|
+
for (const [agentId, meta] of officeData.sessionMeta.entries()) {
|
|
6337
|
+
const sessionId = officeData.sessionIds.get(agentId);
|
|
6338
|
+
merged[agentId] = sessionId ? { ...meta, sessionId } : { ...meta };
|
|
6339
|
+
}
|
|
6340
|
+
for (const [agentId, sessionId] of officeData.sessionIds.entries()) {
|
|
6341
|
+
if (!merged[agentId]) merged[agentId] = { title: "", sessionId };
|
|
6342
|
+
}
|
|
6343
|
+
send({ type: "response", requestId: msg.requestId, result: merged });
|
|
6270
6344
|
break;
|
|
6271
6345
|
}
|
|
6272
6346
|
case "create-office-session": {
|