copilotoffice 1.1.1 → 2.0.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.
@@ -121442,23 +121442,23 @@ var CopilotOffice = (() => {
121442
121442
  * @return {Phaser.Textures.Frame} A clone of this Frame.
121443
121443
  */
121444
121444
  clone: function() {
121445
- var clone = new Frame(this.texture, this.name, this.sourceIndex);
121446
- clone.cutX = this.cutX;
121447
- clone.cutY = this.cutY;
121448
- clone.cutWidth = this.cutWidth;
121449
- clone.cutHeight = this.cutHeight;
121450
- clone.x = this.x;
121451
- clone.y = this.y;
121452
- clone.width = this.width;
121453
- clone.height = this.height;
121454
- clone.halfWidth = this.halfWidth;
121455
- clone.halfHeight = this.halfHeight;
121456
- clone.centerX = this.centerX;
121457
- clone.centerY = this.centerY;
121458
- clone.rotated = this.rotated;
121459
- clone.data = Extend(true, clone.data, this.data);
121460
- clone.updateUVs();
121461
- return clone;
121445
+ var clone2 = new Frame(this.texture, this.name, this.sourceIndex);
121446
+ clone2.cutX = this.cutX;
121447
+ clone2.cutY = this.cutY;
121448
+ clone2.cutWidth = this.cutWidth;
121449
+ clone2.cutHeight = this.cutHeight;
121450
+ clone2.x = this.x;
121451
+ clone2.y = this.y;
121452
+ clone2.width = this.width;
121453
+ clone2.height = this.height;
121454
+ clone2.halfWidth = this.halfWidth;
121455
+ clone2.halfHeight = this.halfHeight;
121456
+ clone2.centerX = this.centerX;
121457
+ clone2.centerY = this.centerY;
121458
+ clone2.rotated = this.rotated;
121459
+ clone2.data = Extend(true, clone2.data, this.data);
121460
+ clone2.updateUVs();
121461
+ return clone2;
121462
121462
  },
121463
121463
  /**
121464
121464
  * Destroys this Frame by nulling its reference to the parent Texture and and data objects.
@@ -138673,15 +138673,15 @@ var CopilotOffice = (() => {
138673
138673
  /***/
138674
138674
  ((module2) => {
138675
138675
  var Clone = function(obj) {
138676
- var clone = {};
138676
+ var clone2 = {};
138677
138677
  for (var key in obj) {
138678
138678
  if (Array.isArray(obj[key])) {
138679
- clone[key] = obj[key].slice(0);
138679
+ clone2[key] = obj[key].slice(0);
138680
138680
  } else {
138681
- clone[key] = obj[key];
138681
+ clone2[key] = obj[key];
138682
138682
  }
138683
138683
  }
138684
- return clone;
138684
+ return clone2;
138685
138685
  };
138686
138686
  module2.exports = Clone;
138687
138687
  })
@@ -138713,7 +138713,7 @@ var CopilotOffice = (() => {
138713
138713
  ((module2, __unused_webpack_exports, __webpack_require__2) => {
138714
138714
  var IsPlainObject = __webpack_require__2(41212);
138715
138715
  var Extend = function() {
138716
- var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {}, i = 1, length = arguments.length, deep = false;
138716
+ var options, name, src, copy, copyIsArray, clone2, target = arguments[0] || {}, i = 1, length = arguments.length, deep = false;
138717
138717
  if (typeof target === "boolean") {
138718
138718
  deep = target;
138719
138719
  target = arguments[1] || {};
@@ -138734,11 +138734,11 @@ var CopilotOffice = (() => {
138734
138734
  if (deep && copy && (IsPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) {
138735
138735
  if (copyIsArray) {
138736
138736
  copyIsArray = false;
138737
- clone = src && Array.isArray(src) ? src : [];
138737
+ clone2 = src && Array.isArray(src) ? src : [];
138738
138738
  } else {
138739
- clone = src && IsPlainObject(src) ? src : {};
138739
+ clone2 = src && IsPlainObject(src) ? src : {};
138740
138740
  }
138741
- target[name] = Extend(deep, clone, copy);
138741
+ target[name] = Extend(deep, clone2, copy);
138742
138742
  } else if (copy !== void 0) {
138743
138743
  target[name] = copy;
138744
138744
  }
@@ -138922,13 +138922,13 @@ var CopilotOffice = (() => {
138922
138922
  ((module2, __unused_webpack_exports, __webpack_require__2) => {
138923
138923
  var Clone = __webpack_require__2(41786);
138924
138924
  var Merge = function(obj1, obj2) {
138925
- var clone = Clone(obj1);
138925
+ var clone2 = Clone(obj1);
138926
138926
  for (var key in obj2) {
138927
- if (!clone.hasOwnProperty(key)) {
138928
- clone[key] = obj2[key];
138927
+ if (!clone2.hasOwnProperty(key)) {
138928
+ clone2[key] = obj2[key];
138929
138929
  }
138930
138930
  }
138931
- return clone;
138931
+ return clone2;
138932
138932
  };
138933
138933
  module2.exports = Merge;
138934
138934
  })
@@ -138939,13 +138939,13 @@ var CopilotOffice = (() => {
138939
138939
  ((module2, __unused_webpack_exports, __webpack_require__2) => {
138940
138940
  var Clone = __webpack_require__2(41786);
138941
138941
  var MergeRight = function(obj1, obj2) {
138942
- var clone = Clone(obj1);
138942
+ var clone2 = Clone(obj1);
138943
138943
  for (var key in obj2) {
138944
- if (clone.hasOwnProperty(key)) {
138945
- clone[key] = obj2[key];
138944
+ if (clone2.hasOwnProperty(key)) {
138945
+ clone2[key] = obj2[key];
138946
138946
  }
138947
138947
  }
138948
- return clone;
138948
+ return clone2;
138949
138949
  };
138950
138950
  module2.exports = MergeRight;
138951
138951
  })
@@ -139192,6 +139192,22 @@ var CopilotOffice = (() => {
139192
139192
  }
139193
139193
  return vy < 0 ? 3 /* UP */ : 0 /* DOWN */;
139194
139194
  }
139195
+ function nextWalkAction(spriteKey, vx, vy, current) {
139196
+ const newDir = directionFromVelocity(vx, vy);
139197
+ if (newDir === null) {
139198
+ return {
139199
+ kind: "idle",
139200
+ direction: current.direction,
139201
+ standFrame: getStandFrame(current.direction)
139202
+ };
139203
+ }
139204
+ return {
139205
+ kind: "play",
139206
+ direction: newDir,
139207
+ animKey: walkAnimKey(spriteKey, newDir),
139208
+ directionChanged: newDir !== current.direction
139209
+ };
139210
+ }
139195
139211
  function registerWalkAnimations(anims, spriteKey) {
139196
139212
  const directions = [0 /* DOWN */, 1 /* LEFT */, 2 /* RIGHT */, 3 /* UP */];
139197
139213
  for (const dir of directions) {
@@ -140056,10 +140072,19 @@ var CopilotOffice = (() => {
140056
140072
  }
140057
140073
  return valid;
140058
140074
  }
140059
- var FLEET_NAMES, FLEET_COLORS, FLEET_SEAT_POSITIONS, ARTHUR_FLEET_SEAT_INDEX, FLEET_AGENTS, RESERVE_AGENTS, SHOW_ARCHITECT_IN_DEFAULT_OFFICE, CORE_AGENT_IDS, RESERVE_AGENT_DESK, AGENTS, DEFAULT_AGENTS, DEFAULT_RESERVE_MAP, RANDOM_POOL_CONFIGS, RANDOM_SPRITE_COUNT, CORE_POSITIONS, RESERVE_DESK_IDS, RESERVE_POSITIONS, ROLE_TITLES;
140075
+ var ARCHITECT_AGENT_ID, GENERALIST_AGENT_ID, DEBUGGER_AGENT_ID, ADMIN_AGENT_ID, DEFAULT_PLAN_AGENT_IDS, FLEET_NAMES, FLEET_COLORS, FLEET_SEAT_POSITIONS, ARTHUR_FLEET_SEAT_INDEX, FLEET_AGENTS, RESERVE_AGENTS, SHOW_ARCHITECT_IN_DEFAULT_OFFICE, CORE_AGENT_IDS, RESERVE_AGENT_DESK, AGENTS, DEFAULT_AGENTS, DEFAULT_RESERVE_MAP, RANDOM_POOL_CONFIGS, RANDOM_SPRITE_COUNT, CORE_POSITIONS, RESERVE_DESK_IDS, RESERVE_POSITIONS, ROLE_TITLES;
140060
140076
  var init_agents = __esm({
140061
140077
  "src/config/agents.ts"() {
140062
140078
  "use strict";
140079
+ ARCHITECT_AGENT_ID = "architect";
140080
+ GENERALIST_AGENT_ID = "generalist";
140081
+ DEBUGGER_AGENT_ID = "debugger";
140082
+ ADMIN_AGENT_ID = "admin";
140083
+ DEFAULT_PLAN_AGENT_IDS = [
140084
+ GENERALIST_AGENT_ID,
140085
+ DEBUGGER_AGENT_ID,
140086
+ ADMIN_AGENT_ID
140087
+ ];
140063
140088
  FLEET_NAMES = [
140064
140089
  "Liam",
140065
140090
  "Emma",
@@ -140216,7 +140241,7 @@ var CopilotOffice = (() => {
140216
140241
  FLEET_AGENTS = FLEET_SEAT_POSITIONS.map((pos, i) => {
140217
140242
  if (i === ARTHUR_FLEET_SEAT_INDEX) {
140218
140243
  return {
140219
- id: "architect",
140244
+ id: ARCHITECT_AGENT_ID,
140220
140245
  name: "Arthur",
140221
140246
  skill: "general",
140222
140247
  sprite: "npc_architect",
@@ -140303,10 +140328,10 @@ var CopilotOffice = (() => {
140303
140328
  };
140304
140329
  SHOW_ARCHITECT_IN_DEFAULT_OFFICE = false;
140305
140330
  CORE_AGENT_IDS = /* @__PURE__ */ new Set([
140306
- "generalist",
140307
- ...SHOW_ARCHITECT_IN_DEFAULT_OFFICE ? ["architect"] : [],
140308
- "debugger",
140309
- "admin"
140331
+ GENERALIST_AGENT_ID,
140332
+ ...SHOW_ARCHITECT_IN_DEFAULT_OFFICE ? [ARCHITECT_AGENT_ID] : [],
140333
+ DEBUGGER_AGENT_ID,
140334
+ ADMIN_AGENT_ID
140310
140335
  ]);
140311
140336
  RESERVE_AGENT_DESK = Object.fromEntries(
140312
140337
  Object.entries(RESERVE_AGENTS).map(([deskId, config]) => [config.id, deskId])
@@ -140325,7 +140350,7 @@ var CopilotOffice = (() => {
140325
140350
  description: "the Generalist"
140326
140351
  },
140327
140352
  ...SHOW_ARCHITECT_IN_DEFAULT_OFFICE ? [{
140328
- id: "architect",
140353
+ id: ARCHITECT_AGENT_ID,
140329
140354
  name: "Arthur",
140330
140355
  skill: "general",
140331
140356
  sprite: "npc_architect",
@@ -141697,13 +141722,15 @@ var CopilotOffice = (() => {
141697
141722
  if (vx !== 0 && vy !== 0) {
141698
141723
  body.velocity.normalize().scale(currentSpeed);
141699
141724
  }
141700
- const newDir = directionFromVelocity(vx, vy);
141701
- if (newDir !== null) {
141702
- const dirChanged = newDir !== this.currentDirection;
141703
- this.currentDirection = newDir;
141704
- if (!this.isWalking || dirChanged) {
141725
+ const action = nextWalkAction("player", vx, vy, {
141726
+ direction: this.currentDirection,
141727
+ isWalking: this.isWalking
141728
+ });
141729
+ if (action.kind === "play") {
141730
+ this.currentDirection = action.direction;
141731
+ if (!this.isWalking || action.directionChanged) {
141705
141732
  this.isWalking = true;
141706
- this.anims.play(walkAnimKey("player", this.currentDirection), true);
141733
+ this.anims.play(action.animKey, true);
141707
141734
  }
141708
141735
  } else {
141709
141736
  this.stopWalking();
@@ -148170,12 +148197,191 @@ WARNING: This link could potentially be dangerous`)) {
148170
148197
  }
148171
148198
  });
148172
148199
 
148200
+ // src/config/zIndex.ts
148201
+ var ZIndex;
148202
+ var init_zIndex = __esm({
148203
+ "src/config/zIndex.ts"() {
148204
+ "use strict";
148205
+ ZIndex = {
148206
+ /** Status bar at the bottom of the app shell. */
148207
+ STATUS_BAR: 100,
148208
+ /** In-scene HTML overlay rendered by OfficeScene above the Phaser canvas
148209
+ * but below all modal surfaces (e.g. fleet-progress sticker). */
148210
+ OFFICE_SCENE_OVERLAY: 150,
148211
+ /** Transient toast notifications (auto-dismissed). */
148212
+ TOAST: 9e3,
148213
+ /** Terminal overlay panel host (xterm.js + tab strip). */
148214
+ TERMINAL_OVERLAY: 1e4,
148215
+ /** Sprite/session card that floats above the terminal overlay. */
148216
+ TERMINAL_SPRITE_CARD: 10001,
148217
+ /** Serious-mode terminal controller (full-pane mode). Sits above the
148218
+ * terminal overlay so its chrome (mode toggle, footer) stays on top. */
148219
+ SERIOUS_TERMINAL: 10003,
148220
+ /** Sprite customizer modal. Above terminal but below settings. */
148221
+ SPRITE_CUSTOMIZER: 15e3,
148222
+ /** Settings panel modal. */
148223
+ SETTINGS: 2e4,
148224
+ /** Notification settings modal. Shares SETTINGS' layer because the two
148225
+ * are never open simultaneously by app construction (both routed via
148226
+ * the same `settings:open` / `settings:close` bus). */
148227
+ NOTIFICATION_SETTINGS: 2e4,
148228
+ /** Generic blocking modal dialog (e.g. layout chooser, new office dialog).
148229
+ * Sits above everything except the absolute top-most modal layer. */
148230
+ MODAL_DIALOG: 99999,
148231
+ /** Reserved for the topmost modal layer (e.g. critical confirmation that
148232
+ * must sit above an already-open MODAL_DIALOG). */
148233
+ TOP_MODAL: 1e5
148234
+ };
148235
+ }
148236
+ });
148237
+
148238
+ // src/util/lifecycleLog.ts
148239
+ function logLifecycleTransition(t) {
148240
+ if (t.from === t.to) return;
148241
+ const full = { ...t, timestamp: Date.now() };
148242
+ const reasonStr = full.reason ? ` reason=${full.reason}` : "";
148243
+ const detailStr = full.detail ? ` detail=${JSON.stringify(full.detail)}` : "";
148244
+ console.log(
148245
+ `[lifecycle] agent=${full.agentId} office=${full.officeId} ${full.from}\u2192${full.to}${reasonStr}${detailStr}`
148246
+ );
148247
+ for (const fn of subscribers) {
148248
+ try {
148249
+ fn(full);
148250
+ } catch (err) {
148251
+ console.warn("[lifecycle] subscriber threw:", err);
148252
+ }
148253
+ }
148254
+ }
148255
+ var subscribers;
148256
+ var init_lifecycleLog = __esm({
148257
+ "src/util/lifecycleLog.ts"() {
148258
+ "use strict";
148259
+ subscribers = /* @__PURE__ */ new Set();
148260
+ }
148261
+ });
148262
+
148263
+ // src/office/officePersistence.ts
148264
+ function serializeOffices(state) {
148265
+ return JSON.stringify(
148266
+ {
148267
+ currentOfficeId: state.currentOfficeId,
148268
+ offices: state.offices
148269
+ },
148270
+ null,
148271
+ 2
148272
+ );
148273
+ }
148274
+ function deserializeOffices(stored) {
148275
+ if (!stored) return { currentOfficeId: null, offices: [] };
148276
+ let data;
148277
+ try {
148278
+ data = JSON.parse(stored);
148279
+ } catch {
148280
+ return { currentOfficeId: null, offices: [] };
148281
+ }
148282
+ if (data === null || typeof data !== "object") {
148283
+ return { currentOfficeId: null, offices: [] };
148284
+ }
148285
+ const record = data;
148286
+ const rawOffices = Array.isArray(record.offices) ? record.offices : [];
148287
+ const offices = [];
148288
+ for (let i = 0; i < rawOffices.length; i++) {
148289
+ const raw = rawOffices[i];
148290
+ if (raw === null || typeof raw !== "object") continue;
148291
+ const cfg = raw;
148292
+ const name = typeof cfg.name === "string" ? cfg.name : `Office ${i}`;
148293
+ const workingDirectory = typeof cfg.workingDirectory === "string" ? cfg.workingDirectory : ".";
148294
+ const createdAt = typeof cfg.createdAt === "number" ? cfg.createdAt : Date.now();
148295
+ const layout = cfg.layout === "fleet-vteam" ? "fleet-vteam" : "default";
148296
+ const seatedAgents = Array.isArray(cfg.seatedAgents) ? cfg.seatedAgents.filter(
148297
+ (s) => !!s && typeof s === "object" && typeof s.deskId === "string" && typeof s.agentId === "string"
148298
+ ) : [];
148299
+ const normalized = {
148300
+ // Reindex from array position — this replaces legacy UUID-style ids and
148301
+ // matches the prior inline loader behaviour exactly.
148302
+ id: `office-${i}`,
148303
+ name,
148304
+ workingDirectory,
148305
+ createdAt,
148306
+ layout,
148307
+ seatedAgents
148308
+ };
148309
+ if (cfg.customAgents !== void 0) {
148310
+ normalized.customAgents = cfg.customAgents;
148311
+ }
148312
+ if (cfg.customReserveAgents !== void 0) {
148313
+ normalized.customReserveAgents = cfg.customReserveAgents;
148314
+ }
148315
+ offices.push(normalized);
148316
+ }
148317
+ let currentOfficeId = null;
148318
+ if (record.currentOfficeId !== void 0 && record.currentOfficeId !== null) {
148319
+ currentOfficeId = String(record.currentOfficeId);
148320
+ }
148321
+ if (!currentOfficeId || !offices.some((o) => o.id === currentOfficeId)) {
148322
+ currentOfficeId = offices.length > 0 ? offices[0].id : null;
148323
+ }
148324
+ return { currentOfficeId, offices };
148325
+ }
148326
+ function createBridgePersistencePort() {
148327
+ const getBridge = () => typeof window !== "undefined" ? window.copilotBridge : void 0;
148328
+ return {
148329
+ async loadDurable() {
148330
+ const bridge = getBridge();
148331
+ if (!bridge?.loadOffices) return null;
148332
+ try {
148333
+ const result = await bridge.loadOffices();
148334
+ if (result?.success && typeof result.data === "string") {
148335
+ return result.data;
148336
+ }
148337
+ } catch (e) {
148338
+ console.warn("[OfficePersistence] loadOffices failed:", e);
148339
+ }
148340
+ return null;
148341
+ },
148342
+ async saveDurable(json) {
148343
+ const bridge = getBridge();
148344
+ if (!bridge?.saveOffices) return;
148345
+ try {
148346
+ await bridge.saveOffices(json);
148347
+ } catch (e) {
148348
+ console.warn("[OfficePersistence] saveOffices failed:", e);
148349
+ }
148350
+ },
148351
+ async createOfficeSession(officeId) {
148352
+ const bridge = getBridge();
148353
+ if (!bridge?.createOfficeSession) return;
148354
+ try {
148355
+ await bridge.createOfficeSession(officeId);
148356
+ } catch (e) {
148357
+ console.warn("[OfficePersistence] createOfficeSession failed:", e);
148358
+ }
148359
+ },
148360
+ async deleteOfficeSession(officeId) {
148361
+ const bridge = getBridge();
148362
+ if (!bridge?.deleteOfficeSession) return;
148363
+ try {
148364
+ await bridge.deleteOfficeSession(officeId);
148365
+ } catch (e) {
148366
+ console.warn("[OfficePersistence] deleteOfficeSession failed:", e);
148367
+ }
148368
+ }
148369
+ };
148370
+ }
148371
+ var init_officePersistence = __esm({
148372
+ "src/office/officePersistence.ts"() {
148373
+ "use strict";
148374
+ }
148375
+ });
148376
+
148173
148377
  // src/office/officeManager.ts
148174
148378
  var VALID_TRANSITIONS, MAX_RECENT_ACTIONS, OfficeManager, officeManager;
148175
148379
  var init_officeManager = __esm({
148176
148380
  "src/office/officeManager.ts"() {
148177
148381
  "use strict";
148178
148382
  init_agents();
148383
+ init_lifecycleLog();
148384
+ init_officePersistence();
148179
148385
  VALID_TRANSITIONS = {
148180
148386
  slacking: /* @__PURE__ */ new Set(["starting", "ready"]),
148181
148387
  starting: /* @__PURE__ */ new Set(["ready", "error", "slacking"]),
@@ -148186,14 +148392,24 @@ WARNING: This link could potentially be dangerous`)) {
148186
148392
  };
148187
148393
  MAX_RECENT_ACTIONS = 8;
148188
148394
  OfficeManager = class {
148189
- constructor() {
148395
+ constructor(persistence = createBridgePersistencePort()) {
148190
148396
  this.offices = /* @__PURE__ */ new Map();
148191
148397
  this.sessionToOffice = /* @__PURE__ */ new Map();
148192
148398
  // sessionId → officeId
148193
148399
  this._currentOfficeId = null;
148400
+ // Spec 008 (2026-06-12): durable load is async but renderer boot triggers
148401
+ // saves (status updates, agent registrations) almost immediately. Without
148402
+ // gating, those saves serialize the localStorage-only state and clobber
148403
+ // the multi-office file on disk before the durable load can hydrate. We
148404
+ // hold all durable writes until loadFromStorage's async path settles, then
148405
+ // flush whatever the most-recent state was. localStorage writes are NOT
148406
+ // gated — they're fast, synchronous, and only matter for next cold boot.
148407
+ this.durableLoadSettled = false;
148408
+ this.pendingDurableWrite = false;
148194
148409
  // Callbacks for UI updates
148195
148410
  this.onOfficeChanged = null;
148196
148411
  this.onOfficesUpdated = null;
148412
+ this.persistence = persistence;
148197
148413
  this.loadFromStorage();
148198
148414
  }
148199
148415
  get currentOfficeId() {
@@ -148237,11 +148453,7 @@ WARNING: This link could potentially be dangerous`)) {
148237
148453
  this._currentOfficeId = id;
148238
148454
  }
148239
148455
  this.saveToStorage();
148240
- if (typeof window !== "undefined" && window.copilotBridge?.createOfficeSession) {
148241
- window.copilotBridge.createOfficeSession(id).catch((e) => {
148242
- console.warn("[OfficeManager] Failed to create session file:", e);
148243
- });
148244
- }
148456
+ void this.persistence.createOfficeSession(id);
148245
148457
  this.onOfficesUpdated?.();
148246
148458
  return data;
148247
148459
  }
@@ -148259,11 +148471,7 @@ WARNING: This link could potentially be dangerous`)) {
148259
148471
  }
148260
148472
  }
148261
148473
  this.offices.delete(officeId);
148262
- if (typeof window !== "undefined" && window.copilotBridge?.deleteOfficeSession) {
148263
- window.copilotBridge.deleteOfficeSession(officeId).catch((e) => {
148264
- console.warn("[OfficeManager] Failed to delete session file:", e);
148265
- });
148266
- }
148474
+ void this.persistence.deleteOfficeSession(officeId);
148267
148475
  if (this._currentOfficeId === officeId) {
148268
148476
  this._currentOfficeId = this.offices.has("office-0") ? "office-0" : this.offices.keys().next().value || null;
148269
148477
  if (this._currentOfficeId) {
@@ -148335,70 +148543,63 @@ WARNING: This link could potentially be dangerous`)) {
148335
148543
  const office = this.offices.get(officeId);
148336
148544
  return office?.config.seatedAgents ?? [];
148337
148545
  }
148338
- // Persistence — saves to both localStorage (fast) and .data/copilot-offices.json (durable)
148546
+ // Persistence — saves to both localStorage (fast) and durable file via port.
148339
148547
  saveToStorage() {
148340
- const data = {
148548
+ const json = serializeOffices({
148341
148549
  currentOfficeId: this._currentOfficeId,
148342
148550
  offices: Array.from(this.offices.values()).map((o) => o.config)
148343
- };
148344
- const json = JSON.stringify(data, null, 2);
148551
+ });
148345
148552
  try {
148346
148553
  localStorage.setItem("copilot-offices", json);
148347
148554
  } catch (e) {
148348
148555
  console.warn("[OfficeManager] Failed to save to localStorage:", e);
148349
148556
  }
148350
- if (typeof window !== "undefined" && window.copilotBridge?.saveOffices) {
148351
- window.copilotBridge.saveOffices(json).catch((e) => {
148352
- console.warn("[OfficeManager] Failed to save to file:", e);
148353
- });
148557
+ if (!this.durableLoadSettled) {
148558
+ this.pendingDurableWrite = true;
148559
+ return;
148354
148560
  }
148561
+ void this.persistence.saveDurable(json);
148562
+ }
148563
+ flushPendingDurableWrite() {
148564
+ if (!this.pendingDurableWrite) return;
148565
+ this.pendingDurableWrite = false;
148566
+ const json = serializeOffices({
148567
+ currentOfficeId: this._currentOfficeId,
148568
+ offices: Array.from(this.offices.values()).map((o) => o.config)
148569
+ });
148570
+ void this.persistence.saveDurable(json);
148355
148571
  }
148356
148572
  loadFromStorage() {
148357
- this.loadFromJson(localStorage.getItem("copilot-offices"));
148358
- if (typeof window !== "undefined" && window.copilotBridge?.loadOffices) {
148359
- window.copilotBridge.loadOffices().then((result) => {
148360
- if (result.success && result.data) {
148361
- console.log("[OfficeManager] Loaded offices from .data/copilot-offices.json");
148362
- this.loadFromJson(result.data);
148363
- this.onOfficesUpdated?.();
148364
- }
148365
- }).catch((e) => {
148366
- console.warn("[OfficeManager] Failed to load from file:", e);
148367
- });
148368
- }
148573
+ this.applyStoredState(localStorage.getItem("copilot-offices"));
148574
+ void this.persistence.loadDurable().then((data) => {
148575
+ if (data) {
148576
+ console.log("[OfficeManager] Loaded offices from durable persistence");
148577
+ this.applyStoredState(data);
148578
+ this.onOfficesUpdated?.();
148579
+ }
148580
+ }).catch((e) => {
148581
+ console.warn("[OfficeManager] Failed to load from durable persistence:", e);
148582
+ }).finally(() => {
148583
+ this.durableLoadSettled = true;
148584
+ this.flushPendingDurableWrite();
148585
+ });
148369
148586
  }
148370
- loadFromJson(stored) {
148371
- if (!stored) return;
148372
- try {
148373
- const data = JSON.parse(stored);
148374
- if (Array.isArray(data.offices)) {
148375
- for (let i = 0; i < data.offices.length; i++) {
148376
- const config = data.offices[i];
148377
- if (!config.layout) config.layout = "default";
148378
- if (!Array.isArray(config.seatedAgents)) config.seatedAgents = [];
148379
- config.id = `office-${i}`;
148380
- delete config.index;
148381
- const id = config.id;
148382
- const existing = this.offices.get(id);
148383
- const officeData = {
148384
- config,
148385
- agents: existing?.agents ?? /* @__PURE__ */ new Map(),
148386
- agentTools: existing?.agentTools ?? /* @__PURE__ */ new Map()
148387
- };
148388
- this.offices.set(id, officeData);
148389
- }
148390
- }
148391
- let currentId = null;
148392
- if (data.currentOfficeId !== void 0 && data.currentOfficeId !== null) {
148393
- currentId = String(data.currentOfficeId);
148394
- }
148395
- if (currentId && this.offices.has(currentId)) {
148396
- this._currentOfficeId = currentId;
148397
- } else if (this.offices.size > 0) {
148398
- this._currentOfficeId = this.offices.keys().next().value;
148399
- }
148400
- } catch (e) {
148401
- console.warn("[OfficeManager] Failed to parse office data:", e);
148587
+ applyStoredState(stored) {
148588
+ const { currentOfficeId, offices } = deserializeOffices(stored);
148589
+ if (offices.length === 0 && currentOfficeId === null) return;
148590
+ for (const config of offices) {
148591
+ const existing = this.offices.get(config.id);
148592
+ const officeData = {
148593
+ config,
148594
+ agents: existing?.agents ?? /* @__PURE__ */ new Map(),
148595
+ agentTools: existing?.agentTools ?? /* @__PURE__ */ new Map()
148596
+ };
148597
+ this.offices.set(config.id, officeData);
148598
+ }
148599
+ if (currentOfficeId && this.offices.has(currentOfficeId)) {
148600
+ this._currentOfficeId = currentOfficeId;
148601
+ } else if (this.offices.size > 0) {
148602
+ this._currentOfficeId = this.offices.keys().next().value;
148402
148603
  }
148403
148604
  }
148404
148605
  // Agent status helpers
@@ -148428,9 +148629,27 @@ WARNING: This link could potentially be dangerous`)) {
148428
148629
  console.warn(`[OfficeManager] Invalid transition: ${agentId} ${from} \u2192 ${target}`);
148429
148630
  }
148430
148631
  }
148431
- setAgentSlacking(officeId, agentId) {
148632
+ /**
148633
+ * Emit a structured lifecycle telemetry entry for a transition that has
148634
+ * already been applied to `status`. Callers pass the pre-mutation effective
148635
+ * state captured before mutating. Safe additive observability — never mutates
148636
+ * state and self-transitions are suppressed by the helper.
148637
+ */
148638
+ emitLifecycleTransition(officeId, agentId, status, from, reason, detail) {
148639
+ const to = this.getEffectiveState(status);
148640
+ logLifecycleTransition({
148641
+ agentId,
148642
+ officeId,
148643
+ from,
148644
+ to,
148645
+ reason,
148646
+ detail
148647
+ });
148648
+ }
148649
+ setAgentSlacking(officeId, agentId, reason) {
148432
148650
  const status = this.getOrCreateStatus(officeId, agentId);
148433
148651
  if (!status) return;
148652
+ const from = this.getEffectiveState(status);
148434
148653
  this.validateTransition(agentId, status, "slacking");
148435
148654
  status.state = "slacking";
148436
148655
  status.subState = null;
@@ -148440,11 +148659,13 @@ WARNING: This link could potentially be dangerous`)) {
148440
148659
  status.activityStartTime = null;
148441
148660
  status.recentActions = [];
148442
148661
  status.taskSummary = null;
148662
+ this.emitLifecycleTransition(officeId, agentId, status, from, reason);
148443
148663
  }
148444
- setAgentStarting(officeId, agentId) {
148664
+ setAgentStarting(officeId, agentId, reason) {
148445
148665
  const status = this.getOrCreateStatus(officeId, agentId);
148446
148666
  if (!status) return;
148447
148667
  if (status.subState === "starting") return;
148668
+ const from = this.getEffectiveState(status);
148448
148669
  this.validateTransition(agentId, status, "starting");
148449
148670
  status.state = "active";
148450
148671
  status.subState = "starting";
@@ -148452,10 +148673,12 @@ WARNING: This link could potentially be dangerous`)) {
148452
148673
  status.currentTool = null;
148453
148674
  status.completionPendingAck = false;
148454
148675
  status.activityStartTime = Date.now();
148676
+ this.emitLifecycleTransition(officeId, agentId, status, from, reason);
148455
148677
  }
148456
- setAgentReady(officeId, agentId) {
148678
+ setAgentReady(officeId, agentId, reason) {
148457
148679
  const status = this.getOrCreateStatus(officeId, agentId);
148458
148680
  if (!status) return;
148681
+ const from = this.getEffectiveState(status);
148459
148682
  this.validateTransition(agentId, status, "ready");
148460
148683
  status.state = "active";
148461
148684
  status.subState = "ready";
@@ -148463,10 +148686,12 @@ WARNING: This link could potentially be dangerous`)) {
148463
148686
  status.currentTool = null;
148464
148687
  status.completionPendingAck = false;
148465
148688
  status.activityStartTime = null;
148689
+ this.emitLifecycleTransition(officeId, agentId, status, from, reason);
148466
148690
  }
148467
- setAgentDonePendingAck(officeId, agentId) {
148691
+ setAgentDonePendingAck(officeId, agentId, reason) {
148468
148692
  const status = this.getOrCreateStatus(officeId, agentId);
148469
148693
  if (!status) return;
148694
+ const from = this.getEffectiveState(status);
148470
148695
  this.validateTransition(agentId, status, "ready");
148471
148696
  status.state = "active";
148472
148697
  status.subState = "ready";
@@ -148474,6 +148699,7 @@ WARNING: This link could potentially be dangerous`)) {
148474
148699
  status.currentTool = null;
148475
148700
  status.completionPendingAck = true;
148476
148701
  status.activityStartTime = null;
148702
+ this.emitLifecycleTransition(officeId, agentId, status, from, reason ?? "done_pending_ack");
148477
148703
  }
148478
148704
  acknowledgeAgentCompletion(officeId, agentId) {
148479
148705
  const status = this.getOrCreateStatus(officeId, agentId);
@@ -148481,9 +148707,10 @@ WARNING: This link could potentially be dangerous`)) {
148481
148707
  status.completionPendingAck = false;
148482
148708
  return true;
148483
148709
  }
148484
- setAgentWaiting(officeId, agentId) {
148710
+ setAgentWaiting(officeId, agentId, reason) {
148485
148711
  const status = this.getOrCreateStatus(officeId, agentId);
148486
148712
  if (!status) return;
148713
+ const from = this.getEffectiveState(status);
148487
148714
  this.validateTransition(agentId, status, "waiting");
148488
148715
  status.state = "active";
148489
148716
  status.subState = "waiting";
@@ -148491,10 +148718,12 @@ WARNING: This link could potentially be dangerous`)) {
148491
148718
  status.currentTool = null;
148492
148719
  status.completionPendingAck = false;
148493
148720
  if (!status.activityStartTime) status.activityStartTime = Date.now();
148721
+ this.emitLifecycleTransition(officeId, agentId, status, from, reason);
148494
148722
  }
148495
- setAgentThinking(officeId, agentId, detail) {
148723
+ setAgentThinking(officeId, agentId, detail, reason) {
148496
148724
  const status = this.getOrCreateStatus(officeId, agentId);
148497
148725
  if (!status) return;
148726
+ const from = this.getEffectiveState(status);
148498
148727
  this.validateTransition(agentId, status, "thinking");
148499
148728
  status.state = "active";
148500
148729
  status.subState = "thinking";
@@ -148504,6 +148733,7 @@ WARNING: This link could potentially be dangerous`)) {
148504
148733
  status.currentTool = tools?.length ? tools[tools.length - 1].name : null;
148505
148734
  status.completionPendingAck = false;
148506
148735
  if (!status.activityStartTime) status.activityStartTime = Date.now();
148736
+ this.emitLifecycleTransition(officeId, agentId, status, from, reason, detail ?? void 0);
148507
148737
  }
148508
148738
  clearAgentThinkingDetail(officeId, agentId) {
148509
148739
  const status = this.getOrCreateStatus(officeId, agentId);
@@ -148524,9 +148754,10 @@ WARNING: This link could potentially be dangerous`)) {
148524
148754
  status.unreadCount = 0;
148525
148755
  status.lastEvent = null;
148526
148756
  }
148527
- setAgentError(officeId, agentId, detail = null) {
148757
+ setAgentError(officeId, agentId, detail = null, reason) {
148528
148758
  const status = this.getOrCreateStatus(officeId, agentId);
148529
148759
  if (!status) return;
148760
+ const from = this.getEffectiveState(status);
148530
148761
  this.validateTransition(agentId, status, "error");
148531
148762
  status.state = "active";
148532
148763
  status.subState = "error";
@@ -148534,6 +148765,7 @@ WARNING: This link could potentially be dangerous`)) {
148534
148765
  status.currentTool = null;
148535
148766
  status.completionPendingAck = false;
148536
148767
  status.activityStartTime = null;
148768
+ this.emitLifecycleTransition(officeId, agentId, status, from, reason, detail ?? void 0);
148537
148769
  }
148538
148770
  setLastCompletedAction(officeId, agentId, action) {
148539
148771
  const status = this.getOrCreateStatus(officeId, agentId);
@@ -148569,6 +148801,271 @@ WARNING: This link could potentially be dangerous`)) {
148569
148801
  }
148570
148802
  });
148571
148803
 
148804
+ // src/ui/clipboardToast.ts
148805
+ function ensureToastElement() {
148806
+ if (typeof document === "undefined") return null;
148807
+ let el = document.getElementById(TOAST_ID);
148808
+ if (el) return el;
148809
+ el = document.createElement("div");
148810
+ el.id = TOAST_ID;
148811
+ el.style.cssText = `
148812
+ position: fixed;
148813
+ top: 24px;
148814
+ left: 50%;
148815
+ transform: translateX(-50%);
148816
+ z-index: ${ZIndex.TERMINAL_SPRITE_CARD + 20};
148817
+ padding: 8px 16px;
148818
+ border-radius: 6px;
148819
+ font-family: 'Cascadia Code', Consolas, monospace;
148820
+ font-size: 13px;
148821
+ color: #ffffff;
148822
+ box-shadow: 0 4px 14px rgba(0, 0, 0, 0.5);
148823
+ pointer-events: none;
148824
+ opacity: 0;
148825
+ transition: opacity 120ms ease-out;
148826
+ max-width: 60vw;
148827
+ text-align: center;
148828
+ `;
148829
+ document.body.appendChild(el);
148830
+ return el;
148831
+ }
148832
+ function showClipboardToast(message, kind = "info") {
148833
+ const el = ensureToastElement();
148834
+ if (!el) return;
148835
+ el.textContent = message;
148836
+ el.style.background = KIND_BG[kind];
148837
+ el.style.opacity = "1";
148838
+ if (dismissTimer) clearTimeout(dismissTimer);
148839
+ dismissTimer = setTimeout(() => {
148840
+ el.style.opacity = "0";
148841
+ }, TOAST_DURATION_MS);
148842
+ }
148843
+ var TOAST_ID, TOAST_DURATION_MS, dismissTimer, KIND_BG;
148844
+ var init_clipboardToast = __esm({
148845
+ "src/ui/clipboardToast.ts"() {
148846
+ "use strict";
148847
+ init_zIndex();
148848
+ TOAST_ID = "copilot-office-clipboard-toast";
148849
+ TOAST_DURATION_MS = 1500;
148850
+ dismissTimer = null;
148851
+ KIND_BG = {
148852
+ success: "#1f6f3a",
148853
+ info: "#27355a",
148854
+ error: "#7a2230"
148855
+ };
148856
+ }
148857
+ });
148858
+
148859
+ // src/ui/xtermStyles.ts
148860
+ function ensureXtermStyles() {
148861
+ if (typeof document === "undefined") return;
148862
+ if (document.getElementById(STYLE_ID)) return;
148863
+ const style = document.createElement("style");
148864
+ style.id = STYLE_ID;
148865
+ style.textContent = `
148866
+ .xterm {
148867
+ height: 100%;
148868
+ }
148869
+ .xterm-viewport {
148870
+ overflow-y: scroll !important;
148871
+ background-color: #0a0a14 !important;
148872
+ scrollbar-width: none;
148873
+ scrollbar-gutter: auto;
148874
+ }
148875
+ .xterm-viewport::-webkit-scrollbar {
148876
+ width: 0 !important;
148877
+ height: 0 !important;
148878
+ display: none !important;
148879
+ }
148880
+ #terminal-container .xterm {
148881
+ height: 100%;
148882
+ }
148883
+ #serious-terminal-container .xterm {
148884
+ height: 100%;
148885
+ }
148886
+ `;
148887
+ document.head.appendChild(style);
148888
+ }
148889
+ var STYLE_ID;
148890
+ var init_xtermStyles = __esm({
148891
+ "src/ui/xtermStyles.ts"() {
148892
+ "use strict";
148893
+ STYLE_ID = "copilot-office-xterm-styles";
148894
+ }
148895
+ });
148896
+
148897
+ // src/ui/terminalWheel.ts
148898
+ function wheelToPtySequence(deltaY, opts = {}) {
148899
+ if (!deltaY) return "";
148900
+ const pages = Math.max(1, Math.round(opts.pagesPerNotch ?? 1));
148901
+ const key = deltaY < 0 ? PAGE_UP : PAGE_DOWN;
148902
+ return key.repeat(pages);
148903
+ }
148904
+ var PAGE_UP, PAGE_DOWN;
148905
+ var init_terminalWheel = __esm({
148906
+ "src/ui/terminalWheel.ts"() {
148907
+ "use strict";
148908
+ PAGE_UP = "\x1B[5~";
148909
+ PAGE_DOWN = "\x1B[6~";
148910
+ }
148911
+ });
148912
+
148913
+ // src/agents/AutoStartCoordinator.ts
148914
+ function readSessionStorage() {
148915
+ try {
148916
+ if (typeof sessionStorage !== "undefined") return sessionStorage;
148917
+ } catch {
148918
+ }
148919
+ return null;
148920
+ }
148921
+ function setAutoStartCoordinator(c) {
148922
+ _instance = c;
148923
+ }
148924
+ function getAutoStartCoordinator() {
148925
+ return _instance;
148926
+ }
148927
+ var WARMED_STORAGE_KEY, WarmedOfficeRegistry, AgentReplaceTracker, AutoStartCoordinator, _instance;
148928
+ var init_AutoStartCoordinator = __esm({
148929
+ "src/agents/AutoStartCoordinator.ts"() {
148930
+ "use strict";
148931
+ WARMED_STORAGE_KEY = "copilot-office-auto-start:warmed";
148932
+ WarmedOfficeRegistry = class {
148933
+ constructor() {
148934
+ const storage = readSessionStorage();
148935
+ let initial = [];
148936
+ if (storage) {
148937
+ try {
148938
+ const raw = storage.getItem(WARMED_STORAGE_KEY);
148939
+ if (raw) {
148940
+ const parsed = JSON.parse(raw);
148941
+ if (Array.isArray(parsed)) initial = parsed.filter((s) => typeof s === "string");
148942
+ }
148943
+ } catch {
148944
+ }
148945
+ }
148946
+ this.warmed = new Set(initial);
148947
+ }
148948
+ has(officeId) {
148949
+ return this.warmed.has(officeId);
148950
+ }
148951
+ mark(officeId) {
148952
+ this.warmed.add(officeId);
148953
+ const storage = readSessionStorage();
148954
+ if (!storage) return;
148955
+ try {
148956
+ storage.setItem(WARMED_STORAGE_KEY, JSON.stringify(Array.from(this.warmed)));
148957
+ } catch {
148958
+ }
148959
+ }
148960
+ /** @internal — test helper. */
148961
+ snapshot() {
148962
+ return Array.from(this.warmed);
148963
+ }
148964
+ /** @internal — e2e helper for spec 009 A4 (Settings OFF gate). */
148965
+ clearAll() {
148966
+ this.warmed.clear();
148967
+ const storage = readSessionStorage();
148968
+ if (storage) {
148969
+ try {
148970
+ storage.removeItem(WARMED_STORAGE_KEY);
148971
+ } catch {
148972
+ }
148973
+ }
148974
+ }
148975
+ };
148976
+ AgentReplaceTracker = class {
148977
+ constructor() {
148978
+ this.inFlight = /* @__PURE__ */ new Map();
148979
+ }
148980
+ has(agentId) {
148981
+ return this.inFlight.has(agentId);
148982
+ }
148983
+ get(agentId) {
148984
+ return this.inFlight.get(agentId);
148985
+ }
148986
+ set(agentId, p) {
148987
+ this.inFlight.set(agentId, p);
148988
+ }
148989
+ delete(agentId) {
148990
+ this.inFlight.delete(agentId);
148991
+ }
148992
+ };
148993
+ AutoStartCoordinator = class {
148994
+ constructor(deps) {
148995
+ this.deps = deps;
148996
+ this.warmedOffices = new WarmedOfficeRegistry();
148997
+ this.replaceTracker = new AgentReplaceTracker();
148998
+ }
148999
+ /** Rule #1 + #2 trigger. Idempotent: no-op if office already warmed or
149000
+ * setting is OFF. Returns the agents it kicked off (for testing). */
149001
+ async tryWarmCurrentOffice() {
149002
+ if (!this.deps.getSettings().autoStartKnownAgents) return [];
149003
+ const oid = this.deps.getCurrentOfficeId();
149004
+ if (!oid) return [];
149005
+ if (this.warmedOffices.has(oid)) return [];
149006
+ this.warmedOffices.mark(oid);
149007
+ let roster;
149008
+ try {
149009
+ roster = this.deps.getCanonicalAgentIds(oid) || [];
149010
+ } catch {
149011
+ roster = [];
149012
+ }
149013
+ let meta;
149014
+ try {
149015
+ meta = await this.deps.getSessionMeta(oid) || {};
149016
+ } catch {
149017
+ meta = {};
149018
+ }
149019
+ const titled = [];
149020
+ for (const id of roster) {
149021
+ const entry = meta[id];
149022
+ const title = entry && typeof entry.title === "string" ? entry.title.trim() : "";
149023
+ if (title) titled.push(id);
149024
+ }
149025
+ const sids = await Promise.all(
149026
+ titled.map(
149027
+ (id) => Promise.resolve().then(() => this.deps.getCurrentSessionId(oid, id)).catch(() => null)
149028
+ )
149029
+ );
149030
+ const qualifying = [];
149031
+ for (let i = 0; i < titled.length; i++) {
149032
+ if (sids[i]) qualifying.push(titled[i]);
149033
+ }
149034
+ for (const id of qualifying) {
149035
+ void Promise.resolve().then(() => this.deps.warmAgentSession(oid, id)).catch((err) => {
149036
+ try {
149037
+ console.warn(
149038
+ `[AutoStartCoordinator] warmAgentSession failed for ${oid}/${id}:`,
149039
+ err
149040
+ );
149041
+ } catch {
149042
+ }
149043
+ });
149044
+ }
149045
+ return qualifying;
149046
+ }
149047
+ /** Rule #3 trigger. Returns the in-flight promise for an existing replace,
149048
+ * otherwise starts and tracks a new one. Setting OFF short-circuits to
149049
+ * just resetSession (acts like Close Session, per FR-017). */
149050
+ replaceSession(officeId, agentId) {
149051
+ const existing = this.replaceTracker.get(agentId);
149052
+ if (existing) return existing;
149053
+ const p = (async () => {
149054
+ await this.deps.resetSession(officeId, agentId);
149055
+ if (this.deps.getSettings().autoStartKnownAgents) {
149056
+ await this.deps.warmAgentSession(officeId, agentId);
149057
+ }
149058
+ })().finally(() => {
149059
+ this.replaceTracker.delete(agentId);
149060
+ });
149061
+ this.replaceTracker.set(agentId, p);
149062
+ return p;
149063
+ }
149064
+ };
149065
+ _instance = null;
149066
+ }
149067
+ });
149068
+
148572
149069
  // src/ui/TerminalOverlay.ts
148573
149070
  function withTimeout(promise, ms, label) {
148574
149071
  let timeoutId = null;
@@ -148584,14 +149081,22 @@ WARNING: This link could potentially be dangerous`)) {
148584
149081
  });
148585
149082
  });
148586
149083
  }
148587
- var import_xterm, import_addon_fit, IPC_TIMEOUT, TerminalOverlay;
149084
+ var import_xterm, import_addon_fit, IPC_TIMEOUT, DEBUG_COLD_START, DEBUG_SPRITE_SERIOUS, TerminalOverlay;
148588
149085
  var init_TerminalOverlay = __esm({
148589
149086
  "src/ui/TerminalOverlay.ts"() {
148590
149087
  "use strict";
148591
149088
  import_xterm = __toESM(require_xterm());
148592
149089
  import_addon_fit = __toESM(require_addon_fit());
149090
+ init_agents();
149091
+ init_zIndex();
148593
149092
  init_officeManager();
149093
+ init_clipboardToast();
149094
+ init_xtermStyles();
149095
+ init_terminalWheel();
149096
+ init_AutoStartCoordinator();
148594
149097
  IPC_TIMEOUT = 1e4;
149098
+ DEBUG_COLD_START = typeof window !== "undefined" && window.__COPILOT_OFFICE_DEBUG_COLD_START__ === true || false;
149099
+ DEBUG_SPRITE_SERIOUS = false;
148595
149100
  TerminalOverlay = class _TerminalOverlay {
148596
149101
  constructor(scene, inputManager, getOfficeId) {
148597
149102
  this.container = null;
@@ -148613,6 +149118,7 @@ WARNING: This link could potentially be dangerous`)) {
148613
149118
  this.mobileKeyboardBtn = null;
148614
149119
  this.isFocused = false;
148615
149120
  this.resizeHandler = null;
149121
+ this.clipboardHandler = null;
148616
149122
  this.resizeObserver = null;
148617
149123
  this.refitTimers = [];
148618
149124
  this.refitGeneration = 0;
@@ -148621,11 +149127,18 @@ WARNING: This link could potentially be dangerous`)) {
148621
149127
  this.isReplaying = false;
148622
149128
  this.launchMode = "copilot";
148623
149129
  this.pendingInputLine = "";
148624
- this.awaitingSessionIdRefresh = false;
148625
- this.sessionRefreshCommandTimer = null;
148626
- this.sessionRefreshExpiryTimer = null;
149130
+ // Spec 007: awaitingSessionIdRefresh / sessionRefresh*Timer fields removed
149131
+ // along with the parseSessionId / scheduleSessionIdRefresh helpers.
148627
149132
  this.currentSessionTitle = null;
148628
149133
  this.isEditingSessionTitle = false;
149134
+ this.terminalContextMenu = null;
149135
+ this.terminalContextMenuDismiss = null;
149136
+ // Disposable returned by xterm.terminal.onData(...). Re-registered per show()
149137
+ // so the handler's closure captures the new agent id (feature 002, C3/V6).
149138
+ this.onDataDisposable = null;
149139
+ // Toggled while show() is awaiting detach/attach so onData cannot fire input
149140
+ // against a half-attached agent (feature 002, V5).
149141
+ this.isSwitchingAgent = false;
148629
149142
  this.scene = scene;
148630
149143
  this.inputManager = inputManager;
148631
149144
  this.getOfficeId = getOfficeId;
@@ -148645,7 +149158,6 @@ WARNING: This link could potentially be dangerous`)) {
148645
149158
  if (this.isReplaying) return;
148646
149159
  if (agentId === this.currentAgentId && this.terminal) {
148647
149160
  this.terminal.write(data);
148648
- this.parseSessionId(data);
148649
149161
  }
148650
149162
  });
148651
149163
  window.copilotBridge.onTerminalExit((agentId, exitCode) => {
@@ -148671,49 +149183,12 @@ WARNING: This link could potentially be dangerous`)) {
148671
149183
  this.setupTerminalListeners();
148672
149184
  console.log("[TerminalOverlay] Re-attached terminal IPC listeners");
148673
149185
  }
148674
- clearSessionRefreshTimers() {
148675
- if (this.sessionRefreshCommandTimer) {
148676
- clearTimeout(this.sessionRefreshCommandTimer);
148677
- this.sessionRefreshCommandTimer = null;
148678
- }
148679
- if (this.sessionRefreshExpiryTimer) {
148680
- clearTimeout(this.sessionRefreshExpiryTimer);
148681
- this.sessionRefreshExpiryTimer = null;
148682
- }
148683
- }
148684
- scheduleSessionIdRefresh(agentId) {
148685
- if (!window.copilotBridge) return;
148686
- const officeId = this.getActiveOfficeId();
148687
- this.awaitingSessionIdRefresh = true;
148688
- this.clearSessionRefreshTimers();
148689
- const el = this.spriteCardElement?.querySelector(".session-id-display");
148690
- if (el) {
148691
- el.textContent = "syncing...";
148692
- }
148693
- this.sessionRefreshCommandTimer = setTimeout(() => {
148694
- this.sessionRefreshCommandTimer = null;
148695
- void window.copilotBridge.terminalWrite(officeId, agentId, "/session\r").catch(() => {
148696
- });
148697
- }, 400);
148698
- this.sessionRefreshExpiryTimer = setTimeout(() => {
148699
- this.sessionRefreshExpiryTimer = null;
148700
- this.awaitingSessionIdRefresh = false;
148701
- this.updateSessionDisplay();
148702
- }, 12e3);
148703
- }
148704
- parseSessionId(data) {
148705
- if (!this.awaitingSessionIdRefresh || !this.currentAgentId || !window.copilotBridge) return;
148706
- const match = data.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i);
148707
- if (!match) return;
148708
- const nextSessionId = match[0].toLowerCase();
148709
- this.awaitingSessionIdRefresh = false;
148710
- this.clearSessionRefreshTimers();
148711
- this.sessionId = nextSessionId;
148712
- this.updateSessionDisplay();
148713
- const officeId = this.getActiveOfficeId();
148714
- void window.copilotBridge.setSessionId(officeId, this.currentAgentId, nextSessionId).catch(() => {
148715
- });
148716
- }
149186
+ // Spec 007: parseSessionId / scheduleSessionIdRefresh / awaitingSessionIdRefresh
149187
+ // were removed. They greedily matched any UUID-shaped substring in PTY data
149188
+ // (which could be a constant install/trace/OAuth UUID emitted by Copilot CLI
149189
+ // before the real session UUID), then persisted that wrong value as the
149190
+ // agent's session id. The server is now the only source of truth for session
149191
+ // ids — see `bridge.resetSession` in the /new path below.
148717
149192
  getActiveOfficeId() {
148718
149193
  return this.attachedOfficeId ?? this.getOfficeId();
148719
149194
  }
@@ -148723,6 +149198,75 @@ WARNING: This link could potentially be dangerous`)) {
148723
149198
  }
148724
149199
  this.refitTimers.length = 0;
148725
149200
  }
149201
+ // Spec 006: diagnostic toast prefix so multi-instance and channel issues
149202
+ // are visible in the UI (no DevTools required).
149203
+ tag() {
149204
+ return `[O${this.instanceId}]`;
149205
+ }
149206
+ // Spec 005 + 006: write text to OS clipboard via Electron main process.
149207
+ // Diagnostic toast on every outcome so we never have to guess again.
149208
+ async copyToClipboard(text, source = "live") {
149209
+ const t = this.tag();
149210
+ if (!text) {
149211
+ showClipboardToast(`${t} empty selection`, "info");
149212
+ return false;
149213
+ }
149214
+ const bridge = window.copilotBridge;
149215
+ if (!bridge?.clipboardWriteText) {
149216
+ try {
149217
+ await navigator.clipboard.writeText(text);
149218
+ showClipboardToast(`${t} OK ${text.length} (fallback)`, "success");
149219
+ return true;
149220
+ } catch {
149221
+ showClipboardToast(`${t} no-bridge`, "error");
149222
+ return false;
149223
+ }
149224
+ }
149225
+ try {
149226
+ const r = await bridge.clipboardWriteText(text);
149227
+ if (r?.success === true) {
149228
+ showClipboardToast(`${t} OK ${text.length} (verified)`, "success");
149229
+ return true;
149230
+ }
149231
+ if (r?.verified === false) {
149232
+ showClipboardToast(`${t} verify-fail (wrote=${text.length})`, "error");
149233
+ } else {
149234
+ showClipboardToast(`${t} bridge-err: ${r?.error || "unknown"}`, "error");
149235
+ }
149236
+ return false;
149237
+ } catch (e) {
149238
+ showClipboardToast(`${t} bridge-err: ${e?.message || "threw"}`, "error");
149239
+ return false;
149240
+ }
149241
+ }
149242
+ // Spec 005: read OS clipboard via Electron main, forward to PTY.
149243
+ async pasteFromClipboardToTerminal() {
149244
+ if (!this.currentAgentId || !window.copilotBridge) return;
149245
+ let text = "";
149246
+ try {
149247
+ const bridge = window.copilotBridge;
149248
+ if (bridge?.clipboardReadText) {
149249
+ const r = await bridge.clipboardReadText();
149250
+ if (r?.success) text = r.text || "";
149251
+ } else if (navigator.clipboard?.readText) {
149252
+ text = await navigator.clipboard.readText();
149253
+ }
149254
+ } catch (e) {
149255
+ showClipboardToast(`Paste failed: ${e?.message || "bridge threw"}`, "error");
149256
+ return;
149257
+ }
149258
+ if (!text) {
149259
+ showClipboardToast("Clipboard is empty", "info");
149260
+ return;
149261
+ }
149262
+ const officeId = this.attachedOfficeId ?? this.getOfficeId();
149263
+ try {
149264
+ await window.copilotBridge.terminalWrite(officeId, this.currentAgentId, text);
149265
+ showClipboardToast(`Pasted ${text.length} char${text.length === 1 ? "" : "s"}`, "success");
149266
+ } catch (e) {
149267
+ showClipboardToast(`Paste failed: ${e?.message || "terminalWrite threw"}`, "error");
149268
+ }
149269
+ }
148726
149270
  resolveTerminalDimensions() {
148727
149271
  if (!this.terminal || !this.fitAddon) return null;
148728
149272
  this.fitAddon.fit();
@@ -148849,20 +149393,42 @@ WARNING: This link could potentially be dangerous`)) {
148849
149393
  async show(agent, onClose, options) {
148850
149394
  const previousAgentId = this.currentAgentId;
148851
149395
  const previousOfficeId = this.attachedOfficeId ?? this.getOfficeId();
149396
+ const nextOfficeId = this.getOfficeId();
149397
+ const isSwitchingAgent = previousAgentId !== null && (previousAgentId !== agent.id || previousOfficeId !== nextOfficeId);
148852
149398
  if (previousAgentId && previousAgentId !== agent.id) {
148853
149399
  this.acknowledgeCompletedWork(previousOfficeId, previousAgentId);
148854
149400
  }
149401
+ if (isSwitchingAgent && previousAgentId && window.copilotBridge) {
149402
+ this.isSwitchingAgent = true;
149403
+ this.onDataDisposable?.dispose();
149404
+ this.onDataDisposable = null;
149405
+ try {
149406
+ const detachStartedAt = Date.now();
149407
+ await withTimeout(
149408
+ window.copilotBridge.terminalDetach(previousOfficeId, previousAgentId),
149409
+ IPC_TIMEOUT,
149410
+ "terminalDetach"
149411
+ );
149412
+ if (DEBUG_COLD_START) {
149413
+ console.log(
149414
+ `[TerminalOverlay] switch from=${previousAgentId} to=${agent.id} detachMs=${Date.now() - detachStartedAt}`
149415
+ );
149416
+ }
149417
+ } catch (e) {
149418
+ console.warn(`[TerminalOverlay] terminalDetach failed for ${previousAgentId}: ${String(e)}`);
149419
+ }
149420
+ }
148855
149421
  this.currentAgentId = agent.id;
148856
149422
  this.onCloseCallback = onClose;
148857
149423
  this.isReadOnly = options?.readOnly ?? false;
148858
149424
  this.launchMode = options?.launchMode ?? "copilot";
148859
- this.attachedOfficeId = this.getOfficeId();
149425
+ this.attachedOfficeId = nextOfficeId;
148860
149426
  const officeId = this.getActiveOfficeId();
148861
149427
  this.currentAgent = agent;
148862
149428
  if (!this.container) {
148863
149429
  this.createContainer();
148864
149430
  }
148865
- const inceptionBadge = agent.id === "admin" ? " \u{1F3AD} INCEPTION MODE" : "";
149431
+ const inceptionBadge = agent.id === ADMIN_AGENT_ID ? " \u{1F3AD} INCEPTION MODE" : "";
148866
149432
  let sessionTitle = null;
148867
149433
  if (window.copilotBridge?.getSessionMeta) {
148868
149434
  try {
@@ -148899,8 +149465,6 @@ WARNING: This link could potentially be dangerous`)) {
148899
149465
  if (this.container) {
148900
149466
  this.container.style.display = "flex";
148901
149467
  }
148902
- this.awaitingSessionIdRefresh = false;
148903
- this.clearSessionRefreshTimers();
148904
149468
  if (this.spriteCardElement) {
148905
149469
  this.spriteCardElement.style.display = "flex";
148906
149470
  }
@@ -148921,8 +149485,6 @@ WARNING: This link could potentially be dangerous`)) {
148921
149485
  this.updateSessionTitleDisplay(sessionTitle);
148922
149486
  this.drawAgentSprite(agent);
148923
149487
  this.pendingInputLine = "";
148924
- this.awaitingSessionIdRefresh = false;
148925
- this.clearSessionRefreshTimers();
148926
149488
  this.clearRefitTimers();
148927
149489
  this.refitGeneration += 1;
148928
149490
  if (!this.terminal) {
@@ -148980,6 +149542,14 @@ WARNING: This link could potentially be dangerous`)) {
148980
149542
  }
148981
149543
  }
148982
149544
  this.isVisible = true;
149545
+ const attachStartedAt = Date.now();
149546
+ this.isSwitchingAgent = false;
149547
+ this.registerOnDataHandler(agent.id, officeId);
149548
+ if (DEBUG_COLD_START && isSwitchingAgent) {
149549
+ console.log(
149550
+ `[TerminalOverlay] switch attach complete to=${agent.id} attachMs=${Date.now() - attachStartedAt}`
149551
+ );
149552
+ }
148983
149553
  this.scene.game.events.emit("npc:highlight", agent.id);
148984
149554
  const spriteCanvas = this.spriteCardElement?.querySelector(".agent-sprite-canvas");
148985
149555
  if (spriteCanvas) {
@@ -149006,8 +149576,6 @@ WARNING: This link could potentially be dangerous`)) {
149006
149576
  }, 50);
149007
149577
  }
149008
149578
  async startNewSession(agentId, workingDir, officeId) {
149009
- this.awaitingSessionIdRefresh = false;
149010
- this.clearSessionRefreshTimers();
149011
149579
  this.sessionId = null;
149012
149580
  this.updateSessionDisplay();
149013
149581
  const el = this.spriteCardElement?.querySelector(".session-id-display");
@@ -149042,8 +149610,19 @@ WARNING: This link could potentially be dangerous`)) {
149042
149610
  this.updateSessionDisplay();
149043
149611
  }
149044
149612
  }
149045
- fetchSessionId(agentId) {
149046
- this.scheduleSessionIdRefresh(agentId);
149613
+ // Spec 007: after the user types /new in the PTY, ask the server to mint
149614
+ // a fresh per-agent session UUID. Authoritative — never parses CLI output.
149615
+ async fetchSessionId(agentId) {
149616
+ if (!window.copilotBridge) return;
149617
+ const officeId = this.getActiveOfficeId();
149618
+ try {
149619
+ const r = await window.copilotBridge.resetSession(officeId, agentId);
149620
+ if (r?.success && r.sessionId && agentId === this.currentAgentId) {
149621
+ this.sessionId = r.sessionId;
149622
+ this.updateSessionDisplay();
149623
+ }
149624
+ } catch {
149625
+ }
149047
149626
  }
149048
149627
  createContainer() {
149049
149628
  this.container = document.createElement("div");
@@ -149059,7 +149638,7 @@ WARNING: This link could potentially be dangerous`)) {
149059
149638
  border-radius: 0;
149060
149639
  display: none;
149061
149640
  flex-direction: column;
149062
- z-index: 10000;
149641
+ z-index: ${ZIndex.TERMINAL_OVERLAY};
149063
149642
  `;
149064
149643
  this.headerElement = document.createElement("div");
149065
149644
  this.headerElement.style.cssText = `
@@ -149080,7 +149659,7 @@ WARNING: This link could potentially be dangerous`)) {
149080
149659
  terminalOuter.id = "terminal-container";
149081
149660
  terminalOuter.style.cssText = `
149082
149661
  flex: 1;
149083
- overflow: hidden;
149662
+ overflow: auto;
149084
149663
  min-height: 0;
149085
149664
  padding: 10px;
149086
149665
  box-sizing: border-box;
@@ -149089,13 +149668,18 @@ WARNING: This link could potentially be dangerous`)) {
149089
149668
  this.terminalDiv.style.cssText = `
149090
149669
  width: 100%;
149091
149670
  height: 100%;
149092
- overflow: hidden;
149093
149671
  `;
149094
149672
  terminalOuter.appendChild(this.terminalDiv);
149095
149673
  terminalOuter.addEventListener("mousedown", () => {
149096
149674
  if (this.isVisible && !this.isFocused) {
149097
149675
  this.focusTerminal();
149098
149676
  }
149677
+ this.terminal?.focus();
149678
+ });
149679
+ terminalOuter.addEventListener("mouseup", () => {
149680
+ if (this.isVisible) {
149681
+ requestAnimationFrame(() => this.terminal?.focus());
149682
+ }
149099
149683
  });
149100
149684
  this.container.appendChild(terminalOuter);
149101
149685
  const terminalPanel = document.getElementById("terminal-panel") || document.body;
@@ -149114,6 +149698,16 @@ WARNING: This link could potentially be dangerous`)) {
149114
149698
  }
149115
149699
  /** Create the SpriteCard — a full-width bottom bar showing agent sprite, info, and controls. */
149116
149700
  createSpriteCard() {
149701
+ const stale = document.getElementById("sprite-card");
149702
+ if (stale) {
149703
+ try {
149704
+ stale.remove();
149705
+ } catch {
149706
+ }
149707
+ if (DEBUG_SPRITE_SERIOUS) {
149708
+ console.log("[TerminalOverlay] createSpriteCard removed stale #sprite-card before append");
149709
+ }
149710
+ }
149117
149711
  this.spriteCardElement = document.createElement("div");
149118
149712
  this.spriteCardElement.id = "sprite-card";
149119
149713
  this.spriteCardElement.style.cssText = `
@@ -149130,7 +149724,7 @@ WARNING: This link could potentially be dangerous`)) {
149130
149724
  padding: 16px 24px;
149131
149725
  box-sizing: border-box;
149132
149726
  position: relative;
149133
- z-index: 10001;
149727
+ z-index: ${ZIndex.TERMINAL_SPRITE_CARD};
149134
149728
  `;
149135
149729
  const agentDisplay = document.createElement("div");
149136
149730
  agentDisplay.className = "agent-display";
@@ -149277,49 +149871,65 @@ WARNING: This link could potentially be dangerous`)) {
149277
149871
  this.footerElement = this.spriteCardElement;
149278
149872
  }
149279
149873
  copySessionId() {
149280
- if (this.sessionId) {
149281
- navigator.clipboard.writeText(this.sessionId).then(() => {
149874
+ if (!this.sessionId) return;
149875
+ void this.copyToClipboard(this.sessionId, "session").then((success) => {
149876
+ if (!this.sessionIdElement) return;
149877
+ const original = this.sessionIdElement.textContent;
149878
+ this.sessionIdElement.textContent = success ? "Copied!" : "Copy failed";
149879
+ this.sessionIdElement.style.color = success ? "#50fa7b" : "#ff6b6b";
149880
+ setTimeout(() => {
149282
149881
  if (this.sessionIdElement) {
149283
- const original = this.sessionIdElement.textContent;
149284
- this.sessionIdElement.textContent = "Copied!";
149285
- this.sessionIdElement.style.color = "#50fa7b";
149286
- setTimeout(() => {
149287
- if (this.sessionIdElement) {
149288
- this.sessionIdElement.textContent = original;
149289
- this.sessionIdElement.style.color = "#4a9eff";
149290
- }
149291
- }, 1e3);
149882
+ this.sessionIdElement.textContent = original;
149883
+ this.sessionIdElement.style.color = "#4a9eff";
149292
149884
  }
149293
- });
149294
- }
149885
+ }, 1e3);
149886
+ });
149295
149887
  }
149296
149888
  async handleNewSession() {
149297
149889
  if (!this.currentAgentId || !this.currentAgent || this.isReadOnly) return;
149298
- this.awaitingSessionIdRefresh = false;
149299
- this.clearSessionRefreshTimers();
149300
149890
  const officeId = this.attachedOfficeId ?? this.getOfficeId();
149301
- console.log(`[TerminalOverlay] handleNewSession: agent=${this.currentAgentId}, office=${officeId}`);
149891
+ const agentId = this.currentAgentId;
149892
+ console.log(`[TerminalOverlay] handleNewSession: agent=${agentId}, office=${officeId}`);
149302
149893
  this.clearRefitTimers();
149303
149894
  this.refitGeneration += 1;
149304
149895
  this.pendingInputLine = "";
149305
149896
  this.terminal?.clear();
149306
149897
  this.terminal?.writeln("\x1B[33m[Starting new session...]\x1B[0m\r\n");
149307
- await withTimeout(
149308
- window.copilotBridge.resetSession(officeId, this.currentAgentId),
149309
- IPC_TIMEOUT,
149310
- "resetSession"
149311
- ).catch(() => {
149312
- });
149313
149898
  const el = this.spriteCardElement?.querySelector(".session-id-display");
149314
149899
  if (el) el.textContent = "starting...";
149315
149900
  this.sessionId = null;
149316
149901
  this.updateSessionDisplay();
149317
149902
  this.updateSessionTitleDisplay(null);
149318
- const dims = this.fitAndResizeTerminal({ officeId, agentId: this.currentAgentId });
149903
+ const coordinator = getAutoStartCoordinator();
149904
+ if (coordinator) {
149905
+ try {
149906
+ await coordinator.replaceSession(officeId, agentId);
149907
+ } catch (err) {
149908
+ this.terminal?.writeln(`Failed to start terminal: ${err?.message || String(err)}`);
149909
+ }
149910
+ try {
149911
+ if (window.copilotBridge?.getSessionId) {
149912
+ const sid = await window.copilotBridge.getSessionId(officeId, agentId);
149913
+ if (sid) {
149914
+ this.sessionId = sid;
149915
+ this.updateSessionDisplay();
149916
+ }
149917
+ }
149918
+ } catch {
149919
+ }
149920
+ return;
149921
+ }
149922
+ await withTimeout(
149923
+ window.copilotBridge.resetSession(officeId, agentId),
149924
+ IPC_TIMEOUT,
149925
+ "resetSession"
149926
+ ).catch(() => {
149927
+ });
149928
+ const dims = this.fitAndResizeTerminal({ officeId, agentId });
149319
149929
  const result = await withTimeout(
149320
149930
  this.launchMode === "shell" ? window.copilotBridge.terminalStart(
149321
149931
  officeId,
149322
- this.currentAgentId,
149932
+ agentId,
149323
149933
  this.currentAgent.workingDir || officeManager.getCurrentWorkingDirectory(),
149324
149934
  dims?.cols,
149325
149935
  dims?.rows,
@@ -149327,7 +149937,7 @@ WARNING: This link could potentially be dangerous`)) {
149327
149937
  "shell"
149328
149938
  ) : window.copilotBridge.terminalStart(
149329
149939
  officeId,
149330
- this.currentAgentId,
149940
+ agentId,
149331
149941
  this.currentAgent.workingDir || officeManager.getCurrentWorkingDirectory(),
149332
149942
  dims?.cols,
149333
149943
  dims?.rows
@@ -149390,7 +150000,7 @@ WARNING: This link could potentially be dangerous`)) {
149390
150000
  min-width: 320px;
149391
150001
  max-height: 250px;
149392
150002
  overflow-y: auto;
149393
- z-index: 10001;
150003
+ z-index: ${ZIndex.TERMINAL_SPRITE_CARD};
149394
150004
  font-family: 'Cascadia Code', Consolas, monospace;
149395
150005
  font-size: 12px;
149396
150006
  box-shadow: 0 -4px 12px rgba(0,0,0,0.5);
@@ -149448,21 +150058,7 @@ WARNING: This link could potentially be dangerous`)) {
149448
150058
  this.closeHistoryPopover();
149449
150059
  }
149450
150060
  injectStyles() {
149451
- if (document.getElementById("xterm-styles")) return;
149452
- const style = document.createElement("style");
149453
- style.id = "xterm-styles";
149454
- style.textContent = `
149455
- .xterm {
149456
- height: 100%;
149457
- }
149458
- .xterm-viewport {
149459
- background-color: #0a0a14 !important;
149460
- }
149461
- #terminal-container .xterm {
149462
- height: 100%;
149463
- }
149464
- `;
149465
- document.head.appendChild(style);
150061
+ ensureXtermStyles();
149466
150062
  }
149467
150063
  createTerminal() {
149468
150064
  if (!this.terminalDiv) return;
@@ -149502,38 +150098,109 @@ WARNING: This link could potentially be dangerous`)) {
149502
150098
  this.terminal.loadAddon(this.fitAddon);
149503
150099
  this.terminal.open(this.terminalDiv);
149504
150100
  this.fitAndResizeTerminal();
150101
+ const MOUSE_MODES = /* @__PURE__ */ new Set([1e3, 1002, 1003, 1006]);
150102
+ this.terminal.parser.registerCsiHandler({ prefix: "?", final: "h" }, (params) => {
150103
+ for (const p of params) {
150104
+ if (typeof p === "number" && MOUSE_MODES.has(p)) return true;
150105
+ }
150106
+ return false;
150107
+ });
150108
+ this.terminal.attachCustomWheelEventHandler((event) => {
150109
+ const term = this.terminal;
150110
+ if (!term) return true;
150111
+ if (term.buffer.active.type !== "alternate") return true;
150112
+ const seq = wheelToPtySequence(event.deltaY);
150113
+ if (!seq) return true;
150114
+ const officeId = this.attachedOfficeId ?? this.getOfficeId();
150115
+ if (officeId && this.currentAgentId && window.copilotBridge) {
150116
+ void window.copilotBridge.terminalWrite(officeId, this.currentAgentId, seq);
150117
+ }
150118
+ return false;
150119
+ });
150120
+ this.installTerminalContextMenu();
149505
150121
  this.terminal.attachCustomKeyEventHandler((event) => {
149506
150122
  const isModifierPressed = event.ctrlKey || event.metaKey;
149507
150123
  const key = event.key.toLowerCase();
149508
150124
  if (event.type !== "keydown" || !isModifierPressed) return true;
149509
150125
  if (key === "c") {
149510
- const selectedText = this.terminal?.hasSelection() ? this.terminal.getSelection() : "";
149511
- if (!selectedText) {
150126
+ const selection = this.terminal?.hasSelection() ? this.terminal.getSelection() ?? "" : "";
150127
+ if (!selection) {
149512
150128
  return true;
149513
150129
  }
149514
150130
  event.preventDefault();
149515
150131
  event.stopPropagation();
149516
- navigator.clipboard.writeText(selectedText).catch((err) => {
149517
- console.warn("[Terminal] Clipboard write failed:", err);
149518
- });
150132
+ void this.copyToClipboard(selection, "live");
149519
150133
  return false;
149520
150134
  }
149521
150135
  if (key === "v") {
149522
150136
  if (this.isReadOnly) return false;
149523
150137
  event.preventDefault();
149524
150138
  event.stopPropagation();
149525
- navigator.clipboard.readText().then((text) => {
149526
- if (text) this.terminal.paste(text);
149527
- }).catch((err) => {
149528
- console.warn("[Terminal] Clipboard read failed:", err);
149529
- });
150139
+ void this.pasteFromClipboardToTerminal();
149530
150140
  return false;
149531
150141
  }
149532
150142
  return true;
149533
150143
  });
149534
- this.terminal.onData((data) => {
150144
+ if (this.clipboardHandler) {
150145
+ document.removeEventListener("keydown", this.clipboardHandler, true);
150146
+ }
150147
+ this.clipboardHandler = (event) => {
150148
+ if (!this.isVisible) return;
150149
+ if (this.isEditingSessionTitle) return;
150150
+ const active = document.activeElement;
150151
+ if (active && (active.tagName === "INPUT" || active.tagName === "TEXTAREA") && active !== this.terminal?.textarea) {
150152
+ return;
150153
+ }
150154
+ const isModifierPressed = event.ctrlKey || event.metaKey;
150155
+ if (!isModifierPressed || event.type !== "keydown") return;
150156
+ const key = event.key.toLowerCase();
150157
+ if (key === "c") {
150158
+ const selection = this.terminal?.hasSelection() ? this.terminal.getSelection() ?? "" : "";
150159
+ if (!selection) return;
150160
+ event.preventDefault();
150161
+ event.stopImmediatePropagation();
150162
+ void this.copyToClipboard(selection, "live");
150163
+ } else if (key === "v") {
150164
+ if (this.isReadOnly) return;
150165
+ event.preventDefault();
150166
+ event.stopImmediatePropagation();
150167
+ void this.pasteFromClipboardToTerminal();
150168
+ }
150169
+ };
150170
+ document.addEventListener("keydown", this.clipboardHandler, true);
150171
+ this.resizeHandler = () => {
150172
+ if (this.isVisible) {
150173
+ this.applySpriteCardResponsiveStyles();
150174
+ this.updateMobileKeyboardButtonVisibility();
150175
+ this.debouncedRefit();
150176
+ }
150177
+ };
150178
+ window.addEventListener("resize", this.resizeHandler);
150179
+ this.resizeObserver = new ResizeObserver(() => {
150180
+ if (this.isVisible) {
150181
+ this.applySpriteCardResponsiveStyles();
150182
+ this.updateMobileKeyboardButtonVisibility();
150183
+ this.debouncedRefit();
150184
+ }
150185
+ });
150186
+ if (this.terminalDiv) {
150187
+ this.resizeObserver.observe(this.terminalDiv);
150188
+ }
150189
+ }
150190
+ /**
150191
+ * Re-register the xterm `onData` handler with a fresh closure that captures
150192
+ * the bound `agentId` and `officeId` at registration time. Disposes any
150193
+ * previous registration so stale closures cannot send user keystrokes to
150194
+ * the wrong agent during an agent switch (feature 002, C3/V6).
150195
+ */
150196
+ registerOnDataHandler(boundAgentId, boundOfficeId) {
150197
+ if (!this.terminal) return;
150198
+ this.onDataDisposable?.dispose();
150199
+ this.onDataDisposable = null;
150200
+ const result = this.terminal.onData((data) => {
149535
150201
  if (this.isReadOnly) return;
149536
- if (!this.currentAgentId || !window.copilotBridge) return;
150202
+ if (this.isSwitchingAgent) return;
150203
+ if (!window.copilotBridge) return;
149537
150204
  let outbound = "";
149538
150205
  let shouldStartSlashNewSession = false;
149539
150206
  for (const ch of data) {
@@ -149557,30 +150224,98 @@ WARNING: This link could potentially be dangerous`)) {
149557
150224
  outbound += ch;
149558
150225
  }
149559
150226
  if (outbound.length > 0) {
149560
- window.copilotBridge.terminalWrite(this.getOfficeId(), this.currentAgentId, outbound);
150227
+ window.copilotBridge.terminalWrite(boundOfficeId, boundAgentId, outbound);
149561
150228
  }
149562
150229
  if (shouldStartSlashNewSession) {
149563
- this.fetchSessionId(this.currentAgentId);
150230
+ this.fetchSessionId(boundAgentId);
149564
150231
  }
149565
150232
  });
149566
- this.resizeHandler = () => {
149567
- if (this.isVisible) {
149568
- this.applySpriteCardResponsiveStyles();
149569
- this.updateMobileKeyboardButtonVisibility();
149570
- this.debouncedRefit();
149571
- }
150233
+ this.onDataDisposable = result && typeof result.dispose === "function" ? result : null;
150234
+ }
150235
+ /**
150236
+ * Spec 004: terminal right-click context menu with Copy (enabled iff
150237
+ * non-empty selection) and Paste (always enabled). Built once, reused
150238
+ * across selections; dismissed on any outside mousedown or Escape.
150239
+ */
150240
+ installTerminalContextMenu() {
150241
+ if (!this.terminal || !this.terminalDiv) return;
150242
+ const menu = document.createElement("div");
150243
+ menu.id = "terminal-context-menu";
150244
+ menu.style.cssText = `
150245
+ position: fixed;
150246
+ display: none;
150247
+ z-index: ${ZIndex.TERMINAL_SPRITE_CARD + 10};
150248
+ min-width: 160px;
150249
+ background: #1c1c2a;
150250
+ border: 1px solid #3a3a55;
150251
+ border-radius: 6px;
150252
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.55);
150253
+ padding: 4px 0;
150254
+ font-family: 'Cascadia Code', Consolas, monospace;
150255
+ font-size: 13px;
150256
+ color: #cfd0e0;
150257
+ user-select: none;
150258
+ `;
150259
+ const makeItem = (label, onClick) => {
150260
+ const item = document.createElement("div");
150261
+ item.textContent = label;
150262
+ item.style.cssText = `
150263
+ padding: 6px 14px;
150264
+ cursor: pointer;
150265
+ `;
150266
+ item.dataset.enabled = "true";
150267
+ item.addEventListener("mouseenter", () => {
150268
+ if (item.dataset.enabled === "true") item.style.background = "#2a2a45";
150269
+ });
150270
+ item.addEventListener("mouseleave", () => {
150271
+ item.style.background = "";
150272
+ });
150273
+ item.addEventListener("mousedown", (e) => {
150274
+ e.preventDefault();
150275
+ e.stopPropagation();
150276
+ });
150277
+ item.addEventListener("click", (e) => {
150278
+ e.preventDefault();
150279
+ e.stopPropagation();
150280
+ if (item.dataset.enabled !== "true") return;
150281
+ this.hideTerminalContextMenu();
150282
+ onClick();
150283
+ });
150284
+ return item;
149572
150285
  };
149573
- window.addEventListener("resize", this.resizeHandler);
149574
- this.resizeObserver = new ResizeObserver(() => {
149575
- if (this.isVisible) {
149576
- this.applySpriteCardResponsiveStyles();
149577
- this.updateMobileKeyboardButtonVisibility();
149578
- this.debouncedRefit();
149579
- }
150286
+ const copyItem = makeItem("Copy", () => {
150287
+ const selection = this.terminal?.hasSelection() ? this.terminal.getSelection() ?? "" : "";
150288
+ void this.copyToClipboard(selection, "live");
149580
150289
  });
149581
- if (this.terminalDiv) {
149582
- this.resizeObserver.observe(this.terminalDiv);
149583
- }
150290
+ const pasteItem = makeItem("Paste", () => {
150291
+ void this.pasteFromClipboardToTerminal();
150292
+ });
150293
+ menu.appendChild(copyItem);
150294
+ menu.appendChild(pasteItem);
150295
+ document.body.appendChild(menu);
150296
+ this.terminalContextMenu = menu;
150297
+ this.terminalDiv.addEventListener("contextmenu", (event) => {
150298
+ if (!this.isVisible) return;
150299
+ event.preventDefault();
150300
+ const vw = window.innerWidth;
150301
+ const vh = window.innerHeight;
150302
+ menu.style.display = "block";
150303
+ const rect = menu.getBoundingClientRect();
150304
+ const left = Math.min(event.clientX, vw - rect.width - 4);
150305
+ const top = Math.min(event.clientY, vh - rect.height - 4);
150306
+ menu.style.left = `${Math.max(0, left)}px`;
150307
+ menu.style.top = `${Math.max(0, top)}px`;
150308
+ });
150309
+ this.terminalContextMenuDismiss = (e) => {
150310
+ if (!this.terminalContextMenu || this.terminalContextMenu.style.display === "none") return;
150311
+ if (e instanceof KeyboardEvent && e.key !== "Escape") return;
150312
+ this.hideTerminalContextMenu();
150313
+ };
150314
+ document.addEventListener("mousedown", this.terminalContextMenuDismiss, true);
150315
+ document.addEventListener("keydown", this.terminalContextMenuDismiss, true);
150316
+ }
150317
+ hideTerminalContextMenu() {
150318
+ if (this.terminalContextMenu) this.terminalContextMenu.style.display = "none";
149584
150319
  }
149585
150320
  /** Toggle between half-width and full-width terminal panel. */
149586
150321
  toggleFullWidth() {
@@ -149681,15 +150416,12 @@ WARNING: This link could potentially be dangerous`)) {
149681
150416
  }
149682
150417
  /** Give keyboard focus to the terminal. Safe to call when already focused. */
149683
150418
  focusTerminal() {
149684
- console.log("[TerminalOverlay] focusTerminal() \u2014 delegating to InputManager");
149685
150419
  this.inputManager.switchToTerminal(
149686
150420
  "TerminalOverlay.focusTerminal()",
149687
150421
  () => this.handleNewSession(),
149688
150422
  () => this.toggleFullWidth()
149689
150423
  );
149690
- if (window.__copilotOfficeMobileModeActive?.() === true) {
149691
- this.terminal?.focus();
149692
- }
150424
+ this.terminal?.focus();
149693
150425
  this.inputManager.focusTerminalXterm(this.terminal);
149694
150426
  if (this.currentAgent) {
149695
150427
  this.scene.game.events.emit("npc:highlight", this.currentAgent.id);
@@ -149704,7 +150436,6 @@ WARNING: This link could potentially be dangerous`)) {
149704
150436
  }
149705
150437
  /** Give keyboard focus back to the game canvas. Safe to call when already blurred. */
149706
150438
  blurTerminal() {
149707
- console.log("[TerminalOverlay] blurTerminal() \u2014 delegating to InputManager");
149708
150439
  const mobileLocked = window.__copilotOfficeMobileModeActive?.() === true;
149709
150440
  if (mobileLocked) {
149710
150441
  this.inputManager.switchToNone("TerminalOverlay.blurTerminal() mobile lock");
@@ -149734,7 +150465,12 @@ WARNING: This link could potentially be dangerous`)) {
149734
150465
  }
149735
150466
  setupKeyboardHandler() {
149736
150467
  }
150468
+ // Spec 008-smoke: expose state for the e2e debug hook.
150469
+ getActiveAgentId() {
150470
+ return this.currentAgentId;
150471
+ }
149737
150472
  hide() {
150473
+ this.hideTerminalContextMenu();
149738
150474
  if (this.container) {
149739
150475
  this.container.style.display = "none";
149740
150476
  }
@@ -149745,8 +150481,6 @@ WARNING: This link could potentially be dangerous`)) {
149745
150481
  this.isVisible = false;
149746
150482
  this.isReadOnly = false;
149747
150483
  this.pendingInputLine = "";
149748
- this.awaitingSessionIdRefresh = false;
149749
- this.clearSessionRefreshTimers();
149750
150484
  this.closeHistoryPopover();
149751
150485
  this.restorePanelLayout();
149752
150486
  this.inputManager.deactivateTerminalF10();
@@ -149785,9 +150519,26 @@ WARNING: This link could potentially be dangerous`)) {
149785
150519
  return false;
149786
150520
  }
149787
150521
  destroy() {
150522
+ this.hideTerminalContextMenu();
150523
+ if (this.clipboardHandler) {
150524
+ document.removeEventListener("keydown", this.clipboardHandler, true);
150525
+ this.clipboardHandler = null;
150526
+ }
150527
+ if (this.terminalContextMenuDismiss) {
150528
+ document.removeEventListener("mousedown", this.terminalContextMenuDismiss, true);
150529
+ document.removeEventListener("keydown", this.terminalContextMenuDismiss, true);
150530
+ this.terminalContextMenuDismiss = null;
150531
+ }
150532
+ if (this.terminalContextMenu && this.terminalContextMenu.parentNode) {
150533
+ try {
150534
+ this.terminalContextMenu.parentNode.removeChild(this.terminalContextMenu);
150535
+ } catch {
150536
+ }
150537
+ }
150538
+ this.terminalContextMenu = null;
150539
+ this.onDataDisposable?.dispose();
150540
+ this.onDataDisposable = null;
149788
150541
  this.clearRefitTimers();
149789
- this.awaitingSessionIdRefresh = false;
149790
- this.clearSessionRefreshTimers();
149791
150542
  if (this.resizeHandler) {
149792
150543
  window.removeEventListener("resize", this.resizeHandler);
149793
150544
  this.resizeHandler = null;
@@ -149800,11 +150551,23 @@ WARNING: This link could potentially be dangerous`)) {
149800
150551
  this.terminal.dispose();
149801
150552
  }
149802
150553
  if (this.container && this.container.parentNode) {
149803
- this.container.parentNode.removeChild(this.container);
150554
+ try {
150555
+ this.container.parentNode.removeChild(this.container);
150556
+ } catch {
150557
+ }
149804
150558
  }
149805
- if (this.spriteCardElement && this.spriteCardElement.parentNode) {
149806
- this.spriteCardElement.parentNode.removeChild(this.spriteCardElement);
150559
+ try {
150560
+ if (this.spriteCardElement && this.spriteCardElement.parentNode) {
150561
+ this.spriteCardElement.parentNode.removeChild(this.spriteCardElement);
150562
+ }
150563
+ } catch {
149807
150564
  }
150565
+ try {
150566
+ const fallback = document.getElementById("sprite-card");
150567
+ if (fallback) fallback.remove();
150568
+ } catch {
150569
+ }
150570
+ this.spriteCardElement = null;
149808
150571
  if (window.copilotBridge) {
149809
150572
  window.copilotBridge.removeTerminalListeners();
149810
150573
  }
@@ -150949,6 +151712,16 @@ WARNING: This link could potentially be dangerous`)) {
150949
151712
  const meta = cachedSessionMeta[agent.id];
150950
151713
  const hasSession = liveStatus?.state === "active";
150951
151714
  const metaTitle = meta?.title || "";
151715
+ const metaSessionId = meta?.sessionId || "";
151716
+ const sessionIdBadgeHtml = metaSessionId ? `<div class="session-id-badge" data-agent="${agent.id}" data-session-id="${metaSessionId}" title="Click to copy: ${metaSessionId}" style="
151717
+ display: inline-block; align-self: flex-start;
151718
+ font-family: ui-monospace, Menlo, Consolas, monospace;
151719
+ font-size: 10px; color: #8ec3ff;
151720
+ background: #1a2030; border: 1px solid #2a3550;
151721
+ padding: 1px 6px; border-radius: 3px;
151722
+ cursor: pointer; user-select: text;
151723
+ max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
151724
+ ">${metaSessionId}</div>` : "";
150952
151725
  const sessionPanelHtml = hasSession ? `
150953
151726
  <div class="session-meta-panel" data-agent="${agent.id}" style="
150954
151727
  flex: 2; min-width: 0;
@@ -150965,6 +151738,7 @@ WARNING: This link could potentially be dangerous`)) {
150965
151738
  overflow: hidden; text-overflow: ellipsis;
150966
151739
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
150967
151740
  " title="${metaTitle ? metaTitle.replace(/"/g, "&quot;") : "Click to set title"}">${metaTitle || "Untitled session"}</div>
151741
+ ${sessionIdBadgeHtml}
150968
151742
  <button class="session-new-btn" data-agent="${agent.id}" style="
150969
151743
  margin-top: 2px;
150970
151744
  align-self: flex-start;
@@ -150972,6 +151746,12 @@ WARNING: This link could potentially be dangerous`)) {
150972
151746
  font-size: ${t.sessionButton}; padding: 4px 10px; border-radius: 4px;
150973
151747
  cursor: pointer; transition: background 0.15s, border-color 0.15s;
150974
151748
  " title="Start a new session for this agent">\u{1F504} New Session</button>
151749
+ <button class="session-close-btn" data-agent="${agent.id}" style="
151750
+ align-self: flex-start;
151751
+ background: #3a2a2a; border: 1px solid #6a4a4a; color: #f7a9a9;
151752
+ font-size: ${t.sessionButton}; padding: 4px 10px; border-radius: 4px;
151753
+ cursor: pointer; transition: background 0.15s, border-color 0.15s;
151754
+ " title="Close this agent's session (agent returns to slacking)">\u2716 Close Session</button>
150975
151755
  <div style="display: flex; justify-content: flex-end;">
150976
151756
  <button class="session-edit-btn" data-agent="${agent.id}" style="
150977
151757
  background: none; border: 1px solid #333; color: #667;
@@ -151090,6 +151870,10 @@ WARNING: This link could potentially be dangerous`)) {
151090
151870
  context.emitOpenTerminal(agentId);
151091
151871
  },
151092
151872
  handleMetaPanelClick(target, agentId, context) {
151873
+ if (target.closest(".session-close-btn")) {
151874
+ context.closeSession(agentId);
151875
+ return;
151876
+ }
151093
151877
  if (target.closest(".session-new-btn")) {
151094
151878
  context.startNewSession(agentId);
151095
151879
  return;
@@ -151108,6 +151892,7 @@ WARNING: This link could potentially be dangerous`)) {
151108
151892
  "src/layouts/fleet/FleetDashboard.ts"() {
151109
151893
  "use strict";
151110
151894
  init_types();
151895
+ init_agents();
151111
151896
  fleetDashboard = {
151112
151897
  renderCards(ctx) {
151113
151898
  const { agents, office, selectedAgentId, formatElapsed, formatRelativeTime } = ctx;
@@ -151157,7 +151942,7 @@ WARNING: This link could potentially be dangerous`)) {
151157
151942
  }
151158
151943
  const colorHex = "#" + agent.color.toString(16).padStart(6, "0");
151159
151944
  const isSelected = agent.id === selectedAgentId;
151160
- const isArthur = agent.id === "architect";
151945
+ const isArthur = agent.id === ARCHITECT_AGENT_ID;
151161
151946
  const borderColor = isSelected ? "#4a5a7a" : "#252540";
151162
151947
  const bgColor = isSelected ? "#1a1e2e" : "#13131f";
151163
151948
  const cursor = isArthur ? "pointer" : "default";
@@ -151260,9 +152045,10 @@ WARNING: This link could potentially be dangerous`)) {
151260
152045
  var init_FleetClickHandler = __esm({
151261
152046
  "src/layouts/fleet/FleetClickHandler.ts"() {
151262
152047
  "use strict";
152048
+ init_agents();
151263
152049
  fleetClickHandler = {
151264
152050
  handleCardClick(agentId, context) {
151265
- if (agentId === "architect") {
152051
+ if (agentId === ARCHITECT_AGENT_ID) {
151266
152052
  context.setSelectedAgent(agentId);
151267
152053
  context.emitOpenTerminal(agentId);
151268
152054
  return;
@@ -151278,7 +152064,7 @@ WARNING: This link could potentially be dangerous`)) {
151278
152064
  function getLayout(layout) {
151279
152065
  return layouts[layout] ?? layouts["default"];
151280
152066
  }
151281
- var layouts;
152067
+ var defaultBehaviors, fleetBehaviors, layouts;
151282
152068
  var init_layouts = __esm({
151283
152069
  "src/layouts/index.ts"() {
151284
152070
  "use strict";
@@ -151287,16 +152073,30 @@ WARNING: This link could potentially be dangerous`)) {
151287
152073
  init_DefaultClickHandler();
151288
152074
  init_FleetDashboard();
151289
152075
  init_FleetClickHandler();
152076
+ defaultBehaviors = {
152077
+ supportsReserveAgents: true,
152078
+ restrictsInteractionToArchitect: false,
152079
+ hasPlayerPcTerminal: true,
152080
+ supportsFleetExecution: false
152081
+ };
152082
+ fleetBehaviors = {
152083
+ supportsReserveAgents: false,
152084
+ restrictsInteractionToArchitect: true,
152085
+ hasPlayerPcTerminal: false,
152086
+ supportsFleetExecution: true
152087
+ };
151290
152088
  layouts = {
151291
152089
  "default": {
151292
152090
  agents: AGENTS,
151293
152091
  dashboard: defaultDashboard,
151294
- clickHandler: defaultClickHandler
152092
+ clickHandler: defaultClickHandler,
152093
+ behaviors: defaultBehaviors
151295
152094
  },
151296
152095
  "fleet-vteam": {
151297
152096
  agents: FLEET_AGENTS,
151298
152097
  dashboard: fleetDashboard,
151299
- clickHandler: fleetClickHandler
152098
+ clickHandler: fleetClickHandler,
152099
+ behaviors: fleetBehaviors
151300
152100
  }
151301
152101
  };
151302
152102
  }
@@ -151640,30 +152440,27 @@ WARNING: This link could potentially be dangerous`)) {
151640
152440
  this.terminal.deactivateF10();
151641
152441
  }
151642
152442
  /**
151643
- * Focus the xterm Terminal instance with retry. Includes the 100 ms delay
151644
- * required for reliable focus transfer after the DOM has updated, plus up to
151645
- * 2 retries with increasing backoff if initial focus doesn't stick.
152443
+ * Focus the xterm Terminal instance. Calls focus() synchronously for
152444
+ * immediate keyboard capture, then verifies after a short delay and retries
152445
+ * if the DOM wasn't ready (e.g. display transition still in flight).
151646
152446
  */
151647
152447
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
151648
152448
  focusTerminalXterm(terminal) {
151649
- console.log(`[InputManager] focusTerminalXterm() scheduled (+100ms) | time: ${Date.now()}`);
151650
- const attempt = (n, delay) => {
152449
+ terminal?.focus();
152450
+ const verify = (n, delay) => {
151651
152451
  setTimeout(() => {
151652
- terminal?.focus();
151653
- console.log(`[InputManager] focusTerminalXterm() attempt ${n} executed | time: ${Date.now()}`);
151654
152452
  const textarea = terminal?.textarea;
151655
- if (textarea && document.activeElement !== textarea && n < 3) {
151656
- console.warn(`[InputManager] focus attempt ${n} didn't stick \u2014 retrying (+${delay * 2}ms)`);
151657
- attempt(n + 1, delay * 2);
152453
+ if (textarea && document.activeElement !== textarea) {
152454
+ terminal?.focus();
152455
+ if (n < 3) verify(n + 1, delay * 2);
151658
152456
  }
151659
152457
  }, delay);
151660
152458
  };
151661
- attempt(1, 100);
152459
+ verify(1, 50);
151662
152460
  }
151663
152461
  /** Blur the xterm Terminal instance (return DOM focus away from xterm). */
151664
152462
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
151665
152463
  blurTerminalXterm(terminal) {
151666
- console.log(`[InputManager] blurTerminalXterm() | time: ${Date.now()}`);
151667
152464
  terminal?.blur();
151668
152465
  }
151669
152466
  /** Enable or disable verbose per-keydown logging in GlobalInputListener. */
@@ -152323,11 +153120,72 @@ WARNING: This link could potentially be dangerous`)) {
152323
153120
  }
152324
153121
  });
152325
153122
 
153123
+ // src/config/agentAutoStart.ts
153124
+ function clone() {
153125
+ return { ...DEFAULT_AGENT_AUTO_START_SETTINGS };
153126
+ }
153127
+ function readStorage() {
153128
+ try {
153129
+ if (typeof localStorage !== "undefined") return localStorage;
153130
+ } catch {
153131
+ }
153132
+ return null;
153133
+ }
153134
+ function getAgentAutoStartSettings() {
153135
+ const storage = readStorage();
153136
+ if (!storage) return clone();
153137
+ let raw;
153138
+ try {
153139
+ raw = storage.getItem(STORAGE_KEY2);
153140
+ } catch {
153141
+ return clone();
153142
+ }
153143
+ if (raw === null || raw === void 0) return clone();
153144
+ let parsed;
153145
+ try {
153146
+ parsed = JSON.parse(raw);
153147
+ } catch {
153148
+ try {
153149
+ storage.removeItem(STORAGE_KEY2);
153150
+ } catch {
153151
+ }
153152
+ return clone();
153153
+ }
153154
+ if (!parsed || typeof parsed !== "object") return clone();
153155
+ const candidate = parsed.autoStartKnownAgents;
153156
+ if (typeof candidate !== "boolean") return clone();
153157
+ return { autoStartKnownAgents: candidate };
153158
+ }
153159
+ function setAgentAutoStartSettings(next) {
153160
+ const storage = readStorage();
153161
+ if (!storage) return;
153162
+ try {
153163
+ storage.setItem(
153164
+ STORAGE_KEY2,
153165
+ JSON.stringify({ autoStartKnownAgents: !!next.autoStartKnownAgents })
153166
+ );
153167
+ } catch {
153168
+ }
153169
+ }
153170
+ function shouldAutoStart() {
153171
+ return getAgentAutoStartSettings().autoStartKnownAgents === true;
153172
+ }
153173
+ var DEFAULT_AGENT_AUTO_START_SETTINGS, STORAGE_KEY2;
153174
+ var init_agentAutoStart = __esm({
153175
+ "src/config/agentAutoStart.ts"() {
153176
+ "use strict";
153177
+ DEFAULT_AGENT_AUTO_START_SETTINGS = {
153178
+ autoStartKnownAgents: true
153179
+ };
153180
+ STORAGE_KEY2 = "copilot-office-agent-auto-start";
153181
+ }
153182
+ });
153183
+
152326
153184
  // src/scenes/OfficeScene.ts
152327
153185
  function debugLog(scene, ...args) {
152328
153186
  if (scene.physics.world.drawDebug) console.log("[Debug]", ...args);
152329
153187
  }
152330
- var import_phaser8, ENABLE_DECORATIONS, ENABLE_BASKETBALL, ENABLE_GALAXIAN, ENABLE_ZOOM_BAR, PC_TERMINAL_ID, OfficeScene;
153188
+ var import_phaser8, DEBUG_COLD_START2, ENABLE_DECORATIONS, ENABLE_BASKETBALL, ENABLE_GALAXIAN, ENABLE_ZOOM_BAR, PC_TERMINAL_ID, OfficeScene;
152331
153189
  var init_OfficeScene = __esm({
152332
153190
  "src/scenes/OfficeScene.ts"() {
152333
153191
  "use strict";
@@ -152340,12 +153198,15 @@ WARNING: This link could potentially be dangerous`)) {
152340
153198
  init_agents();
152341
153199
  init_layouts();
152342
153200
  init_depths();
153201
+ init_zIndex();
152343
153202
  init_InputManager();
152344
153203
  init_officeManager();
152345
153204
  init_fleetTracker();
152346
153205
  init_fleetVisualizer();
152347
153206
  init_DirectionalSprite();
152348
153207
  init_CameraDragController();
153208
+ init_agentAutoStart();
153209
+ DEBUG_COLD_START2 = typeof window !== "undefined" && window.__COPILOT_OFFICE_DEBUG_COLD_START__ === true || false;
152349
153210
  ENABLE_DECORATIONS = false;
152350
153211
  ENABLE_BASKETBALL = false;
152351
153212
  ENABLE_GALAXIAN = true;
@@ -152386,6 +153247,10 @@ WARNING: This link could potentially be dangerous`)) {
152386
153247
  this.skipButton = null;
152387
153248
  this.isPortraitDashboardMode = false;
152388
153249
  }
153250
+ // Spec 008-smoke: accessor for the e2e debug hook in src/main.ts.
153251
+ getTerminalOverlay() {
153252
+ return this.terminalOverlay;
153253
+ }
152389
153254
  setAnimating(value) {
152390
153255
  this.animating = value;
152391
153256
  this.game.registry.set("animating", value);
@@ -152471,13 +153336,13 @@ WARNING: This link could potentially be dangerous`)) {
152471
153336
  } else {
152472
153337
  this.triggerEntrance();
152473
153338
  }
152474
- const arthurNPC = this.npcs.find((n) => n.config.id === "architect");
153339
+ const arthurNPC = this.npcs.find((n) => n.config.id === ARCHITECT_AGENT_ID);
152475
153340
  if (arthurNPC) {
152476
153341
  arthurNPC.updateAgentStatus(void 0);
152477
153342
  }
152478
153343
  const seatedNpcs = [];
152479
153344
  for (const agent of AGENTS) {
152480
- if (agent.id === "architect") continue;
153345
+ if (agent.id === ARCHITECT_AGENT_ID) continue;
152481
153346
  const npc = this.npcs.find((n) => n.config.id === agent.id);
152482
153347
  if (!npc) continue;
152483
153348
  const deskX = npc.config.position.x * this.tileSize + this.tileSize / 2;
@@ -152491,7 +153356,7 @@ WARNING: This link could potentially be dangerous`)) {
152491
153356
  const showAllBadges = () => {
152492
153357
  seatedNpcs.forEach((n) => n.setBadgeVisible(true));
152493
153358
  };
152494
- this.triggerAgentWalkIn(["architect"], showAllBadges);
153359
+ this.triggerAgentWalkIn([ARCHITECT_AGENT_ID], showAllBadges);
152495
153360
  }
152496
153361
  });
152497
153362
  this.basketballGame = new BasketballGame(this);
@@ -152592,7 +153457,7 @@ WARNING: This link could potentially be dangerous`)) {
152592
153457
  this.openPlayerPcTerminal();
152593
153458
  return;
152594
153459
  }
152595
- if (this.currentLayout === "fleet-vteam" && agentId !== "architect") return;
153460
+ if (getLayout(this.currentLayout).behaviors.restrictsInteractionToArchitect && agentId !== ARCHITECT_AGENT_ID) return;
152596
153461
  const agents = getLayout(this.currentLayout).agents;
152597
153462
  const agent = agents.find((a) => a.id === agentId);
152598
153463
  if (agent) this.startConversation(agent);
@@ -152787,6 +153652,14 @@ WARNING: This link could potentially be dangerous`)) {
152787
153652
  this.game.events.off("zoom:change");
152788
153653
  this.cameraDrag?.destroy();
152789
153654
  this.inputManager.destroy();
153655
+ if (DEBUG_SPRITE_SERIOUS) {
153656
+ console.log("[OfficeScene] shutdown destroying terminalOverlay");
153657
+ }
153658
+ try {
153659
+ this.terminalOverlay?.destroy();
153660
+ } catch (e) {
153661
+ console.warn("[OfficeScene] shutdown overlay destroy failed", e);
153662
+ }
152790
153663
  }, this);
152791
153664
  }
152792
153665
  startBackgroundMusic() {
@@ -153682,7 +154555,7 @@ WARNING: This link could potentially be dangerous`)) {
153682
154555
  font-family: 'Cascadia Code', Consolas, monospace;
153683
154556
  font-size: 16px;
153684
154557
  font-weight: bold;
153685
- z-index: 150;
154558
+ z-index: ${ZIndex.OFFICE_SCENE_OVERLAY};
153686
154559
  transition: background 0.15s;
153687
154560
  `;
153688
154561
  btn.addEventListener("mouseenter", () => {
@@ -153808,7 +154681,7 @@ WARNING: This link could potentially be dangerous`)) {
153808
154681
  const isReEntry = !this.fleetPrompt;
153809
154682
  console.log(`[OfficeScene] initFleetPipeline: attachOfficeId=${attachOfficeId}, sourceOfficeId=${this.fleetSourceOfficeId}, currentOfficeId=${officeManager.currentOfficeId}, isReEntry=${isReEntry}`);
153810
154683
  try {
153811
- this.fleetTracker = new FleetTracker("architect", attachOfficeId);
154684
+ this.fleetTracker = new FleetTracker(ARCHITECT_AGENT_ID, attachOfficeId);
153812
154685
  await this.fleetTracker.startTracking();
153813
154686
  this.fleetVisualizer = new FleetVisualizer(
153814
154687
  this.fleetTracker,
@@ -153821,8 +154694,8 @@ WARNING: This link could potentially be dangerous`)) {
153821
154694
  const fleetOfficeId = officeManager.currentOfficeId;
153822
154695
  if (fleetOfficeId && window.copilotBridge?.terminalWrite) {
153823
154696
  const fleetCmd = `/fleet ${this.fleetPrompt}\r`;
153824
- console.log(`[OfficeScene] Sending /fleet to ${fleetOfficeId}:architect (${fleetCmd.length} chars)`);
153825
- const result = await window.copilotBridge.terminalWrite(fleetOfficeId, "architect", fleetCmd);
154697
+ console.log(`[OfficeScene] Sending /fleet to ${fleetOfficeId}:${ARCHITECT_AGENT_ID} (${fleetCmd.length} chars)`);
154698
+ const result = await window.copilotBridge.terminalWrite(fleetOfficeId, ARCHITECT_AGENT_ID, fleetCmd);
153826
154699
  console.log(`[OfficeScene] /fleet command result:`, result);
153827
154700
  } else {
153828
154701
  console.warn("[OfficeScene] Cannot send /fleet \u2014 no officeId or copilotBridge");
@@ -153872,23 +154745,49 @@ WARNING: This link could potentially be dangerous`)) {
153872
154745
  });
153873
154746
  }
153874
154747
  async preStartAgentSessions() {
154748
+ if (!shouldAutoStart()) {
154749
+ if (DEBUG_COLD_START2) {
154750
+ console.log("[OfficeScene] preStart skipped (autoStartKnownAgents=false)");
154751
+ }
154752
+ return;
154753
+ }
153875
154754
  if (typeof window !== "undefined" && window.copilotBridge) {
153876
154755
  const oid = officeManager.currentOfficeId || "office-0";
153877
- const startAgent = async (agentId, label) => {
153878
- const agent = AGENTS.find((a) => a.id === agentId);
153879
- const savedSessionId = await window.copilotBridge.getSessionId(oid, agentId);
154756
+ const startAgent = async (agentConfig, label) => {
154757
+ const startedAt = Date.now();
154758
+ const savedSessionId = await window.copilotBridge.getSessionId(oid, agentConfig.id);
153880
154759
  if (savedSessionId) {
153881
154760
  console.log(`[CopilotOffice] Resuming ${label} session: ${savedSessionId}`);
153882
154761
  } else {
153883
154762
  console.log(`[CopilotOffice] Starting new ${label} session (no saved session found)`);
153884
154763
  }
153885
- await window.copilotBridge.terminalStart(oid, agentId, agent?.workingDir || officeManager.getCurrentWorkingDirectory());
154764
+ const result = await window.copilotBridge.terminalStart(
154765
+ oid,
154766
+ agentConfig.id,
154767
+ agentConfig.workingDir || officeManager.getCurrentWorkingDirectory()
154768
+ );
153886
154769
  console.log(`[CopilotOffice] ${label} session ready`);
154770
+ if (DEBUG_COLD_START2) {
154771
+ const sessionId = (result && result.sessionId) ?? "unknown";
154772
+ const elapsedMs = Date.now() - startedAt;
154773
+ console.log(
154774
+ `[OfficeScene] preStart agent=${agentConfig.id} sessionId=${sessionId} elapsedMs=${elapsedMs}`
154775
+ );
154776
+ }
153887
154777
  };
153888
- const agentsToStart = AGENTS.slice(0, 2);
153889
- await Promise.all(
153890
- agentsToStart.map((a) => startAgent(a.id, `${a.name} (${a.description})`))
154778
+ const roster = getLayout(this.currentLayout).agents;
154779
+ const agentsToStart = roster.length > 0 ? roster : AGENTS;
154780
+ const results = await Promise.allSettled(
154781
+ agentsToStart.map((a) => startAgent(a, `${a.name} (${a.description})`))
153891
154782
  );
154783
+ for (let i = 0; i < results.length; i++) {
154784
+ const r = results[i];
154785
+ if (r.status === "rejected") {
154786
+ console.warn(
154787
+ `[CopilotOffice] preStart failed for ${agentsToStart[i].id}: ${String(r.reason)}`
154788
+ );
154789
+ }
154790
+ }
153892
154791
  }
153893
154792
  }
153894
154793
  update() {
@@ -153941,7 +154840,7 @@ WARNING: This link could potentially be dangerous`)) {
153941
154840
  }
153942
154841
  }
153943
154842
  }
153944
- if (import_phaser8.default.Input.Keyboard.JustDown(this.dismissKey) && this.currentLayout === "default") {
154843
+ if (import_phaser8.default.Input.Keyboard.JustDown(this.dismissKey) && getLayout(this.currentLayout).behaviors.supportsReserveAgents) {
153945
154844
  const targetNPC = this.nearestNPC ?? (this.nearestDesk ? this.npcs.find((n) => n.config.id === this.nearestDesk.agentId) ?? null : null);
153946
154845
  if (targetNPC && !CORE_AGENT_IDS.has(targetNPC.config.id) && RESERVE_AGENT_DESK[targetNPC.config.id]) {
153947
154846
  this.dismissReserveAgent(targetNPC);
@@ -154129,7 +155028,7 @@ WARNING: This link could potentially be dangerous`)) {
154129
155028
  }
154130
155029
  startConversation(agent) {
154131
155030
  if (this.currentLayout === "fleet-vteam") {
154132
- if (agent.id !== "architect") return;
155031
+ if (agent.id !== ARCHITECT_AGENT_ID) return;
154133
155032
  this.game.events.emit("agent:interact", agent.id);
154134
155033
  this.terminalOverlay.show(
154135
155034
  agent,
@@ -154139,7 +155038,7 @@ WARNING: This link could potentially be dangerous`)) {
154139
155038
  );
154140
155039
  return;
154141
155040
  }
154142
- if (agent.id === "architect") {
155041
+ if (agent.id === ARCHITECT_AGENT_ID) {
154143
155042
  this.enterMeeting();
154144
155043
  return;
154145
155044
  }
@@ -154170,7 +155069,7 @@ WARNING: This link could potentially be dangerous`)) {
154170
155069
  );
154171
155070
  }
154172
155071
  openPlayerPcTerminal() {
154173
- if (this.currentLayout !== "default") return;
155072
+ if (!getLayout(this.currentLayout).behaviors.hasPlayerPcTerminal) return;
154174
155073
  const workingDir = officeManager.getCurrentWorkingDirectory();
154175
155074
  const pcTerminalConfig = {
154176
155075
  id: PC_TERMINAL_ID,
@@ -154494,7 +155393,8 @@ WARNING: This link could potentially be dangerous`)) {
154494
155393
  var init_planParser = __esm({
154495
155394
  "src/meeting/planParser.ts"() {
154496
155395
  "use strict";
154497
- DEFAULT_VALID_AGENT_IDS = ["generalist", "debugger", "admin"];
155396
+ init_agents();
155397
+ DEFAULT_VALID_AGENT_IDS = DEFAULT_PLAN_AGENT_IDS;
154498
155398
  }
154499
155399
  });
154500
155400
 
@@ -154563,7 +155463,7 @@ WARNING: This link could potentially be dangerous`)) {
154563
155463
  this.inputManager = new InputManager(this);
154564
155464
  this.planApproval = new PlanApprovalOverlay();
154565
155465
  this.terminalOverlay = new TerminalOverlay(this, this.inputManager, () => officeManager.currentOfficeId || "office-0");
154566
- const arthur = AGENTS.find((a) => a.id === "architect");
155466
+ const arthur = AGENTS.find((a) => a.id === ARCHITECT_AGENT_ID);
154567
155467
  if (arthur) {
154568
155468
  this.terminalOverlay.show(arthur, () => {
154569
155469
  console.log("[MeetingScene] Terminal closed");
@@ -154720,7 +155620,7 @@ WARNING: This link could potentially be dangerous`)) {
154720
155620
  if (typeof window === "undefined" || !window.copilotBridge) return;
154721
155621
  const handler = (_agentId, data) => {
154722
155622
  if (this.isExiting) return;
154723
- if (_agentId !== "architect") return;
155623
+ if (_agentId !== ARCHITECT_AGENT_ID) return;
154724
155624
  this.terminalOutputBuffer += data;
154725
155625
  this.time.delayedCall(500, () => {
154726
155626
  if (this.isExiting) return;
@@ -154746,7 +155646,7 @@ WARNING: This link could potentially be dangerous`)) {
154746
155646
  onRevise: (feedback) => {
154747
155647
  console.log("[MeetingScene] Revision requested:", feedback);
154748
155648
  if (window.copilotBridge) {
154749
- window.copilotBridge.terminalWrite(officeManager.currentOfficeId || "office-0", "architect", feedback + "\r");
155649
+ window.copilotBridge.terminalWrite(officeManager.currentOfficeId || "office-0", ARCHITECT_AGENT_ID, feedback + "\r");
154750
155650
  }
154751
155651
  this.meetingPlan = null;
154752
155652
  this.terminalOutputBuffer = "";
@@ -154859,6 +155759,14 @@ WARNING: This link could potentially be dangerous`)) {
154859
155759
  this.terminalOverlay?.hide();
154860
155760
  }
154861
155761
  this.inputManager?.destroy();
155762
+ if (DEBUG_SPRITE_SERIOUS) {
155763
+ console.log("[MeetingScene] shutdown destroying terminalOverlay");
155764
+ }
155765
+ try {
155766
+ this.terminalOverlay?.destroy();
155767
+ } catch (e) {
155768
+ console.warn("[MeetingScene] shutdown overlay destroy failed", e);
155769
+ }
154862
155770
  this.meetingPlan = null;
154863
155771
  this.terminalOutputBuffer = "";
154864
155772
  this.isExiting = false;
@@ -154889,6 +155797,7 @@ WARNING: This link could potentially be dangerous`)) {
154889
155797
  var init_ToastNotification = __esm({
154890
155798
  "src/ui/ToastNotification.ts"() {
154891
155799
  "use strict";
155800
+ init_zIndex();
154892
155801
  MAX_VISIBLE = 3;
154893
155802
  AUTO_DISMISS_MS = 5e3;
154894
155803
  ANIMATION_MS = 300;
@@ -154905,7 +155814,7 @@ WARNING: This link could potentially be dangerous`)) {
154905
155814
  top: 80px;
154906
155815
  left: 16px;
154907
155816
  width: calc(50% - 32px);
154908
- z-index: 9000;
155817
+ z-index: ${ZIndex.TOAST};
154909
155818
  pointer-events: none;
154910
155819
  display: flex;
154911
155820
  flex-direction: column;
@@ -155002,7 +155911,7 @@ WARNING: This link could potentially be dangerous`)) {
155002
155911
  // src/config/notifications.ts
155003
155912
  function loadNotificationSettings() {
155004
155913
  try {
155005
- const raw = localStorage.getItem(STORAGE_KEY2);
155914
+ const raw = localStorage.getItem(STORAGE_KEY3);
155006
155915
  if (!raw) return structuredClone(DEFAULT_SETTINGS);
155007
155916
  const parsed = JSON.parse(raw);
155008
155917
  const merged = structuredClone(DEFAULT_SETTINGS);
@@ -155022,13 +155931,13 @@ WARNING: This link could potentially be dangerous`)) {
155022
155931
  }
155023
155932
  }
155024
155933
  function saveNotificationSettings(settings) {
155025
- localStorage.setItem(STORAGE_KEY2, JSON.stringify(settings));
155934
+ localStorage.setItem(STORAGE_KEY3, JSON.stringify(settings));
155026
155935
  }
155027
155936
  function resetNotificationSettings() {
155028
- localStorage.removeItem(STORAGE_KEY2);
155937
+ localStorage.removeItem(STORAGE_KEY3);
155029
155938
  return structuredClone(DEFAULT_SETTINGS);
155030
155939
  }
155031
- var NOTIFICATION_EVENT_LABELS, DEFAULT_SETTINGS, STORAGE_KEY2;
155940
+ var NOTIFICATION_EVENT_LABELS, DEFAULT_SETTINGS, STORAGE_KEY3;
155032
155941
  var init_notifications = __esm({
155033
155942
  "src/config/notifications.ts"() {
155034
155943
  "use strict";
@@ -155088,7 +155997,7 @@ WARNING: This link could potentially be dangerous`)) {
155088
155997
  }
155089
155998
  }
155090
155999
  };
155091
- STORAGE_KEY2 = "copilot-notification-settings";
156000
+ STORAGE_KEY3 = "copilot-notification-settings";
155092
156001
  }
155093
156002
  });
155094
156003
 
@@ -155189,6 +156098,8 @@ WARNING: This link could potentially be dangerous`)) {
155189
156098
  "src/ui/SettingsPanel.ts"() {
155190
156099
  "use strict";
155191
156100
  init_notifications();
156101
+ init_agentAutoStart();
156102
+ init_zIndex();
155192
156103
  ALL_EVENT_TYPES = [
155193
156104
  "turnEnd",
155194
156105
  "askUser",
@@ -155222,7 +156133,7 @@ WARNING: This link could potentially be dangerous`)) {
155222
156133
  position: fixed;
155223
156134
  inset: 0;
155224
156135
  background: rgba(0,0,0,0.6);
155225
- z-index: 20000;
156136
+ z-index: ${ZIndex.SETTINGS};
155226
156137
  display: flex;
155227
156138
  align-items: center;
155228
156139
  justify-content: center;
@@ -155273,8 +156184,31 @@ WARNING: This link could potentially be dangerous`)) {
155273
156184
  </div>
155274
156185
 
155275
156186
  ${this.renderAudioSection()}
156187
+ ${this.renderAgentsSection()}
155276
156188
  ${this.renderNotificationsSection()}
155277
156189
  ${this.renderAboutSection()}
156190
+ `;
156191
+ }
156192
+ renderAgentsSection() {
156193
+ const settings = getAgentAutoStartSettings();
156194
+ return `
156195
+ <div class="settings-section" style="margin-bottom: 20px;">
156196
+ <h3 style="margin: 0 0 12px; font-size: 14px; color: #889; border-bottom: 1px solid #2a2a3e; padding-bottom: 8px;">
156197
+ \u{1F916} Agents
156198
+ </h3>
156199
+ <label style="display: flex; align-items: center; gap: 10px; padding: 8px 0; cursor: pointer;">
156200
+ <input
156201
+ type="checkbox"
156202
+ id="settings-agent-auto-start"
156203
+ ${settings.autoStartKnownAgents ? "checked" : ""}
156204
+ style="cursor: pointer; width: 15px; height: 15px;"
156205
+ />
156206
+ <span style="color: #aab; font-size: 13px;">Auto-start known agents</span>
156207
+ </label>
156208
+ <p style="margin: 4px 0 0 25px; font-size: 10px; color: #556;">
156209
+ When ON, agents with a saved session resume automatically on launch, office switch, and New Session.
156210
+ </p>
156211
+ </div>
155278
156212
  `;
155279
156213
  }
155280
156214
  renderAudioSection() {
@@ -155423,8 +156357,16 @@ WARNING: This link could potentially be dangerous`)) {
155423
156357
  bindEvents(panel) {
155424
156358
  panel.querySelector("#settings-close-btn")?.addEventListener("click", () => this.close());
155425
156359
  this.bindAudioEvents(panel);
156360
+ this.bindAgentsEvents(panel);
155426
156361
  this.bindNotificationEvents(panel);
155427
156362
  }
156363
+ bindAgentsEvents(panel) {
156364
+ const cb = panel.querySelector("#settings-agent-auto-start");
156365
+ if (!cb) return;
156366
+ cb.addEventListener("change", () => {
156367
+ setAgentAutoStartSettings({ autoStartKnownAgents: cb.checked });
156368
+ });
156369
+ }
155428
156370
  bindAudioEvents(panel) {
155429
156371
  const muteBtn = panel.querySelector("#settings-bgm-mute-btn");
155430
156372
  const slider = panel.querySelector("#settings-bgm-slider");
@@ -155463,7 +156405,7 @@ WARNING: This link could potentially be dangerous`)) {
155463
156405
  settings.events[eventType].enabled = checkbox.checked;
155464
156406
  });
155465
156407
  });
155466
- panel.querySelectorAll('input[type="checkbox"]:not([data-field="enabled"])').forEach((checkbox) => {
156408
+ panel.querySelectorAll('input[type="checkbox"][data-event]:not([data-field="enabled"])').forEach((checkbox) => {
155467
156409
  checkbox.addEventListener("change", () => {
155468
156410
  const eventType = checkbox.dataset.event;
155469
156411
  const field = checkbox.dataset.field;
@@ -155518,6 +156460,7 @@ WARNING: This link could potentially be dangerous`)) {
155518
156460
  "src/ui/SpriteCustomizerPanel.ts"() {
155519
156461
  "use strict";
155520
156462
  init_playerCustomization();
156463
+ init_zIndex();
155521
156464
  COLOR_REGIONS = ["hair", "skin", "suit", "tie", "pants", "shoes"];
155522
156465
  SpriteCustomizerPanel = class {
155523
156466
  constructor(options) {
@@ -155526,15 +156469,18 @@ WARNING: This link could potentially be dangerous`)) {
155526
156469
  this.outsideClickHandler = null;
155527
156470
  this.escapeHandler = null;
155528
156471
  this.onColorsChanged = options.onColorsChanged;
156472
+ this.onOpenCallback = options.onOpen;
156473
+ this.onCloseCallback = options.onClose;
155529
156474
  this.currentColors = loadPlayerColors();
155530
156475
  }
155531
156476
  show(anchorElement) {
156477
+ const wasOpen = this.container !== null;
155532
156478
  if (this.container) this.hide();
155533
156479
  this.currentColors = loadPlayerColors();
155534
156480
  this.container = document.createElement("div");
155535
156481
  this.container.style.cssText = `
155536
156482
  position: fixed;
155537
- z-index: 15000;
156483
+ z-index: ${ZIndex.SPRITE_CUSTOMIZER};
155538
156484
  width: 320px;
155539
156485
  background: #1a1a2e;
155540
156486
  border: 2px solid #333;
@@ -155558,8 +156504,10 @@ WARNING: This link could potentially be dangerous`)) {
155558
156504
  }
155559
156505
  this.previewImg = this.container.querySelector("#sprite-preview-img");
155560
156506
  this.bindEvents();
156507
+ if (!wasOpen) this.onOpenCallback?.();
155561
156508
  }
155562
156509
  hide() {
156510
+ const wasOpen = this.container !== null;
155563
156511
  if (this.outsideClickHandler) {
155564
156512
  document.removeEventListener("mousedown", this.outsideClickHandler, true);
155565
156513
  this.outsideClickHandler = null;
@@ -155573,6 +156521,7 @@ WARNING: This link could potentially be dangerous`)) {
155573
156521
  this.container = null;
155574
156522
  }
155575
156523
  this.previewImg = null;
156524
+ if (wasOpen) this.onCloseCallback?.();
155576
156525
  }
155577
156526
  toggle(anchorElement) {
155578
156527
  if (this.container) {
@@ -155746,6 +156695,12 @@ WARNING: This link could potentially be dangerous`)) {
155746
156695
  "use strict";
155747
156696
  import_xterm2 = __toESM(require_xterm());
155748
156697
  import_addon_fit2 = __toESM(require_addon_fit());
156698
+ init_zIndex();
156699
+ init_TerminalOverlay();
156700
+ init_clipboardToast();
156701
+ init_xtermStyles();
156702
+ init_terminalWheel();
156703
+ init_AutoStartCoordinator();
155749
156704
  SeriousTerminalController = class _SeriousTerminalController {
155750
156705
  constructor(host, options = {}) {
155751
156706
  this.historyPopover = null;
@@ -155761,6 +156716,15 @@ WARNING: This link could potentially be dangerous`)) {
155761
156716
  this.sessionId = null;
155762
156717
  this.activeOptions = null;
155763
156718
  this.isFullWidth = false;
156719
+ this.terminalContextMenu = null;
156720
+ this.terminalContextMenuDismiss = null;
156721
+ this.seriousInstanceId = String(_SeriousTerminalController.nextSeriousId++);
156722
+ // Spec 003 V13/V14: the onData callback registered on the xterm closes
156723
+ // over the office/agent ids captured at openAgentTerminal time, not the
156724
+ // live this.activeOfficeId/this.activeAgentId. Holding the disposable
156725
+ // here lets the next open() drop the previous binding before installing
156726
+ // a new one — exactly one live onData per controller at any moment.
156727
+ this.onDataDisposable = null;
155764
156728
  this.host = host;
155765
156729
  this.onClose = options.onClose;
155766
156730
  this.isFullWidth = localStorage.getItem(_SeriousTerminalController.FULL_WIDTH_STORAGE_KEY) === "true";
@@ -155819,13 +156783,13 @@ WARNING: This link could potentially be dangerous`)) {
155819
156783
  this.terminalOuterEl.style.cssText = `
155820
156784
  flex: 1;
155821
156785
  min-height: 0;
155822
- overflow: hidden;
156786
+ overflow: auto;
155823
156787
  background: #0d111b;
155824
156788
  padding: 10px;
155825
156789
  box-sizing: border-box;
155826
156790
  `;
155827
156791
  this.terminalDivEl = document.createElement("div");
155828
- this.terminalDivEl.style.cssText = "width: 100%; height: 100%; overflow: hidden;";
156792
+ this.terminalDivEl.style.cssText = "width: 100%; height: 100%;";
155829
156793
  this.terminalOuterEl.appendChild(this.terminalDivEl);
155830
156794
  this.terminalOuterEl.addEventListener("mousedown", () => this.terminal?.focus());
155831
156795
  this.spriteCardEl = document.createElement("div");
@@ -155969,9 +156933,29 @@ WARNING: This link could potentially be dangerous`)) {
155969
156933
  static {
155970
156934
  this.FULL_WIDTH_STORAGE_KEY = "agencyOffice:seriousTerminalFullWidth";
155971
156935
  }
156936
+ static {
156937
+ this.nextSeriousId = 0;
156938
+ }
155972
156939
  isVisible() {
155973
156940
  return this.visible;
155974
156941
  }
156942
+ // Spec 008-smoke: expose active agent id for the e2e debug hook.
156943
+ getActiveAgentId() {
156944
+ return this.activeAgentId ?? null;
156945
+ }
156946
+ // Spec 008-smoke (T10): expose the visible sprite-card + session-id panel
156947
+ // text so e2e tests can assert what the operator actually sees after agent
156948
+ // switches. Used to repro the user-reported "locked to one agent" symptom.
156949
+ getPanelSnapshot() {
156950
+ return {
156951
+ activeAgentId: this.activeAgentId ?? null,
156952
+ titleText: this.titleEl.textContent ?? "",
156953
+ spriteName: this.spriteNameEl.textContent ?? "",
156954
+ spriteSubtitle: this.spriteSubtitleEl.textContent ?? "",
156955
+ sessionIdText: this.sessionIdEl.textContent ?? "",
156956
+ sessionIdField: this.sessionId ?? null
156957
+ };
156958
+ }
155975
156959
  refreshCardFromOverview() {
155976
156960
  if (!this.visible || !this.activeAgentId) return;
155977
156961
  this.renderExactOverviewCard(this.activeAgentId);
@@ -155989,15 +156973,51 @@ WARNING: This link could potentially be dangerous`)) {
155989
156973
  this.openedAt = Date.now();
155990
156974
  this.sessionId = null;
155991
156975
  this.container.style.display = "flex";
155992
- this.terminal.clear();
155993
- this.titleEl.textContent = `${options.name} (${options.agentId})`;
155994
- this.subtitleEl.textContent = options.description;
155995
- this.updateSpriteCard(options);
155996
- void this.updateSessionTitle(options.officeId, options.agentId);
155997
- this.updateSessionIdDisplay();
155998
- this.setStatus("Opening...");
155999
- this.applyPanelLayout();
156000
- this.refitAndResize(options.officeId, options.agentId);
156976
+ try {
156977
+ this.terminal.clear();
156978
+ this.titleEl.textContent = `${options.name} (${options.agentId})`;
156979
+ this.subtitleEl.textContent = options.description;
156980
+ this.updateSpriteCard(options);
156981
+ void this.updateSessionTitle(options.officeId, options.agentId);
156982
+ this.updateSessionIdDisplay();
156983
+ this.setStatus("Opening...");
156984
+ this.applyPanelLayout();
156985
+ this.refitAndResize(options.officeId, options.agentId);
156986
+ } catch (err) {
156987
+ const message = `serious-mode open failed during render: ${err?.message || String(err)}`;
156988
+ try {
156989
+ this.setStatus(message);
156990
+ } catch {
156991
+ }
156992
+ try {
156993
+ this.terminal.writeln(`\r
156994
+ [render error: ${message}]\r
156995
+ `);
156996
+ } catch {
156997
+ }
156998
+ if (DEBUG_SPRITE_SERIOUS) {
156999
+ console.log(
157000
+ `[SeriousTerminalController] openAgentTerminal render failure (officeId=${options.officeId} agentId=${options.agentId}): ${message}`
157001
+ );
157002
+ } else {
157003
+ console.warn("[SeriousTerminalController] openAgentTerminal render failure", err);
157004
+ }
157005
+ }
157006
+ const boundOfficeId = options.officeId;
157007
+ const boundAgentId = options.agentId;
157008
+ try {
157009
+ this.onDataDisposable?.dispose();
157010
+ } catch {
157011
+ }
157012
+ this.onDataDisposable = this.terminal.onData((data) => {
157013
+ if (!window.copilotBridge) return;
157014
+ void window.copilotBridge.terminalWrite(boundOfficeId, boundAgentId, data);
157015
+ });
157016
+ if (DEBUG_SPRITE_SERIOUS) {
157017
+ console.log(
157018
+ `[SeriousTerminalController] onData rebound officeId=${boundOfficeId} agentId=${boundAgentId}`
157019
+ );
157020
+ }
156001
157021
  try {
156002
157022
  const exists = await window.copilotBridge.terminalExists(options.officeId, options.agentId);
156003
157023
  if (!exists) {
@@ -156049,6 +157069,21 @@ Terminal error: ${error?.message || String(error)}`);
156049
157069
  }
156050
157070
  async startNewSession(options) {
156051
157071
  if (!window.copilotBridge) return;
157072
+ const coordinator = getAutoStartCoordinator();
157073
+ if (coordinator) {
157074
+ try {
157075
+ await coordinator.replaceSession(options.officeId, options.agentId);
157076
+ } catch (err) {
157077
+ console.warn(
157078
+ `[SeriousTerminalController] replaceSession failed for ${options.agentId}: ${err?.message || String(err)}`
157079
+ );
157080
+ }
157081
+ const isCurrentView2 = this.visible && this.activeOfficeId === options.officeId && this.activeAgentId === options.agentId;
157082
+ if (isCurrentView2) {
157083
+ await this.openAgentTerminal(options);
157084
+ }
157085
+ return;
157086
+ }
156052
157087
  try {
156053
157088
  await window.copilotBridge.resetSession(options.officeId, options.agentId);
156054
157089
  } catch {
@@ -156083,6 +157118,12 @@ Terminal error: ${error?.message || String(error)}`);
156083
157118
  }
156084
157119
  this.visible = false;
156085
157120
  this.container.style.display = "none";
157121
+ this.hideTerminalContextMenu();
157122
+ try {
157123
+ this.onDataDisposable?.dispose();
157124
+ } catch {
157125
+ }
157126
+ this.onDataDisposable = null;
156086
157127
  this.activeOfficeId = null;
156087
157128
  this.activeAgentId = null;
156088
157129
  this.activeOptions = null;
@@ -156208,7 +157249,7 @@ Terminal error: ${error?.message || String(error)}`);
156208
157249
  border-radius: 8px;
156209
157250
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.45);
156210
157251
  padding: 10px 12px;
156211
- z-index: 10003;
157252
+ z-index: ${ZIndex.SERIOUS_TERMINAL};
156212
157253
  color: #c8d4ff;
156213
157254
  font-size: 12px;
156214
157255
  white-space: pre-wrap;
@@ -156244,17 +157285,166 @@ Terminal error: ${error?.message || String(error)}`);
156244
157285
  }
156245
157286
  copySessionId() {
156246
157287
  if (!this.sessionId) return;
156247
- navigator.clipboard.writeText(this.sessionId).then(() => {
157288
+ void this.copyToClipboard(this.sessionId, "session").then((success) => {
156248
157289
  const original = this.sessionIdEl.textContent;
156249
- this.sessionIdEl.textContent = "Copied!";
156250
- this.sessionIdEl.style.color = "#61d394";
157290
+ this.sessionIdEl.textContent = success ? "Copied!" : "Copy failed";
157291
+ this.sessionIdEl.style.color = success ? "#61d394" : "#ff6b6b";
156251
157292
  setTimeout(() => {
156252
157293
  this.sessionIdEl.textContent = original;
156253
157294
  this.sessionIdEl.style.color = "#8ec3ff";
156254
157295
  }, 900);
156255
- }).catch(() => {
156256
157296
  });
156257
157297
  }
157298
+ // Spec 006: diagnostic prefix so multi-instance and channel issues
157299
+ // are visible in the toast UI without opening DevTools.
157300
+ tag() {
157301
+ return `[S${this.seriousInstanceId}]`;
157302
+ }
157303
+ // Spec 005 + 006: single canonical clipboard write path via Electron main.
157304
+ // Diagnostic toast on every outcome.
157305
+ async copyToClipboard(text, source = "live") {
157306
+ const t = this.tag();
157307
+ if (!text) {
157308
+ showClipboardToast(`${t} empty selection`, "info");
157309
+ return false;
157310
+ }
157311
+ const bridge = window.copilotBridge;
157312
+ if (!bridge?.clipboardWriteText) {
157313
+ try {
157314
+ await navigator.clipboard.writeText(text);
157315
+ showClipboardToast(`${t} OK ${text.length} (fallback)`, "success");
157316
+ return true;
157317
+ } catch {
157318
+ showClipboardToast(`${t} no-bridge`, "error");
157319
+ return false;
157320
+ }
157321
+ }
157322
+ try {
157323
+ const r = await bridge.clipboardWriteText(text);
157324
+ if (r?.success === true) {
157325
+ showClipboardToast(`${t} OK ${text.length} (verified)`, "success");
157326
+ return true;
157327
+ }
157328
+ if (r?.verified === false) {
157329
+ showClipboardToast(`${t} verify-fail (wrote=${text.length})`, "error");
157330
+ } else {
157331
+ showClipboardToast(`${t} bridge-err: ${r?.error || "unknown"}`, "error");
157332
+ }
157333
+ return false;
157334
+ } catch (e) {
157335
+ showClipboardToast(`${t} bridge-err: ${e?.message || "threw"}`, "error");
157336
+ return false;
157337
+ }
157338
+ }
157339
+ // Spec 005: read OS clipboard via Electron main, forward to PTY.
157340
+ async pasteFromClipboardToTerminal() {
157341
+ if (!this.activeOfficeId || !this.activeAgentId || !window.copilotBridge) return;
157342
+ let text = "";
157343
+ try {
157344
+ const bridge = window.copilotBridge;
157345
+ if (bridge?.clipboardReadText) {
157346
+ const r = await bridge.clipboardReadText();
157347
+ if (r?.success) text = r.text || "";
157348
+ } else if (navigator.clipboard?.readText) {
157349
+ text = await navigator.clipboard.readText();
157350
+ }
157351
+ } catch (e) {
157352
+ showClipboardToast(`Paste failed: ${e?.message || "bridge threw"}`, "error");
157353
+ return;
157354
+ }
157355
+ if (!text) {
157356
+ showClipboardToast("Clipboard is empty", "info");
157357
+ return;
157358
+ }
157359
+ try {
157360
+ await window.copilotBridge.terminalWrite(this.activeOfficeId, this.activeAgentId, text);
157361
+ showClipboardToast(`Pasted ${text.length} char${text.length === 1 ? "" : "s"}`, "success");
157362
+ } catch (e) {
157363
+ showClipboardToast(`Paste failed: ${e?.message || "terminalWrite threw"}`, "error");
157364
+ }
157365
+ }
157366
+ /**
157367
+ * Spec 004: terminal right-click context menu (Copy / Paste). Built once
157368
+ * during attachTerminal, reused per right-click. Dismissed on outside
157369
+ * mousedown or Escape.
157370
+ */
157371
+ installTerminalContextMenu() {
157372
+ if (!this.terminal || !this.terminalDivEl) return;
157373
+ const menu = document.createElement("div");
157374
+ menu.id = "serious-terminal-context-menu";
157375
+ menu.style.cssText = `
157376
+ position: fixed;
157377
+ display: none;
157378
+ z-index: ${ZIndex.TERMINAL_SPRITE_CARD + 10};
157379
+ min-width: 160px;
157380
+ background: #1c1c2a;
157381
+ border: 1px solid #3a3a55;
157382
+ border-radius: 6px;
157383
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.55);
157384
+ padding: 4px 0;
157385
+ font-family: 'Cascadia Code', Consolas, monospace;
157386
+ font-size: 13px;
157387
+ color: #cfd0e0;
157388
+ user-select: none;
157389
+ `;
157390
+ const makeItem = (label, onClick) => {
157391
+ const item = document.createElement("div");
157392
+ item.textContent = label;
157393
+ item.style.cssText = `padding: 6px 14px; cursor: pointer;`;
157394
+ item.dataset.enabled = "true";
157395
+ item.addEventListener("mouseenter", () => {
157396
+ if (item.dataset.enabled === "true") item.style.background = "#2a2a45";
157397
+ });
157398
+ item.addEventListener("mouseleave", () => {
157399
+ item.style.background = "";
157400
+ });
157401
+ item.addEventListener("mousedown", (e) => {
157402
+ e.preventDefault();
157403
+ e.stopPropagation();
157404
+ });
157405
+ item.addEventListener("click", (e) => {
157406
+ e.preventDefault();
157407
+ e.stopPropagation();
157408
+ if (item.dataset.enabled !== "true") return;
157409
+ this.hideTerminalContextMenu();
157410
+ onClick();
157411
+ });
157412
+ return item;
157413
+ };
157414
+ const copyItem = makeItem("Copy", () => {
157415
+ const selection = this.terminal?.hasSelection() ? this.terminal.getSelection() ?? "" : "";
157416
+ void this.copyToClipboard(selection, "live");
157417
+ });
157418
+ const pasteItem = makeItem("Paste", () => {
157419
+ void this.pasteFromClipboardToTerminal();
157420
+ });
157421
+ menu.appendChild(copyItem);
157422
+ menu.appendChild(pasteItem);
157423
+ document.body.appendChild(menu);
157424
+ this.terminalContextMenu = menu;
157425
+ this.terminalDivEl.addEventListener("contextmenu", (event) => {
157426
+ if (!this.visible) return;
157427
+ event.preventDefault();
157428
+ const vw = window.innerWidth;
157429
+ const vh = window.innerHeight;
157430
+ menu.style.display = "block";
157431
+ const rect = menu.getBoundingClientRect();
157432
+ const left = Math.min(event.clientX, vw - rect.width - 4);
157433
+ const top = Math.min(event.clientY, vh - rect.height - 4);
157434
+ menu.style.left = `${Math.max(0, left)}px`;
157435
+ menu.style.top = `${Math.max(0, top)}px`;
157436
+ });
157437
+ this.terminalContextMenuDismiss = (e) => {
157438
+ if (!this.terminalContextMenu || this.terminalContextMenu.style.display === "none") return;
157439
+ if (e instanceof KeyboardEvent && e.key !== "Escape") return;
157440
+ this.hideTerminalContextMenu();
157441
+ };
157442
+ document.addEventListener("mousedown", this.terminalContextMenuDismiss, true);
157443
+ document.addEventListener("keydown", this.terminalContextMenuDismiss, true);
157444
+ }
157445
+ hideTerminalContextMenu() {
157446
+ if (this.terminalContextMenu) this.terminalContextMenu.style.display = "none";
157447
+ }
156258
157448
  renderAgentSprite(seed, baseColor) {
156259
157449
  const ctx = this.spriteCanvasEl.getContext("2d");
156260
157450
  if (!ctx) return;
@@ -156292,15 +157482,7 @@ Terminal error: ${error?.message || String(error)}`);
156292
157482
  return r << 16 | g << 8 | b;
156293
157483
  }
156294
157484
  ensureXtermStyles() {
156295
- if (document.getElementById("xterm-styles")) return;
156296
- const style = document.createElement("style");
156297
- style.id = "xterm-styles";
156298
- style.textContent = `
156299
- .xterm { height: 100%; }
156300
- .xterm-viewport { overflow-y: auto !important; }
156301
- #serious-terminal-container .xterm { height: 100%; }
156302
- `;
156303
- document.head.appendChild(style);
157485
+ ensureXtermStyles();
156304
157486
  }
156305
157487
  createTerminal() {
156306
157488
  this.terminal = new import_xterm2.Terminal({
@@ -156323,28 +157505,43 @@ Terminal error: ${error?.message || String(error)}`);
156323
157505
  this.terminal.loadAddon(this.fitAddon);
156324
157506
  this.terminalDivEl.id = "serious-terminal-container";
156325
157507
  this.terminal.open(this.terminalDivEl);
157508
+ const MOUSE_MODES = /* @__PURE__ */ new Set([1e3, 1002, 1003, 1006]);
157509
+ this.terminal.parser.registerCsiHandler({ prefix: "?", final: "h" }, (params) => {
157510
+ for (const p of params) {
157511
+ if (typeof p === "number" && MOUSE_MODES.has(p)) return true;
157512
+ }
157513
+ return false;
157514
+ });
157515
+ this.terminal.attachCustomWheelEventHandler((event) => {
157516
+ const term = this.terminal;
157517
+ if (!term) return true;
157518
+ if (term.buffer.active.type !== "alternate") return true;
157519
+ const seq = wheelToPtySequence(event.deltaY);
157520
+ if (!seq) return true;
157521
+ if (this.activeOfficeId && this.activeAgentId && window.copilotBridge) {
157522
+ void window.copilotBridge.terminalWrite(this.activeOfficeId, this.activeAgentId, seq);
157523
+ }
157524
+ return false;
157525
+ });
157526
+ this.installTerminalContextMenu();
156326
157527
  this.terminal.attachCustomKeyEventHandler((event) => {
156327
157528
  const isModifierPressed = event.ctrlKey || event.metaKey;
156328
157529
  const key = event.key.toLowerCase();
156329
157530
  if (event.type !== "keydown" || !isModifierPressed) return true;
156330
157531
  if (key === "c") {
156331
- const selectedText = this.terminal?.hasSelection() ? this.terminal.getSelection() : "";
156332
- if (!selectedText) {
157532
+ const selection = this.terminal?.hasSelection() ? this.terminal.getSelection() ?? "" : "";
157533
+ if (!selection) {
156333
157534
  return true;
156334
157535
  }
156335
157536
  event.preventDefault();
156336
157537
  event.stopPropagation();
156337
- navigator.clipboard.writeText(selectedText).catch(() => {
156338
- });
157538
+ void this.copyToClipboard(selection, "live");
156339
157539
  return false;
156340
157540
  }
156341
157541
  if (key === "v") {
156342
157542
  event.preventDefault();
156343
157543
  event.stopPropagation();
156344
- navigator.clipboard.readText().then((text) => {
156345
- if (text) this.terminal?.paste(text);
156346
- }).catch(() => {
156347
- });
157544
+ void this.pasteFromClipboardToTerminal();
156348
157545
  return false;
156349
157546
  }
156350
157547
  if (key === "f") {
@@ -156355,9 +157552,7 @@ Terminal error: ${error?.message || String(error)}`);
156355
157552
  }
156356
157553
  return true;
156357
157554
  });
156358
- this.terminal.onData((data) => {
156359
- if (!window.copilotBridge || !this.activeOfficeId || !this.activeAgentId) return;
156360
- void window.copilotBridge.terminalWrite(this.activeOfficeId, this.activeAgentId, data);
157555
+ this.terminal.onData((_data) => {
156361
157556
  });
156362
157557
  this.resizeHandler = () => {
156363
157558
  if (!this.visible || !this.activeOfficeId || !this.activeAgentId) return;
@@ -156443,6 +157638,48 @@ Terminal error: ${error?.message || String(error)}`);
156443
157638
  }
156444
157639
  });
156445
157640
 
157641
+ // src/util/toolStatus.ts
157642
+ function normalizeToolName(toolName) {
157643
+ return (toolName ?? "").trim().toLowerCase().replace(/[\s-]+/g, "_");
157644
+ }
157645
+ function isAskUserTool(toolName, status) {
157646
+ const normalized = normalizeToolName(toolName);
157647
+ if (normalized === "ask_user" || normalized === "askuser") return true;
157648
+ const statusText = (status ?? "").toLowerCase();
157649
+ return statusText.includes("waiting for your answer") || statusText.includes("waiting on user input");
157650
+ }
157651
+ function nextSubStateAfterToolComplete(remainingTools) {
157652
+ if (remainingTools.length === 0) return { kind: "idle" };
157653
+ if (remainingTools.some((t) => isAskUserTool(t.name, t.status))) {
157654
+ return { kind: "waiting" };
157655
+ }
157656
+ const last = remainingTools[remainingTools.length - 1];
157657
+ return { kind: "thinking", detail: String(last.name ?? "") };
157658
+ }
157659
+ var init_toolStatus = __esm({
157660
+ "src/util/toolStatus.ts"() {
157661
+ "use strict";
157662
+ }
157663
+ });
157664
+
157665
+ // src/util/startupTimeoutGuard.ts
157666
+ function decideStartupTimeoutTransition(inputs) {
157667
+ if (inputs.subState !== "starting") return { kind: "no-transition" };
157668
+ if (!inputs.activityStartTime) return { kind: "no-transition" };
157669
+ if (inputs.now - inputs.activityStartTime <= inputs.timeoutMs) {
157670
+ return { kind: "no-transition" };
157671
+ }
157672
+ if (inputs.serverAlive === true) {
157673
+ return { kind: "recover-to-ready" };
157674
+ }
157675
+ return { kind: "transition-to-error", reason: "Startup timed out" };
157676
+ }
157677
+ var init_startupTimeoutGuard = __esm({
157678
+ "src/util/startupTimeoutGuard.ts"() {
157679
+ "use strict";
157680
+ }
157681
+ });
157682
+
156446
157683
  // src/main.ts
156447
157684
  var require_main = __commonJS({
156448
157685
  "src/main.ts"() {
@@ -156453,6 +157690,7 @@ Terminal error: ${error?.message || String(error)}`);
156453
157690
  init_officeManager();
156454
157691
  init_agents();
156455
157692
  init_responsiveLayout();
157693
+ init_zIndex();
156456
157694
  init_layouts();
156457
157695
  init_ToastNotification();
156458
157696
  init_NotificationService();
@@ -156460,7 +157698,12 @@ Terminal error: ${error?.message || String(error)}`);
156460
157698
  init_SpriteCustomizerPanel();
156461
157699
  init_SeriousTerminalController();
156462
157700
  init_SpriteGenerator();
157701
+ init_toolStatus();
157702
+ init_startupTimeoutGuard();
157703
+ init_AutoStartCoordinator();
157704
+ init_agentAutoStart();
156463
157705
  officeManager.ensureDefaultOffice();
157706
+ recordOfficeAccess(officeManager.currentOfficeId);
156464
157707
  function getCurrentLayout() {
156465
157708
  return officeManager.currentOffice?.config.layout ?? "default";
156466
157709
  }
@@ -156478,15 +157721,6 @@ Terminal error: ${error?.message || String(error)}`);
156478
157721
  restoreSeatedReserveAgents(officeManager.getSeatedAgents(office.config.id));
156479
157722
  }
156480
157723
  }
156481
- function normalizeToolName(toolName) {
156482
- return (toolName ?? "").trim().toLowerCase().replace(/[\s-]+/g, "_");
156483
- }
156484
- function isAskUserTool(toolName, status) {
156485
- const normalized = normalizeToolName(toolName);
156486
- if (normalized === "ask_user" || normalized === "askuser") return true;
156487
- const statusText = (status ?? "").toLowerCase();
156488
- return statusText.includes("waiting for your answer") || statusText.includes("waiting on user input");
156489
- }
156490
157724
  function isDonePendingAck(status) {
156491
157725
  return !!status?.completionPendingAck;
156492
157726
  }
@@ -156500,6 +157734,25 @@ Terminal error: ${error?.message || String(error)}`);
156500
157734
  var SESSION_META_CACHE_STORAGE_KEY = "agencyOffice:sessionMetaCacheByOffice";
156501
157735
  var OVERVIEW_SPRITE_CACHE_STORAGE_KEY = "agencyOffice:overviewSpriteCache";
156502
157736
  var PC_TERMINAL_ID2 = "pc-terminal";
157737
+ var OFFICE_SORT_STORAGE_KEY = "agencyOffice:officeSortMode";
157738
+ var OFFICE_ACCESS_TIMES_KEY = "agencyOffice:officeAccessTimes";
157739
+ var officeSortMode = localStorage.getItem(OFFICE_SORT_STORAGE_KEY) || "default";
157740
+ function getOfficeAccessTimes() {
157741
+ try {
157742
+ const raw = localStorage.getItem(OFFICE_ACCESS_TIMES_KEY);
157743
+ return raw ? JSON.parse(raw) : {};
157744
+ } catch {
157745
+ return {};
157746
+ }
157747
+ }
157748
+ function recordOfficeAccess(officeId) {
157749
+ const times = getOfficeAccessTimes();
157750
+ times[officeId] = Date.now();
157751
+ try {
157752
+ localStorage.setItem(OFFICE_ACCESS_TIMES_KEY, JSON.stringify(times));
157753
+ } catch {
157754
+ }
157755
+ }
156503
157756
  function sanitizeAppMode(value) {
156504
157757
  return value === "serious" ? "serious" : "game";
156505
157758
  }
@@ -156725,6 +157978,17 @@ Terminal error: ${error?.message || String(error)}`);
156725
157978
  if (previousMode === "serious" && appMode === "game") {
156726
157979
  void seriousTerminalController?.closeView({ detach: true, silent: true });
156727
157980
  }
157981
+ if (previousMode === "game" && appMode === "serious") {
157982
+ try {
157983
+ const scene = phaserGameRef?.scene.getScene("OfficeScene");
157984
+ const overlay = scene?.getTerminalOverlay?.();
157985
+ if (overlay?.getIsVisible?.()) {
157986
+ overlay.hide?.();
157987
+ }
157988
+ } catch (err) {
157989
+ console.warn("[main] failed to hide game-mode terminal overlay on mode flip", err);
157990
+ }
157991
+ }
156728
157992
  if (appMode === "serious") {
156729
157993
  prewarmOverviewSpriteCacheFromTextures();
156730
157994
  teardownPhaserGame();
@@ -156763,9 +158027,86 @@ Terminal error: ${error?.message || String(error)}`);
156763
158027
  if (typeof window !== "undefined") {
156764
158028
  window.__copilotOfficeMobileModeActive = isMobileModeActive;
156765
158029
  }
158030
+ if (typeof window !== "undefined" && window.__copilotOfficeE2E === true) {
158031
+ installE2eDebugHook();
158032
+ }
158033
+ function installE2eDebugHook() {
158034
+ const debugApi = {
158035
+ getActiveMode: () => appMode,
158036
+ setMode: (mode) => {
158037
+ if (mode === appMode) return;
158038
+ applyAppMode(mode, { persist: true, refreshTabs: true });
158039
+ },
158040
+ getCurrentOfficeId: () => officeManager.currentOfficeId,
158041
+ listAgents: () => {
158042
+ return getCurrentAgents().map((a) => ({
158043
+ id: a.id,
158044
+ name: a.name,
158045
+ tileX: a.tileX,
158046
+ tileY: a.tileY
158047
+ }));
158048
+ },
158049
+ getActiveTerminalAgentId: () => {
158050
+ if (appMode === "serious") {
158051
+ return seriousTerminalController?.getActiveAgentId?.() ?? null;
158052
+ }
158053
+ const scene = phaserGameRef?.scene.getScene("OfficeScene");
158054
+ const overlay = scene?.getTerminalOverlay?.();
158055
+ if (!overlay || !overlay.getIsVisible()) return null;
158056
+ return overlay.getActiveAgentId();
158057
+ },
158058
+ openAgentTerminal: async (agentId) => {
158059
+ await openAgentTerminal(agentId);
158060
+ },
158061
+ closeActiveTerminal: async () => {
158062
+ if (appMode === "serious") {
158063
+ await seriousTerminalController?.closeView({ detach: true });
158064
+ return;
158065
+ }
158066
+ const scene = phaserGameRef?.scene.getScene("OfficeScene");
158067
+ scene?.getTerminalOverlay?.()?.hide();
158068
+ },
158069
+ switchOffice: (officeId) => {
158070
+ switchToOffice(officeId);
158071
+ },
158072
+ getCachedSessionMetaForRender: () => {
158073
+ return { ...cachedSessionMeta };
158074
+ },
158075
+ getSeriousPanelSnapshot: () => {
158076
+ if (appMode !== "serious") return null;
158077
+ const snap = seriousTerminalController?.getPanelSnapshot?.();
158078
+ return snap ?? null;
158079
+ },
158080
+ getWarmedOfficeIds: () => autoStartCoordinator.warmedOffices.snapshot(),
158081
+ getAutoStartTerminalStartCount: () => autoStartTerminalStartCount,
158082
+ triggerAutoStartForCurrentOffice: () => autoStartCoordinator.tryWarmCurrentOffice(),
158083
+ replaceAgentSession: (officeId, agentId) => autoStartCoordinator.replaceSession(officeId, agentId),
158084
+ setAutoStartEnabled: (enabled) => {
158085
+ setAgentAutoStartSettings({ autoStartKnownAgents: enabled });
158086
+ },
158087
+ getAutoStartEnabled: () => getAgentAutoStartSettings().autoStartKnownAgents,
158088
+ clearWarmedOfficeRegistry: () => {
158089
+ autoStartCoordinator.warmedOffices.clearAll();
158090
+ },
158091
+ getCurrentSessionIdForAgent: async (officeId, agentId) => {
158092
+ if (!window.copilotBridge?.getSessionId) return null;
158093
+ try {
158094
+ return await window.copilotBridge.getSessionId(officeId, agentId);
158095
+ } catch {
158096
+ return null;
158097
+ }
158098
+ }
158099
+ };
158100
+ window.__copilotOfficeDebug = debugApi;
158101
+ console.log("[main] Spec 008-smoke: __copilotOfficeDebug installed");
158102
+ }
156766
158103
  function renderOfficeTabs() {
156767
- const offices = officeManager.getAllOffices();
158104
+ let offices = officeManager.getAllOffices();
156768
158105
  const currentId = officeManager.currentOfficeId;
158106
+ if (officeSortMode === "recent") {
158107
+ const accessTimes = getOfficeAccessTimes();
158108
+ offices = [...offices].sort((a, b) => (accessTimes[b.id] || 0) - (accessTimes[a.id] || 0));
158109
+ }
156769
158110
  let html = "";
156770
158111
  for (const office of offices) {
156771
158112
  const isActive = office.id === currentId;
@@ -156976,6 +158317,7 @@ Terminal error: ${error?.message || String(error)}`);
156976
158317
  selectedAgentId = null;
156977
158318
  void seriousTerminalController?.closeView({ detach: true });
156978
158319
  officeManager.switchOffice(officeId);
158320
+ recordOfficeAccess(officeId);
156979
158321
  cachedSessionMeta = getSessionMetaCacheForOffice(officeId);
156980
158322
  syncActiveRosterForCurrentOffice();
156981
158323
  phaserGameRef?.events.emit("office:switch", officeId, officeManager.currentOffice?.config.workingDirectory);
@@ -156984,6 +158326,7 @@ Terminal error: ${error?.message || String(error)}`);
156984
158326
  updateStatusBar();
156985
158327
  void reconnectAgentStatuses();
156986
158328
  fetchSessionMeta();
158329
+ void autoStartCoordinator.tryWarmCurrentOffice();
156987
158330
  console.log(`[Office] Switched to office: ${officeManager.currentOffice?.config.name}`);
156988
158331
  }
156989
158332
  function showNewOfficeDialog() {
@@ -156991,7 +158334,7 @@ Terminal error: ${error?.message || String(error)}`);
156991
158334
  const overlay = document.createElement("div");
156992
158335
  overlay.style.cssText = `
156993
158336
  position: fixed; top: 0; left: 0; width: 100%; height: 100%;
156994
- background: rgba(0,0,0,0.7); z-index: 99999;
158337
+ background: rgba(0,0,0,0.7); z-index: ${ZIndex.MODAL_DIALOG};
156995
158338
  display: flex; align-items: center; justify-content: center;
156996
158339
  `;
156997
158340
  const dialog = document.createElement("div");
@@ -157104,7 +158447,7 @@ Terminal error: ${error?.message || String(error)}`);
157104
158447
  min-width: 280px;
157105
158448
  font-family: 'Cascadia Code', Consolas, monospace;
157106
158449
  color: #eee;
157107
- z-index: 100000;
158450
+ z-index: ${ZIndex.TOP_MODAL};
157108
158451
  box-shadow: 0 8px 24px rgba(0,0,0,0.5);
157109
158452
  `;
157110
158453
  const escapeHtml = (s) => s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -157193,20 +158536,49 @@ Terminal error: ${error?.message || String(error)}`);
157193
158536
  <div id="terminal-title" style="font-size: 18px; font-weight: bold; color: #8af; margin-bottom: 4px;">\u{1F3E2} Office Overview</div>
157194
158537
  <div id="terminal-subtitle" style="font-size: 12px; color: #555;"></div>
157195
158538
  </div>
157196
- <button id="close-office-btn" style="
157197
- display: none;
157198
- padding: 6px 14px;
157199
- background: #cc3344;
157200
- color: #fff;
157201
- border: none;
157202
- border-radius: 4px;
157203
- font-family: 'Cascadia Code', Consolas, monospace;
157204
- font-size: 12px;
157205
- cursor: pointer;
157206
- white-space: nowrap;
157207
- ">\u2715 Close Office</button>
158539
+ <div style="display: flex; align-items: center; gap: 8px;">
158540
+ <button id="office-sort-btn" style="
158541
+ padding: 6px 12px;
158542
+ background: ${officeSortMode === "recent" ? "#2a3a5a" : "#252538"};
158543
+ color: ${officeSortMode === "recent" ? "#8af" : "#666"};
158544
+ border: 1px solid ${officeSortMode === "recent" ? "#4488ff" : "#444"};
158545
+ border-radius: 4px;
158546
+ font-family: 'Cascadia Code', Consolas, monospace;
158547
+ font-size: 12px;
158548
+ cursor: pointer;
158549
+ white-space: nowrap;
158550
+ transition: all 0.2s;
158551
+ " title="Sort office tabs">\u21C5 ${officeSortMode === "recent" ? "Recent" : "Default"}</button>
158552
+ <button id="close-office-btn" style="
158553
+ display: none;
158554
+ padding: 6px 14px;
158555
+ background: #cc3344;
158556
+ color: #fff;
158557
+ border: none;
158558
+ border-radius: 4px;
158559
+ font-family: 'Cascadia Code', Consolas, monospace;
158560
+ font-size: 12px;
158561
+ cursor: pointer;
158562
+ white-space: nowrap;
158563
+ ">\u2715 Close Office</button>
158564
+ </div>
157208
158565
  `;
157209
158566
  overviewHost.appendChild(overviewHeader);
158567
+ document.getElementById("office-sort-btn").addEventListener("click", () => {
158568
+ officeSortMode = officeSortMode === "default" ? "recent" : "default";
158569
+ try {
158570
+ localStorage.setItem(OFFICE_SORT_STORAGE_KEY, officeSortMode);
158571
+ } catch {
158572
+ }
158573
+ renderOfficeTabs();
158574
+ const sortBtn = document.getElementById("office-sort-btn");
158575
+ if (sortBtn) {
158576
+ sortBtn.textContent = `\u21C5 ${officeSortMode === "recent" ? "Recent" : "Default"}`;
158577
+ sortBtn.style.background = officeSortMode === "recent" ? "#2a3a5a" : "#252538";
158578
+ sortBtn.style.color = officeSortMode === "recent" ? "#8af" : "#666";
158579
+ sortBtn.style.borderColor = officeSortMode === "recent" ? "#4488ff" : "#444";
158580
+ }
158581
+ });
157210
158582
  document.getElementById("close-office-btn").addEventListener("click", () => {
157211
158583
  const currentId = officeManager.currentOfficeId;
157212
158584
  const office = officeManager.currentOffice;
@@ -157244,7 +158616,7 @@ Terminal error: ${error?.message || String(error)}`);
157244
158616
  font-family: monospace;
157245
158617
  font-size: 16px;
157246
158618
  color: #888;
157247
- z-index: 100;
158619
+ z-index: ${ZIndex.STATUS_BAR};
157248
158620
  `;
157249
158621
  document.body.appendChild(statusBar);
157250
158622
  var bgmMuted = localStorage.getItem("copilot-office-bgm-muted") !== "false";
@@ -157324,6 +158696,7 @@ Terminal error: ${error?.message || String(error)}`);
157324
158696
  seriousPlaceholder.style.display = "none";
157325
158697
  }
157326
158698
  async function openAgentTerminal(agentId) {
158699
+ selectedAgentId = agentId;
157327
158700
  if (appMode === "game") {
157328
158701
  phaserGameRef?.events.emit("open:agent:terminal", agentId);
157329
158702
  return;
@@ -157353,6 +158726,80 @@ Terminal error: ${error?.message || String(error)}`);
157353
158726
  updateTerminalContent();
157354
158727
  }
157355
158728
  });
158729
+ var autoStartTerminalStartCount = 0;
158730
+ async function warmAgentSession(officeId, agentId) {
158731
+ if (!window.copilotBridge) return;
158732
+ const launchConfig = getSeriousLaunchConfig(agentId);
158733
+ if (!launchConfig) return;
158734
+ const workingDir = launchConfig.workingDir;
158735
+ const launchMode = launchConfig.launchMode;
158736
+ const officeStatus = officeManager.getAgentStatus(officeId, agentId);
158737
+ if (officeStatus?.state === "slacking") {
158738
+ officeManager.setAgentStarting(officeId, agentId);
158739
+ phaserGameRef?.events.emit("agent:status:changed", agentId);
158740
+ updateStatusBar();
158741
+ updateTerminalContent();
158742
+ }
158743
+ autoStartTerminalStartCount += 1;
158744
+ await window.copilotBridge.terminalStart(
158745
+ officeId,
158746
+ agentId,
158747
+ workingDir,
158748
+ void 0,
158749
+ void 0,
158750
+ void 0,
158751
+ launchMode
158752
+ );
158753
+ }
158754
+ function buildCanonicalAgentIdsForOffice(officeId) {
158755
+ const office = officeManager.currentOfficeId === officeId ? officeManager.currentOffice : null;
158756
+ if (office && officeManager.currentOfficeId === officeId) {
158757
+ return getCurrentAgents().map((a) => a.id);
158758
+ }
158759
+ const layoutKey = officeManager.currentOffice?.config.layout ?? "default";
158760
+ const layoutAgents = getLayout(layoutKey).agents.map((a) => a.id);
158761
+ const customIds = (officeManager.currentOffice?.config.customAgents ?? []).map(
158762
+ (a) => a.id
158763
+ );
158764
+ return Array.from(/* @__PURE__ */ new Set([...layoutAgents, ...customIds]));
158765
+ }
158766
+ var autoStartCoordinator = new AutoStartCoordinator({
158767
+ getCurrentOfficeId: () => officeManager.currentOfficeId,
158768
+ getCanonicalAgentIds: (oid) => buildCanonicalAgentIdsForOffice(oid),
158769
+ getSessionMeta: async (oid) => {
158770
+ if (!window.copilotBridge?.getAllSessionMeta) {
158771
+ return officeManager.currentOfficeId === oid ? cachedSessionMeta : getSessionMetaCacheForOffice(oid);
158772
+ }
158773
+ try {
158774
+ const fresh = await window.copilotBridge.getAllSessionMeta(oid);
158775
+ return fresh || {};
158776
+ } catch {
158777
+ return officeManager.currentOfficeId === oid ? cachedSessionMeta : getSessionMetaCacheForOffice(oid);
158778
+ }
158779
+ },
158780
+ getCurrentSessionId: async (oid, aid) => {
158781
+ if (!window.copilotBridge?.getSessionId) return null;
158782
+ try {
158783
+ return await window.copilotBridge.getSessionId(oid, aid);
158784
+ } catch {
158785
+ return null;
158786
+ }
158787
+ },
158788
+ getAgentLaunchConfig: (_oid, aid) => {
158789
+ const cfg = getSeriousLaunchConfig(aid);
158790
+ return {
158791
+ workingDir: cfg?.workingDir ?? officeManager.getCurrentWorkingDirectory(),
158792
+ launchMode: cfg?.launchMode ?? "copilot"
158793
+ };
158794
+ },
158795
+ resetSession: async (oid, aid) => {
158796
+ if (!window.copilotBridge) return;
158797
+ await window.copilotBridge.resetSession(oid, aid);
158798
+ },
158799
+ warmAgentSession: (oid, aid) => warmAgentSession(oid, aid),
158800
+ getSettings: () => getAgentAutoStartSettings()
158801
+ });
158802
+ setAutoStartCoordinator(autoStartCoordinator);
157356
158803
  var notificationService = new NotificationService(
157357
158804
  toastManager,
157358
158805
  (agentId) => {
@@ -157408,6 +158855,14 @@ Terminal error: ${error?.message || String(error)}`);
157408
158855
  const base64 = scene.textures.getBase64("player");
157409
158856
  spriteCustomizerPanel.updatePreview(base64);
157410
158857
  }
158858
+ },
158859
+ // Route focus through InputManager: reuse the settings:open / settings:close
158860
+ // bus that OfficeScene already wires to suspendGameInput / resumeGameInput.
158861
+ onOpen: () => {
158862
+ phaserGameRef?.events.emit("settings:open");
158863
+ },
158864
+ onClose: () => {
158865
+ phaserGameRef?.events.emit("settings:close");
157411
158866
  }
157412
158867
  });
157413
158868
  var lastTerminalContentHtml = "";
@@ -157593,10 +159048,27 @@ Terminal error: ${error?.message || String(error)}`);
157593
159048
  e.stopPropagation();
157594
159049
  const agentId = metaPanel.dataset.agent;
157595
159050
  if (!agentId) return;
159051
+ const idBadge = target.closest(".session-id-badge");
159052
+ if (idBadge) {
159053
+ const fullId = idBadge.dataset.sessionId ?? idBadge.textContent?.trim() ?? "";
159054
+ if (fullId) {
159055
+ void navigator.clipboard?.writeText(fullId).catch(() => {
159056
+ });
159057
+ const original = idBadge.textContent;
159058
+ idBadge.textContent = "\u2713 copied";
159059
+ setTimeout(() => {
159060
+ if (idBadge) idBadge.textContent = original;
159061
+ }, 700);
159062
+ }
159063
+ return;
159064
+ }
157596
159065
  layout.clickHandler.handleMetaPanelClick(target, agentId, {
157597
159066
  startSessionMetaEdit,
157598
159067
  startNewSession: (id) => {
157599
159068
  void startSessionFromOverview(id);
159069
+ },
159070
+ closeSession: (id) => {
159071
+ void closeSessionFromOverview(id);
157600
159072
  }
157601
159073
  });
157602
159074
  return;
@@ -157656,6 +159128,25 @@ Terminal error: ${error?.message || String(error)}`);
157656
159128
  updateStatusBar();
157657
159129
  updateTerminalContent();
157658
159130
  }
159131
+ async function closeSessionFromOverview(agentId) {
159132
+ if (!window.copilotBridge) return;
159133
+ const officeId = officeManager.currentOfficeId || "office-0";
159134
+ try {
159135
+ await window.copilotBridge.resetSession(officeId, agentId);
159136
+ } catch (error) {
159137
+ console.warn(`[Office] Failed to close session from overview for ${agentId}:`, error);
159138
+ return;
159139
+ }
159140
+ const meta = cachedSessionMeta[agentId];
159141
+ if (meta) {
159142
+ cachedSessionMeta[agentId] = { ...meta, sessionId: void 0 };
159143
+ setSessionMetaCacheForOffice(officeId, cachedSessionMeta);
159144
+ }
159145
+ officeManager.setAgentSlacking(officeId, agentId);
159146
+ phaserGameRef?.events.emit("agent:status:changed", agentId);
159147
+ updateStatusBar();
159148
+ updateTerminalContent();
159149
+ }
157659
159150
  function startSessionMetaEdit(agentId) {
157660
159151
  const panel = overviewHost.querySelector(`.session-meta-panel[data-agent="${agentId}"]`);
157661
159152
  if (!panel) return;
@@ -157731,11 +159222,11 @@ Terminal error: ${error?.message || String(error)}`);
157731
159222
  officeManager.setTaskSummary(officeId, agentId, status);
157732
159223
  }
157733
159224
  if (isAskUserTool(toolName, status)) {
157734
- officeManager.setAgentWaiting(officeId, agentId);
159225
+ officeManager.setAgentWaiting(officeId, agentId, "ask_user");
157735
159226
  console.log(`[Office] Status: ${agentId} \u2192 waiting (ask_user)`);
157736
159227
  notifyAgent(agentId, "askUser");
157737
159228
  } else {
157738
- officeManager.setAgentThinking(officeId, agentId, `${toolName}`);
159229
+ officeManager.setAgentThinking(officeId, agentId, `${toolName}`, "tool_start");
157739
159230
  console.log(`[Office] Status: ${agentId} \u2192 thinking (${toolName})`);
157740
159231
  notifyAgent(agentId, "toolStart", { toolName });
157741
159232
  }
@@ -157757,18 +159248,18 @@ Terminal error: ${error?.message || String(error)}`);
157757
159248
  officeManager.setLastCompletedAction(officeId, agentId, completedToolName);
157758
159249
  officeManager.pushRecentAction(officeId, agentId, completedToolName, "completed");
157759
159250
  notifyAgent(agentId, "toolComplete", { toolName: completedToolName });
157760
- if (remaining.length === 0) {
159251
+ const next = nextSubStateAfterToolComplete(remaining);
159252
+ if (next.kind === "idle") {
157761
159253
  const currentStatus = officeManager.getAgentStatus(officeId, agentId);
157762
159254
  if (currentStatus?.subState === "thinking") {
157763
- officeManager.setAgentThinking(officeId, agentId, currentStatus.thinkingDetail ?? "Processing...");
159255
+ officeManager.setAgentThinking(officeId, agentId, currentStatus.thinkingDetail ?? "Processing...", "tool_complete_settling");
157764
159256
  } else {
157765
- officeManager.setAgentReady(officeId, agentId);
159257
+ officeManager.setAgentReady(officeId, agentId, "tool_complete");
157766
159258
  }
157767
- } else if (remaining.some((t) => isAskUserTool(t.name, t.status))) {
157768
- officeManager.setAgentWaiting(officeId, agentId);
159259
+ } else if (next.kind === "waiting") {
159260
+ officeManager.setAgentWaiting(officeId, agentId, "ask_user_race_guard");
157769
159261
  } else {
157770
- const last = remaining[remaining.length - 1];
157771
- officeManager.setAgentThinking(officeId, agentId, `${last.name}`);
159262
+ officeManager.setAgentThinking(officeId, agentId, next.detail, "tool_complete");
157772
159263
  }
157773
159264
  }
157774
159265
  phaserGameRef?.events.emit("agent:status:changed", agentId);
@@ -157787,9 +159278,9 @@ Terminal error: ${error?.message || String(error)}`);
157787
159278
  agentTools.set(agentId, []);
157788
159279
  }
157789
159280
  if (waitingToolActive) {
157790
- officeManager.setAgentWaiting(officeId, agentId);
159281
+ officeManager.setAgentWaiting(officeId, agentId, "turn_end_ask_user_active");
157791
159282
  } else {
157792
- officeManager.setAgentDonePendingAck(officeId, agentId);
159283
+ officeManager.setAgentDonePendingAck(officeId, agentId, "turn_end");
157793
159284
  }
157794
159285
  notifyAgent(agentId, "turnEnd");
157795
159286
  }
@@ -157843,10 +159334,10 @@ Terminal error: ${error?.message || String(error)}`);
157843
159334
  const current = officeManager.getAgentStatus(officeId, agentId);
