copilotoffice 2.1.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.
@@ -151867,7 +151867,7 @@ ${h2.join(`
151867
151867
  document.body.appendChild(el2);
151868
151868
  return el2;
151869
151869
  }
151870
- function showClipboardToast(message, kind = "info") {
151870
+ function showClipboardToast(message, kind = "info", durationMs = TOAST_DURATION_MS) {
151871
151871
  const el2 = ensureToastElement();
151872
151872
  if (!el2) return;
151873
151873
  el2.textContent = message;
@@ -151876,7 +151876,7 @@ ${h2.join(`
151876
151876
  if (dismissTimer) clearTimeout(dismissTimer);
151877
151877
  dismissTimer = setTimeout(() => {
151878
151878
  el2.style.opacity = "0";
151879
- }, TOAST_DURATION_MS);
151879
+ }, durationMs);
151880
151880
  }
151881
151881
  var TOAST_ID, TOAST_DURATION_MS, dismissTimer, KIND_BG;
151882
151882
  var init_clipboardToast = __esm({
@@ -152554,6 +152554,14 @@ ${h2.join(`
152554
152554
  // Disposable returned by xterm.terminal.onData(...). Re-registered per show()
152555
152555
  // so the handler's closure captures the new agent id (feature 002, C3/V6).
152556
152556
  this.onDataDisposable = null;
152557
+ // Unsubscribe functions for the bridge IPC listeners this overlay owns
152558
+ // (terminal-data, terminal-exit, session-meta-updated). Disposed and
152559
+ // re-created on every setupTerminalListeners() call so an overlay can never
152560
+ // accumulate duplicate listeners — duplicates would write each PTY byte to
152561
+ // xterm more than once (the "double characters" bug).
152562
+ this.bridgeListenerDisposers = [];
152563
+ // Teams status listener has no disposer on the bridge; bind it at most once.
152564
+ this.teamsListenerBound = false;
152557
152565
  // Toggled while show() is awaiting detach/attach so onData cannot fire input
152558
152566
  // against a half-attached agent (feature 002, V5).
152559
152567
  this.isSwitchingAgent = false;
@@ -152582,35 +152590,50 @@ ${h2.join(`
152582
152590
  }
152583
152591
  setupTerminalListeners() {
152584
152592
  if (typeof window !== "undefined" && window.copilotBridge) {
152585
- window.copilotBridge.onTerminalData((agentId, data) => {
152586
- if (this.isReplaying) return;
152587
- if (agentId === this.currentAgentId && this.terminal) {
152588
- this.terminal.write(data);
152593
+ for (const dispose of this.bridgeListenerDisposers) {
152594
+ try {
152595
+ dispose();
152596
+ } catch {
152589
152597
  }
152590
- });
152591
- window.copilotBridge.onTerminalExit((agentId, exitCode) => {
152592
- if (agentId === this.currentAgentId && this.terminal) {
152593
- this.terminal.writeln(`\r
152598
+ }
152599
+ this.bridgeListenerDisposers = [];
152600
+ this.bridgeListenerDisposers.push(
152601
+ window.copilotBridge.onTerminalData((agentId, data) => {
152602
+ if (this.isReplaying) return;
152603
+ if (agentId === this.currentAgentId && this.terminal) {
152604
+ this.terminal.write(data);
152605
+ }
152606
+ })
152607
+ );
152608
+ this.bridgeListenerDisposers.push(
152609
+ window.copilotBridge.onTerminalExit((agentId, exitCode) => {
152610
+ if (agentId === this.currentAgentId && this.terminal) {
152611
+ this.terminal.writeln(`\r
152594
152612
  [Process exited with code ${exitCode}]`);
152595
- }
152596
- });
152597
- window.copilotBridge.onSessionMetaUpdated((agentId, meta) => {
152598
- if (agentId === this.currentAgentId) {
152599
- this.updateSessionTitleDisplay(meta?.title || null);
152600
- }
152601
- });
152602
- window.copilotBridge.onTeamsStatusChanged?.((status) => {
152603
- if (status?.agentId === this.currentAgentId) {
152604
- this.setTeamsButtonState(!!status.online);
152605
- }
152606
- });
152613
+ }
152614
+ })
152615
+ );
152616
+ this.bridgeListenerDisposers.push(
152617
+ window.copilotBridge.onSessionMetaUpdated((agentId, meta) => {
152618
+ if (agentId === this.currentAgentId) {
152619
+ this.updateSessionTitleDisplay(meta?.title || null);
152620
+ }
152621
+ })
152622
+ );
152623
+ if (!this.teamsListenerBound) {
152624
+ window.copilotBridge.onTeamsStatusChanged?.((status) => {
152625
+ if (status?.agentId === this.currentAgentId) {
152626
+ this.setTeamsButtonState(!!status.online);
152627
+ }
152628
+ });
152629
+ this.teamsListenerBound = true;
152630
+ }
152607
152631
  }
152608
152632
  }
152609
152633
  /**
152610
- * Re-register IPC listeners that may have been removed by another scene's
152611
- * cleanup (e.g. MeetingScene calling removeTerminalListeners()).
152612
- * Safe to call multiple times additive listeners are fine because they
152613
- * all guard on `this.currentAgentId`.
152634
+ * Re-register this overlay's IPC listeners. setupTerminalListeners() is
152635
+ * idempotent (it disposes prior registrations first), so calling this after
152636
+ * returning from another scene cannot create duplicate listeners.
152614
152637
  */
152615
152638
  reattachListeners() {
152616
152639
  this.setupTerminalListeners();
@@ -152948,7 +152971,7 @@ ${h2.join(`
152948
152971
  } else {
152949
152972
  this.fitAndResizeTerminal({ officeId, agentId: agent.id });
152950
152973
  const attachResult = await withTimeout(
152951
- window.copilotBridge.terminalAttach(officeId, agent.id),
152974
+ window.copilotBridge.terminalAttach(officeId, agent.id, true),
152952
152975
  IPC_TIMEOUT,
152953
152976
  "terminalAttach"
152954
152977
  );
@@ -154107,9 +154130,13 @@ ${h2.join(`
154107
154130
  } catch {
154108
154131
  }
154109
154132
  this.spriteCardElement = null;
154110
- if (window.copilotBridge) {
154111
- window.copilotBridge.removeTerminalListeners();
154133
+ for (const dispose of this.bridgeListenerDisposers) {
154134
+ try {
154135
+ dispose();
154136
+ } catch {
154137
+ }
154112
154138
  }
154139
+ this.bridgeListenerDisposers = [];
154113
154140
  }
154114
154141
  };
154115
154142
  }
@@ -160844,7 +160871,7 @@ Failed to start terminal: ${startResult.error || "unknown error"}`);
160844
160871
  this.updateSessionIdDisplay();
160845
160872
  }
160846
160873
  }
160847
- const attachResult = await window.copilotBridge.terminalAttach(options.officeId, options.agentId);
160874
+ const attachResult = await window.copilotBridge.terminalAttach(options.officeId, options.agentId, true);
160848
160875
  if (!attachResult.success) {
160849
160876
  this.terminal.writeln("\r\nFailed to attach terminal session.");
160850
160877
  this.setStatus("Attach failed");
@@ -163315,6 +163342,24 @@ Terminal error: ${error?.message || String(error)}`);
163315
163342
  const kind = toast.level === "error" ? "error" : toast.level === "warn" ? "error" : "info";
163316
163343
  showClipboardToast(toast.message, kind);
163317
163344
  });
163345
+ let backendFallbackToastShown = false;
163346
+ const showBackendFallbackToast = (info) => {
163347
+ if (!info || !info.fellBack || backendFallbackToastShown) return;
163348
+ backendFallbackToastShown = true;
163349
+ const detail = info.reason ? ` (${info.reason})` : "";
163350
+ showClipboardToast(`Terminal: ${info.requested} unavailable \u2014 using ${info.name}${detail}`, "error", 1e4);
163351
+ };
163352
+ window.copilotBridge.onBackendFallback?.((info) => showBackendFallbackToast(info));
163353
+ void window.copilotBridge.getBackendInfo?.().then((info) => showBackendFallbackToast(info));
163354
+ window.copilotBridge.onBackendOnline?.((officeId, _backend) => {
163355
+ const officeName = officeManager.getOffice(officeId)?.config.name ?? officeId;
163356
+ showClipboardToast(`GitHub Copilot SDK server online for ${officeName}`, "success", 1e4);
163357
+ });
163358
+ window.copilotBridge.onBackendSessionFallback?.((_officeId, agentId, reason) => {
163359
+ const agentName = getAgentConfig(agentId)?.name ?? agentId;
163360
+ const detail = reason ? ` (${reason})` : "";
163361
+ showClipboardToast(`${agentName}: UI-server unavailable \u2014 using node-pty${detail}`, "error", 1e4);
163362
+ });
163318
163363
  window.copilotBridge.onTeamsStatusChanged?.((status) => {
163319
163364
  if (!status?.agentId) return;
163320
163365
  if (status.online) teamsOnlineAgentIds.add(status.agentId);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "copilotoffice",
3
- "version": "2.1.0",
4
- "description": "A 2D pixel-art game where you interact with AI agents in a virtual office",
3
+ "version": "2.2.0",
4
+ "description": "A 2D pixel-art desktop game where you walk around a virtual office and interact with AI agents that run real GitHub Copilot CLI sessions — plan, debug, orchestrate fleets, and drive agents from Microsoft Teams.",
5
5
  "main": "dist/electron/main.js",
6
6
  "bin": {
7
7
  "copilotoffice": "bin/copilotoffice.js"
@@ -26,15 +26,19 @@
26
26
  },
27
27
  "keywords": [
28
28
  "copilot",
29
+ "copilot-cli",
30
+ "ai-agents",
29
31
  "game",
30
32
  "pixel-art",
31
- "electron"
33
+ "phaser",
34
+ "electron",
35
+ "typescript"
32
36
  ],
33
37
  "author": "",
34
38
  "license": "ISC",
35
39
  "type": "commonjs",
36
40
  "dependencies": {
37
- "@github/copilot-sdk": "^0.1.32",
41
+ "@github/copilot-sdk": "1.0.5",
38
42
  "@xterm/addon-fit": "^0.11.0",
39
43
  "@xterm/xterm": "^6.0.0",
40
44
  "ansi-to-html": "^0.7.2",