copilotoffice 2.1.0 → 2.3.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.
@@ -141772,23 +141772,173 @@ var CopilotOffice = (() => {
141772
141772
  }
141773
141773
  });
141774
141774
 
141775
+ // src/config/agentStatusPresentation.ts
141776
+ function resolveStatusKey(status) {
141777
+ if (!status || status.state === "slacking") return "slacking";
141778
+ switch (status.subState) {
141779
+ case "starting":
141780
+ return "starting";
141781
+ case "ready":
141782
+ return status.completionPendingAck ? "done" : "ready";
141783
+ case "waiting":
141784
+ return "waiting";
141785
+ case "thinking":
141786
+ return "thinking";
141787
+ case "error":
141788
+ return "error";
141789
+ default:
141790
+ return "slacking";
141791
+ }
141792
+ }
141793
+ function computeStall(status, now = Date.now()) {
141794
+ if (!status || !status.activityStartTime) return NOT_STALLED;
141795
+ const key = resolveStatusKey(status);
141796
+ const pres = STATUS_PRESENTATION[key];
141797
+ if (!pres.isActive || key === "error") return NOT_STALLED;
141798
+ if (now - status.activityStartTime < STALL_THRESHOLD_MS) return NOT_STALLED;
141799
+ return {
141800
+ isStalled: true,
141801
+ stallColorHex: STALL_COLOR_HEX,
141802
+ stallColorNum: STALL_COLOR_NUM,
141803
+ stallStrokeNum: STALL_STROKE_NUM
141804
+ };
141805
+ }
141806
+ function describeActivity(status) {
141807
+ if (!status) return "";
141808
+ const key = resolveStatusKey(status);
141809
+ if (key !== "waiting" && key !== "starting") return "";
141810
+ const detail = status.thinkingDetail?.trim();
141811
+ if (detail) return detail;
141812
+ const tool = status.currentTool?.trim();
141813
+ if (tool) return friendlyToolName(tool);
141814
+ return key === "waiting" ? "Waiting for your answer" : "Working\u2026";
141815
+ }
141816
+ function friendlyToolName(tool) {
141817
+ const normalized = tool.trim().toLowerCase();
141818
+ const map = {
141819
+ ask_user: "Waiting for your answer",
141820
+ edit: "Editing a file",
141821
+ create: "Creating a file",
141822
+ view: "Reading a file",
141823
+ grep: "Searching",
141824
+ glob: "Finding files",
141825
+ powershell: "Running a command",
141826
+ bash: "Running a command"
141827
+ };
141828
+ return map[normalized] ?? tool;
141829
+ }
141830
+ function formatElapsedMmSs(startTime, now = Date.now()) {
141831
+ if (!startTime) return "";
141832
+ const totalSeconds = Math.max(0, Math.floor((now - startTime) / 1e3));
141833
+ const minutes = Math.floor(totalSeconds / 60);
141834
+ const seconds = totalSeconds % 60;
141835
+ return `${minutes}:${seconds.toString().padStart(2, "0")}`;
141836
+ }
141837
+ var STATUS_PRESENTATION, STALL_THRESHOLD_MS, STALL_COLOR_HEX, STALL_COLOR_NUM, STALL_STROKE_NUM, NOT_STALLED;
141838
+ var init_agentStatusPresentation = __esm({
141839
+ "src/config/agentStatusPresentation.ts"() {
141840
+ "use strict";
141841
+ STATUS_PRESENTATION = {
141842
+ slacking: {
141843
+ key: "slacking",
141844
+ label: "Slacking",
141845
+ shortLabel: "Slacking",
141846
+ colorHex: "#555555",
141847
+ colorNum: 5592405,
141848
+ strokeNum: 6710886,
141849
+ icon: "\u{1F4A4}",
141850
+ badgeAnimation: "none",
141851
+ isActive: false
141852
+ },
141853
+ starting: {
141854
+ key: "starting",
141855
+ label: "Starting\u2026",
141856
+ shortLabel: "Starting",
141857
+ colorHex: "#ff9944",
141858
+ colorNum: 16750916,
141859
+ strokeNum: 16759654,
141860
+ icon: "\u{1F680}",
141861
+ badgeAnimation: "pulse",
141862
+ isActive: true
141863
+ },
141864
+ ready: {
141865
+ key: "ready",
141866
+ label: "Ready",
141867
+ shortLabel: "Ready",
141868
+ colorHex: "#ffffff",
141869
+ colorNum: 16777215,
141870
+ strokeNum: 14540253,
141871
+ icon: "\u{1F4ED}",
141872
+ badgeAnimation: "none",
141873
+ isActive: false
141874
+ },
141875
+ done: {
141876
+ key: "done",
141877
+ label: "Done",
141878
+ shortLabel: "Done",
141879
+ colorHex: "#4a78ff",
141880
+ colorNum: 4880639,
141881
+ strokeNum: 7049471,
141882
+ icon: "\u{1F4EC}",
141883
+ badgeAnimation: "none",
141884
+ isActive: false
141885
+ },
141886
+ waiting: {
141887
+ key: "waiting",
141888
+ label: "Waiting for input",
141889
+ shortLabel: "Waiting",
141890
+ colorHex: "#ffb86c",
141891
+ colorNum: 16758892,
141892
+ strokeNum: 16764040,
141893
+ icon: "\u23F3",
141894
+ badgeAnimation: "none",
141895
+ isActive: true
141896
+ },
141897
+ thinking: {
141898
+ key: "thinking",
141899
+ label: "Thinking\u2026",
141900
+ shortLabel: "Thinking\u2026",
141901
+ colorHex: "#50fa7b",
141902
+ colorNum: 5307003,
141903
+ strokeNum: 6750105,
141904
+ icon: "\u{1F9E0}",
141905
+ badgeAnimation: "pulse",
141906
+ isActive: true
141907
+ },
141908
+ error: {
141909
+ key: "error",
141910
+ label: "Error",
141911
+ shortLabel: "Error",
141912
+ colorHex: "#ff4444",
141913
+ colorNum: 16729156,
141914
+ strokeNum: 16737894,
141915
+ icon: "\u274C",
141916
+ badgeAnimation: "none",
141917
+ isActive: true
141918
+ }
141919
+ };
141920
+ STALL_THRESHOLD_MS = 6e4;
141921
+ STALL_COLOR_HEX = "#ffb020";
141922
+ STALL_COLOR_NUM = 16756768;
141923
+ STALL_STROKE_NUM = 16762967;
141924
+ NOT_STALLED = {
141925
+ isStalled: false,
141926
+ stallColorHex: STALL_COLOR_HEX,
141927
+ stallColorNum: STALL_COLOR_NUM,
141928
+ stallStrokeNum: STALL_STROKE_NUM
141929
+ };
141930
+ }
141931
+ });
141932
+
141775
141933
  // src/entities/NPC.ts