157844
159335
  if (status === "preloading") {
157845
159336
  if (!current || current.state === "slacking") {
157846
- officeManager.setAgentStarting(officeId, agentId);
159337
+ officeManager.setAgentStarting(officeId, agentId, "preload");
157847
159338
  }
157848
159339
  } else if (status === "ready") {
157849
- officeManager.setAgentReady(officeId, agentId);
159340
+ officeManager.setAgentReady(officeId, agentId, "preload_ready");
157850
159341
  const agentTools = getCurrentAgentTools();
157851
159342
  if (agentTools.has(agentId)) {
157852
159343
  agentTools.set(agentId, []);
@@ -157854,7 +159345,7 @@ Terminal error: ${error?.message || String(error)}`);
157854
159345
  notifyAgent(agentId, "sessionReady");
157855
159346
  } else if (status === "failed") {
157856
159347
  console.warn(`[Office] Preload FAILED for ${agentId}`);
157857
- officeManager.setAgentError(officeId, agentId, "Preload failed");
159348
+ officeManager.setAgentError(officeId, agentId, "Preload failed", "preload_failed");
157858
159349
  notifyAgent(agentId, "sessionError");
157859
159350
  }
157860
159351
  }
@@ -157912,9 +159403,24 @@ Terminal error: ${error?.message || String(error)}`);
157912
159403
  const staleInTurnThinking = Boolean(
157913
159404
  current?.subState === "thinking" && thinkingSince && now - thinkingSince > STALE_IN_TURN_THINKING_TIMEOUT_MS && activeTools.length === 0
157914
159405
  );
157915
- if (current?.subState === "starting" && current.activityStartTime && now - current.activityStartTime > STARTING_TIMEOUT_MS) {
159406
+ const decision = decideStartupTimeoutTransition({
159407
+ subState: current?.subState,
159408
+ activityStartTime: current?.activityStartTime,
159409
+ now,
159410
+ timeoutMs: STARTING_TIMEOUT_MS,
159411
+ serverAlive: serverStatus?.alive
159412
+ });
159413
+ if (decision.kind === "recover-to-ready") {
159414
+ console.warn(
159415
+ `[Office] Agent ${agent.id} stuck in starting past timeout but PTY alive \u2014 recovering to ready`
159416
+ );
159417
+ officeManager.setAgentReady(officeId, agent.id);
159418
+ changed = true;
159419
+ continue;
159420
+ }
159421
+ if (decision.kind === "transition-to-error") {
157916
159422
  console.warn(`[Office] Agent ${agent.id} stuck in starting for >${STARTING_TIMEOUT_MS / 1e3}s \u2014 transitioning to error`);
157917
- officeManager.setAgentError(officeId, agent.id, "Startup timed out");
159423
+ officeManager.setAgentError(officeId, agent.id, decision.reason);
157918
159424
  changed = true;
157919
159425
  continue;
157920
159426
  }
@@ -158116,7 +159622,7 @@ Terminal error: ${error?.message || String(error)}`);
158116
159622
  setupTerminalClickHandler();
158117
159623
  function onAgentSessionClosed(agentId) {
158118
159624
  const officeId = officeManager.currentOfficeId;
158119
- if (officeId) officeManager.setAgentSlacking(officeId, agentId);
159625
+ if (officeId) officeManager.setAgentSlacking(officeId, agentId, "session_closed");
158120
159626
  phaserGameRef?.events.emit("agent:status:changed", agentId);
158121
159627
  updateTerminalContent();
158122
159628
  updateStatusBar();
@@ -158133,7 +159639,7 @@ Terminal error: ${error?.message || String(error)}`);
158133
159639
  console.log(`[Office] Fleet V-Team office created: ${officeId} (source: ${sourceOfficeId ?? "none"})`);
158134
159640
  if (sourceOfficeId && window.copilotBridge?.transferSession) {
158135
159641
  try {
158136
- const result = await window.copilotBridge.transferSession(sourceOfficeId, officeId, "architect");
159642
+ const result = await window.copilotBridge.transferSession(sourceOfficeId, officeId, ARCHITECT_AGENT_ID);
158137
159643
  console.log(`[Office] Arthur session transfer: ${result.success ? "OK" : result.error ?? "failed"}`);
158138
159644
  } catch (e) {
158139
159645
  console.warn("[Office] Failed to transfer Arthur session:", e);
@@ -158151,7 +159657,7 @@ Terminal error: ${error?.message || String(error)}`);
158151
159657
  console.log(`[Fleet] Created fleet office: ${officeId}`);
158152
159658
  if (window.copilotBridge?.transferSession) {
158153
159659
  try {
158154
- const result = await window.copilotBridge.transferSession(data.sourceOfficeId, officeId, "architect");
159660
+ const result = await window.copilotBridge.transferSession(data.sourceOfficeId, officeId, ARCHITECT_AGENT_ID);
158155
159661
  console.log(`[Fleet] Arthur session transfer: ${result.success ? "OK" : "failed"}`, result);
158156
159662
  } catch (e) {
158157
159663
  console.warn("[Fleet] Failed to transfer Arthur session:", e);
@@ -158225,6 +159731,7 @@ Terminal error: ${error?.message || String(error)}`);
158225
159731
  scene: [BootScene, OfficeScene, MeetingScene]
158226
159732
  });
158227
159733
  phaserGameRef = game;
159734
+ window.__phaserGame = game;
158228
159735
  bindPhaserEventListeners(game);
158229
159736
  }
158230
159737
  function teardownPhaserGame() {
@@ -158236,9 +159743,18 @@ Terminal error: ${error?.message || String(error)}`);
158236
159743
  } catch (error) {
158237
159744
  console.warn("[main] Failed to destroy Phaser game cleanly:", error);
158238
159745
  }
159746
+ delete window.__phaserGame;
158239
159747
  officePanel.innerHTML = "";
158240
159748
  }
158241
159749
  bindOfficePanelListeners();
159750
+ officeManager.onOfficesUpdated = () => {
159751
+ syncActiveRosterForCurrentOffice();
159752
+ renderOfficeTabs();
159753
+ fetchSessionMeta();
159754
+ updateTerminalContent();
159755
+ updateStatusBar();
159756
+ void autoStartCoordinator.tryWarmCurrentOffice();
159757
+ };
158242
159758
  window.addEventListener("focus", () => {
158243
159759
  catchUpStatusViews("window focus");
158244
159760
  });