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.
@@ -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 = path.join(distDir, "terminal", "server.js");
179
+ const serverPath = path2.join(distDir, "terminal", "server.js");
58
180
  console.log("[Relay] Forking terminal server:", serverPath);
59
- this.server = (0, import_child_process.fork)(serverPath, [], {
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, import_child_process.execSync)(`taskkill /T /F /PID ${oldPid}`, { stdio: "ignore" });
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 crypto.randomUUID();
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
- import_electron.ipcRenderer.on("terminal-data", (_event, agentId, data) => callback(agentId, data));
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
- import_electron.ipcRenderer.on("terminal-exit", (_event, agentId, exitCode) => callback(agentId, exitCode));
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
- import_electron.ipcRenderer.on("terminal-preload-status", (_event, agentId, status) => callback(agentId, status));
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
- import_electron.ipcRenderer.on("copilot-event", (_event, agentId, copilotEvent) => callback(agentId, copilotEvent));
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
- import_electron.ipcRenderer.on("copilot-tool-start", (_event, agentId, toolName, toolId, status) => callback(agentId, toolName, toolId, status));
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
- import_electron.ipcRenderer.on("copilot-tool-complete", (_event, agentId, toolId, success) => callback(agentId, toolId, success));
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
- import_electron.ipcRenderer.on("copilot-turn-end", (_event, agentId) => callback(agentId));
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
- import_electron.ipcRenderer.on("copilot-turn-start", (_event, agentId) => callback(agentId));
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
- import_electron.ipcRenderer.on("copilot-user-message", (_event, agentId) => callback(agentId));
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
- import_electron.ipcRenderer.on("session-meta-updated", (_event, agentId, meta) => callback(agentId, meta));
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
  });