141776
- var import_phaser3, BADGE_COLORS, NPC;
141934
+ var import_phaser3, NPC;
141777
141935
  var init_NPC = __esm({
141778
141936
  "src/entities/NPC.ts"() {
141779
141937
  "use strict";
141780
141938
  import_phaser3 = __toESM(require_phaser());
141781
141939
  init_depths();
141940
+ init_agentStatusPresentation();
141782
141941
  init_DirectionalSprite();
141783
- BADGE_COLORS = {
141784
- slacking: { fill: 5592405, stroke: 6710886 },
141785
- starting: { fill: 16750916, stroke: 16759654 },
141786
- ready: { fill: 16777215, stroke: 14540253 },
141787
- done: { fill: 4880639, stroke: 7049471 },
141788
- waiting: { fill: 16758892, stroke: 16764040 },
141789
- thinking: { fill: 5307003, stroke: 6750105 },
141790
- error: { fill: 16729156, stroke: 16737894 }
141791
- };
141792
141942
  NPC = class extends import_phaser3.default.Physics.Arcade.Sprite {
141793
141943
  constructor(scene, config, tileSize, spriteScale = 1) {
141794
141944
  const x = config.position.x * tileSize + tileSize / 2;
@@ -141797,6 +141947,8 @@ var CopilotOffice = (() => {
141797
141947
  this._isHighlighted = false;
141798
141948
  this.highlightTween = null;
141799
141949
  this.badgePulseTween = null;
141950
+ this.stallTween = null;
141951
+ this.isStalled = false;
141800
141952
  this.isNearPlayer = false;
141801
141953
  this.hasActiveSession = false;
141802
141954
  this.currentBadgeState = "slacking";
@@ -141862,13 +142014,19 @@ var CopilotOffice = (() => {
141862
142014
  this.sessionBadge.setPosition(x + 16 * spriteScale, y - 24 * spriteScale);
141863
142015
  this.sessionBadge.setDepth(Depths.BADGES);
141864
142016
  this.updateSessionBadge(spriteScale);
141865
- this.sessionText = scene.add.text(x + 16 * spriteScale, y - 24 * spriteScale, "\u{1F4A4}", {
142017
+ this.sessionText = scene.add.text(x + 16 * spriteScale, y - 24 * spriteScale, STATUS_PRESENTATION.slacking.icon, {
141866
142018
  font: `bold ${badgeFontSize * 4}px monospace`,
141867
142019
  color: "#ffffff"
141868
142020
  });
141869
142021
  this.sessionText.setOrigin(0.5, 0.5);
141870
142022
  this.sessionText.setVisible(true);
141871
142023
  this.sessionText.setDepth(Depths.BADGES + 1);
142024
+ this.stallRing = scene.add.graphics();
142025
+ this.stallRing.setPosition(x + 16 * spriteScale, y - 24 * spriteScale);
142026
+ this.stallRing.setDepth(Depths.BADGES + 2);
142027
+ this.stallRing.setVisible(false);
142028
+ this.stallRing.lineStyle(2.5, 16756768, 0.95);
142029
+ this.stallRing.strokeCircle(0, 0, 20 * spriteScale);
141872
142030
  this.indicator = scene.add.sprite(x, y - 48 * spriteScale, "indicator");
141873
142031
  this.indicator.setScale(0.6 * spriteScale);
141874
142032
  this.indicator.setVisible(false);
@@ -141887,10 +142045,11 @@ var CopilotOffice = (() => {
141887
142045
  updateSessionBadge(scale = 1) {
141888
142046
  this.sessionBadge.clear();
141889
142047
  const stateKey = this.currentBadgeState;
141890
- const colors = BADGE_COLORS[stateKey] || BADGE_COLORS.slacking;
141891
- this.sessionBadge.fillStyle(colors.fill, stateKey === "slacking" ? 0.7 : 1);
142048
+ const pres = STATUS_PRESENTATION[stateKey] ?? STATUS_PRESENTATION.slacking;
142049
+ const isSlacking = stateKey === "slacking";
142050
+ this.sessionBadge.fillStyle(pres.colorNum, isSlacking ? 0.7 : 1);
141892
142051
  this.sessionBadge.fillCircle(0, 0, 16 * scale);
141893
- this.sessionBadge.lineStyle(2, colors.stroke, stateKey === "slacking" ? 0.5 : 1);
142052
+ this.sessionBadge.lineStyle(2, pres.strokeNum, isSlacking ? 0.5 : 1);
141894
142053
  this.sessionBadge.strokeCircle(0, 0, 16 * scale);
141895
142054
  }
141896
142055
  _drawHighlightRing(color, scale) {
@@ -141957,8 +142116,9 @@ var CopilotOffice = (() => {
141957
142116
  setHasActiveSession(hasSession, messageCount) {
141958
142117
  this.hasActiveSession = hasSession;
141959
142118
  if (!hasSession) {
142119
+ this.setStalled(false);
141960
142120
  this.updateBadgeForState("slacking");
141961
- this.sessionText.setText("\u{1F4A4}");
142121
+ this.sessionText.setText(STATUS_PRESENTATION.slacking.icon);
141962
142122
  if (!this.badgeHidden) {
141963
142123
  this.sessionText.setVisible(true);
141964
142124
  }
@@ -141978,6 +142138,7 @@ var CopilotOffice = (() => {
141978
142138
  this.badgeHidden = !visible;
141979
142139
  this.sessionBadge.setVisible(visible);
141980
142140
  this.sessionText.setVisible(visible);
142141
+ this.stallRing.setVisible(visible && this.isStalled);
141981
142142
  }
141982
142143
  /** Toggle visibility of name and description labels. */
141983
142144
  setLabelsVisible(visible) {
@@ -141988,29 +142149,57 @@ var CopilotOffice = (() => {
141988
142149
  updateAgentStatus(status) {
141989
142150
  if (!status || status.state === "slacking") {
141990
142151
  this.hasActiveSession = false;
142152
+ this.setStalled(false);
141991
142153
  this.updateBadgeForState("slacking");
141992
- this.sessionText.setText("\u{1F4A4}");
142154
+ this.sessionText.setText(STATUS_PRESENTATION.slacking.icon);
141993
142155
  if (!this.badgeHidden) {
141994
142156
  this.sessionText.setVisible(true);
141995
142157
  }
141996
142158
  return;
141997
142159
  }
141998
142160
  this.hasActiveSession = true;
141999
- const stateKey = status.subState === "ready" && status.completionPendingAck ? "done" : status.subState || "ready";
142161
+ const stateKey = resolveStatusKey(status);
142000
142162
  this.updateBadgeForState(stateKey);
142001
- const icons = {
142002
- starting: "\u{1F680}",
142003
- ready: "\u{1F4ED}",
142004
- done: "\u{1F4EC}",
142005
- waiting: "\u23F3",
142006
- thinking: "\u{1F9E0}",
142007
- error: "\u274C"
142008
- };
142009
- this.sessionText.setText(icons[stateKey] || "");
142163
+ this.sessionText.setText(STATUS_PRESENTATION[stateKey].icon || "");
142010
142164
  if (!this.badgeHidden) {
142011
142165
  this.sessionText.setVisible(stateKey !== "slacking");
142012
142166
  }
142013
142167
  }
142168
+ /**
142169
+ * FR-013: toggle the ~60s stall treatment. Shows an amber ring on a slower,
142170
+ * "laboring" pulse cadence (distinct from the normal in-progress pulse and
142171
+ * from the error state). Clearing it restores the badge to normal. Idempotent.
142172
+ */
142173
+ setStalled(on2) {
142174
+ if (this.isStalled === on2) return;
142175
+ this.isStalled = on2;
142176
+ if (this.stallTween) {
142177
+ this.stallTween.stop();
142178
+ this.stallTween = null;
142179
+ }
142180
+ if (on2) {
142181
+ this.stallRing.setScale(1);
142182
+ this.stallRing.setAlpha(1);
142183
+ this.stallRing.setVisible(!this.badgeHidden);
142184
+ this.stallTween = this.scene.tweens.add({
142185
+ targets: this.stallRing,
142186
+ scaleX: { from: 0.85, to: 1.18 },
142187
+ scaleY: { from: 0.85, to: 1.18 },
142188
+ alpha: { from: 0.55, to: 1 },
142189
+ duration: 1100,
142190
+ yoyo: true,
142191
+ repeat: -1,
142192
+ ease: "Sine.easeInOut"
142193
+ });
142194
+ } else {
142195
+ this.stallRing.setScale(1);
142196
+ this.stallRing.setVisible(false);
142197
+ }
142198
+ }
142199
+ /** True while the stall treatment is active (test/inspection aid). */
142200
+ get stalled() {
142201
+ return this.isStalled;
142202
+ }
142014
142203
  updateBadgeForState(stateKey) {
142015
142204
  if (this.currentBadgeState === stateKey) return;
142016
142205
  this.currentBadgeState = stateKey;
@@ -142020,7 +142209,8 @@ var CopilotOffice = (() => {
142020
142209
  }
142021
142210
  this.sessionBadge.setScale(1);
142022
142211
  this.updateSessionBadge(this.spriteScale);
142023
- if (stateKey === "thinking" || stateKey === "starting") {
142212
+ const pres = STATUS_PRESENTATION[stateKey];
142213
+ if (pres?.badgeAnimation === "pulse") {
142024
142214
  this.badgePulseTween = this.scene.tweens.add({
142025
142215
  targets: this.sessionBadge,
142026
142216
  scaleX: { from: 0.925, to: 1.075 },
@@ -142084,6 +142274,7 @@ var CopilotOffice = (() => {
142084
142274
  this.descriptionLabel.setPosition(x, y - 28 * s15);
142085
142275
  this.sessionBadge.setPosition(x + 16 * s15, y - 24 * s15);
142086
142276
  this.sessionText.setPosition(x + 16 * s15, y - 24 * s15);
142277
+ this.stallRing.setPosition(x + 16 * s15, y - 24 * s15);
142087
142278
  this.indicator.setPosition(x, this.indicator.y);
142088
142279
  this.highlightGlow.setPosition(x, y);
142089
142280
  this.highlightRing.setPosition(x, y);
@@ -142096,6 +142287,7 @@ var CopilotOffice = (() => {
142096
142287
  this.highlightRing.destroy();
142097
142288
  this.sessionBadge.destroy();
142098
142289
  this.sessionText.destroy();
142290
+ this.stallRing.destroy();
142099
142291
  super.destroy(fromScene);
142100
142292
  }
142101
142293
  };
@@ -151349,6 +151541,12 @@ ${h2.join(`
151349
151541
  if (cfg.teamsChannelUrl !== void 0) {
151350
151542
  normalized.teamsChannelUrl = cfg.teamsChannelUrl;
151351
151543
  }
151544
+ if (cfg.teamsMentionType !== void 0) {
151545
+ normalized.teamsMentionType = cfg.teamsMentionType;
151546
+ }
151547
+ if (cfg.teamsMentionValue !== void 0) {
151548
+ normalized.teamsMentionValue = cfg.teamsMentionValue;
151549
+ }
151352
151550
  offices.push(normalized);
151353
151551
  }
151354
151552
  let currentOfficeId = null;
@@ -151536,6 +151734,8 @@ ${h2.join(`
151536
151734
  if (updates.workingDirectory !== void 0) office.config.workingDirectory = updates.workingDirectory;
151537
151735
  if (updates.layout !== void 0) office.config.layout = updates.layout;
151538
151736
  if (updates.teamsChannelUrl !== void 0) office.config.teamsChannelUrl = updates.teamsChannelUrl;
151737
+ if (updates.teamsMentionType !== void 0) office.config.teamsMentionType = updates.teamsMentionType;
151738
+ if (updates.teamsMentionValue !== void 0) office.config.teamsMentionValue = updates.teamsMentionValue;
151539
151739
  this.saveToStorage();
151540
151740
  this.onOfficesUpdated?.();
151541
151741
  return true;
@@ -151637,7 +151837,7 @@ ${h2.join(`
151637
151837
  if (currentOfficeId && this.offices.has(currentOfficeId)) {
151638
151838
  this._currentOfficeId = currentOfficeId;
151639
151839
  } else if (this.offices.size > 0) {
151640
- this._currentOfficeId = this.offices.keys().next().value;
151840
+ this._currentOfficeId = this.offices.keys().next().value ?? null;
151641
151841
  }
151642
151842
  }
151643
151843
  // Agent status helpers
@@ -151768,7 +151968,7 @@ ${h2.join(`
151768
151968
  status.thinkingDetail = detail;
151769
151969
  const office = this.offices.get(officeId);
151770
151970
  const tools = office?.agentTools.get(agentId);
151771
- status.currentTool = tools?.length ? tools[tools.length - 1].name : null;
151971
+ status.currentTool = tools?.length ? tools[tools.length - 1].name ?? null : null;
151772
151972
  status.completionPendingAck = false;
151773
151973
  if (!status.activityStartTime) status.activityStartTime = Date.now();
151774
151974
  this.emitLifecycleTransition(officeId, agentId, status, from, reason, detail ?? void 0);
@@ -151867,7 +152067,7 @@ ${h2.join(`
151867
152067
  document.body.appendChild(el2);
151868
152068
  return el2;
151869
152069
  }
151870
- function showClipboardToast(message, kind = "info") {
152070
+ function showClipboardToast(message, kind = "info", durationMs = TOAST_DURATION_MS) {
151871
152071
  const el2 = ensureToastElement();
151872
152072
  if (!el2) return;
151873
152073
  el2.textContent = message;
@@ -151876,7 +152076,7 @@ ${h2.join(`
151876
152076
  if (dismissTimer) clearTimeout(dismissTimer);
151877
152077
  dismissTimer = setTimeout(() => {
151878
152078
  el2.style.opacity = "0";
151879
- }, TOAST_DURATION_MS);
152079
+ }, durationMs);
151880
152080
  }
151881
152081
  var TOAST_ID, TOAST_DURATION_MS, dismissTimer, KIND_BG;
151882
152082
  var init_clipboardToast = __esm({
@@ -152539,6 +152739,7 @@ ${h2.join(`
152539
152739
  this.wheelPager = new WheelPager();
152540
152740
  this.resizeObserver = null;
152541
152741
  this.refitTimers = [];
152742
+ this.refitRaf = null;
152542
152743
  this.refitGeneration = 0;
152543
152744
  this.attachedOfficeId = null;
152544
152745
  this.isReadOnly = false;
@@ -152554,6 +152755,14 @@ ${h2.join(`
152554
152755
  // Disposable returned by xterm.terminal.onData(...). Re-registered per show()
152555
152756
  // so the handler's closure captures the new agent id (feature 002, C3/V6).
152556
152757
  this.onDataDisposable = null;
152758
+ // Unsubscribe functions for the bridge IPC listeners this overlay owns
152759
+ // (terminal-data, terminal-exit, session-meta-updated). Disposed and
152760
+ // re-created on every setupTerminalListeners() call so an overlay can never
152761
+ // accumulate duplicate listeners — duplicates would write each PTY byte to
152762
+ // xterm more than once (the "double characters" bug).
152763
+ this.bridgeListenerDisposers = [];
152764
+ // Teams status listener has no disposer on the bridge; bind it at most once.
152765
+ this.teamsListenerBound = false;
152557
152766
  // Toggled while show() is awaiting detach/attach so onData cannot fire input
152558
152767
  // against a half-attached agent (feature 002, V5).
152559
152768
  this.isSwitchingAgent = false;
@@ -152582,35 +152791,50 @@ ${h2.join(`
152582
152791
  }
152583
152792
  setupTerminalListeners() {
152584
152793
  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);
152794
+ for (const dispose of this.bridgeListenerDisposers) {
152795
+ try {
152796
+ dispose();
152797
+ } catch {
152589
152798
  }
152590
- });
152591
- window.copilotBridge.onTerminalExit((agentId, exitCode) => {
152592
- if (agentId === this.currentAgentId && this.terminal) {
152593
- this.terminal.writeln(`\r
152799
+ }
152800
+ this.bridgeListenerDisposers = [];
152801
+ this.bridgeListenerDisposers.push(
152802
+ window.copilotBridge.onTerminalData((agentId, data) => {
152803
+ if (this.isReplaying) return;
152804
+ if (agentId === this.currentAgentId && this.terminal) {
152805
+ this.terminal.write(data);
152806
+ }
152807
+ })
152808
+ );
152809
+ this.bridgeListenerDisposers.push(
152810
+ window.copilotBridge.onTerminalExit((agentId, exitCode) => {
152811
+ if (agentId === this.currentAgentId && this.terminal) {
152812
+ this.terminal.writeln(`\r
152594
152813
  [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
- });
152814
+ }
152815
+ })
152816
+ );
152817
+ this.bridgeListenerDisposers.push(
152818
+ window.copilotBridge.onSessionMetaUpdated((agentId, meta) => {
152819
+ if (agentId === this.currentAgentId) {
152820
+ this.updateSessionTitleDisplay(meta?.title || null);
152821
+ }
152822
+ })
152823
+ );
152824
+ if (!this.teamsListenerBound) {
152825
+ window.copilotBridge.onTeamsStatusChanged?.((status) => {
152826
+ if (status?.agentId === this.currentAgentId) {
152827
+ this.setTeamsButtonState(!!status.online);
152828
+ }
152829
+ });
152830
+ this.teamsListenerBound = true;
152831
+ }
152607
152832
  }
152608
152833
  }
152609
152834
  /**
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`.
152835
+ * Re-register this overlay's IPC listeners. setupTerminalListeners() is
152836
+ * idempotent (it disposes prior registrations first), so calling this after
152837
+ * returning from another scene cannot create duplicate listeners.
152614
152838
  */
152615
152839
  reattachListeners() {
152616
152840
  this.setupTerminalListeners();
@@ -152626,6 +152850,10 @@ ${h2.join(`
152626
152850
  return this.attachedOfficeId ?? this.getOfficeId();
152627
152851
  }
152628
152852
  clearRefitTimers() {
152853
+ if (this.refitRaf !== null) {
152854
+ cancelAnimationFrame(this.refitRaf);
152855
+ this.refitRaf = null;
152856
+ }
152629
152857
  for (const timer of this.refitTimers) {
152630
152858
  clearTimeout(timer);
152631
152859
  }
@@ -152724,7 +152952,7 @@ ${h2.join(`
152724
152952
  const dims = this.resolveTerminalDimensions();
152725
152953
  if (!dims) return null;
152726
152954
  const agentId = options?.agentId ?? this.currentAgentId;
152727
- if (!agentId || !window.copilotBridge) return dims;
152955
+ if (!agentId || typeof window === "undefined" || !window.copilotBridge) return dims;
152728
152956
  const officeId = options?.officeId ?? this.getActiveOfficeId();
152729
152957
  void window.copilotBridge.terminalResize(officeId, agentId, dims.cols, dims.rows).catch(() => {
152730
152958
  });
@@ -152837,6 +153065,7 @@ ${h2.join(`
152837
153065
  if (previousAgentId && previousAgentId !== agent.id) {
152838
153066
  this.acknowledgeCompletedWork(previousOfficeId, previousAgentId);
152839
153067
  }
153068
+ this.acknowledgeCompletedWork(nextOfficeId, agent.id);
152840
153069
  if (isSwitchingAgent && previousAgentId && window.copilotBridge) {
152841
153070
  this.isSwitchingAgent = true;
152842
153071
  this.onDataDisposable?.dispose();
@@ -152948,7 +153177,7 @@ ${h2.join(`
152948
153177
  } else {
152949
153178
  this.fitAndResizeTerminal({ officeId, agentId: agent.id });
152950
153179
  const attachResult = await withTimeout(
152951
- window.copilotBridge.terminalAttach(officeId, agent.id),
153180
+ window.copilotBridge.terminalAttach(officeId, agent.id, true),
152952
153181
  IPC_TIMEOUT,
152953
153182
  "terminalAttach"
152954
153183
  );
@@ -153385,14 +153614,17 @@ ${h2.join(`
153385
153614
  return;
153386
153615
  }
153387
153616
  this.setTeamsButtonState(false, true);
153388
- const officeChannelUrl = officeManager.getOffice(officeId)?.config.teamsChannelUrl;
153617
+ const office = officeManager.getOffice(officeId)?.config;
153618
+ const officeChannelUrl = office?.teamsChannelUrl;
153389
153619
  const workingDir = this.currentAgent.workingDir || officeManager.getCurrentWorkingDirectory();
153390
153620
  const res = await window.copilotBridge.teamsRegister({
153391
153621
  officeId,
153392
153622
  agentId,
153393
153623
  displayName: this.currentAgent.name,
153394
153624
  workingDir,
153395
- officeChannelUrl
153625
+ officeChannelUrl,
153626
+ officeMentionType: office?.teamsMentionType,
153627
+ officeMentionValue: office?.teamsMentionValue
153396
153628
  });
153397
153629
  if (res?.success) {
153398
153630
  this.setTeamsButtonState(true);
@@ -153923,7 +154155,8 @@ ${h2.join(`
153923
154155
  if (this.getActiveOfficeId() !== officeId) return;
153924
154156
  this.fitAndResizeTerminal({ officeId, agentId });
153925
154157
  };
153926
- requestAnimationFrame(() => {
154158
+ this.refitRaf = requestAnimationFrame(() => {
154159
+ this.refitRaf = null;
153927
154160
  doFit();
153928
154161
  this.refitTimers.push(setTimeout(() => {
153929
154162
  doFit();
@@ -154107,9 +154340,13 @@ ${h2.join(`
154107
154340
  } catch {
154108
154341
  }
154109
154342
  this.spriteCardElement = null;
154110
- if (window.copilotBridge) {
154111
- window.copilotBridge.removeTerminalListeners();
154343
+ for (const dispose of this.bridgeListenerDisposers) {
154344
+ try {
154345
+ dispose();
154346
+ } catch {
154347
+ }
154112
154348
  }
154349
+ this.bridgeListenerDisposers = [];
154113
154350
  }
154114
154351
  };
154115
154352
  }
@@ -155095,6 +155332,7 @@ ${h2.join(`
155095
155332
  "src/layouts/default/DefaultDashboard.ts"() {
155096
155333
  "use strict";
155097
155334
  init_types();
155335
+ init_agentStatusPresentation();
155098
155336
  defaultDashboard = {
155099
155337
  renderCards(ctx) {
155100
155338
  const { agents, office, selectedAgentId, cachedSessionMeta, agentTools, formatElapsed, formatRelativeTime } = ctx;
@@ -155143,46 +155381,12 @@ ${h2.join(`
155143
155381
  for (const agent of agents) {
155144
155382
  const liveStatus = office?.agents.get(agent.id);
155145
155383
  const tools = agentTools.get(agent.id) || [];
155146
- let statusDot = "#555";
155147
- let statusLabel = "Slacking";
155148
- let statusIcon = "\u{1F4A4}";
155149
- if (liveStatus) {
155150
- if (liveStatus.state === "active") {
155151
- switch (liveStatus.subState) {
155152
- case "starting":
155153
- statusDot = "#ff9944";
155154
- statusLabel = "Starting...";
155155
- statusIcon = "\u{1F680}";
155156
- break;
155157
- case "ready":
155158
- if (liveStatus.completionPendingAck) {
155159
- statusDot = "#4a78ff";
155160
- statusLabel = "Done";
155161
- statusIcon = "\u{1F4EC}";
155162
- } else {
155163
- statusDot = "#ffffff";
155164
- statusLabel = "Ready";
155165
- statusIcon = "\u{1F4ED}";
155166
- }
155167
- break;
155168
- case "waiting":
155169
- statusDot = "#ffb86c";
155170
- statusLabel = "Waiting for input";
155171
- statusIcon = "\u23F3";
155172
- break;
155173
- case "thinking":
155174
- statusDot = "#50fa7b";
155175
- statusLabel = liveStatus.thinkingDetail ? `Thinking: ${liveStatus.thinkingDetail}` : "Thinking...";
155176
- statusIcon = "\u26A1";
155177
- break;
155178
- case "error":
155179
- statusDot = "#f44";
155180
- statusLabel = liveStatus.thinkingDetail ? `Error: ${liveStatus.thinkingDetail}` : "Error";
155181
- statusIcon = "\u274C";
155182
- break;
155183
- }
155184
- }
155185
- }
155384
+ const statusPres = STATUS_PRESENTATION[resolveStatusKey(liveStatus)];
155385
+ const statusDot = statusPres.colorHex;
155386
+ const statusLabel = statusPres.label;
155387
+ const statusIcon = statusPres.icon;
155388
+ const activityDetail = describeActivity(liveStatus);
155389
+ const activityDetailEsc = activityDetail.replace(/"/g, "&quot;");
155186
155390
  const colorHex = "#" + agent.color.toString(16).padStart(6, "0");
155187
155391
  const isSelected = agent.id === selectedAgentId;
155188
155392
  const borderColor = isSelected ? "#6677ff" : "#252540";
@@ -155211,13 +155415,13 @@ ${h2.join(`
155211
155415
  ">${toolCount} tools queued</div>` : "";
155212
155416
  let toolPipelineHtml = "";
155213
155417
  if (tools.length > 0) {
155214
- const toolRows = tools.map((t2, i) => {
155418
+ const toolRows = tools.map((tool, i) => {
155215
155419
  const isLast = i === tools.length - 1;
155216
155420
  const icon = isLast ? "\u25B8" : "\u25E6";
155217
155421
  const color = isLast ? "#8af" : "#556";
155218
- const statusText = isLast ? t2.status : "(queued)";
155219
- return `<div style="font-size: ${t2.toolRow}; color: ${color}; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding: 1px 0;">
155220
- ${icon} <span style="color: #9ab;">${t2.name}</span> <span style="color: #556;">\u2014 ${statusText}</span>
155422
+ const statusText = isLast ? tool.status : "(queued)";
155423
+ return `<div style="font-size: ${t.toolRow}; color: ${color}; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding: 1px 0;">
155424
+ ${icon} <span style="color: #9ab;">${tool.name}</span> <span style="color: #556;">\u2014 ${statusText}</span>
155221
155425
  </div>`;
155222
155426
  }).join("");
155223
155427
  toolPipelineHtml = `
@@ -155388,6 +155592,11 @@ ${h2.join(`
155388
155592
  <div style="font-weight: bold; color: #dde; font-size: ${t.cardTitleLg};">${agent.name}</div>
155389
155593
  <div style="color: #778; font-size: ${t.cardDescription}; margin-top: 3px; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;">${agent.description}</div>
155390
155594
  </div>
155595
+ <div data-activity-detail-agent="${agent.id}" style="
155596
+ height: 18px; line-height: 18px;
155597
+ font-size: ${t.taskSummary}; color: #7f88b0;
155598
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
155599
+ " title="${activityDetailEsc}">${activityDetail}</div>
155391
155600
  <div>
155392
155601
  ${taskSummaryHtml}
155393
155602
  </div>
@@ -155446,6 +155655,7 @@ ${h2.join(`
155446
155655
  "use strict";
155447
155656
  init_types();
155448
155657
  init_agents();
155658
+ init_agentStatusPresentation();
155449
155659
  fleetDashboard = {
155450
155660
  renderCards(ctx) {
155451
155661
  const { agents, office, selectedAgentId, formatElapsed, formatRelativeTime } = ctx;
@@ -155453,46 +155663,12 @@ ${h2.join(`
155453
155663
  let html = "";
155454
155664
  for (const agent of agents) {
155455
155665
  const liveStatus = office?.agents.get(agent.id);
155456
- let statusDot = "#555";
155457
- let statusLabel = "Slacking";
155458
- let statusIcon = "\u{1F4A4}";
155459
- if (liveStatus) {
155460
- if (liveStatus.state === "active") {
155461
- switch (liveStatus.subState) {
155462
- case "starting":
155463
- statusDot = "#ff9944";
155464
- statusLabel = "Starting...";
155465
- statusIcon = "\u{1F680}";
155466
- break;
155467
- case "ready":
155468
- if (liveStatus.completionPendingAck) {
155469
- statusDot = "#4a78ff";
155470
- statusLabel = "Done";
155471
- statusIcon = "\u{1F4EC}";
155472
- } else {
155473
- statusDot = "#ffffff";
155474
- statusLabel = "Ready";
155475
- statusIcon = "\u{1F4ED}";
155476
- }
155477
- break;
155478
- case "waiting":
155479
- statusDot = "#ffb86c";
155480
- statusLabel = "Waiting for input";
155481
- statusIcon = "\u23F3";
155482
- break;
155483
- case "thinking":
155484
- statusDot = "#50fa7b";
155485
- statusLabel = liveStatus.thinkingDetail ? `Thinking: ${liveStatus.thinkingDetail}` : "Thinking...";
155486
- statusIcon = "\u26A1";
155487
- break;
155488
- case "error":
155489
- statusDot = "#f44";
155490
- statusLabel = liveStatus.thinkingDetail ? `Error: ${liveStatus.thinkingDetail}` : "Error";
155491
- statusIcon = "\u274C";
155492
- break;
155493
- }
155494
- }
155495
- }
155666
+ const statusPres = STATUS_PRESENTATION[resolveStatusKey(liveStatus)];
155667
+ const statusDot = statusPres.colorHex;
155668
+ const statusLabel = statusPres.label;
155669
+ const statusIcon = statusPres.icon;
155670
+ const activityDetail = describeActivity(liveStatus);
155671
+ const activityDetailEsc = activityDetail.replace(/"/g, "&quot;");
155496
155672
  const colorHex = "#" + agent.color.toString(16).padStart(6, "0");
155497
155673
  const isSelected = agent.id === selectedAgentId;
155498
155674
  const isArthur = agent.id === ARCHITECT_AGENT_ID;
@@ -155567,6 +155743,11 @@ ${h2.join(`
155567
155743
  <div style="font-weight: bold; color: #dde; font-size: ${t.cardTitle};">${agent.name}</div>
155568
155744
  <div style="color: #778; font-size: ${t.cardDescription}; margin-top: 2px;">${agent.description}</div>
155569
155745
  </div>
155746
+ <div data-activity-detail-agent="${agent.id}" style="
155747
+ height: 16px; line-height: 16px;
155748
+ font-size: ${t.taskSummary}; color: #7f88b0;
155749
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
155750
+ " title="${activityDetailEsc}">${activityDetail}</div>
155570
155751
  <div>
155571
155752
  <div style="display: flex; align-items: center; flex-wrap: wrap; margin-top: 3px;">
155572
155753
  <div style="
@@ -157031,6 +157212,10 @@ ${h2.join(`
157031
157212
  this.game.events.on("agent:status:changed", () => {
157032
157213
  this.updateSessionBadges();
157033
157214
  }, this);
157215
+ this.game.events.on("agent:stall", (agentId, stalled) => {
157216
+ const npc = this.npcs.find((n) => n.config.id === agentId);
157217
+ npc?.setStalled(stalled);
157218
+ }, this);
157034
157219
  this.updateSessionBadges();
157035
157220
  this.game.events.on("office:switch", (officeId, _workingDir) => {
157036
157221
  if (this.animating) {
@@ -158503,7 +158688,7 @@ ${h2.join(`
158503
158688
  let nearestNPCDist = interactionDistance;
158504
158689
  let nearestDesk = null;
158505
158690
  let nearestDeskDist = interactionDistance;
158506
- this.npcs.forEach((npc) => {
158691
+ for (const npc of this.npcs) {
158507
158692
  const dist = import_phaser8.default.Math.Distance.Between(
158508
158693
  this.player.x,
158509
158694
  this.player.y,
@@ -158515,7 +158700,7 @@ ${h2.join(`
158515
158700
  nearestNPC = npc;
158516
158701
  }
158517
158702
  npc.setNearPlayer(false);
158518
- });
158703
+ }
158519
158704
  this.desks.forEach((desk) => {
158520
158705
  const dist = import_phaser8.default.Math.Distance.Between(
158521
158706
  this.player.x,
@@ -159385,12 +159570,13 @@ ${h2.join(`
159385
159570
  while (this.toasts.length >= MAX_VISIBLE) {
159386
159571
  this.dismiss(this.toasts[0]);
159387
159572
  }
159573
+ const accentColor = options.statusColorHex ?? options.agentColor;
159388
159574
  const el2 = document.createElement("div");
159389
159575
  el2.style.cssText = `
159390
159576
  pointer-events: auto;
159391
159577
  background: #1e1e3a;
159392
- border: 1.5px solid ${options.agentColor}66;
159393
- border-left: 4px solid ${options.agentColor};
159578
+ border: 1.5px solid ${accentColor}66;
159579
+ border-left: 4px solid ${accentColor};
159394
159580
  border-radius: 8px;
159395
159581
  padding: 12px 16px;
159396
159582
  display: flex;
@@ -159403,13 +159589,14 @@ ${h2.join(`
159403
159589
  opacity: 0;
159404
159590
  transition: transform ${ANIMATION_MS}ms ease, opacity ${ANIMATION_MS}ms ease;
159405
159591
  `;
159592
+ const markerHtml = options.statusIcon ? `<div style="font-size: 16px; line-height: 1; flex-shrink: 0;">${options.statusIcon}</div>` : `<div style="
159593
+ width: 8px; height: 8px;
159594
+ border-radius: 50%;
159595
+ background: ${accentColor};
159596
+ flex-shrink: 0;
159597
+ "></div>`;
159406
159598
  el2.innerHTML = `
159407
- <div style="
159408
- width: 8px; height: 8px;
159409
- border-radius: 50%;
159410
- background: ${options.agentColor};
159411
- flex-shrink: 0;
159412
- "></div>
159599
+ ${markerHtml}
159413
159600
  <div style="flex: 1; min-width: 0;">
159414
159601
  <div style="font-size: 12px; font-weight: bold; color: #dde;">${options.agentName}</div>
159415
159602
  <div style="font-size: 11px; color: #889; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${options.message}</div>
@@ -159555,11 +159742,21 @@ ${h2.join(`
159555
159742
  });
159556
159743
 
159557
159744
  // src/ui/NotificationService.ts
159558
- var NotificationService;
159745
+ var EVENT_STATUS_KEY, NotificationService;
159559
159746
  var init_NotificationService = __esm({
159560
159747
  "src/ui/NotificationService.ts"() {
159561
159748
  "use strict";
159562
159749
  init_notifications();
159750
+ init_agentStatusPresentation();
159751
+ EVENT_STATUS_KEY = {
159752
+ turnEnd: "done",
159753
+ askUser: "waiting",
159754
+ turnStart: "thinking",
159755
+ toolStart: "thinking",
159756
+ toolComplete: null,
159757
+ sessionReady: "ready",
159758
+ sessionError: "error"
159759
+ };
159563
159760
  NotificationService = class {
159564
159761
  constructor(toastManager, resolveAgent, onClickAgent) {
159565
159762
  this.dedupeMap = /* @__PURE__ */ new Map();
@@ -159603,18 +159800,23 @@ ${h2.join(`
159603
159800
  if (!agent) return;
159604
159801
  const message = this.formatMessage(eventConfig.message, agent.name, context);
159605
159802
  const colorHex = "#" + agent.color.toString(16).padStart(6, "0");
159803
+ const statusKey = EVENT_STATUS_KEY[eventType];
159804
+ const statusPres = statusKey ? STATUS_PRESENTATION[statusKey] : void 0;
159606
159805
  if (eventConfig.toast) {
159607
159806
  this.toastManager.show({
159608
159807
  agentId,
159609
159808
  agentName: agent.name,
159610
159809
  agentColor: colorHex,
159611
159810
  message,
159612
- onClick: () => this.onClickAgent?.(agentId)
159811
+ onClick: () => this.onClickAgent?.(agentId),
159812
+ statusIcon: statusPres?.icon,
159813
+ statusColorHex: statusPres?.colorHex
159613
159814
  });
159614
159815
  }
159615
159816
  if (eventConfig.osNotification) {
159817
+ const titlePrefix = statusPres?.icon ?? "\u{1F3E2}";
159616
159818
  window.copilotBridge?.showNativeNotification(
159617
- `\u{1F3E2} ${agent.name}`,
159819
+ `${titlePrefix} ${agent.name}`,
159618
159820
  message
159619
159821
  );
159620
159822
  }
@@ -160844,7 +161046,7 @@ Failed to start terminal: ${startResult.error || "unknown error"}`);
160844
161046
  this.updateSessionIdDisplay();
160845
161047
  }
160846
161048
  }
160847
- const attachResult = await window.copilotBridge.terminalAttach(options.officeId, options.agentId);
161049
+ const attachResult = await window.copilotBridge.terminalAttach(options.officeId, options.agentId, true);
160848
161050
  if (!attachResult.success) {
160849
161051
  this.terminal.writeln("\r\nFailed to attach terminal session.");
160850
161052
  this.setStatus("Attach failed");
@@ -161061,14 +161263,17 @@ Terminal error: ${error?.message || String(error)}`);
161061
161263
  return;
161062
161264
  }
161063
161265
  this.setTeamsButtonState(false, true);
161064
- const officeChannelUrl = officeManager.getOffice(officeId)?.config.teamsChannelUrl;
161266
+ const office = officeManager.getOffice(officeId)?.config;
161267
+ const officeChannelUrl = office?.teamsChannelUrl;
161065
161268
  const workingDir = this.activeOptions.workingDir || officeManager.getCurrentWorkingDirectory();
161066
161269
  const res = await window.copilotBridge.teamsRegister({
161067
161270
  officeId,
161068
161271
  agentId,
161069
161272
  displayName: this.activeOptions.name,
161070
161273
  workingDir,
161071
- officeChannelUrl
161274
+ officeChannelUrl,
161275
+ officeMentionType: office?.teamsMentionType,
161276
+ officeMentionValue: office?.teamsMentionValue
161072
161277
  });
161073
161278
  if (res?.success) {
161074
161279
  this.setTeamsButtonState(true);
@@ -161539,6 +161744,17 @@ Terminal error: ${error?.message || String(error)}`);
161539
161744
  const last = remainingTools[remainingTools.length - 1];
161540
161745
  return { kind: "thinking", detail: String(last.name ?? "") };
161541
161746
  }
161747
+ function addActiveTool(tools, entry) {
161748
+ if (tools.some((t) => t.toolId === entry.toolId)) {
161749
+ return { tools: tools.slice(), added: false };
161750
+ }
161751
+ return { tools: [...tools, entry], added: true };
161752
+ }
161753
+ function removeCompletedTool(tools, toolId) {
161754
+ const completed = tools.find((t) => t.toolId === toolId) ?? null;
161755
+ if (!completed) return { tools: tools.slice(), completed: null };
161756
+ return { tools: tools.filter((t) => t.toolId !== toolId), completed };
161757
+ }
161542
161758
  var init_toolStatus = __esm({
161543
161759
  "src/util/toolStatus.ts"() {
161544
161760
  "use strict";
@@ -161584,6 +161800,7 @@ Terminal error: ${error?.message || String(error)}`);
161584
161800
  init_SeriousTerminalController();
161585
161801
  init_SpriteGenerator();
161586
161802
  init_toolStatus();
161803
+ init_agentStatusPresentation();
161587
161804
  init_startupTimeoutGuard();
161588
161805
  init_AutoStartCoordinator();
161589
161806
  init_agentAutoStart();
@@ -161597,6 +161814,22 @@ Terminal error: ${error?.message || String(error)}`);
161597
161814
  function getCurrentAgents() {
161598
161815
  return getLayout(getCurrentLayout()).agents;
161599
161816
  }
161817
+ function sortAgentsByMode(agents, office) {
161818
+ if (agentSortMode !== "recent" || !office) return agents;
161819
+ const persisted = getOfficeAgentActivityTimes(office.config.id);
161820
+ const recency = (id) => {
161821
+ let t = persisted[id] ?? 0;
161822
+ const status = office.agents.get(id);
161823
+ if (status) {
161824
+ if ((status.activityStartTime ?? 0) > t) t = status.activityStartTime;
161825
+ for (const action of status.recentActions) {
161826
+ if (action.timestamp > t) t = action.timestamp;
161827
+ }
161828
+ }
161829
+ return t;
161830
+ };
161831
+ return [...agents].sort((a, b2) => recency(b2.id) - recency(a.id));
161832
+ }
161600
161833
  function getCurrentAgentTools() {
161601
161834
  return officeManager.currentOffice?.agentTools || /* @__PURE__ */ new Map();
161602
161835
  }
@@ -161621,9 +161854,32 @@ Terminal error: ${error?.message || String(error)}`);
161621
161854
  var SESSION_META_CACHE_STORAGE_KEY = "agencyOffice:sessionMetaCacheByOffice";
161622
161855
  var OVERVIEW_SPRITE_CACHE_STORAGE_KEY = "agencyOffice:overviewSpriteCache";
161623
161856
  var PC_TERMINAL_ID2 = "pc-terminal";
161624
- var OFFICE_SORT_STORAGE_KEY = "agencyOffice:officeSortMode";
161857
+ var AGENT_SORT_STORAGE_KEY = "agencyOffice:agentSortMode";
161625
161858
  var OFFICE_ACCESS_TIMES_KEY = "agencyOffice:officeAccessTimes";
161626
- var officeSortMode = localStorage.getItem(OFFICE_SORT_STORAGE_KEY) || "default";
161859
+ var AGENT_ACTIVITY_TIMES_KEY = "agencyOffice:agentActivityTimes";
161860
+ var agentSortMode = localStorage.getItem(AGENT_SORT_STORAGE_KEY) || "default";
161861
+ function getAgentActivityTimes() {
161862
+ try {
161863
+ const raw = localStorage.getItem(AGENT_ACTIVITY_TIMES_KEY);
161864
+ return raw ? JSON.parse(raw) : {};
161865
+ } catch {
161866
+ return {};
161867
+ }
161868
+ }
161869
+ function getOfficeAgentActivityTimes(officeId) {
161870
+ return getAgentActivityTimes()[officeId] ?? {};
161871
+ }
161872
+ function recordAgentActivity(officeId, agentId) {
161873
+ if (!officeId) return;
161874
+ const all = getAgentActivityTimes();
161875
+ const office = all[officeId] ?? {};
161876
+ office[agentId] = Date.now();
161877
+ all[officeId] = office;
161878
+ try {
161879
+ localStorage.setItem(AGENT_ACTIVITY_TIMES_KEY, JSON.stringify(all));
161880
+ } catch {
161881
+ }
161882
+ }
161627
161883
  function getOfficeAccessTimes() {
161628
161884
  try {
161629
161885
  const raw = localStorage.getItem(OFFICE_ACCESS_TIMES_KEY);
@@ -161633,6 +161889,7 @@ Terminal error: ${error?.message || String(error)}`);
161633
161889
  }
161634
161890
  }
161635
161891
  function recordOfficeAccess(officeId) {
161892
+ if (!officeId) return;
161636
161893
  const times = getOfficeAccessTimes();
161637
161894
  times[officeId] = Date.now();
161638
161895
  try {
@@ -161929,8 +162186,8 @@ Terminal error: ${error?.message || String(error)}`);
161929
162186
  return getCurrentAgents().map((a) => ({
161930
162187
  id: a.id,
161931
162188
  name: a.name,
161932
- tileX: a.tileX,
161933
- tileY: a.tileY
162189
+ tileX: a.position.x,
162190
+ tileY: a.position.y
161934
162191
  }));
161935
162192
  },
161936
162193
  getActiveTerminalAgentId: () => {
@@ -161988,12 +162245,8 @@ Terminal error: ${error?.message || String(error)}`);
161988
162245
  console.log("[main] Spec 008-smoke: __copilotOfficeDebug installed");
161989
162246
  }
161990
162247
  function renderOfficeTabs() {
161991
- let offices = officeManager.getAllOffices();
162248
+ const offices = officeManager.getAllOffices();
161992
162249
  const currentId = officeManager.currentOfficeId;
161993
- if (officeSortMode === "recent") {
161994
- const accessTimes = getOfficeAccessTimes();
161995
- offices = [...offices].sort((a, b2) => (accessTimes[b2.id] || 0) - (accessTimes[a.id] || 0));
161996
- }
161997
162250
  let html = "";
161998
162251
  for (const office of offices) {
161999
162252
  const isActive = office.id === currentId;
@@ -162358,6 +162611,21 @@ Terminal error: ${error?.message || String(error)}`);
162358
162611
  width: 100%; padding: 6px 8px; margin-bottom: 14px; background: #12121f; border: 1px solid #333;
162359
162612
  border-radius: 5px; color: #899; font-family: inherit; font-size: 11px; box-sizing: border-box;
162360
162613
  " />
162614
+ <label style="display: block; margin-bottom: 3px; color: #889; font-size: 11px;">Teams Mention Override <span style="color:#667;">(optional)</span></label>
162615
+ <div style="display: flex; gap: 6px; margin-bottom: 14px;">
162616
+ <select class="osp-teams-mention-type" style="
162617
+ padding: 6px 8px; background: #12121f; border: 1px solid #333; border-radius: 5px;
162618
+ color: #899; font-family: inherit; font-size: 11px; box-sizing: border-box;
162619
+ ">
162620
+ <option value="none"${(office.config.teamsMentionType ?? "none") === "none" ? " selected" : ""}>Default</option>
162621
+ <option value="tag"${office.config.teamsMentionType === "tag" ? " selected" : ""}>Tag</option>
162622
+ <option value="user"${office.config.teamsMentionType === "user" ? " selected" : ""}>User</option>
162623
+ </select>
162624
+ <input class="osp-teams-mention-value" type="text" value="${escapeHtml(office.config.teamsMentionValue ?? "")}" placeholder="Tag name / user (empty = use default)" style="
162625
+ flex: 1; padding: 6px 8px; background: #12121f; border: 1px solid #333; border-radius: 5px;
162626
+ color: #899; font-family: inherit; font-size: 11px; box-sizing: border-box;
162627
+ " />
162628
+ </div>
162361
162629
  <div style="display: flex; gap: 8px; justify-content: flex-end;">
162362
162630
  ${canDelete ? `<button class="osp-delete" style="
162363
162631
  padding: 5px 12px; background: #2a1a1a; border: 1px solid #633; border-radius: 5px;
@@ -162377,6 +162645,8 @@ Terminal error: ${error?.message || String(error)}`);
162377
162645
  const nameInput = popover.querySelector(".osp-name");
162378
162646
  const pathInput = popover.querySelector(".osp-path");
162379
162647
  const teamsInput = popover.querySelector(".osp-teams");
162648
+ const teamsMentionTypeInput = popover.querySelector(".osp-teams-mention-type");
162649
+ const teamsMentionValueInput = popover.querySelector(".osp-teams-mention-value");
162380
162650
  popover.querySelector(".osp-close")?.addEventListener("click", closeOfficePopover);
162381
162651
  popover.querySelector(".osp-save")?.addEventListener("click", () => {
162382
162652
  const newName = nameInput.value.trim();
@@ -162384,6 +162654,10 @@ Terminal error: ${error?.message || String(error)}`);
162384
162654
  if (newName) officeManager.updateOffice(officeId, { name: newName });
162385
162655
  if (newPath) officeManager.updateOffice(officeId, { workingDirectory: newPath });
162386
162656
  officeManager.updateOffice(officeId, { teamsChannelUrl: teamsInput.value.trim() });
162657
+ officeManager.updateOffice(officeId, {
162658
+ teamsMentionType: teamsMentionTypeInput.value,
162659
+ teamsMentionValue: teamsMentionValueInput.value.trim()
162660
+ });
162387
162661
  renderOfficeTabs();
162388
162662
  updateTerminalContent();
162389
162663
  closeOfficePopover();
@@ -162431,18 +162705,18 @@ Terminal error: ${error?.message || String(error)}`);
162431
162705
  <div id="terminal-subtitle" style="font-size: 12px; color: #555;"></div>
162432
162706
  </div>
162433
162707
  <div style="display: flex; align-items: center; gap: 8px;">
162434
- <button id="office-sort-btn" style="
162708
+ <button id="agent-sort-btn" style="
162435
162709
  padding: 6px 12px;
162436
- background: ${officeSortMode === "recent" ? "#2a3a5a" : "#252538"};
162437
- color: ${officeSortMode === "recent" ? "#8af" : "#666"};
162438
- border: 1px solid ${officeSortMode === "recent" ? "#4488ff" : "#444"};
162710
+ background: ${agentSortMode === "recent" ? "#2a3a5a" : "#252538"};
162711
+ color: ${agentSortMode === "recent" ? "#8af" : "#666"};
162712
+ border: 1px solid ${agentSortMode === "recent" ? "#4488ff" : "#444"};
162439
162713
  border-radius: 4px;
162440
162714
  font-family: 'Cascadia Code', Consolas, monospace;
162441
162715
  font-size: 12px;
162442
162716
  cursor: pointer;
162443
162717
  white-space: nowrap;
162444
162718
  transition: all 0.2s;
162445
- " title="Sort office tabs">\u21C5 ${officeSortMode === "recent" ? "Recent" : "Default"}</button>
162719
+ " title="Sort agents in this office">\u21C5 ${agentSortMode === "recent" ? "Recent" : "Default"}</button>
162446
162720
  <button id="close-office-btn" style="
162447
162721
  display: none;
162448
162722
  padding: 6px 14px;
@@ -162458,19 +162732,19 @@ Terminal error: ${error?.message || String(error)}`);
162458
162732
  </div>
162459
162733
  `;
162460
162734
  overviewHost.appendChild(overviewHeader);
162461
- document.getElementById("office-sort-btn").addEventListener("click", () => {
162462
- officeSortMode = officeSortMode === "default" ? "recent" : "default";
162735
+ document.getElementById("agent-sort-btn").addEventListener("click", () => {
162736
+ agentSortMode = agentSortMode === "default" ? "recent" : "default";
162463
162737
  try {
162464
- localStorage.setItem(OFFICE_SORT_STORAGE_KEY, officeSortMode);
162738
+ localStorage.setItem(AGENT_SORT_STORAGE_KEY, agentSortMode);
162465
162739
  } catch {
162466
162740
  }
162467
- renderOfficeTabs();
162468
- const sortBtn = document.getElementById("office-sort-btn");
162741
+ updateTerminalContent();
162742
+ const sortBtn = document.getElementById("agent-sort-btn");
162469
162743
  if (sortBtn) {
162470
- sortBtn.textContent = `\u21C5 ${officeSortMode === "recent" ? "Recent" : "Default"}`;
162471
- sortBtn.style.background = officeSortMode === "recent" ? "#2a3a5a" : "#252538";
162472
- sortBtn.style.color = officeSortMode === "recent" ? "#8af" : "#666";
162473
- sortBtn.style.borderColor = officeSortMode === "recent" ? "#4488ff" : "#444";
162744
+ sortBtn.textContent = `\u21C5 ${agentSortMode === "recent" ? "Recent" : "Default"}`;
162745
+ sortBtn.style.background = agentSortMode === "recent" ? "#2a3a5a" : "#252538";
162746
+ sortBtn.style.color = agentSortMode === "recent" ? "#8af" : "#666";
162747
+ sortBtn.style.borderColor = agentSortMode === "recent" ? "#4488ff" : "#444";
162474
162748
  }
162475
162749
  });
162476
162750
  document.getElementById("close-office-btn").addEventListener("click", () => {
@@ -162522,11 +162796,7 @@ Terminal error: ${error?.message || String(error)}`);
162522
162796
  var toastManager = new ToastNotificationManager(document.body);
162523
162797
  function formatElapsed(startTime) {
162524
162798
  if (!startTime) return "";
162525
- const seconds = Math.floor((Date.now() - startTime) / 1e3);
162526
- if (seconds < 60) return `${seconds}s`;
162527
- const mins = Math.floor(seconds / 60);
162528
- const secs = seconds % 60;
162529
- return `${mins}m ${secs}s`;
162799
+ return formatElapsedMmSs(startTime);
162530
162800
  }
162531
162801
  function formatRelativeTime(timestamp) {
162532
162802
  const seconds = Math.floor((Date.now() - timestamp) / 1e3);
@@ -162589,8 +162859,18 @@ Terminal error: ${error?.message || String(error)}`);
162589
162859
  terminalHost.style.display = "none";
162590
162860
  seriousPlaceholder.style.display = "none";
162591
162861
  }
162862
+ function clearCompletionAck(agentId) {
162863
+ const officeId = officeManager.currentOfficeId;
162864
+ if (!officeId) return;
162865
+ if (officeManager.acknowledgeAgentCompletion(officeId, agentId)) {
162866
+ phaserGameRef?.events.emit("agent:status:changed", agentId);
162867
+ updateStatusBar();
162868
+ updateTerminalContent();
162869
+ }
162870
+ }
162592
162871
  async function openAgentTerminal(agentId) {
162593
162872
  selectedAgentId = agentId;
162873
+ clearCompletionAck(agentId);
162594
162874
  if (appMode === "game") {
162595
162875
  phaserGameRef?.events.emit("open:agent:terminal", agentId);
162596
162876
  return;
@@ -162627,12 +162907,13 @@ Terminal error: ${error?.message || String(error)}`);
162627
162907
  }
162628
162908
  });
162629
162909
  var autoStartTerminalStartCount = 0;
162630
- async function warmAgentSession(officeId, agentId) {
162631
- if (!window.copilotBridge) return;
162632
- const launchConfig = getSeriousLaunchConfig(agentId);
162633
- if (!launchConfig) return;
162634
- const workingDir = launchConfig.workingDir;
162635
- const launchMode = launchConfig.launchMode;
162910
+ async function warmAgentSession(officeId, agentId, fallback) {
162911
+ if (!window.copilotBridge) return false;
162912
+ const isCurrentOffice = officeId === officeManager.currentOfficeId;
162913
+ const launchConfig = isCurrentOffice ? getSeriousLaunchConfig(agentId) : null;
162914
+ const workingDir = launchConfig?.workingDir ?? fallback?.workingDir;
162915
+ const launchMode = launchConfig?.launchMode ?? fallback?.launchMode ?? "copilot";
162916
+ if (!workingDir) return false;
162636
162917
  const officeStatus = officeManager.getAgentStatus(officeId, agentId);
162637
162918
  if (officeStatus?.state === "slacking") {
162638
162919
  officeManager.setAgentStarting(officeId, agentId);
@@ -162641,7 +162922,7 @@ Terminal error: ${error?.message || String(error)}`);
162641
162922
  updateTerminalContent();
162642
162923
  }
162643
162924
  autoStartTerminalStartCount += 1;
162644
- await window.copilotBridge.terminalStart(
162925
+ const res = await window.copilotBridge.terminalStart(
162645
162926
  officeId,
162646
162927
  agentId,
162647
162928
  workingDir,
@@ -162650,6 +162931,74 @@ Terminal error: ${error?.message || String(error)}`);
162650
162931
  void 0,
162651
162932
  launchMode
162652
162933
  );
162934
+ return res?.success !== false;
162935
+ }
162936
+ var teamsColdWarmDone = false;
162937
+ var teamsColdWarmInFlight = false;
162938
+ var teamsColdWarmAttempts = 0;
162939
+ var TEAMS_COLD_WARM_MAX_ATTEMPTS = 5;
162940
+ var TEAMS_COLD_WARM_RETRY_MS = 1500;
162941
+ function scheduleTeamsColdWarmRetry() {
162942
+ if (teamsColdWarmDone) return;
162943
+ if (teamsColdWarmAttempts >= TEAMS_COLD_WARM_MAX_ATTEMPTS) return;
162944
+ setTimeout(() => {
162945
+ void warmAllTeamsBoundAgents();
162946
+ }, TEAMS_COLD_WARM_RETRY_MS);
162947
+ }
162948
+ async function warmAllTeamsBoundAgents() {
162949
+ if (teamsColdWarmDone || teamsColdWarmInFlight) return;
162950
+ if (!window.copilotBridge?.teamsStatus) return;
162951
+ teamsColdWarmInFlight = true;
162952
+ teamsColdWarmAttempts += 1;
162953
+ try {
162954
+ const teamsRes = await window.copilotBridge.teamsStatus();
162955
+ if (!teamsRes?.success || !Array.isArray(teamsRes.bindings)) {
162956
+ scheduleTeamsColdWarmRetry();
162957
+ return;
162958
+ }
162959
+ const bindings = teamsRes.bindings;
162960
+ if (bindings.length === 0) {
162961
+ scheduleTeamsColdWarmRetry();
162962
+ return;
162963
+ }
162964
+ const results = await Promise.all(
162965
+ bindings.map(async (b2) => {
162966
+ try {
162967
+ return await warmAgentSession(
162968
+ b2.officeId,
162969
+ b2.agentId,
162970
+ b2.workingDir ? {
162971
+ workingDir: b2.workingDir,
162972
+ // A raw shell can't be Teams-bound (nothing to resume), but
162973
+ // guard defensively so PC_TERMINAL never resumes as copilot.
162974
+ launchMode: b2.agentId === PC_TERMINAL_ID2 ? "shell" : "copilot"
162975
+ } : void 0
162976
+ );
162977
+ } catch (err) {
162978
+ console.warn(
162979
+ `[Teams] cold-launch warm failed for ${b2.officeId}/${b2.agentId}:`,
162980
+ err
162981
+ );
162982
+ return false;
162983
+ }
162984
+ })
162985
+ );
162986
+ if (!results.some(Boolean)) {
162987
+ scheduleTeamsColdWarmRetry();
162988
+ return;
162989
+ }
162990
+ teamsColdWarmDone = true;
162991
+ try {
162992
+ await window.copilotBridge.teamsReconcile?.();
162993
+ } catch {
162994
+ }
162995
+ void refreshTeamsDashboardState();
162996
+ } catch (e) {
162997
+ console.warn("[Teams] warmAllTeamsBoundAgents failed:", e);
162998
+ scheduleTeamsColdWarmRetry();
162999
+ } finally {
163000
+ teamsColdWarmInFlight = false;
163001
+ }
162653
163002
  }
162654
163003
  function buildCanonicalAgentIdsForOffice(officeId) {
162655
163004
  const office = officeManager.currentOfficeId === officeId ? officeManager.currentOffice : null;
@@ -162696,7 +163045,9 @@ Terminal error: ${error?.message || String(error)}`);
162696
163045
  if (!window.copilotBridge) return;
162697
163046
  await window.copilotBridge.resetSession(oid, aid);
162698
163047
  },
162699
- warmAgentSession: (oid, aid) => warmAgentSession(oid, aid),
163048
+ warmAgentSession: async (oid, aid) => {
163049
+ await warmAgentSession(oid, aid);
163050
+ },
162700
163051
  getSettings: () => getAgentAutoStartSettings()
162701
163052
  });
162702
163053
  setAutoStartCoordinator(autoStartCoordinator);
@@ -162828,13 +163179,16 @@ Terminal error: ${error?.message || String(error)}`);
162828
163179
  }
162829
163180
  const agent = getSeriousLaunchConfig(agentId);
162830
163181
  if (!agent) return;
162831
- const officeChannelUrl = officeManager.getOffice(officeId)?.config.teamsChannelUrl;
163182
+ const office = officeManager.getOffice(officeId)?.config;
163183
+ const officeChannelUrl = office?.teamsChannelUrl;
162832
163184
  const res = await window.copilotBridge.teamsRegister({
162833
163185
  officeId,
162834
163186
  agentId,
162835
163187
  displayName: agent.name,
162836
163188
  workingDir: agent.workingDir || officeManager.getCurrentWorkingDirectory(),
162837
- officeChannelUrl
163189
+ officeChannelUrl,
163190
+ officeMentionType: office?.teamsMentionType,
163191
+ officeMentionValue: office?.teamsMentionValue
162838
163192
  });
162839
163193
  if (res?.success) {
162840
163194
  teamsOnlineAgentIds.add(agentId);
@@ -162879,7 +163233,7 @@ Terminal error: ${error?.message || String(error)}`);
162879
163233
  }
162880
163234
  const layout = getLayout(getCurrentLayout());
162881
163235
  const html = layout.dashboard.renderCards({
162882
- agents: layout.agents,
163236
+ agents: sortAgentsByMode(layout.agents, office || null),
162883
163237
  office: office || null,
162884
163238
  selectedAgentId,
162885
163239
  cachedSessionMeta,
@@ -163200,8 +163554,14 @@ Terminal error: ${error?.message || String(error)}`);
163200
163554
  if (!agentTools.has(agentId)) {
163201
163555
  agentTools.set(agentId, []);
163202
163556
  }
163203
- agentTools.get(agentId).push({ toolId, name: toolName, status });
163557
+ const startResult = addActiveTool(agentTools.get(agentId), { toolId, name: toolName, status });
163558
+ if (!startResult.added) {
163559
+ console.log(`[Office] Ignoring duplicate tool_start for ${agentId} \u2014 toolId ${toolId} already active`);
163560
+ return;
163561
+ }
163562
+ agentTools.set(agentId, startResult.tools);
163204
163563
  officeManager.pushRecentAction(officeId, agentId, toolName, "started");
163564
+ recordAgentActivity(officeId, agentId);
163205
163565
  if (status) {
163206
163566
  officeManager.setTaskSummary(officeId, agentId, status);
163207
163567
  }
@@ -163225,9 +163585,13 @@ Terminal error: ${error?.message || String(error)}`);
163225
163585
  const agentTools = getCurrentAgentTools();
163226
163586
  const tools = agentTools.get(agentId);
163227
163587
  if (tools) {
163228
- const completedTool = tools.find((t) => t.toolId === toolId);
163229
- const completedToolName = completedTool?.name || "tool";
163230
- const remaining = tools.filter((t) => t.toolId !== toolId);
163588
+ const completeResult = removeCompletedTool(tools, toolId);
163589
+ if (!completeResult.completed) {
163590
+ console.log(`[Office] Ignoring tool_complete for ${agentId} \u2014 unknown toolId ${toolId}`);
163591
+ return;
163592
+ }
163593
+ const completedToolName = completeResult.completed.name || "tool";
163594
+ const remaining = completeResult.tools;
163231
163595
  agentTools.set(agentId, remaining);
163232
163596
  officeManager.setLastCompletedAction(officeId, agentId, completedToolName);
163233
163597
  officeManager.pushRecentAction(officeId, agentId, completedToolName, "completed");
@@ -163254,6 +163618,11 @@ Terminal error: ${error?.message || String(error)}`);
163254
163618
  console.log(`[Office] Turn end: ${agentId}`);
163255
163619
  const officeId = officeManager.currentOfficeId;
163256
163620
  if (officeId) {
163621
+ const current = officeManager.getAgentStatus(officeId, agentId);
163622
+ if (current?.subState === "starting") {
163623
+ console.log(`[Office] Ignoring turn_end for ${agentId} \u2014 still in starting state`);
163624
+ return;
163625
+ }
163257
163626
  const agentTools = getCurrentAgentTools();
163258
163627
  const activeTools = agentTools.get(agentId) || [];
163259
163628
  const waitingToolActive = activeTools.some((t) => isAskUserTool(t.name, t.status));
@@ -163282,6 +163651,7 @@ Terminal error: ${error?.message || String(error)}`);
163282
163651
  return;
163283
163652
  }
163284
163653
  officeManager.setTaskSummary(officeId, agentId, "Processing...");
163654
+ recordAgentActivity(officeId, agentId);
163285
163655
  officeManager.setAgentThinking(officeId, agentId, "Processing...");
163286
163656
  console.log(`[Office] Status: ${agentId} \u2192 thinking (turn start)`);
163287
163657
  notifyAgent(agentId, "turnStart");
@@ -163313,7 +163683,29 @@ Terminal error: ${error?.message || String(error)}`);
163313
163683
  window.copilotBridge.onTeamsToast?.((toast) => {
163314
163684
  if (!toast?.message) return;
163315
163685
  const kind = toast.level === "error" ? "error" : toast.level === "warn" ? "error" : "info";
163316
- showClipboardToast(toast.message, kind);
163686
+ if (typeof toast.durationMs === "number" && toast.durationMs > 0) {
163687
+ showClipboardToast(toast.message, kind, toast.durationMs);
163688
+ } else {
163689
+ showClipboardToast(toast.message, kind);
163690
+ }
163691
+ });
163692
+ let backendFallbackToastShown = false;
163693
+ const showBackendFallbackToast = (info) => {
163694
+ if (!info || !info.fellBack || backendFallbackToastShown) return;
163695
+ backendFallbackToastShown = true;
163696
+ const detail = info.reason ? ` (${info.reason})` : "";
163697
+ showClipboardToast(`Terminal: ${info.requested} unavailable \u2014 using ${info.name}${detail}`, "error", 1e4);
163698
+ };
163699
+ window.copilotBridge.onBackendFallback?.((info) => showBackendFallbackToast(info));
163700
+ void window.copilotBridge.getBackendInfo?.().then((info) => showBackendFallbackToast(info));
163701
+ window.copilotBridge.onBackendOnline?.((officeId, _backend) => {
163702
+ const officeName = officeManager.getOffice(officeId)?.config.name ?? officeId;
163703
+ showClipboardToast(`GitHub Copilot SDK server online for ${officeName}`, "success", 1e4);
163704
+ });
163705
+ window.copilotBridge.onBackendSessionFallback?.((_officeId, agentId, reason) => {
163706
+ const agentName = getAgentConfig(agentId)?.name ?? agentId;
163707
+ const detail = reason ? ` (${reason})` : "";
163708
+ showClipboardToast(`${agentName}: UI-server unavailable \u2014 using node-pty${detail}`, "error", 1e4);
163317
163709
  });
163318
163710
  window.copilotBridge.onTeamsStatusChanged?.((status) => {
163319
163711
  if (!status?.agentId) return;
@@ -163534,9 +163926,11 @@ Terminal error: ${error?.message || String(error)}`);
163534
163926
  await syncAgentStatuses(true);
163535
163927
  }
163536
163928
  var ELAPSED_TICK_MS = 1e3;
163929
+ var lastStalledState = /* @__PURE__ */ new Map();
163537
163930
  setInterval(() => {
163538
163931
  const office = officeManager.currentOffice;
163539
163932
  if (!office) return;
163933
+ const now = Date.now();
163540
163934
  for (const agent of getCurrentAgents()) {
163541
163935
  const status = office.agents.get(agent.id);
163542
163936
  if (status?.activityStartTime) {
@@ -163547,6 +163941,16 @@ Terminal error: ${error?.message || String(error)}`);
163547
163941
  }
163548
163942
  }
163549
163943
  }
163944
+ const stalled = computeStall(status, now).isStalled;
163945
+ const card = document.querySelector(`.agent-card[data-agent="${agent.id}"]`);
163946
+ if (card) {
163947
+ card.classList.toggle("agent-stalled", stalled);
163948
+ card.dataset.stalled = stalled ? "true" : "false";
163949
+ }
163950
+ if ((lastStalledState.get(agent.id) ?? false) !== stalled) {
163951
+ lastStalledState.set(agent.id, stalled);
163952
+ phaserGameRef?.events.emit("agent:stall", agent.id, stalled);
163953
+ }
163550
163954
  const actionEls = document.querySelectorAll(`.agent-card[data-agent="${agent.id}"] [data-action-ts]`);
163551
163955
  actionEls.forEach((el2) => {
163552
163956
  const ts2 = parseInt(el2.dataset.actionTs || "0", 10);
@@ -163771,6 +164175,7 @@ Terminal error: ${error?.message || String(error)}`);
163771
164175
  updateTerminalContent();
163772
164176
  updateStatusBar();
163773
164177
  void autoStartCoordinator.tryWarmCurrentOffice();
164178
+ void warmAllTeamsBoundAgents();
163774
164179
  };
163775
164180
  window.addEventListener("focus", () => {
163776
164181
  catchUpStatusViews("window focus");