copilotoffice 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +108 -75
- package/dist/electron/main.js +2307 -138
- package/dist/electron/terminal/ipc-relay.js +210 -7
- package/dist/electron/terminal/preload.js +83 -13
- package/dist/electron/terminal/server.js +5961 -940
- package/dist/game.bundle.js +12559 -8482
- package/package.json +13 -7
|
@@ -34,17 +34,139 @@ __export(ipc_relay_exports, {
|
|
|
34
34
|
});
|
|
35
35
|
module.exports = __toCommonJS(ipc_relay_exports);
|
|
36
36
|
var import_electron = require("electron");
|
|
37
|
+
var import_child_process2 = require("child_process");
|
|
38
|
+
var import_events = require("events");
|
|
39
|
+
var crypto2 = __toESM(require("crypto"));
|
|
40
|
+
var path2 = __toESM(require("path"));
|
|
41
|
+
|
|
42
|
+
// electron/terminal/pty-registry.ts
|
|
37
43
|
var import_child_process = require("child_process");
|
|
38
44
|
var crypto = __toESM(require("crypto"));
|
|
45
|
+
var fs = __toESM(require("fs"));
|
|
46
|
+
var os = __toESM(require("os"));
|
|
39
47
|
var path = __toESM(require("path"));
|
|
48
|
+
var START_TIME_GRACE_MS = 2e3;
|
|
49
|
+
var DEFAULT_REGISTRY_FILE = path.join(process.cwd(), ".data", "pty-pids.json");
|
|
50
|
+
function readPtyRegistry(file = DEFAULT_REGISTRY_FILE) {
|
|
51
|
+
try {
|
|
52
|
+
const raw = fs.readFileSync(file, "utf8");
|
|
53
|
+
const parsed = JSON.parse(raw);
|
|
54
|
+
if (!Array.isArray(parsed)) return [];
|
|
55
|
+
return parsed.filter(
|
|
56
|
+
(r) => !!r && typeof r.pid === "number" && Number.isFinite(r.pid)
|
|
57
|
+
);
|
|
58
|
+
} catch {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function writePtyRegistry(records, file = DEFAULT_REGISTRY_FILE) {
|
|
63
|
+
try {
|
|
64
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
65
|
+
const tmp = `${file}.${process.pid}.${crypto.randomBytes(4).toString("hex")}.tmp`;
|
|
66
|
+
fs.writeFileSync(tmp, JSON.stringify(records, null, 2));
|
|
67
|
+
fs.renameSync(tmp, file);
|
|
68
|
+
} catch {
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function isPidAlive(pid) {
|
|
72
|
+
try {
|
|
73
|
+
process.kill(pid, 0);
|
|
74
|
+
return true;
|
|
75
|
+
} catch (err) {
|
|
76
|
+
return err?.code === "EPERM";
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function processStartTimeMs(pid) {
|
|
80
|
+
try {
|
|
81
|
+
if (os.platform() === "win32") {
|
|
82
|
+
const cmd = `powershell -NoProfile -NonInteractive -Command "$p = Get-CimInstance Win32_Process -Filter 'ProcessId=${pid}'; if ($p) { [DateTimeOffset]::new($p.CreationDate).ToUnixTimeMilliseconds() }"`;
|
|
83
|
+
const out2 = (0, import_child_process.execSync)(cmd, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
84
|
+
const ms2 = parseInt(out2, 10);
|
|
85
|
+
return Number.isFinite(ms2) ? ms2 : null;
|
|
86
|
+
}
|
|
87
|
+
const out = (0, import_child_process.execSync)(`ps -o lstart= -p ${pid}`, {
|
|
88
|
+
encoding: "utf8",
|
|
89
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
90
|
+
}).trim();
|
|
91
|
+
if (!out) return null;
|
|
92
|
+
const ms = Date.parse(out);
|
|
93
|
+
return Number.isNaN(ms) ? null : ms;
|
|
94
|
+
} catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function processIdentityMatches(record, startTimeOf) {
|
|
99
|
+
const startMs = startTimeOf(record.pid);
|
|
100
|
+
if (startMs == null) return false;
|
|
101
|
+
return startMs <= record.startedAt + START_TIME_GRACE_MS;
|
|
102
|
+
}
|
|
103
|
+
function killTree(pid) {
|
|
104
|
+
try {
|
|
105
|
+
if (os.platform() === "win32") {
|
|
106
|
+
(0, import_child_process.execSync)(`taskkill /T /F /PID ${pid}`, { stdio: "ignore" });
|
|
107
|
+
} else {
|
|
108
|
+
try {
|
|
109
|
+
process.kill(-pid, "SIGKILL");
|
|
110
|
+
} catch {
|
|
111
|
+
process.kill(pid, "SIGKILL");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return true;
|
|
115
|
+
} catch {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function reapRegisteredPtys(options = {}) {
|
|
120
|
+
const file = options.file ?? DEFAULT_REGISTRY_FILE;
|
|
121
|
+
const isAlive = options.isAlive ?? isPidAlive;
|
|
122
|
+
const kill = options.kill ?? killTree;
|
|
123
|
+
const startTimeOf = options.startTimeOf ?? processStartTimeMs;
|
|
124
|
+
const protectedPids = /* @__PURE__ */ new Set([process.pid, ...options.protectedPids ?? []]);
|
|
125
|
+
const records = readPtyRegistry(file);
|
|
126
|
+
const reaped = [];
|
|
127
|
+
const skipped = [];
|
|
128
|
+
const failed = [];
|
|
129
|
+
const survivors = [];
|
|
130
|
+
for (const record of records) {
|
|
131
|
+
const { pid } = record;
|
|
132
|
+
if (protectedPids.has(pid) || !isAlive(pid)) {
|
|
133
|
+
skipped.push(pid);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (!processIdentityMatches(record, startTimeOf)) {
|
|
137
|
+
skipped.push(pid);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (kill(pid)) {
|
|
141
|
+
reaped.push(pid);
|
|
142
|
+
} else {
|
|
143
|
+
failed.push(pid);
|
|
144
|
+
survivors.push(record);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
writePtyRegistry(survivors, file);
|
|
148
|
+
return { reaped, skipped, failed };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// electron/terminal/ipc-relay.ts
|
|
40
152
|
var TerminalRelay = class _TerminalRelay {
|
|
41
153
|
constructor(getWindow) {
|
|
154
|
+
/**
|
|
155
|
+
* Event bus for main-process consumers (e.g. the Teams service) that need
|
|
156
|
+
* server→main copilot/terminal events without going through the renderer.
|
|
157
|
+
* Emits: 'copilot-event' (agentId, event), 'copilot-turn-start' (agentId),
|
|
158
|
+
* 'copilot-turn-end' (agentId), 'copilot-tool-start' (agentId, toolName, toolId, status),
|
|
159
|
+
* 'session-meta-updated' (agentId, meta), 'terminal-exit' (agentId, exitCode).
|
|
160
|
+
*/
|
|
161
|
+
this.mainEvents = new import_events.EventEmitter();
|
|
42
162
|
this.server = null;
|
|
43
163
|
this.pendingRequests = /* @__PURE__ */ new Map();
|
|
44
164
|
/** True while a deliberate shutdown→respawn is in progress (prevents double-spawn). */
|
|
45
165
|
this.shuttingDown = false;
|
|
46
166
|
/** Requests that arrived while the server was not connected. Flushed on ready. */
|
|
47
167
|
this.queuedRequests = [];
|
|
168
|
+
/** Latest backend-selection outcome reported by the server on 'ready'. */
|
|
169
|
+
this.backendInfo = null;
|
|
48
170
|
this.getWindow = getWindow;
|
|
49
171
|
}
|
|
50
172
|
static {
|
|
@@ -54,9 +176,9 @@ var TerminalRelay = class _TerminalRelay {
|
|
|
54
176
|
// ── Server Lifecycle ──────────────────────────────────────────
|
|
55
177
|
spawnServer(distDir) {
|
|
56
178
|
return new Promise((resolve, reject) => {
|
|
57
|
-
const serverPath =
|
|
179
|
+
const serverPath = path2.join(distDir, "terminal", "server.js");
|
|
58
180
|
console.log("[Relay] Forking terminal server:", serverPath);
|
|
59
|
-
this.server = (0,
|
|
181
|
+
this.server = (0, import_child_process2.fork)(serverPath, [], {
|
|
60
182
|
cwd: process.cwd(),
|
|
61
183
|
stdio: ["pipe", "inherit", "inherit", "ipc"]
|
|
62
184
|
});
|
|
@@ -80,6 +202,14 @@ var TerminalRelay = class _TerminalRelay {
|
|
|
80
202
|
}
|
|
81
203
|
const win = this.getWindow();
|
|
82
204
|
if (win && !win.isDestroyed()) {
|
|
205
|
+
try {
|
|
206
|
+
const { reaped } = reapRegisteredPtys();
|
|
207
|
+
if (reaped.length > 0) {
|
|
208
|
+
console.log(`[Relay] Reaped ${reaped.length} orphaned PTY tree(s) from crashed server:`, reaped);
|
|
209
|
+
}
|
|
210
|
+
} catch (e) {
|
|
211
|
+
console.error("[Relay] Failed to reap orphaned PTYs after crash:", e);
|
|
212
|
+
}
|
|
83
213
|
console.log("[Relay] Respawning terminal server...");
|
|
84
214
|
this.spawnServer(distDir).catch(
|
|
85
215
|
(e) => console.error("[Relay] Failed to respawn:", e)
|
|
@@ -124,7 +254,7 @@ var TerminalRelay = class _TerminalRelay {
|
|
|
124
254
|
process.kill(oldPid, 0);
|
|
125
255
|
console.log(`[Relay] Server PID ${oldPid} still alive after timeout \u2014 force killing`);
|
|
126
256
|
if (process.platform === "win32") {
|
|
127
|
-
(0,
|
|
257
|
+
(0, import_child_process2.execSync)(`taskkill /T /F /PID ${oldPid}`, { stdio: "ignore" });
|
|
128
258
|
} else {
|
|
129
259
|
process.kill(oldPid, "SIGKILL");
|
|
130
260
|
}
|
|
@@ -163,13 +293,48 @@ var TerminalRelay = class _TerminalRelay {
|
|
|
163
293
|
});
|
|
164
294
|
}
|
|
165
295
|
id() {
|
|
166
|
-
return
|
|
296
|
+
return crypto2.randomUUID();
|
|
297
|
+
}
|
|
298
|
+
// ── Main-process gateway (for the Teams service) ──────────────
|
|
299
|
+
// These let a main-process consumer talk to the terminal server directly,
|
|
300
|
+
// reusing the same request/response plumbing as the renderer IPC handlers.
|
|
301
|
+
mainGetSessionId(officeId, agentId) {
|
|
302
|
+
return this.request({ type: "get-session-id", requestId: this.id(), officeId, agentId });
|
|
303
|
+
}
|
|
304
|
+
/** True only when the agent's PTY is alive AND the CLI has signalled ready. */
|
|
305
|
+
mainIsAgentReady(officeId, agentId) {
|
|
306
|
+
return this.request({ type: "query-agent-statuses", requestId: this.id(), officeId }).then((statuses) => {
|
|
307
|
+
const s = statuses?.[agentId];
|
|
308
|
+
return !!(s && s.alive && s.ready);
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
mainGetSessionMeta(officeId, agentId) {
|
|
312
|
+
return this.request({ type: "get-session-meta", requestId: this.id(), officeId, agentId });
|
|
313
|
+
}
|
|
314
|
+
mainWrite(officeId, agentId, data) {
|
|
315
|
+
return this.request({ type: "write", requestId: this.id(), officeId, agentId, data });
|
|
316
|
+
}
|
|
317
|
+
mainSubmitPrompt(officeId, agentId, prompt, label) {
|
|
318
|
+
return this.request({ type: "submit-prompt", requestId: this.id(), officeId, agentId, prompt, label });
|
|
319
|
+
}
|
|
320
|
+
/** Fire-and-forget: control whether copilot-events are mirrored to main for an agent without a viewer. */
|
|
321
|
+
mainSetAgentForwarding(officeId, agentId, enabled) {
|
|
322
|
+
this.send({ type: "set-agent-forwarding", officeId, agentId, enabled });
|
|
167
323
|
}
|
|
168
324
|
handleServerMessage(msg, readyTimeout, onReady) {
|
|
169
325
|
if (msg.type === "ready") {
|
|
170
326
|
clearTimeout(readyTimeout);
|
|
171
327
|
this.shuttingDown = false;
|
|
172
328
|
console.log("[Relay] Terminal server ready");
|
|
329
|
+
if (msg.backend) {
|
|
330
|
+
this.backendInfo = msg.backend;
|
|
331
|
+
if (msg.backend.fellBack) {
|
|
332
|
+
const win2 = this.getWindow();
|
|
333
|
+
if (win2 && !win2.isDestroyed()) {
|
|
334
|
+
win2.webContents.send("backend-fallback", this.backendInfo);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
173
338
|
if (this.queuedRequests.length > 0) {
|
|
174
339
|
console.log(`[Relay] Flushing ${this.queuedRequests.length} queued request(s)`);
|
|
175
340
|
for (const queued of this.queuedRequests) {
|
|
@@ -189,6 +354,29 @@ var TerminalRelay = class _TerminalRelay {
|
|
|
189
354
|
}
|
|
190
355
|
return;
|
|
191
356
|
}
|
|
357
|
+
switch (msg.type) {
|
|
358
|
+
case "copilot-event":
|
|
359
|
+
this.mainEvents.emit("copilot-event", msg.agentId, msg.event);
|
|
360
|
+
break;
|
|
361
|
+
case "copilot-turn-start":
|
|
362
|
+
this.mainEvents.emit("copilot-turn-start", msg.agentId);
|
|
363
|
+
break;
|
|
364
|
+
case "copilot-turn-end":
|
|
365
|
+
this.mainEvents.emit("copilot-turn-end", msg.agentId);
|
|
366
|
+
break;
|
|
367
|
+
case "copilot-user-message":
|
|
368
|
+
this.mainEvents.emit("copilot-user-message", msg.agentId, msg.text);
|
|
369
|
+
break;
|
|
370
|
+
case "copilot-tool-start":
|
|
371
|
+
this.mainEvents.emit("copilot-tool-start", msg.agentId, msg.toolName, msg.toolId, msg.status);
|
|
372
|
+
break;
|
|
373
|
+
case "session-meta-updated":
|
|
374
|
+
this.mainEvents.emit("session-meta-updated", msg.agentId, msg.meta);
|
|
375
|
+
break;
|
|
376
|
+
case "terminal-exit":
|
|
377
|
+
this.mainEvents.emit("terminal-exit", msg.agentId, msg.exitCode);
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
192
380
|
const win = this.getWindow();
|
|
193
381
|
if (!win || win.isDestroyed()) return;
|
|
194
382
|
switch (msg.type) {
|
|
@@ -199,7 +387,7 @@ var TerminalRelay = class _TerminalRelay {
|
|
|
199
387
|
win.webContents.send("terminal-exit", msg.agentId, msg.exitCode);
|
|
200
388
|
break;
|
|
201
389
|
case "copilot-event":
|
|
202
|
-
win.webContents.send("copilot-event", msg.agentId, msg.event);
|
|
390
|
+
if (!msg.mainOnly) win.webContents.send("copilot-event", msg.agentId, msg.event);
|
|
203
391
|
break;
|
|
204
392
|
case "copilot-tool-start":
|
|
205
393
|
win.webContents.send("copilot-tool-start", msg.agentId, msg.toolName, msg.toolId, msg.status);
|
|
@@ -214,7 +402,7 @@ var TerminalRelay = class _TerminalRelay {
|
|
|
214
402
|
win.webContents.send("copilot-turn-start", msg.agentId);
|
|
215
403
|
break;
|
|
216
404
|
case "copilot-user-message":
|
|
217
|
-
win.webContents.send("copilot-user-message", msg.agentId);
|
|
405
|
+
win.webContents.send("copilot-user-message", msg.agentId, msg.text);
|
|
218
406
|
break;
|
|
219
407
|
case "session-meta-updated":
|
|
220
408
|
win.webContents.send("session-meta-updated", msg.agentId, msg.meta);
|
|
@@ -222,17 +410,24 @@ var TerminalRelay = class _TerminalRelay {
|
|
|
222
410
|
case "terminal-preload-status":
|
|
223
411
|
win.webContents.send("terminal-preload-status", msg.agentId, msg.status);
|
|
224
412
|
break;
|
|
413
|
+
case "backend-online":
|
|
414
|
+
win.webContents.send("backend-online", msg.officeId, msg.backend);
|
|
415
|
+
break;
|
|
416
|
+
case "backend-session-fallback":
|
|
417
|
+
win.webContents.send("backend-session-fallback", msg.officeId, msg.agentId, msg.reason);
|
|
418
|
+
break;
|
|
225
419
|
}
|
|
226
420
|
}
|
|
227
421
|
// ── IPC Handler Registration ──────────────────────────────────
|
|
228
422
|
registerIpc() {
|
|
423
|
+
import_electron.ipcMain.handle("terminal-backend-info", () => this.backendInfo);
|
|
229
424
|
import_electron.ipcMain.handle(
|
|
230
425
|
"terminal-start",
|
|
231
426
|
(_event, officeId, agentId, workingDir, cols, rows, preseededPrompt, launchMode) => this.request({ type: "start", requestId: this.id(), officeId, agentId, workingDir, cols, rows, preseededPrompt, launchMode })
|
|
232
427
|
);
|
|
233
428
|
import_electron.ipcMain.handle(
|
|
234
429
|
"terminal-attach",
|
|
235
|
-
(_event, officeId, agentId) => this.request({ type: "attach", requestId: this.id(), officeId, agentId })
|
|
430
|
+
(_event, officeId, agentId, foreground) => this.request({ type: "attach", requestId: this.id(), officeId, agentId, foreground })
|
|
236
431
|
);
|
|
237
432
|
import_electron.ipcMain.handle("terminal-detach", (_event, officeId, agentId) => {
|
|
238
433
|
this.send({ type: "detach", officeId, agentId });
|
|
@@ -246,6 +441,14 @@ var TerminalRelay = class _TerminalRelay {
|
|
|
246
441
|
this.send({ type: "resize", officeId, agentId, cols, rows });
|
|
247
442
|
return { success: true };
|
|
248
443
|
});
|
|
444
|
+
import_electron.ipcMain.handle("set-yolo", (_event, enabled) => {
|
|
445
|
+
this.send({ type: "set-yolo", enabled: !!enabled });
|
|
446
|
+
return { success: true };
|
|
447
|
+
});
|
|
448
|
+
import_electron.ipcMain.handle("set-additional-params", (_event, params) => {
|
|
449
|
+
this.send({ type: "set-additional-params", params: String(params ?? "") });
|
|
450
|
+
return { success: true };
|
|
451
|
+
});
|
|
249
452
|
import_electron.ipcMain.handle(
|
|
250
453
|
"terminal-kill",
|
|
251
454
|
(_event, officeId, agentId) => this.request({ type: "kill", requestId: this.id(), officeId, agentId })
|
|
@@ -32,11 +32,19 @@ import_electron.contextBridge.exposeInMainWorld("copilotBridge", {
|
|
|
32
32
|
terminalKill: (officeId, agentId) => {
|
|
33
33
|
return import_electron.ipcRenderer.invoke("terminal-kill", officeId, agentId);
|
|
34
34
|
},
|
|
35
|
+
// YOLO mode: push global flag to the PTY server so new copilot sessions launch with --yolo.
|
|
36
|
+
setYolo: (enabled) => {
|
|
37
|
+
return import_electron.ipcRenderer.invoke("set-yolo", enabled);
|
|
38
|
+
},
|
|
39
|
+
// Additional parameters: push the effective param string (empty = none) to the PTY server.
|
|
40
|
+
setAdditionalParams: (params) => {
|
|
41
|
+
return import_electron.ipcRenderer.invoke("set-additional-params", params);
|
|
42
|
+
},
|
|
35
43
|
terminalExists: (officeId, agentId) => {
|
|
36
44
|
return import_electron.ipcRenderer.invoke("terminal-exists", officeId, agentId);
|
|
37
45
|
},
|
|
38
|
-
terminalAttach: (officeId, agentId) => {
|
|
39
|
-
return import_electron.ipcRenderer.invoke("terminal-attach", officeId, agentId);
|
|
46
|
+
terminalAttach: (officeId, agentId, foreground) => {
|
|
47
|
+
return import_electron.ipcRenderer.invoke("terminal-attach", officeId, agentId, foreground);
|
|
40
48
|
},
|
|
41
49
|
terminalDetach: (officeId, agentId) => {
|
|
42
50
|
return import_electron.ipcRenderer.invoke("terminal-detach", officeId, agentId);
|
|
@@ -97,37 +105,61 @@ import_electron.contextBridge.exposeInMainWorld("copilotBridge", {
|
|
|
97
105
|
loadOffices: () => {
|
|
98
106
|
return import_electron.ipcRenderer.invoke("load-offices");
|
|
99
107
|
},
|
|
100
|
-
// Terminal event listeners
|
|
108
|
+
// Terminal event listeners. Each returns an unsubscribe function that removes
|
|
109
|
+
// ONLY this registration (not every listener on the channel), so a controller
|
|
110
|
+
// can dispose its own listener before re-registering. This prevents duplicate
|
|
111
|
+
// registrations from writing the same PTY byte to xterm more than once (the
|
|
112
|
+
// classic "double characters" bug). Callers may ignore the return value.
|
|
101
113
|
onTerminalData: (callback) => {
|
|
102
|
-
|
|
114
|
+
const handler = (_event, agentId, data) => callback(agentId, data);
|
|
115
|
+
import_electron.ipcRenderer.on("terminal-data", handler);
|
|
116
|
+
return () => import_electron.ipcRenderer.removeListener("terminal-data", handler);
|
|
103
117
|
},
|
|
104
118
|
onTerminalExit: (callback) => {
|
|
105
|
-
|
|
119
|
+
const handler = (_event, agentId, exitCode) => callback(agentId, exitCode);
|
|
120
|
+
import_electron.ipcRenderer.on("terminal-exit", handler);
|
|
121
|
+
return () => import_electron.ipcRenderer.removeListener("terminal-exit", handler);
|
|
106
122
|
},
|
|
107
123
|
onTerminalPreloadStatus: (callback) => {
|
|
108
|
-
|
|
124
|
+
const handler = (_event, agentId, status) => callback(agentId, status);
|
|
125
|
+
import_electron.ipcRenderer.on("terminal-preload-status", handler);
|
|
126
|
+
return () => import_electron.ipcRenderer.removeListener("terminal-preload-status", handler);
|
|
109
127
|
},
|
|
110
128
|
// Copilot activity event listeners
|
|
111
129
|
onCopilotEvent: (callback) => {
|
|
112
|
-
|
|
130
|
+
const handler = (_event, agentId, copilotEvent) => callback(agentId, copilotEvent);
|
|
131
|
+
import_electron.ipcRenderer.on("copilot-event", handler);
|
|
132
|
+
return () => import_electron.ipcRenderer.removeListener("copilot-event", handler);
|
|
113
133
|
},
|
|
114
134
|
onCopilotToolStart: (callback) => {
|
|
115
|
-
|
|
135
|
+
const handler = (_event, agentId, toolName, toolId, status) => callback(agentId, toolName, toolId, status);
|
|
136
|
+
import_electron.ipcRenderer.on("copilot-tool-start", handler);
|
|
137
|
+
return () => import_electron.ipcRenderer.removeListener("copilot-tool-start", handler);
|
|
116
138
|
},
|
|
117
139
|
onCopilotToolComplete: (callback) => {
|
|
118
|
-
|
|
140
|
+
const handler = (_event, agentId, toolId, success) => callback(agentId, toolId, success);
|
|
141
|
+
import_electron.ipcRenderer.on("copilot-tool-complete", handler);
|
|
142
|
+
return () => import_electron.ipcRenderer.removeListener("copilot-tool-complete", handler);
|
|
119
143
|
},
|
|
120
144
|
onCopilotTurnEnd: (callback) => {
|
|
121
|
-
|
|
145
|
+
const handler = (_event, agentId) => callback(agentId);
|
|
146
|
+
import_electron.ipcRenderer.on("copilot-turn-end", handler);
|
|
147
|
+
return () => import_electron.ipcRenderer.removeListener("copilot-turn-end", handler);
|
|
122
148
|
},
|
|
123
149
|
onCopilotTurnStart: (callback) => {
|
|
124
|
-
|
|
150
|
+
const handler = (_event, agentId) => callback(agentId);
|
|
151
|
+
import_electron.ipcRenderer.on("copilot-turn-start", handler);
|
|
152
|
+
return () => import_electron.ipcRenderer.removeListener("copilot-turn-start", handler);
|
|
125
153
|
},
|
|
126
154
|
onCopilotUserMessage: (callback) => {
|
|
127
|
-
|
|
155
|
+
const handler = (_event, agentId) => callback(agentId);
|
|
156
|
+
import_electron.ipcRenderer.on("copilot-user-message", handler);
|
|
157
|
+
return () => import_electron.ipcRenderer.removeListener("copilot-user-message", handler);
|
|
128
158
|
},
|
|
129
159
|
onSessionMetaUpdated: (callback) => {
|
|
130
|
-
|
|
160
|
+
const handler = (_event, agentId, meta) => callback(agentId, meta);
|
|
161
|
+
import_electron.ipcRenderer.on("session-meta-updated", handler);
|
|
162
|
+
return () => import_electron.ipcRenderer.removeListener("session-meta-updated", handler);
|
|
131
163
|
},
|
|
132
164
|
removeTerminalListeners: () => {
|
|
133
165
|
import_electron.ipcRenderer.removeAllListeners("terminal-data");
|
|
@@ -150,6 +182,19 @@ import_electron.contextBridge.exposeInMainWorld("copilotBridge", {
|
|
|
150
182
|
showNativeNotification: (title, body) => {
|
|
151
183
|
return import_electron.ipcRenderer.invoke("show-native-notification", title, body);
|
|
152
184
|
},
|
|
185
|
+
// Terminal backend selection (ui-server / node-pty / sdk).
|
|
186
|
+
getBackendInfo: () => {
|
|
187
|
+
return import_electron.ipcRenderer.invoke("terminal-backend-info");
|
|
188
|
+
},
|
|
189
|
+
onBackendFallback: (callback) => {
|
|
190
|
+
import_electron.ipcRenderer.on("backend-fallback", (_event, info) => callback(info));
|
|
191
|
+
},
|
|
192
|
+
onBackendOnline: (callback) => {
|
|
193
|
+
import_electron.ipcRenderer.on("backend-online", (_event, officeId, backend) => callback(officeId, backend));
|
|
194
|
+
},
|
|
195
|
+
onBackendSessionFallback: (callback) => {
|
|
196
|
+
import_electron.ipcRenderer.on("backend-session-fallback", (_event, officeId, agentId, reason) => callback(officeId, agentId, reason));
|
|
197
|
+
},
|
|
153
198
|
// Spec 003 follow-up: write to OS clipboard via Electron main process.
|
|
154
199
|
// Bypasses Permissions API + focus restrictions that make
|
|
155
200
|
// navigator.clipboard.writeText unreliable in xterm-focused contexts.
|
|
@@ -160,5 +205,30 @@ import_electron.contextBridge.exposeInMainWorld("copilotBridge", {
|
|
|
160
205
|
// terminalWrite to implement Paste in the terminal context menu.
|
|
161
206
|
clipboardReadText: () => {
|
|
162
207
|
return import_electron.ipcRenderer.invoke("clipboard-read-text");
|
|
208
|
+
},
|
|
209
|
+
// ── Teams Remote Agents (011) ────────────────────────────────
|
|
210
|
+
teamsStatus: (args) => {
|
|
211
|
+
return import_electron.ipcRenderer.invoke("teams:status", args ?? {});
|
|
212
|
+
},
|
|
213
|
+
teamsRegister: (ctx) => {
|
|
214
|
+
return import_electron.ipcRenderer.invoke("teams:register", ctx);
|
|
215
|
+
},
|
|
216
|
+
teamsStop: (args) => {
|
|
217
|
+
return import_electron.ipcRenderer.invoke("teams:stop", args);
|
|
218
|
+
},
|
|
219
|
+
teamsReconcile: () => {
|
|
220
|
+
return import_electron.ipcRenderer.invoke("teams:reconcile");
|
|
221
|
+
},
|
|
222
|
+
teamsGetSettings: () => {
|
|
223
|
+
return import_electron.ipcRenderer.invoke("teams:getSettings");
|
|
224
|
+
},
|
|
225
|
+
teamsSaveSettings: (settings) => {
|
|
226
|
+
return import_electron.ipcRenderer.invoke("teams:saveSettings", { settings });
|
|
227
|
+
},
|
|
228
|
+
onTeamsStatusChanged: (callback) => {
|
|
229
|
+
import_electron.ipcRenderer.on("teams:status:changed", (_event, status) => callback(status));
|
|
230
|
+
},
|
|
231
|
+
onTeamsToast: (callback) => {
|
|
232
|
+
import_electron.ipcRenderer.on("teams:toast", (_event, toast) => callback(toast));
|
|
163
233
|
}
|
|
164
234
|
});
|