copilotoffice 2.2.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);
@@ -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;
@@ -152649,6 +152850,10 @@ ${h2.join(`
152649
152850
  return this.attachedOfficeId ?? this.getOfficeId();
152650
152851
  }
152651
152852
  clearRefitTimers() {
152853
+ if (this.refitRaf !== null) {
152854
+ cancelAnimationFrame(this.refitRaf);
152855
+ this.refitRaf = null;
152856
+ }
152652
152857
  for (const timer of this.refitTimers) {
152653
152858
  clearTimeout(timer);
152654
152859
  }
@@ -152747,7 +152952,7 @@ ${h2.join(`
152747
152952
  const dims = this.resolveTerminalDimensions();
152748
152953
  if (!dims) return null;
152749
152954
  const agentId = options?.agentId ?? this.currentAgentId;
152750
- if (!agentId || !window.copilotBridge) return dims;
152955
+ if (!agentId || typeof window === "undefined" || !window.copilotBridge) return dims;
152751
152956
  const officeId = options?.officeId ?? this.getActiveOfficeId();
152752
152957
  void window.copilotBridge.terminalResize(officeId, agentId, dims.cols, dims.rows).catch(() => {
152753
152958
  });
@@ -152860,6 +153065,7 @@ ${h2.join(`
152860
153065
  if (previousAgentId && previousAgentId !== agent.id) {
152861
153066
  this.acknowledgeCompletedWork(previousOfficeId, previousAgentId);
152862
153067
  }
153068
+ this.acknowledgeCompletedWork(nextOfficeId, agent.id);
152863
153069
  if (isSwitchingAgent && previousAgentId && window.copilotBridge) {
152864
153070
  this.isSwitchingAgent = true;
152865
153071
  this.onDataDisposable?.dispose();
@@ -153408,14 +153614,17 @@ ${h2.join(`
153408
153614
  return;
153409
153615
  }
153410
153616
  this.setTeamsButtonState(false, true);
153411
- const officeChannelUrl = officeManager.getOffice(officeId)?.config.teamsChannelUrl;
153617
+ const office = officeManager.getOffice(officeId)?.config;
153618
+ const officeChannelUrl = office?.teamsChannelUrl;
153412
153619
  const workingDir = this.currentAgent.workingDir || officeManager.getCurrentWorkingDirectory();
153413
153620
  const res = await window.copilotBridge.teamsRegister({
153414
153621
  officeId,
153415
153622
  agentId,
153416
153623
  displayName: this.currentAgent.name,
153417
153624
  workingDir,
153418
- officeChannelUrl
153625
+ officeChannelUrl,
153626
+ officeMentionType: office?.teamsMentionType,
153627
+ officeMentionValue: office?.teamsMentionValue
153419
153628
  });
153420
153629
  if (res?.success) {
153421
153630
  this.setTeamsButtonState(true);
@@ -153946,7 +154155,8 @@ ${h2.join(`
153946
154155
  if (this.getActiveOfficeId() !== officeId) return;
153947
154156
  this.fitAndResizeTerminal({ officeId, agentId });
153948
154157
  };
153949
- requestAnimationFrame(() => {
154158
+ this.refitRaf = requestAnimationFrame(() => {
154159
+ this.refitRaf = null;
153950
154160
  doFit();
153951
154161
  this.refitTimers.push(setTimeout(() => {
153952
154162
  doFit();
@@ -155122,6 +155332,7 @@ ${h2.join(`
155122
155332
  "src/layouts/default/DefaultDashboard.ts"() {
155123
155333
  "use strict";
155124
155334
  init_types();
155335
+ init_agentStatusPresentation();
155125
155336
  defaultDashboard = {
155126
155337
  renderCards(ctx) {
155127
155338
  const { agents, office, selectedAgentId, cachedSessionMeta, agentTools, formatElapsed, formatRelativeTime } = ctx;
@@ -155170,46 +155381,12 @@ ${h2.join(`
155170
155381
  for (const agent of agents) {
155171
155382
  const liveStatus = office?.agents.get(agent.id);
155172
155383
  const tools = agentTools.get(agent.id) || [];
155173
- let statusDot = "#555";
155174
- let statusLabel = "Slacking";
155175
- let statusIcon = "\u{1F4A4}";
155176
- if (liveStatus) {
155177
- if (liveStatus.state === "active") {
155178
- switch (liveStatus.subState) {
155179
- case "starting":
155180
- statusDot = "#ff9944";
155181
- statusLabel = "Starting...";
155182
- statusIcon = "\u{1F680}";
155183
- break;
155184
- case "ready":
155185
- if (liveStatus.completionPendingAck) {
155186
- statusDot = "#4a78ff";
155187
- statusLabel = "Done";
155188
- statusIcon = "\u{1F4EC}";
155189
- } else {
155190
- statusDot = "#ffffff";
155191
- statusLabel = "Ready";
155192
- statusIcon = "\u{1F4ED}";
155193
- }
155194
- break;
155195
- case "waiting":
155196
- statusDot = "#ffb86c";
155197
- statusLabel = "Waiting for input";
155198
- statusIcon = "\u23F3";
155199
- break;
155200
- case "thinking":
155201
- statusDot = "#50fa7b";
155202
- statusLabel = liveStatus.thinkingDetail ? `Thinking: ${liveStatus.thinkingDetail}` : "Thinking...";
155203
- statusIcon = "\u26A1";
155204
- break;
155205
- case "error":
155206
- statusDot = "#f44";
155207
- statusLabel = liveStatus.thinkingDetail ? `Error: ${liveStatus.thinkingDetail}` : "Error";
155208
- statusIcon = "\u274C";
155209
- break;
155210
- }
155211
- }
155212
- }
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;");
155213
155390
  const colorHex = "#" + agent.color.toString(16).padStart(6, "0");
155214
155391
  const isSelected = agent.id === selectedAgentId;
155215
155392
  const borderColor = isSelected ? "#6677ff" : "#252540";
@@ -155238,13 +155415,13 @@ ${h2.join(`
155238
155415
  ">${toolCount} tools queued</div>` : "";
155239
155416
  let toolPipelineHtml = "";
155240
155417
  if (tools.length > 0) {
155241
- const toolRows = tools.map((t2, i) => {
155418
+ const toolRows = tools.map((tool, i) => {
155242
155419
  const isLast = i === tools.length - 1;
155243
155420
  const icon = isLast ? "\u25B8" : "\u25E6";
155244
155421
  const color = isLast ? "#8af" : "#556";
155245
- const statusText = isLast ? t2.status : "(queued)";
155246
- return `<div style="font-size: ${t2.toolRow}; color: ${color}; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding: 1px 0;">
155247
- ${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>
155248
155425
  </div>`;
155249
155426
  }).join("");
155250
155427
  toolPipelineHtml = `
@@ -155415,6 +155592,11 @@ ${h2.join(`
155415
155592
  <div style="font-weight: bold; color: #dde; font-size: ${t.cardTitleLg};">${agent.name}</div>
155416
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>
155417
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>
155418
155600
  <div>
155419
155601
  ${taskSummaryHtml}
155420
155602
  </div>
@@ -155473,6 +155655,7 @@ ${h2.join(`
155473
155655
  "use strict";
155474
155656
  init_types();
155475
155657
  init_agents();
155658
+ init_agentStatusPresentation();
155476
155659
  fleetDashboard = {
155477
155660
  renderCards(ctx) {
155478
155661
  const { agents, office, selectedAgentId, formatElapsed, formatRelativeTime } = ctx;
@@ -155480,46 +155663,12 @@ ${h2.join(`
155480
155663
  let html = "";
155481
155664
  for (const agent of agents) {
155482
155665
  const liveStatus = office?.agents.get(agent.id);
155483
- let statusDot = "#555";
155484
- let statusLabel = "Slacking";
155485
- let statusIcon = "\u{1F4A4}";
155486
- if (liveStatus) {
155487
- if (liveStatus.state === "active") {
155488
- switch (liveStatus.subState) {
155489
- case "starting":
155490
- statusDot = "#ff9944";
155491
- statusLabel = "Starting...";
155492
- statusIcon = "\u{1F680}";
155493
- break;
155494
- case "ready":
155495
- if (liveStatus.completionPendingAck) {
155496
- statusDot = "#4a78ff";
155497
- statusLabel = "Done";
155498
- statusIcon = "\u{1F4EC}";
155499
- } else {
155500
- statusDot = "#ffffff";
155501
- statusLabel = "Ready";
155502
- statusIcon = "\u{1F4ED}";
155503
- }
155504
- break;
155505
- case "waiting":
155506
- statusDot = "#ffb86c";
155507
- statusLabel = "Waiting for input";
155508
- statusIcon = "\u23F3";
155509
- break;
155510
- case "thinking":
155511
- statusDot = "#50fa7b";
155512
- statusLabel = liveStatus.thinkingDetail ? `Thinking: ${liveStatus.thinkingDetail}` : "Thinking...";
155513
- statusIcon = "\u26A1";
155514
- break;
155515
- case "error":
155516
- statusDot = "#f44";
155517
- statusLabel = liveStatus.thinkingDetail ? `Error: ${liveStatus.thinkingDetail}` : "Error";
155518
- statusIcon = "\u274C";
155519
- break;
155520
- }
155521
- }
155522
- }
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;");
155523
155672
  const colorHex = "#" + agent.color.toString(16).padStart(6, "0");
155524
155673
  const isSelected = agent.id === selectedAgentId;
155525
155674
  const isArthur = agent.id === ARCHITECT_AGENT_ID;
@@ -155594,6 +155743,11 @@ ${h2.join(`
155594
155743
  <div style="font-weight: bold; color: #dde; font-size: ${t.cardTitle};">${agent.name}</div>
155595
155744
  <div style="color: #778; font-size: ${t.cardDescription}; margin-top: 2px;">${agent.description}</div>
155596
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>
155597
155751
  <div>
155598
155752
  <div style="display: flex; align-items: center; flex-wrap: wrap; margin-top: 3px;">
155599
155753
  <div style="
@@ -157058,6 +157212,10 @@ ${h2.join(`
157058
157212
  this.game.events.on("agent:status:changed", () => {
157059
157213
  this.updateSessionBadges();
157060
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);
157061
157219
  this.updateSessionBadges();
157062
157220
  this.game.events.on("office:switch", (officeId, _workingDir) => {
157063
157221
  if (this.animating) {
@@ -158530,7 +158688,7 @@ ${h2.join(`
158530
158688
  let nearestNPCDist = interactionDistance;
158531
158689
  let nearestDesk = null;
158532
158690
  let nearestDeskDist = interactionDistance;
158533
- this.npcs.forEach((npc) => {
158691
+ for (const npc of this.npcs) {
158534
158692
  const dist = import_phaser8.default.Math.Distance.Between(
158535
158693
  this.player.x,
158536
158694
  this.player.y,
@@ -158542,7 +158700,7 @@ ${h2.join(`
158542
158700
  nearestNPC = npc;
158543
158701
  }
158544
158702
  npc.setNearPlayer(false);
158545
- });
158703
+ }
158546
158704
  this.desks.forEach((desk) => {
158547
158705
  const dist = import_phaser8.default.Math.Distance.Between(
158548
158706
  this.player.x,
@@ -159412,12 +159570,13 @@ ${h2.join(`
159412
159570
  while (this.toasts.length >= MAX_VISIBLE) {
159413
159571
  this.dismiss(this.toasts[0]);
159414
159572
  }
159573
+ const accentColor = options.statusColorHex ?? options.agentColor;
159415
159574
  const el2 = document.createElement("div");
159416
159575
  el2.style.cssText = `
159417
159576
  pointer-events: auto;
159418
159577
  background: #1e1e3a;
159419
- border: 1.5px solid ${options.agentColor}66;
159420
- border-left: 4px solid ${options.agentColor};
159578
+ border: 1.5px solid ${accentColor}66;
159579
+ border-left: 4px solid ${accentColor};
159421
159580
  border-radius: 8px;
159422
159581
  padding: 12px 16px;
159423
159582
  display: flex;
@@ -159430,13 +159589,14 @@ ${h2.join(`
159430
159589
  opacity: 0;
159431
159590
  transition: transform ${ANIMATION_MS}ms ease, opacity ${ANIMATION_MS}ms ease;
159432
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>`;
159433
159598
  el2.innerHTML = `
159434
- <div style="
159435
- width: 8px; height: 8px;
159436
- border-radius: 50%;
159437
- background: ${options.agentColor};
159438
- flex-shrink: 0;
159439
- "></div>
159599
+ ${markerHtml}
159440
159600
  <div style="flex: 1; min-width: 0;">
159441
159601
  <div style="font-size: 12px; font-weight: bold; color: #dde;">${options.agentName}</div>
159442
159602
  <div style="font-size: 11px; color: #889; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${options.message}</div>
@@ -159582,11 +159742,21 @@ ${h2.join(`
159582
159742
  });
159583
159743
 
159584
159744
  // src/ui/NotificationService.ts
159585
- var NotificationService;
159745
+ var EVENT_STATUS_KEY, NotificationService;
159586
159746
  var init_NotificationService = __esm({
159587
159747
  "src/ui/NotificationService.ts"() {
159588
159748
  "use strict";
159589
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
+ };
159590
159760
  NotificationService = class {
159591
159761
  constructor(toastManager, resolveAgent, onClickAgent) {
159592
159762
  this.dedupeMap = /* @__PURE__ */ new Map();
@@ -159630,18 +159800,23 @@ ${h2.join(`
159630
159800
  if (!agent) return;
159631
159801
  const message = this.formatMessage(eventConfig.message, agent.name, context);
159632
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;
159633
159805
  if (eventConfig.toast) {
159634
159806
  this.toastManager.show({
159635
159807
  agentId,
159636
159808
  agentName: agent.name,
159637
159809
  agentColor: colorHex,
159638
159810
  message,
159639
- onClick: () => this.onClickAgent?.(agentId)
159811
+ onClick: () => this.onClickAgent?.(agentId),
159812
+ statusIcon: statusPres?.icon,
159813
+ statusColorHex: statusPres?.colorHex
159640
159814
  });
159641
159815
  }
159642
159816
  if (eventConfig.osNotification) {
159817
+ const titlePrefix = statusPres?.icon ?? "\u{1F3E2}";
159643
159818
  window.copilotBridge?.showNativeNotification(
159644
- `\u{1F3E2} ${agent.name}`,
159819
+ `${titlePrefix} ${agent.name}`,
159645
159820
  message
159646
159821
  );
159647
159822
  }
@@ -161088,14 +161263,17 @@ Terminal error: ${error?.message || String(error)}`);
161088
161263
  return;
161089
161264
  }
161090
161265
  this.setTeamsButtonState(false, true);
161091
- const officeChannelUrl = officeManager.getOffice(officeId)?.config.teamsChannelUrl;
161266
+ const office = officeManager.getOffice(officeId)?.config;
161267
+ const officeChannelUrl = office?.teamsChannelUrl;
161092
161268
  const workingDir = this.activeOptions.workingDir || officeManager.getCurrentWorkingDirectory();
161093
161269
  const res = await window.copilotBridge.teamsRegister({
161094
161270
  officeId,
161095
161271
  agentId,
161096
161272
  displayName: this.activeOptions.name,
161097
161273
  workingDir,
161098
- officeChannelUrl
161274
+ officeChannelUrl,
161275
+ officeMentionType: office?.teamsMentionType,
161276
+ officeMentionValue: office?.teamsMentionValue
161099
161277
  });
161100
161278
  if (res?.success) {
161101
161279
  this.setTeamsButtonState(true);
@@ -161566,6 +161744,17 @@ Terminal error: ${error?.message || String(error)}`);
161566
161744
  const last = remainingTools[remainingTools.length - 1];
161567
161745
  return { kind: "thinking", detail: String(last.name ?? "") };
161568
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
+ }
161569
161758
  var init_toolStatus = __esm({
161570
161759
  "src/util/toolStatus.ts"() {
161571
161760
  "use strict";
@@ -161611,6 +161800,7 @@ Terminal error: ${error?.message || String(error)}`);
161611
161800
  init_SeriousTerminalController();
161612
161801
  init_SpriteGenerator();
161613
161802
  init_toolStatus();
161803
+ init_agentStatusPresentation();
161614
161804
  init_startupTimeoutGuard();
161615
161805
  init_AutoStartCoordinator();
161616
161806
  init_agentAutoStart();
@@ -161624,6 +161814,22 @@ Terminal error: ${error?.message || String(error)}`);
161624
161814
  function getCurrentAgents() {
161625
161815
  return getLayout(getCurrentLayout()).agents;
161626
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
+ }
161627
161833
  function getCurrentAgentTools() {
161628
161834
  return officeManager.currentOffice?.agentTools || /* @__PURE__ */ new Map();
161629
161835
  }
@@ -161648,9 +161854,32 @@ Terminal error: ${error?.message || String(error)}`);
161648
161854
  var SESSION_META_CACHE_STORAGE_KEY = "agencyOffice:sessionMetaCacheByOffice";
161649
161855
  var OVERVIEW_SPRITE_CACHE_STORAGE_KEY = "agencyOffice:overviewSpriteCache";
161650
161856
  var PC_TERMINAL_ID2 = "pc-terminal";
161651
- var OFFICE_SORT_STORAGE_KEY = "agencyOffice:officeSortMode";
161857
+ var AGENT_SORT_STORAGE_KEY = "agencyOffice:agentSortMode";
161652
161858
  var OFFICE_ACCESS_TIMES_KEY = "agencyOffice:officeAccessTimes";
161653
- 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
+ }
161654
161883
  function getOfficeAccessTimes() {
161655
161884
  try {
161656
161885
  const raw = localStorage.getItem(OFFICE_ACCESS_TIMES_KEY);
@@ -161660,6 +161889,7 @@ Terminal error: ${error?.message || String(error)}`);
161660
161889
  }
161661
161890
  }
161662
161891
  function recordOfficeAccess(officeId) {
161892
+ if (!officeId) return;
161663
161893
  const times = getOfficeAccessTimes();
161664
161894
  times[officeId] = Date.now();
161665
161895
  try {
@@ -161956,8 +162186,8 @@ Terminal error: ${error?.message || String(error)}`);
161956
162186
  return getCurrentAgents().map((a) => ({
161957
162187
  id: a.id,
161958
162188
  name: a.name,
161959
- tileX: a.tileX,
161960
- tileY: a.tileY
162189
+ tileX: a.position.x,
162190
+ tileY: a.position.y
161961
162191
  }));
161962
162192
  },
161963
162193
  getActiveTerminalAgentId: () => {
@@ -162015,12 +162245,8 @@ Terminal error: ${error?.message || String(error)}`);
162015
162245
  console.log("[main] Spec 008-smoke: __copilotOfficeDebug installed");
162016
162246
  }
162017
162247
  function renderOfficeTabs() {
162018
- let offices = officeManager.getAllOffices();
162248
+ const offices = officeManager.getAllOffices();
162019
162249
  const currentId = officeManager.currentOfficeId;
162020
- if (officeSortMode === "recent") {
162021
- const accessTimes = getOfficeAccessTimes();
162022
- offices = [...offices].sort((a, b2) => (accessTimes[b2.id] || 0) - (accessTimes[a.id] || 0));
162023
- }
162024
162250
  let html = "";
162025
162251
  for (const office of offices) {
162026
162252
  const isActive = office.id === currentId;
@@ -162385,6 +162611,21 @@ Terminal error: ${error?.message || String(error)}`);
162385
162611
  width: 100%; padding: 6px 8px; margin-bottom: 14px; background: #12121f; border: 1px solid #333;
162386
162612
  border-radius: 5px; color: #899; font-family: inherit; font-size: 11px; box-sizing: border-box;
162387
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>
162388
162629
  <div style="display: flex; gap: 8px; justify-content: flex-end;">
162389
162630
  ${canDelete ? `<button class="osp-delete" style="
162390
162631
  padding: 5px 12px; background: #2a1a1a; border: 1px solid #633; border-radius: 5px;
@@ -162404,6 +162645,8 @@ Terminal error: ${error?.message || String(error)}`);
162404
162645
  const nameInput = popover.querySelector(".osp-name");
162405
162646
  const pathInput = popover.querySelector(".osp-path");
162406
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");
162407
162650
  popover.querySelector(".osp-close")?.addEventListener("click", closeOfficePopover);
162408
162651
  popover.querySelector(".osp-save")?.addEventListener("click", () => {
162409
162652
  const newName = nameInput.value.trim();
@@ -162411,6 +162654,10 @@ Terminal error: ${error?.message || String(error)}`);
162411
162654
  if (newName) officeManager.updateOffice(officeId, { name: newName });
162412
162655
  if (newPath) officeManager.updateOffice(officeId, { workingDirectory: newPath });
162413
162656
  officeManager.updateOffice(officeId, { teamsChannelUrl: teamsInput.value.trim() });
162657
+ officeManager.updateOffice(officeId, {
162658
+ teamsMentionType: teamsMentionTypeInput.value,
162659
+ teamsMentionValue: teamsMentionValueInput.value.trim()
162660
+ });
162414
162661
  renderOfficeTabs();
162415
162662
  updateTerminalContent();
162416
162663
  closeOfficePopover();
@@ -162458,18 +162705,18 @@ Terminal error: ${error?.message || String(error)}`);
162458
162705
  <div id="terminal-subtitle" style="font-size: 12px; color: #555;"></div>
162459
162706
  </div>
162460
162707
  <div style="display: flex; align-items: center; gap: 8px;">
162461
- <button id="office-sort-btn" style="
162708
+ <button id="agent-sort-btn" style="
162462
162709
  padding: 6px 12px;
162463
- background: ${officeSortMode === "recent" ? "#2a3a5a" : "#252538"};
162464
- color: ${officeSortMode === "recent" ? "#8af" : "#666"};
162465
- 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"};
162466
162713
  border-radius: 4px;
162467
162714
  font-family: 'Cascadia Code', Consolas, monospace;
162468
162715
  font-size: 12px;
162469
162716
  cursor: pointer;
162470
162717
  white-space: nowrap;
162471
162718
  transition: all 0.2s;
162472
- " title="Sort office tabs">\u21C5 ${officeSortMode === "recent" ? "Recent" : "Default"}</button>
162719
+ " title="Sort agents in this office">\u21C5 ${agentSortMode === "recent" ? "Recent" : "Default"}</button>
162473
162720
  <button id="close-office-btn" style="
162474
162721
  display: none;
162475
162722
  padding: 6px 14px;
@@ -162485,19 +162732,19 @@ Terminal error: ${error?.message || String(error)}`);
162485
162732
  </div>
162486
162733
  `;
162487
162734
  overviewHost.appendChild(overviewHeader);
162488
- document.getElementById("office-sort-btn").addEventListener("click", () => {
162489
- officeSortMode = officeSortMode === "default" ? "recent" : "default";
162735
+ document.getElementById("agent-sort-btn").addEventListener("click", () => {
162736
+ agentSortMode = agentSortMode === "default" ? "recent" : "default";
162490
162737
  try {
162491
- localStorage.setItem(OFFICE_SORT_STORAGE_KEY, officeSortMode);
162738
+ localStorage.setItem(AGENT_SORT_STORAGE_KEY, agentSortMode);
162492
162739
  } catch {
162493
162740
  }
162494
- renderOfficeTabs();
162495
- const sortBtn = document.getElementById("office-sort-btn");
162741
+ updateTerminalContent();
162742
+ const sortBtn = document.getElementById("agent-sort-btn");
162496
162743
  if (sortBtn) {
162497
- sortBtn.textContent = `\u21C5 ${officeSortMode === "recent" ? "Recent" : "Default"}`;
162498
- sortBtn.style.background = officeSortMode === "recent" ? "#2a3a5a" : "#252538";
162499
- sortBtn.style.color = officeSortMode === "recent" ? "#8af" : "#666";
162500
- 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";
162501
162748
  }
162502
162749
  });
162503
162750
  document.getElementById("close-office-btn").addEventListener("click", () => {
@@ -162549,11 +162796,7 @@ Terminal error: ${error?.message || String(error)}`);
162549
162796
  var toastManager = new ToastNotificationManager(document.body);
162550
162797
  function formatElapsed(startTime) {
162551
162798
  if (!startTime) return "";
162552
- const seconds = Math.floor((Date.now() - startTime) / 1e3);
162553
- if (seconds < 60) return `${seconds}s`;
162554
- const mins = Math.floor(seconds / 60);
162555
- const secs = seconds % 60;
162556
- return `${mins}m ${secs}s`;
162799
+ return formatElapsedMmSs(startTime);
162557
162800
  }
162558
162801
  function formatRelativeTime(timestamp) {
162559
162802
  const seconds = Math.floor((Date.now() - timestamp) / 1e3);
@@ -162616,8 +162859,18 @@ Terminal error: ${error?.message || String(error)}`);
162616
162859
  terminalHost.style.display = "none";
162617
162860
  seriousPlaceholder.style.display = "none";
162618
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
+ }
162619
162871
  async function openAgentTerminal(agentId) {
162620
162872
  selectedAgentId = agentId;
162873
+ clearCompletionAck(agentId);
162621
162874
  if (appMode === "game") {
162622
162875
  phaserGameRef?.events.emit("open:agent:terminal", agentId);
162623
162876
  return;
@@ -162654,12 +162907,13 @@ Terminal error: ${error?.message || String(error)}`);
162654
162907
  }
162655
162908
  });
162656
162909
  var autoStartTerminalStartCount = 0;
162657
- async function warmAgentSession(officeId, agentId) {
162658
- if (!window.copilotBridge) return;
162659
- const launchConfig = getSeriousLaunchConfig(agentId);
162660
- if (!launchConfig) return;
162661
- const workingDir = launchConfig.workingDir;
162662
- 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;
162663
162917
  const officeStatus = officeManager.getAgentStatus(officeId, agentId);
162664
162918
  if (officeStatus?.state === "slacking") {
162665
162919
  officeManager.setAgentStarting(officeId, agentId);
@@ -162668,7 +162922,7 @@ Terminal error: ${error?.message || String(error)}`);
162668
162922
  updateTerminalContent();
162669
162923
  }
162670
162924
  autoStartTerminalStartCount += 1;
162671
- await window.copilotBridge.terminalStart(
162925
+ const res = await window.copilotBridge.terminalStart(
162672
162926
  officeId,
162673
162927
  agentId,
162674
162928
  workingDir,
@@ -162677,6 +162931,74 @@ Terminal error: ${error?.message || String(error)}`);
162677
162931
  void 0,
162678
162932
  launchMode
162679
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
+ }
162680
163002
  }
162681
163003
  function buildCanonicalAgentIdsForOffice(officeId) {
162682
163004
  const office = officeManager.currentOfficeId === officeId ? officeManager.currentOffice : null;
@@ -162723,7 +163045,9 @@ Terminal error: ${error?.message || String(error)}`);
162723
163045
  if (!window.copilotBridge) return;
162724
163046
  await window.copilotBridge.resetSession(oid, aid);
162725
163047
  },
162726
- warmAgentSession: (oid, aid) => warmAgentSession(oid, aid),
163048
+ warmAgentSession: async (oid, aid) => {
163049
+ await warmAgentSession(oid, aid);
163050
+ },
162727
163051
  getSettings: () => getAgentAutoStartSettings()
162728
163052
  });
162729
163053
  setAutoStartCoordinator(autoStartCoordinator);
@@ -162855,13 +163179,16 @@ Terminal error: ${error?.message || String(error)}`);
162855
163179
  }
162856
163180
  const agent = getSeriousLaunchConfig(agentId);
162857
163181
  if (!agent) return;
162858
- const officeChannelUrl = officeManager.getOffice(officeId)?.config.teamsChannelUrl;
163182
+ const office = officeManager.getOffice(officeId)?.config;
163183
+ const officeChannelUrl = office?.teamsChannelUrl;
162859
163184
  const res = await window.copilotBridge.teamsRegister({
162860
163185
  officeId,
162861
163186
  agentId,
162862
163187
  displayName: agent.name,
162863
163188
  workingDir: agent.workingDir || officeManager.getCurrentWorkingDirectory(),
162864
- officeChannelUrl
163189
+ officeChannelUrl,
163190
+ officeMentionType: office?.teamsMentionType,
163191
+ officeMentionValue: office?.teamsMentionValue
162865
163192
  });
162866
163193
  if (res?.success) {
162867
163194
  teamsOnlineAgentIds.add(agentId);
@@ -162906,7 +163233,7 @@ Terminal error: ${error?.message || String(error)}`);
162906
163233
  }
162907
163234
  const layout = getLayout(getCurrentLayout());
162908
163235
  const html = layout.dashboard.renderCards({
162909
- agents: layout.agents,
163236
+ agents: sortAgentsByMode(layout.agents, office || null),
162910
163237
  office: office || null,
162911
163238
  selectedAgentId,
162912
163239
  cachedSessionMeta,
@@ -163227,8 +163554,14 @@ Terminal error: ${error?.message || String(error)}`);
163227
163554
  if (!agentTools.has(agentId)) {
163228
163555
  agentTools.set(agentId, []);
163229
163556
  }
163230
- 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);
163231
163563
  officeManager.pushRecentAction(officeId, agentId, toolName, "started");
163564
+ recordAgentActivity(officeId, agentId);
163232
163565
  if (status) {
163233
163566
  officeManager.setTaskSummary(officeId, agentId, status);
163234
163567
  }
@@ -163252,9 +163585,13 @@ Terminal error: ${error?.message || String(error)}`);
163252
163585
  const agentTools = getCurrentAgentTools();
163253
163586
  const tools = agentTools.get(agentId);
163254
163587
  if (tools) {
163255
- const completedTool = tools.find((t) => t.toolId === toolId);
163256
- const completedToolName = completedTool?.name || "tool";
163257
- 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;
163258
163595
  agentTools.set(agentId, remaining);
163259
163596
  officeManager.setLastCompletedAction(officeId, agentId, completedToolName);
163260
163597
  officeManager.pushRecentAction(officeId, agentId, completedToolName, "completed");
@@ -163281,6 +163618,11 @@ Terminal error: ${error?.message || String(error)}`);
163281
163618
  console.log(`[Office] Turn end: ${agentId}`);
163282
163619
  const officeId = officeManager.currentOfficeId;
163283
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
+ }
163284
163626
  const agentTools = getCurrentAgentTools();
163285
163627
  const activeTools = agentTools.get(agentId) || [];
163286
163628
  const waitingToolActive = activeTools.some((t) => isAskUserTool(t.name, t.status));
@@ -163309,6 +163651,7 @@ Terminal error: ${error?.message || String(error)}`);
163309
163651
  return;
163310
163652
  }
163311
163653
  officeManager.setTaskSummary(officeId, agentId, "Processing...");
163654
+ recordAgentActivity(officeId, agentId);
163312
163655
  officeManager.setAgentThinking(officeId, agentId, "Processing...");
163313
163656
  console.log(`[Office] Status: ${agentId} \u2192 thinking (turn start)`);
163314
163657
  notifyAgent(agentId, "turnStart");
@@ -163340,7 +163683,11 @@ Terminal error: ${error?.message || String(error)}`);
163340
163683
  window.copilotBridge.onTeamsToast?.((toast) => {
163341
163684
  if (!toast?.message) return;
163342
163685
  const kind = toast.level === "error" ? "error" : toast.level === "warn" ? "error" : "info";
163343
- 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
+ }
163344
163691
  });
163345
163692
  let backendFallbackToastShown = false;
163346
163693
  const showBackendFallbackToast = (info) => {
@@ -163579,9 +163926,11 @@ Terminal error: ${error?.message || String(error)}`);
163579
163926
  await syncAgentStatuses(true);
163580
163927
  }
163581
163928
  var ELAPSED_TICK_MS = 1e3;
163929
+ var lastStalledState = /* @__PURE__ */ new Map();
163582
163930
  setInterval(() => {
163583
163931
  const office = officeManager.currentOffice;
163584
163932
  if (!office) return;
163933
+ const now = Date.now();
163585
163934
  for (const agent of getCurrentAgents()) {
163586
163935
  const status = office.agents.get(agent.id);
163587
163936
  if (status?.activityStartTime) {
@@ -163592,6 +163941,16 @@ Terminal error: ${error?.message || String(error)}`);
163592
163941
  }
163593
163942
  }
163594
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
+ }
163595
163954
  const actionEls = document.querySelectorAll(`.agent-card[data-agent="${agent.id}"] [data-action-ts]`);
163596
163955
  actionEls.forEach((el2) => {
163597
163956
  const ts2 = parseInt(el2.dataset.actionTs || "0", 10);
@@ -163816,6 +164175,7 @@ Terminal error: ${error?.message || String(error)}`);
163816
164175
  updateTerminalContent();
163817
164176
  updateStatusBar();
163818
164177
  void autoStartCoordinator.tryWarmCurrentOffice();
164178
+ void warmAllTeamsBoundAgents();
163819
164179
  };
163820
164180
  window.addEventListener("focus", () => {
163821
164181
  catchUpStatusViews("window focus");