copilotoffice 1.1.0 → 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();
@@ -148760,7 +149304,7 @@ WARNING: This link could potentially be dangerous`)) {
148760
149304
  const el = this.spriteCardElement?.querySelector(".session-id-display");
148761
149305
  if (el && this.sessionId) {
148762
149306
  el.textContent = this.sessionId;
148763
- el.title = `Click to copy. Resume with: copilot --resume ${this.sessionId}`;
149307
+ el.title = `Click to copy. Resume with: copilot --session-id ${this.sessionId}`;
148764
149308
  el.onclick = () => this.copySessionId();
148765
149309
  }
148766
149310
  }
@@ -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,23 +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
- if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "v" && event.type === "keydown") {
150122
+ const isModifierPressed = event.ctrlKey || event.metaKey;
150123
+ const key = event.key.toLowerCase();
150124
+ if (event.type !== "keydown" || !isModifierPressed) return true;
150125
+ if (key === "c") {
150126
+ const selection = this.terminal?.hasSelection() ? this.terminal.getSelection() ?? "" : "";
150127
+ if (!selection) {
150128
+ return true;
150129
+ }
150130
+ event.preventDefault();
150131
+ event.stopPropagation();
150132
+ void this.copyToClipboard(selection, "live");
150133
+ return false;
150134
+ }
150135
+ if (key === "v") {
149507
150136
  if (this.isReadOnly) return false;
149508
150137
  event.preventDefault();
149509
150138
  event.stopPropagation();
149510
- navigator.clipboard.readText().then((text) => {
149511
- if (text) this.terminal.paste(text);
149512
- }).catch((err) => {
149513
- console.warn("[Terminal] Clipboard read failed:", err);
149514
- });
150139
+ void this.pasteFromClipboardToTerminal();
149515
150140
  return false;
149516
150141
  }
149517
150142
  return true;
149518
150143
  });
149519
- 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) => {
149520
150201
  if (this.isReadOnly) return;
149521
- if (!this.currentAgentId || !window.copilotBridge) return;
150202
+ if (this.isSwitchingAgent) return;
150203
+ if (!window.copilotBridge) return;
149522
150204
  let outbound = "";
149523
150205
  let shouldStartSlashNewSession = false;
149524
150206
  for (const ch of data) {
@@ -149542,30 +150224,98 @@ WARNING: This link could potentially be dangerous`)) {
149542
150224
  outbound += ch;
149543
150225
  }
149544
150226
  if (outbound.length > 0) {
149545
- window.copilotBridge.terminalWrite(this.getOfficeId(), this.currentAgentId, outbound);
150227
+ window.copilotBridge.terminalWrite(boundOfficeId, boundAgentId, outbound);
149546
150228
  }
149547
150229
  if (shouldStartSlashNewSession) {
149548
- this.fetchSessionId(this.currentAgentId);
150230
+ this.fetchSessionId(boundAgentId);
149549
150231
  }
149550
150232
  });
149551
- this.resizeHandler = () => {
149552
- if (this.isVisible) {
149553
- this.applySpriteCardResponsiveStyles();
149554
- this.updateMobileKeyboardButtonVisibility();
149555
- this.debouncedRefit();
149556
- }
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;
149557
150285
  };
149558
- window.addEventListener("resize", this.resizeHandler);
149559
- this.resizeObserver = new ResizeObserver(() => {
149560
- if (this.isVisible) {
149561
- this.applySpriteCardResponsiveStyles();
149562
- this.updateMobileKeyboardButtonVisibility();
149563
- this.debouncedRefit();
149564
- }
150286
+ const copyItem = makeItem("Copy", () => {
150287
+ const selection = this.terminal?.hasSelection() ? this.terminal.getSelection() ?? "" : "";
150288
+ void this.copyToClipboard(selection, "live");
149565
150289
  });
149566
- if (this.terminalDiv) {
149567
- this.resizeObserver.observe(this.terminalDiv);
149568
- }
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";
149569
150319
  }
149570
150320
  /** Toggle between half-width and full-width terminal panel. */
149571
150321
  toggleFullWidth() {
@@ -149666,15 +150416,12 @@ WARNING: This link could potentially be dangerous`)) {
149666
150416
  }
149667
150417
  /** Give keyboard focus to the terminal. Safe to call when already focused. */
149668
150418
  focusTerminal() {
149669
- console.log("[TerminalOverlay] focusTerminal() \u2014 delegating to InputManager");
149670
150419
  this.inputManager.switchToTerminal(
149671
150420
  "TerminalOverlay.focusTerminal()",
149672
150421
  () => this.handleNewSession(),
149673
150422
  () => this.toggleFullWidth()
149674
150423
  );
149675
- if (window.__copilotOfficeMobileModeActive?.() === true) {
149676
- this.terminal?.focus();
149677
- }
150424
+ this.terminal?.focus();
149678
150425
  this.inputManager.focusTerminalXterm(this.terminal);
149679
150426
  if (this.currentAgent) {
149680
150427
  this.scene.game.events.emit("npc:highlight", this.currentAgent.id);
@@ -149689,7 +150436,6 @@ WARNING: This link could potentially be dangerous`)) {
149689
150436
  }
149690
150437
  /** Give keyboard focus back to the game canvas. Safe to call when already blurred. */
149691
150438
  blurTerminal() {
149692
- console.log("[TerminalOverlay] blurTerminal() \u2014 delegating to InputManager");
149693
150439
  const mobileLocked = window.__copilotOfficeMobileModeActive?.() === true;
149694
150440
  if (mobileLocked) {
149695
150441
  this.inputManager.switchToNone("TerminalOverlay.blurTerminal() mobile lock");
@@ -149719,7 +150465,12 @@ WARNING: This link could potentially be dangerous`)) {
149719
150465
  }
149720
150466
  setupKeyboardHandler() {
149721
150467
  }
150468
+ // Spec 008-smoke: expose state for the e2e debug hook.
150469
+ getActiveAgentId() {
150470
+ return this.currentAgentId;
150471
+ }
149722
150472
  hide() {
150473
+ this.hideTerminalContextMenu();
149723
150474
  if (this.container) {
149724
150475
  this.container.style.display = "none";
149725
150476
  }
@@ -149730,8 +150481,6 @@ WARNING: This link could potentially be dangerous`)) {
149730
150481
  this.isVisible = false;
149731
150482
  this.isReadOnly = false;
149732
150483
  this.pendingInputLine = "";
149733
- this.awaitingSessionIdRefresh = false;
149734
- this.clearSessionRefreshTimers();
149735
150484
  this.closeHistoryPopover();
149736
150485
  this.restorePanelLayout();
149737
150486
  this.inputManager.deactivateTerminalF10();
@@ -149770,9 +150519,26 @@ WARNING: This link could potentially be dangerous`)) {
149770
150519
  return false;
149771
150520
  }
149772
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;
149773
150541
  this.clearRefitTimers();
149774
- this.awaitingSessionIdRefresh = false;
149775
- this.clearSessionRefreshTimers();
149776
150542
  if (this.resizeHandler) {
149777
150543
  window.removeEventListener("resize", this.resizeHandler);
149778
150544
  this.resizeHandler = null;
@@ -149785,11 +150551,23 @@ WARNING: This link could potentially be dangerous`)) {
149785
150551
  this.terminal.dispose();
149786
150552
  }
149787
150553
  if (this.container && this.container.parentNode) {
149788
- this.container.parentNode.removeChild(this.container);
150554
+ try {
150555
+ this.container.parentNode.removeChild(this.container);
150556
+ } catch {
150557
+ }
150558
+ }
150559
+ try {
150560
+ if (this.spriteCardElement && this.spriteCardElement.parentNode) {
150561
+ this.spriteCardElement.parentNode.removeChild(this.spriteCardElement);
150562
+ }
150563
+ } catch {
149789
150564
  }
149790
- if (this.spriteCardElement && this.spriteCardElement.parentNode) {
149791
- this.spriteCardElement.parentNode.removeChild(this.spriteCardElement);
150565
+ try {
150566
+ const fallback = document.getElementById("sprite-card");
150567
+ if (fallback) fallback.remove();
150568
+ } catch {
149792
150569
  }
150570
+ this.spriteCardElement = null;
149793
150571
  if (window.copilotBridge) {
149794
150572
  window.copilotBridge.removeTerminalListeners();
149795
150573
  }
@@ -150934,6 +151712,16 @@ WARNING: This link could potentially be dangerous`)) {
150934
151712
  const meta = cachedSessionMeta[agent.id];
150935
151713
  const hasSession = liveStatus?.state === "active";
150936
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>` : "";
150937
151725
  const sessionPanelHtml = hasSession ? `
150938
151726
  <div class="session-meta-panel" data-agent="${agent.id}" style="
150939
151727
  flex: 2; min-width: 0;
@@ -150950,6 +151738,7 @@ WARNING: This link could potentially be dangerous`)) {
150950
151738
  overflow: hidden; text-overflow: ellipsis;
150951
151739
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
150952
151740
  " title="${metaTitle ? metaTitle.replace(/"/g, "&quot;") : "Click to set title"}">${metaTitle || "Untitled session"}</div>
151741
+ ${sessionIdBadgeHtml}
150953
151742
  <button class="session-new-btn" data-agent="${agent.id}" style="
150954
151743
  margin-top: 2px;
150955
151744
  align-self: flex-start;
@@ -150957,6 +151746,12 @@ WARNING: This link could potentially be dangerous`)) {
150957
151746
  font-size: ${t.sessionButton}; padding: 4px 10px; border-radius: 4px;
150958
151747
  cursor: pointer; transition: background 0.15s, border-color 0.15s;
150959
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>
150960
151755
  <div style="display: flex; justify-content: flex-end;">
150961
151756
  <button class="session-edit-btn" data-agent="${agent.id}" style="
150962
151757
  background: none; border: 1px solid #333; color: #667;
@@ -151075,6 +151870,10 @@ WARNING: This link could potentially be dangerous`)) {
151075
151870
  context.emitOpenTerminal(agentId);
151076
151871
  },
151077
151872
  handleMetaPanelClick(target, agentId, context) {
151873
+ if (target.closest(".session-close-btn")) {
151874
+ context.closeSession(agentId);
151875
+ return;
151876
+ }
151078
151877
  if (target.closest(".session-new-btn")) {
151079
151878
  context.startNewSession(agentId);
151080
151879
  return;
@@ -151093,6 +151892,7 @@ WARNING: This link could potentially be dangerous`)) {
151093
151892
  "src/layouts/fleet/FleetDashboard.ts"() {
151094
151893
  "use strict";
151095
151894
  init_types();
151895
+ init_agents();
151096
151896
  fleetDashboard = {
151097
151897
  renderCards(ctx) {
151098
151898
  const { agents, office, selectedAgentId, formatElapsed, formatRelativeTime } = ctx;
@@ -151142,7 +151942,7 @@ WARNING: This link could potentially be dangerous`)) {
151142
151942
  }
151143
151943
  const colorHex = "#" + agent.color.toString(16).padStart(6, "0");
151144
151944
  const isSelected = agent.id === selectedAgentId;
151145
- const isArthur = agent.id === "architect";
151945
+ const isArthur = agent.id === ARCHITECT_AGENT_ID;
151146
151946
  const borderColor = isSelected ? "#4a5a7a" : "#252540";
151147
151947
  const bgColor = isSelected ? "#1a1e2e" : "#13131f";
151148
151948
  const cursor = isArthur ? "pointer" : "default";
@@ -151245,9 +152045,10 @@ WARNING: This link could potentially be dangerous`)) {
151245
152045
  var init_FleetClickHandler = __esm({
151246
152046
  "src/layouts/fleet/FleetClickHandler.ts"() {
151247
152047
  "use strict";
152048
+ init_agents();
151248
152049
  fleetClickHandler = {
151249
152050
  handleCardClick(agentId, context) {
151250
- if (agentId === "architect") {
152051
+ if (agentId === ARCHITECT_AGENT_ID) {
151251
152052
  context.setSelectedAgent(agentId);
151252
152053
  context.emitOpenTerminal(agentId);
151253
152054
  return;
@@ -151263,7 +152064,7 @@ WARNING: This link could potentially be dangerous`)) {
151263
152064
  function getLayout(layout) {
151264
152065
  return layouts[layout] ?? layouts["default"];
151265
152066
  }
151266
- var layouts;
152067
+ var defaultBehaviors, fleetBehaviors, layouts;
151267
152068
  var init_layouts = __esm({
151268
152069
  "src/layouts/index.ts"() {
151269
152070
  "use strict";
@@ -151272,16 +152073,30 @@ WARNING: This link could potentially be dangerous`)) {
151272
152073
  init_DefaultClickHandler();
151273
152074
  init_FleetDashboard();
151274
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
+ };
151275
152088
  layouts = {
151276
152089
  "default": {
151277
152090
  agents: AGENTS,
151278
152091
  dashboard: defaultDashboard,
151279
- clickHandler: defaultClickHandler
152092
+ clickHandler: defaultClickHandler,
152093
+ behaviors: defaultBehaviors
151280
152094
  },
151281
152095
  "fleet-vteam": {
151282
152096
  agents: FLEET_AGENTS,
151283
152097
  dashboard: fleetDashboard,
151284
- clickHandler: fleetClickHandler
152098
+ clickHandler: fleetClickHandler,
152099
+ behaviors: fleetBehaviors
151285
152100
  }
151286
152101
  };
151287
152102
  }
@@ -151625,30 +152440,27 @@ WARNING: This link could potentially be dangerous`)) {
151625
152440
  this.terminal.deactivateF10();
151626
152441
  }
151627
152442
  /**
151628
- * Focus the xterm Terminal instance with retry. Includes the 100 ms delay
151629
- * required for reliable focus transfer after the DOM has updated, plus up to
151630
- * 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).
151631
152446
  */
151632
152447
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
151633
152448
  focusTerminalXterm(terminal) {
151634
- console.log(`[InputManager] focusTerminalXterm() scheduled (+100ms) | time: ${Date.now()}`);
151635
- const attempt = (n, delay) => {
152449
+ terminal?.focus();
152450
+ const verify = (n, delay) => {
151636
152451
  setTimeout(() => {
151637
- terminal?.focus();
151638
- console.log(`[InputManager] focusTerminalXterm() attempt ${n} executed | time: ${Date.now()}`);
151639
152452
  const textarea = terminal?.textarea;
151640
- if (textarea && document.activeElement !== textarea && n < 3) {
151641
- console.warn(`[InputManager] focus attempt ${n} didn't stick \u2014 retrying (+${delay * 2}ms)`);
151642
- attempt(n + 1, delay * 2);
152453
+ if (textarea && document.activeElement !== textarea) {
152454
+ terminal?.focus();
152455
+ if (n < 3) verify(n + 1, delay * 2);
151643
152456
  }
151644
152457
  }, delay);
151645
152458
  };
151646
- attempt(1, 100);
152459
+ verify(1, 50);
151647
152460
  }
151648
152461
  /** Blur the xterm Terminal instance (return DOM focus away from xterm). */
151649
152462
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
151650
152463
  blurTerminalXterm(terminal) {
151651
- console.log(`[InputManager] blurTerminalXterm() | time: ${Date.now()}`);
151652
152464
  terminal?.blur();
151653
152465
  }
151654
152466
  /** Enable or disable verbose per-keydown logging in GlobalInputListener. */
@@ -152308,11 +153120,72 @@ WARNING: This link could potentially be dangerous`)) {
152308
153120
  }
152309
153121
  });
152310
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
+
152311
153184
  // src/scenes/OfficeScene.ts
152312
153185
  function debugLog(scene, ...args) {
152313
153186
  if (scene.physics.world.drawDebug) console.log("[Debug]", ...args);
152314
153187
  }
152315
- 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;
152316
153189
  var init_OfficeScene = __esm({
152317
153190
  "src/scenes/OfficeScene.ts"() {
152318
153191
  "use strict";
@@ -152325,12 +153198,15 @@ WARNING: This link could potentially be dangerous`)) {
152325
153198
  init_agents();
152326
153199
  init_layouts();
152327
153200
  init_depths();
153201
+ init_zIndex();
152328
153202
  init_InputManager();
152329
153203
  init_officeManager();
152330
153204
  init_fleetTracker();
152331
153205
  init_fleetVisualizer();
152332
153206
  init_DirectionalSprite();
152333
153207
  init_CameraDragController();
153208
+ init_agentAutoStart();
153209
+ DEBUG_COLD_START2 = typeof window !== "undefined" && window.__COPILOT_OFFICE_DEBUG_COLD_START__ === true || false;
152334
153210
  ENABLE_DECORATIONS = false;
152335
153211
  ENABLE_BASKETBALL = false;
152336
153212
  ENABLE_GALAXIAN = true;
@@ -152371,6 +153247,10 @@ WARNING: This link could potentially be dangerous`)) {
152371
153247
  this.skipButton = null;
152372
153248
  this.isPortraitDashboardMode = false;
152373
153249
  }
153250
+ // Spec 008-smoke: accessor for the e2e debug hook in src/main.ts.
153251
+ getTerminalOverlay() {
153252
+ return this.terminalOverlay;
153253
+ }
152374
153254
  setAnimating(value) {
152375
153255
  this.animating = value;
152376
153256
  this.game.registry.set("animating", value);
@@ -152456,13 +153336,13 @@ WARNING: This link could potentially be dangerous`)) {
152456
153336
  } else {
152457
153337
  this.triggerEntrance();
152458
153338
  }
152459
- const arthurNPC = this.npcs.find((n) => n.config.id === "architect");
153339
+ const arthurNPC = this.npcs.find((n) => n.config.id === ARCHITECT_AGENT_ID);
152460
153340
  if (arthurNPC) {
152461
153341
  arthurNPC.updateAgentStatus(void 0);
152462
153342
  }
152463
153343
  const seatedNpcs = [];
152464
153344
  for (const agent of AGENTS) {
152465
- if (agent.id === "architect") continue;
153345
+ if (agent.id === ARCHITECT_AGENT_ID) continue;
152466
153346
  const npc = this.npcs.find((n) => n.config.id === agent.id);
152467
153347
  if (!npc) continue;
152468
153348
  const deskX = npc.config.position.x * this.tileSize + this.tileSize / 2;
@@ -152476,7 +153356,7 @@ WARNING: This link could potentially be dangerous`)) {
152476
153356
  const showAllBadges = () => {
152477
153357
  seatedNpcs.forEach((n) => n.setBadgeVisible(true));
152478
153358
  };
152479
- this.triggerAgentWalkIn(["architect"], showAllBadges);
153359
+ this.triggerAgentWalkIn([ARCHITECT_AGENT_ID], showAllBadges);
152480
153360
  }
152481
153361
  });
152482
153362
  this.basketballGame = new BasketballGame(this);
@@ -152577,7 +153457,7 @@ WARNING: This link could potentially be dangerous`)) {
152577
153457
  this.openPlayerPcTerminal();
152578
153458
  return;
152579
153459
  }
152580
- if (this.currentLayout === "fleet-vteam" && agentId !== "architect") return;
153460
+ if (getLayout(this.currentLayout).behaviors.restrictsInteractionToArchitect && agentId !== ARCHITECT_AGENT_ID) return;
152581
153461
  const agents = getLayout(this.currentLayout).agents;
152582
153462
  const agent = agents.find((a) => a.id === agentId);
152583
153463
  if (agent) this.startConversation(agent);
@@ -152772,6 +153652,14 @@ WARNING: This link could potentially be dangerous`)) {
152772
153652
  this.game.events.off("zoom:change");
152773
153653
  this.cameraDrag?.destroy();
152774
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
+ }
152775
153663
  }, this);
152776
153664
  }
152777
153665
  startBackgroundMusic() {
@@ -153667,7 +154555,7 @@ WARNING: This link could potentially be dangerous`)) {
153667
154555
  font-family: 'Cascadia Code', Consolas, monospace;
153668
154556
  font-size: 16px;
153669
154557
  font-weight: bold;
153670
- z-index: 150;
154558
+ z-index: ${ZIndex.OFFICE_SCENE_OVERLAY};
153671
154559
  transition: background 0.15s;
153672
154560
  `;
153673
154561
  btn.addEventListener("mouseenter", () => {
@@ -153793,7 +154681,7 @@ WARNING: This link could potentially be dangerous`)) {
153793
154681
  const isReEntry = !this.fleetPrompt;
153794
154682
  console.log(`[OfficeScene] initFleetPipeline: attachOfficeId=${attachOfficeId}, sourceOfficeId=${this.fleetSourceOfficeId}, currentOfficeId=${officeManager.currentOfficeId}, isReEntry=${isReEntry}`);
153795
154683
  try {
153796
- this.fleetTracker = new FleetTracker("architect", attachOfficeId);
154684
+ this.fleetTracker = new FleetTracker(ARCHITECT_AGENT_ID, attachOfficeId);
153797
154685
  await this.fleetTracker.startTracking();
153798
154686
  this.fleetVisualizer = new FleetVisualizer(
153799
154687
  this.fleetTracker,
@@ -153806,8 +154694,8 @@ WARNING: This link could potentially be dangerous`)) {
153806
154694
  const fleetOfficeId = officeManager.currentOfficeId;
153807
154695
  if (fleetOfficeId && window.copilotBridge?.terminalWrite) {
153808
154696
  const fleetCmd = `/fleet ${this.fleetPrompt}\r`;
153809
- console.log(`[OfficeScene] Sending /fleet to ${fleetOfficeId}:architect (${fleetCmd.length} chars)`);
153810
- 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);
153811
154699
  console.log(`[OfficeScene] /fleet command result:`, result);
153812
154700
  } else {
153813
154701
  console.warn("[OfficeScene] Cannot send /fleet \u2014 no officeId or copilotBridge");
@@ -153857,23 +154745,49 @@ WARNING: This link could potentially be dangerous`)) {
153857
154745
  });
153858
154746
  }
153859
154747
  async preStartAgentSessions() {
154748
+ if (!shouldAutoStart()) {
154749
+ if (DEBUG_COLD_START2) {
154750
+ console.log("[OfficeScene] preStart skipped (autoStartKnownAgents=false)");
154751
+ }
154752
+ return;
154753
+ }
153860
154754
  if (typeof window !== "undefined" && window.copilotBridge) {
153861
154755
  const oid = officeManager.currentOfficeId || "office-0";
153862
- const startAgent = async (agentId, label) => {
153863
- const agent = AGENTS.find((a) => a.id === agentId);
153864
- 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);
153865
154759
  if (savedSessionId) {
153866
154760
  console.log(`[CopilotOffice] Resuming ${label} session: ${savedSessionId}`);
153867
154761
  } else {
153868
154762
  console.log(`[CopilotOffice] Starting new ${label} session (no saved session found)`);
153869
154763
  }
153870
- 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
+ );
153871
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
+ }
153872
154777
  };
153873
- const agentsToStart = AGENTS.slice(0, 2);
153874
- await Promise.all(
153875
- 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})`))
153876
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
+ }
153877
154791
  }
153878
154792
  }
153879
154793
  update() {
@@ -153926,7 +154840,7 @@ WARNING: This link could potentially be dangerous`)) {
153926
154840
  }
153927
154841
  }
153928
154842
  }
153929
- 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) {
153930
154844
  const targetNPC = this.nearestNPC ?? (this.nearestDesk ? this.npcs.find((n) => n.config.id === this.nearestDesk.agentId) ?? null : null);
153931
154845
  if (targetNPC && !CORE_AGENT_IDS.has(targetNPC.config.id) && RESERVE_AGENT_DESK[targetNPC.config.id]) {
153932
154846
  this.dismissReserveAgent(targetNPC);
@@ -154114,7 +155028,7 @@ WARNING: This link could potentially be dangerous`)) {
154114
155028
  }
154115
155029
  startConversation(agent) {
154116
155030
  if (this.currentLayout === "fleet-vteam") {
154117
- if (agent.id !== "architect") return;
155031
+ if (agent.id !== ARCHITECT_AGENT_ID) return;
154118
155032
  this.game.events.emit("agent:interact", agent.id);
154119
155033
  this.terminalOverlay.show(
154120
155034
  agent,
@@ -154124,7 +155038,7 @@ WARNING: This link could potentially be dangerous`)) {
154124
155038
  );
154125
155039
  return;
154126
155040
  }
154127
- if (agent.id === "architect") {
155041
+ if (agent.id === ARCHITECT_AGENT_ID) {
154128
155042
  this.enterMeeting();
154129
155043
  return;
154130
155044
  }
@@ -154155,7 +155069,7 @@ WARNING: This link could potentially be dangerous`)) {
154155
155069
  );
154156
155070
  }
154157
155071
  openPlayerPcTerminal() {
154158
- if (this.currentLayout !== "default") return;
155072
+ if (!getLayout(this.currentLayout).behaviors.hasPlayerPcTerminal) return;
154159
155073
  const workingDir = officeManager.getCurrentWorkingDirectory();
154160
155074
  const pcTerminalConfig = {
154161
155075
  id: PC_TERMINAL_ID,
@@ -154479,7 +155393,8 @@ WARNING: This link could potentially be dangerous`)) {
154479
155393
  var init_planParser = __esm({
154480
155394
  "src/meeting/planParser.ts"() {
154481
155395
  "use strict";
154482
- DEFAULT_VALID_AGENT_IDS = ["generalist", "debugger", "admin"];
155396
+ init_agents();
155397
+ DEFAULT_VALID_AGENT_IDS = DEFAULT_PLAN_AGENT_IDS;
154483
155398
  }
154484
155399
  });
154485
155400
 
@@ -154548,7 +155463,7 @@ WARNING: This link could potentially be dangerous`)) {
154548
155463
  this.inputManager = new InputManager(this);
154549
155464
  this.planApproval = new PlanApprovalOverlay();
154550
155465
  this.terminalOverlay = new TerminalOverlay(this, this.inputManager, () => officeManager.currentOfficeId || "office-0");
154551
- const arthur = AGENTS.find((a) => a.id === "architect");
155466
+ const arthur = AGENTS.find((a) => a.id === ARCHITECT_AGENT_ID);
154552
155467
  if (arthur) {
154553
155468
  this.terminalOverlay.show(arthur, () => {
154554
155469
  console.log("[MeetingScene] Terminal closed");
@@ -154705,7 +155620,7 @@ WARNING: This link could potentially be dangerous`)) {
154705
155620
  if (typeof window === "undefined" || !window.copilotBridge) return;
154706
155621
  const handler = (_agentId, data) => {
154707
155622
  if (this.isExiting) return;
154708
- if (_agentId !== "architect") return;
155623
+ if (_agentId !== ARCHITECT_AGENT_ID) return;
154709
155624
  this.terminalOutputBuffer += data;
154710
155625
  this.time.delayedCall(500, () => {
154711
155626
  if (this.isExiting) return;
@@ -154731,7 +155646,7 @@ WARNING: This link could potentially be dangerous`)) {
154731
155646
  onRevise: (feedback) => {
154732
155647
  console.log("[MeetingScene] Revision requested:", feedback);
154733
155648
  if (window.copilotBridge) {
154734
- window.copilotBridge.terminalWrite(officeManager.currentOfficeId || "office-0", "architect", feedback + "\r");
155649
+ window.copilotBridge.terminalWrite(officeManager.currentOfficeId || "office-0", ARCHITECT_AGENT_ID, feedback + "\r");
154735
155650
  }
154736
155651
  this.meetingPlan = null;
154737
155652
  this.terminalOutputBuffer = "";
@@ -154844,6 +155759,14 @@ WARNING: This link could potentially be dangerous`)) {
154844
155759
  this.terminalOverlay?.hide();
154845
155760
  }
154846
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
+ }
154847
155770
  this.meetingPlan = null;
154848
155771
  this.terminalOutputBuffer = "";
154849
155772
  this.isExiting = false;
@@ -154874,6 +155797,7 @@ WARNING: This link could potentially be dangerous`)) {
154874
155797
  var init_ToastNotification = __esm({
154875
155798
  "src/ui/ToastNotification.ts"() {
154876
155799
  "use strict";
155800
+ init_zIndex();
154877
155801
  MAX_VISIBLE = 3;
154878
155802
  AUTO_DISMISS_MS = 5e3;
154879
155803
  ANIMATION_MS = 300;
@@ -154890,7 +155814,7 @@ WARNING: This link could potentially be dangerous`)) {
154890
155814
  top: 80px;
154891
155815
  left: 16px;
154892
155816
  width: calc(50% - 32px);
154893
- z-index: 9000;
155817
+ z-index: ${ZIndex.TOAST};
154894
155818
  pointer-events: none;
154895
155819
  display: flex;
154896
155820
  flex-direction: column;
@@ -154987,7 +155911,7 @@ WARNING: This link could potentially be dangerous`)) {
154987
155911
  // src/config/notifications.ts
154988
155912
  function loadNotificationSettings() {
154989
155913
  try {
154990
- const raw = localStorage.getItem(STORAGE_KEY2);
155914
+ const raw = localStorage.getItem(STORAGE_KEY3);
154991
155915
  if (!raw) return structuredClone(DEFAULT_SETTINGS);
154992
155916
  const parsed = JSON.parse(raw);
154993
155917
  const merged = structuredClone(DEFAULT_SETTINGS);
@@ -155007,13 +155931,13 @@ WARNING: This link could potentially be dangerous`)) {
155007
155931
  }
155008
155932
  }
155009
155933
  function saveNotificationSettings(settings) {
155010
- localStorage.setItem(STORAGE_KEY2, JSON.stringify(settings));
155934
+ localStorage.setItem(STORAGE_KEY3, JSON.stringify(settings));
155011
155935
  }
155012
155936
  function resetNotificationSettings() {
155013
- localStorage.removeItem(STORAGE_KEY2);
155937
+ localStorage.removeItem(STORAGE_KEY3);
155014
155938
  return structuredClone(DEFAULT_SETTINGS);
155015
155939
  }
155016
- var NOTIFICATION_EVENT_LABELS, DEFAULT_SETTINGS, STORAGE_KEY2;
155940
+ var NOTIFICATION_EVENT_LABELS, DEFAULT_SETTINGS, STORAGE_KEY3;
155017
155941
  var init_notifications = __esm({
155018
155942
  "src/config/notifications.ts"() {
155019
155943
  "use strict";
@@ -155073,7 +155997,7 @@ WARNING: This link could potentially be dangerous`)) {
155073
155997
  }
155074
155998
  }
155075
155999
  };
155076
- STORAGE_KEY2 = "copilot-notification-settings";
156000
+ STORAGE_KEY3 = "copilot-notification-settings";
155077
156001
  }
155078
156002
  });
155079
156003
 
@@ -155174,6 +156098,8 @@ WARNING: This link could potentially be dangerous`)) {
155174
156098
  "src/ui/SettingsPanel.ts"() {
155175
156099
  "use strict";
155176
156100
  init_notifications();
156101
+ init_agentAutoStart();
156102
+ init_zIndex();
155177
156103
  ALL_EVENT_TYPES = [
155178
156104
  "turnEnd",
155179
156105
  "askUser",
@@ -155207,7 +156133,7 @@ WARNING: This link could potentially be dangerous`)) {
155207
156133
  position: fixed;
155208
156134
  inset: 0;
155209
156135
  background: rgba(0,0,0,0.6);
155210
- z-index: 20000;
156136
+ z-index: ${ZIndex.SETTINGS};
155211
156137
  display: flex;
155212
156138
  align-items: center;
155213
156139
  justify-content: center;
@@ -155258,8 +156184,31 @@ WARNING: This link could potentially be dangerous`)) {
155258
156184
  </div>
155259
156185
 
155260
156186
  ${this.renderAudioSection()}
156187
+ ${this.renderAgentsSection()}
155261
156188
  ${this.renderNotificationsSection()}
155262
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>
155263
156212
  `;
155264
156213
  }
155265
156214
  renderAudioSection() {
@@ -155408,8 +156357,16 @@ WARNING: This link could potentially be dangerous`)) {
155408
156357
  bindEvents(panel) {
155409
156358
  panel.querySelector("#settings-close-btn")?.addEventListener("click", () => this.close());
155410
156359
  this.bindAudioEvents(panel);
156360
+ this.bindAgentsEvents(panel);
155411
156361
  this.bindNotificationEvents(panel);
155412
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
+ }
155413
156370
  bindAudioEvents(panel) {
155414
156371
  const muteBtn = panel.querySelector("#settings-bgm-mute-btn");
155415
156372
  const slider = panel.querySelector("#settings-bgm-slider");
@@ -155448,7 +156405,7 @@ WARNING: This link could potentially be dangerous`)) {
155448
156405
  settings.events[eventType].enabled = checkbox.checked;
155449
156406
  });
155450
156407
  });
155451
- 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) => {
155452
156409
  checkbox.addEventListener("change", () => {
155453
156410
  const eventType = checkbox.dataset.event;
155454
156411
  const field = checkbox.dataset.field;
@@ -155503,6 +156460,7 @@ WARNING: This link could potentially be dangerous`)) {
155503
156460
  "src/ui/SpriteCustomizerPanel.ts"() {
155504
156461
  "use strict";
155505
156462
  init_playerCustomization();
156463
+ init_zIndex();
155506
156464
  COLOR_REGIONS = ["hair", "skin", "suit", "tie", "pants", "shoes"];
155507
156465
  SpriteCustomizerPanel = class {
155508
156466
  constructor(options) {
@@ -155511,15 +156469,18 @@ WARNING: This link could potentially be dangerous`)) {
155511
156469
  this.outsideClickHandler = null;
155512
156470
  this.escapeHandler = null;
155513
156471
  this.onColorsChanged = options.onColorsChanged;
156472
+ this.onOpenCallback = options.onOpen;
156473
+ this.onCloseCallback = options.onClose;
155514
156474
  this.currentColors = loadPlayerColors();
155515
156475
  }
155516
156476
  show(anchorElement) {
156477
+ const wasOpen = this.container !== null;
155517
156478
  if (this.container) this.hide();
155518
156479
  this.currentColors = loadPlayerColors();
155519
156480
  this.container = document.createElement("div");
155520
156481
  this.container.style.cssText = `
155521
156482
  position: fixed;
155522
- z-index: 15000;
156483
+ z-index: ${ZIndex.SPRITE_CUSTOMIZER};
155523
156484
  width: 320px;
155524
156485
  background: #1a1a2e;
155525
156486
  border: 2px solid #333;
@@ -155543,8 +156504,10 @@ WARNING: This link could potentially be dangerous`)) {
155543
156504
  }
155544
156505
  this.previewImg = this.container.querySelector("#sprite-preview-img");
155545
156506
  this.bindEvents();
156507
+ if (!wasOpen) this.onOpenCallback?.();
155546
156508
  }
155547
156509
  hide() {
156510
+ const wasOpen = this.container !== null;
155548
156511
  if (this.outsideClickHandler) {
155549
156512
  document.removeEventListener("mousedown", this.outsideClickHandler, true);
155550
156513
  this.outsideClickHandler = null;
@@ -155558,6 +156521,7 @@ WARNING: This link could potentially be dangerous`)) {
155558
156521
  this.container = null;
155559
156522
  }
155560
156523
  this.previewImg = null;
156524
+ if (wasOpen) this.onCloseCallback?.();
155561
156525
  }
155562
156526
  toggle(anchorElement) {
155563
156527
  if (this.container) {
@@ -155731,6 +156695,12 @@ WARNING: This link could potentially be dangerous`)) {
155731
156695
  "use strict";
155732
156696
  import_xterm2 = __toESM(require_xterm());
155733
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();
155734
156704
  SeriousTerminalController = class _SeriousTerminalController {
155735
156705
  constructor(host, options = {}) {
155736
156706
  this.historyPopover = null;
@@ -155746,6 +156716,15 @@ WARNING: This link could potentially be dangerous`)) {
155746
156716
  this.sessionId = null;
155747
156717
  this.activeOptions = null;
155748
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;
155749
156728
  this.host = host;
155750
156729
  this.onClose = options.onClose;
155751
156730
  this.isFullWidth = localStorage.getItem(_SeriousTerminalController.FULL_WIDTH_STORAGE_KEY) === "true";
@@ -155804,13 +156783,13 @@ WARNING: This link could potentially be dangerous`)) {
155804
156783
  this.terminalOuterEl.style.cssText = `
155805
156784
  flex: 1;
155806
156785
  min-height: 0;
155807
- overflow: hidden;
156786
+ overflow: auto;
155808
156787
  background: #0d111b;
155809
156788
  padding: 10px;
155810
156789
  box-sizing: border-box;
155811
156790
  `;
155812
156791
  this.terminalDivEl = document.createElement("div");
155813
- this.terminalDivEl.style.cssText = "width: 100%; height: 100%; overflow: hidden;";
156792
+ this.terminalDivEl.style.cssText = "width: 100%; height: 100%;";
155814
156793
  this.terminalOuterEl.appendChild(this.terminalDivEl);
155815
156794
  this.terminalOuterEl.addEventListener("mousedown", () => this.terminal?.focus());
155816
156795
  this.spriteCardEl = document.createElement("div");
@@ -155954,9 +156933,29 @@ WARNING: This link could potentially be dangerous`)) {
155954
156933
  static {
155955
156934
  this.FULL_WIDTH_STORAGE_KEY = "agencyOffice:seriousTerminalFullWidth";
155956
156935
  }
156936
+ static {
156937
+ this.nextSeriousId = 0;
156938
+ }
155957
156939
  isVisible() {
155958
156940
  return this.visible;
155959
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
+ }
155960
156959
  refreshCardFromOverview() {
155961
156960
  if (!this.visible || !this.activeAgentId) return;
155962
156961
  this.renderExactOverviewCard(this.activeAgentId);
@@ -155974,15 +156973,51 @@ WARNING: This link could potentially be dangerous`)) {
155974
156973
  this.openedAt = Date.now();
155975
156974
  this.sessionId = null;
155976
156975
  this.container.style.display = "flex";
155977
- this.terminal.clear();
155978
- this.titleEl.textContent = `${options.name} (${options.agentId})`;
155979
- this.subtitleEl.textContent = options.description;
155980
- this.updateSpriteCard(options);
155981
- void this.updateSessionTitle(options.officeId, options.agentId);
155982
- this.updateSessionIdDisplay();
155983
- this.setStatus("Opening...");
155984
- this.applyPanelLayout();
155985
- 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
+ }
155986
157021
  try {
155987
157022
  const exists = await window.copilotBridge.terminalExists(options.officeId, options.agentId);
155988
157023
  if (!exists) {
@@ -156034,6 +157069,21 @@ Terminal error: ${error?.message || String(error)}`);
156034
157069
  }
156035
157070
  async startNewSession(options) {
156036
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
+ }
156037
157087
  try {
156038
157088
  await window.copilotBridge.resetSession(options.officeId, options.agentId);
156039
157089
  } catch {
@@ -156068,6 +157118,12 @@ Terminal error: ${error?.message || String(error)}`);
156068
157118
  }
156069
157119
  this.visible = false;
156070
157120
  this.container.style.display = "none";
157121
+ this.hideTerminalContextMenu();
157122
+ try {
157123
+ this.onDataDisposable?.dispose();
157124
+ } catch {
157125
+ }
157126
+ this.onDataDisposable = null;
156071
157127
  this.activeOfficeId = null;
156072
157128
  this.activeAgentId = null;
156073
157129
  this.activeOptions = null;
@@ -156193,7 +157249,7 @@ Terminal error: ${error?.message || String(error)}`);
156193
157249
  border-radius: 8px;
156194
157250
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.45);
156195
157251
  padding: 10px 12px;
156196
- z-index: 10003;
157252
+ z-index: ${ZIndex.SERIOUS_TERMINAL};
156197
157253
  color: #c8d4ff;
156198
157254
  font-size: 12px;
156199
157255
  white-space: pre-wrap;
@@ -156229,17 +157285,166 @@ Terminal error: ${error?.message || String(error)}`);
156229
157285
  }
156230
157286
  copySessionId() {
156231
157287
  if (!this.sessionId) return;
156232
- navigator.clipboard.writeText(this.sessionId).then(() => {
157288
+ void this.copyToClipboard(this.sessionId, "session").then((success) => {
156233
157289
  const original = this.sessionIdEl.textContent;
156234
- this.sessionIdEl.textContent = "Copied!";
156235
- this.sessionIdEl.style.color = "#61d394";
157290
+ this.sessionIdEl.textContent = success ? "Copied!" : "Copy failed";
157291
+ this.sessionIdEl.style.color = success ? "#61d394" : "#ff6b6b";
156236
157292
  setTimeout(() => {
156237
157293
  this.sessionIdEl.textContent = original;
156238
157294
  this.sessionIdEl.style.color = "#8ec3ff";
156239
157295
  }, 900);
156240
- }).catch(() => {
156241
157296
  });
156242
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
+ }
156243
157448
  renderAgentSprite(seed, baseColor) {
156244
157449
  const ctx = this.spriteCanvasEl.getContext("2d");
156245
157450
  if (!ctx) return;
@@ -156277,15 +157482,7 @@ Terminal error: ${error?.message || String(error)}`);
156277
157482
  return r << 16 | g << 8 | b;
156278
157483
  }
156279
157484
  ensureXtermStyles() {
156280
- if (document.getElementById("xterm-styles")) return;
156281
- const style = document.createElement("style");
156282
- style.id = "xterm-styles";
156283
- style.textContent = `
156284
- .xterm { height: 100%; }
156285
- .xterm-viewport { overflow-y: auto !important; }
156286
- #serious-terminal-container .xterm { height: 100%; }
156287
- `;
156288
- document.head.appendChild(style);
157485
+ ensureXtermStyles();
156289
157486
  }
156290
157487
  createTerminal() {
156291
157488
  this.terminal = new import_xterm2.Terminal({
@@ -156308,21 +157505,54 @@ Terminal error: ${error?.message || String(error)}`);
156308
157505
  this.terminal.loadAddon(this.fitAddon);
156309
157506
  this.terminalDivEl.id = "serious-terminal-container";
156310
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();
156311
157527
  this.terminal.attachCustomKeyEventHandler((event) => {
156312
- if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "v" && event.type === "keydown") {
157528
+ const isModifierPressed = event.ctrlKey || event.metaKey;
157529
+ const key = event.key.toLowerCase();
157530
+ if (event.type !== "keydown" || !isModifierPressed) return true;
157531
+ if (key === "c") {
157532
+ const selection = this.terminal?.hasSelection() ? this.terminal.getSelection() ?? "" : "";
157533
+ if (!selection) {
157534
+ return true;
157535
+ }
156313
157536
  event.preventDefault();
156314
157537
  event.stopPropagation();
156315
- navigator.clipboard.readText().then((text) => {
156316
- if (text) this.terminal?.paste(text);
156317
- }).catch(() => {
156318
- });
157538
+ void this.copyToClipboard(selection, "live");
157539
+ return false;
157540
+ }
157541
+ if (key === "v") {
157542
+ event.preventDefault();
157543
+ event.stopPropagation();
157544
+ void this.pasteFromClipboardToTerminal();
157545
+ return false;
157546
+ }
157547
+ if (key === "f") {
157548
+ event.preventDefault();
157549
+ event.stopPropagation();
157550
+ this.toggleFullWidth();
156319
157551
  return false;
156320
157552
  }
156321
157553
  return true;
156322
157554
  });
156323
- this.terminal.onData((data) => {
156324
- if (!window.copilotBridge || !this.activeOfficeId || !this.activeAgentId) return;
156325
- void window.copilotBridge.terminalWrite(this.activeOfficeId, this.activeAgentId, data);
157555
+ this.terminal.onData((_data) => {
156326
157556
  });
156327
157557
  this.resizeHandler = () => {
156328
157558
  if (!this.visible || !this.activeOfficeId || !this.activeAgentId) return;
@@ -156408,6 +157638,48 @@ Terminal error: ${error?.message || String(error)}`);
156408
157638
  }
156409
157639
  });
156410
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
+
156411
157683
  // src/main.ts
156412
157684
  var require_main = __commonJS({
156413
157685
  "src/main.ts"() {
@@ -156418,6 +157690,7 @@ Terminal error: ${error?.message || String(error)}`);
156418
157690
  init_officeManager();
156419
157691
  init_agents();
156420
157692
  init_responsiveLayout();
157693
+ init_zIndex();
156421
157694
  init_layouts();
156422
157695
  init_ToastNotification();
156423
157696
  init_NotificationService();
@@ -156425,7 +157698,12 @@ Terminal error: ${error?.message || String(error)}`);
156425
157698
  init_SpriteCustomizerPanel();
156426
157699
  init_SeriousTerminalController();
156427
157700
  init_SpriteGenerator();
157701
+ init_toolStatus();
157702
+ init_startupTimeoutGuard();
157703
+ init_AutoStartCoordinator();
157704
+ init_agentAutoStart();
156428
157705
  officeManager.ensureDefaultOffice();
157706
+ recordOfficeAccess(officeManager.currentOfficeId);
156429
157707
  function getCurrentLayout() {
156430
157708
  return officeManager.currentOffice?.config.layout ?? "default";
156431
157709
  }
@@ -156443,15 +157721,6 @@ Terminal error: ${error?.message || String(error)}`);
156443
157721
  restoreSeatedReserveAgents(officeManager.getSeatedAgents(office.config.id));
156444
157722
  }
156445
157723
  }
156446
- function normalizeToolName(toolName) {
156447
- return (toolName ?? "").trim().toLowerCase().replace(/[\s-]+/g, "_");
156448
- }
156449
- function isAskUserTool(toolName, status) {
156450
- const normalized = normalizeToolName(toolName);
156451
- if (normalized === "ask_user" || normalized === "askuser") return true;
156452
- const statusText = (status ?? "").toLowerCase();
156453
- return statusText.includes("waiting for your answer") || statusText.includes("waiting on user input");
156454
- }
156455
157724
  function isDonePendingAck(status) {
156456
157725
  return !!status?.completionPendingAck;
156457
157726
  }
@@ -156465,6 +157734,25 @@ Terminal error: ${error?.message || String(error)}`);
156465
157734
  var SESSION_META_CACHE_STORAGE_KEY = "agencyOffice:sessionMetaCacheByOffice";
156466
157735
  var OVERVIEW_SPRITE_CACHE_STORAGE_KEY = "agencyOffice:overviewSpriteCache";
156467
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
+ }
156468
157756
  function sanitizeAppMode(value) {
156469
157757
  return value === "serious" ? "serious" : "game";
156470
157758
  }
@@ -156690,6 +157978,17 @@ Terminal error: ${error?.message || String(error)}`);
156690
157978
  if (previousMode === "serious" && appMode === "game") {
156691
157979
  void seriousTerminalController?.closeView({ detach: true, silent: true });
156692
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
+ }
156693
157992
  if (appMode === "serious") {
156694
157993
  prewarmOverviewSpriteCacheFromTextures();
156695
157994
  teardownPhaserGame();
@@ -156728,9 +158027,86 @@ Terminal error: ${error?.message || String(error)}`);
156728
158027
  if (typeof window !== "undefined") {
156729
158028
  window.__copilotOfficeMobileModeActive = isMobileModeActive;
156730
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
+ }
156731
158103
  function renderOfficeTabs() {
156732
- const offices = officeManager.getAllOffices();
158104
+ let offices = officeManager.getAllOffices();
156733
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
+ }
156734
158110
  let html = "";
156735
158111
  for (const office of offices) {
156736
158112
  const isActive = office.id === currentId;
@@ -156941,6 +158317,7 @@ Terminal error: ${error?.message || String(error)}`);
156941
158317
  selectedAgentId = null;
156942
158318
  void seriousTerminalController?.closeView({ detach: true });
156943
158319
  officeManager.switchOffice(officeId);
158320
+ recordOfficeAccess(officeId);
156944
158321
  cachedSessionMeta = getSessionMetaCacheForOffice(officeId);
156945
158322
  syncActiveRosterForCurrentOffice();
156946
158323
  phaserGameRef?.events.emit("office:switch", officeId, officeManager.currentOffice?.config.workingDirectory);
@@ -156949,6 +158326,7 @@ Terminal error: ${error?.message || String(error)}`);
156949
158326
  updateStatusBar();
156950
158327
  void reconnectAgentStatuses();
156951
158328
  fetchSessionMeta();
158329
+ void autoStartCoordinator.tryWarmCurrentOffice();
156952
158330
  console.log(`[Office] Switched to office: ${officeManager.currentOffice?.config.name}`);
156953
158331
  }
156954
158332
  function showNewOfficeDialog() {
@@ -156956,7 +158334,7 @@ Terminal error: ${error?.message || String(error)}`);
156956
158334
  const overlay = document.createElement("div");
156957
158335
  overlay.style.cssText = `
156958
158336
  position: fixed; top: 0; left: 0; width: 100%; height: 100%;
156959
- background: rgba(0,0,0,0.7); z-index: 99999;
158337
+ background: rgba(0,0,0,0.7); z-index: ${ZIndex.MODAL_DIALOG};
156960
158338
  display: flex; align-items: center; justify-content: center;
156961
158339
  `;
156962
158340
  const dialog = document.createElement("div");
@@ -157069,7 +158447,7 @@ Terminal error: ${error?.message || String(error)}`);
157069
158447
  min-width: 280px;
157070
158448
  font-family: 'Cascadia Code', Consolas, monospace;
157071
158449
  color: #eee;
157072
- z-index: 100000;
158450
+ z-index: ${ZIndex.TOP_MODAL};
157073
158451
  box-shadow: 0 8px 24px rgba(0,0,0,0.5);
157074
158452
  `;
157075
158453
  const escapeHtml = (s) => s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -157158,20 +158536,49 @@ Terminal error: ${error?.message || String(error)}`);
157158
158536
  <div id="terminal-title" style="font-size: 18px; font-weight: bold; color: #8af; margin-bottom: 4px;">\u{1F3E2} Office Overview</div>
157159
158537
  <div id="terminal-subtitle" style="font-size: 12px; color: #555;"></div>
157160
158538
  </div>
157161
- <button id="close-office-btn" style="
157162
- display: none;
157163
- padding: 6px 14px;
157164
- background: #cc3344;
157165
- color: #fff;
157166
- border: none;
157167
- border-radius: 4px;
157168
- font-family: 'Cascadia Code', Consolas, monospace;
157169
- font-size: 12px;
157170
- cursor: pointer;
157171
- white-space: nowrap;
157172
- ">\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>
157173
158565
  `;
157174
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
+ });
157175
158582
  document.getElementById("close-office-btn").addEventListener("click", () => {
157176
158583
  const currentId = officeManager.currentOfficeId;
157177
158584
  const office = officeManager.currentOffice;
@@ -157209,7 +158616,7 @@ Terminal error: ${error?.message || String(error)}`);
157209
158616
  font-family: monospace;
157210
158617
  font-size: 16px;
157211
158618
  color: #888;
157212
- z-index: 100;
158619
+ z-index: ${ZIndex.STATUS_BAR};
157213
158620
  `;
157214
158621
  document.body.appendChild(statusBar);
157215
158622
  var bgmMuted = localStorage.getItem("copilot-office-bgm-muted") !== "false";
@@ -157289,6 +158696,7 @@ Terminal error: ${error?.message || String(error)}`);
157289
158696
  seriousPlaceholder.style.display = "none";
157290
158697
  }
157291
158698
  async function openAgentTerminal(agentId) {
158699
+ selectedAgentId = agentId;
157292
158700
  if (appMode === "game") {
157293
158701
  phaserGameRef?.events.emit("open:agent:terminal", agentId);
157294
158702
  return;
@@ -157318,6 +158726,80 @@ Terminal error: ${error?.message || String(error)}`);
157318
158726
  updateTerminalContent();
157319
158727
  }
157320
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);
157321
158803
  var notificationService = new NotificationService(
157322
158804
  toastManager,
157323
158805
  (agentId) => {
@@ -157373,6 +158855,14 @@ Terminal error: ${error?.message || String(error)}`);
157373
158855
  const base64 = scene.textures.getBase64("player");
157374
158856
  spriteCustomizerPanel.updatePreview(base64);
157375
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");
157376
158866
  }
157377
158867
  });
157378
158868
  var lastTerminalContentHtml = "";
@@ -157558,10 +159048,27 @@ Terminal error: ${error?.message || String(error)}`);
157558
159048
  e.stopPropagation();
157559
159049
  const agentId = metaPanel.dataset.agent;
157560
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
+ }
157561
159065
  layout.clickHandler.handleMetaPanelClick(target, agentId, {
157562
159066
  startSessionMetaEdit,
157563
159067
  startNewSession: (id) => {
157564
159068
  void startSessionFromOverview(id);
159069
+ },
159070
+ closeSession: (id) => {
159071
+ void closeSessionFromOverview(id);
157565
159072
  }
157566
159073
  });
157567
159074
  return;
@@ -157621,6 +159128,25 @@ Terminal error: ${error?.message || String(error)}`);
157621
159128
  updateStatusBar();
157622
159129
  updateTerminalContent();
157623
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
+ }
157624
159150
  function startSessionMetaEdit(agentId) {
157625
159151
  const panel = overviewHost.querySelector(`.session-meta-panel[data-agent="${agentId}"]`);
157626
159152
  if (!panel) return;
@@ -157696,11 +159222,11 @@ Terminal error: ${error?.message || String(error)}`);
157696
159222
  officeManager.setTaskSummary(officeId, agentId, status);
157697
159223
  }
157698
159224
  if (isAskUserTool(toolName, status)) {
157699
- officeManager.setAgentWaiting(officeId, agentId);
159225
+ officeManager.setAgentWaiting(officeId, agentId, "ask_user");
157700
159226
  console.log(`[Office] Status: ${agentId} \u2192 waiting (ask_user)`);
157701
159227
  notifyAgent(agentId, "askUser");
157702
159228
  } else {
157703
- officeManager.setAgentThinking(officeId, agentId, `${toolName}`);
159229
+ officeManager.setAgentThinking(officeId, agentId, `${toolName}`, "tool_start");
157704
159230
  console.log(`[Office] Status: ${agentId} \u2192 thinking (${toolName})`);
157705
159231
  notifyAgent(agentId, "toolStart", { toolName });
157706
159232
  }
@@ -157722,18 +159248,18 @@ Terminal error: ${error?.message || String(error)}`);
157722
159248
  officeManager.setLastCompletedAction(officeId, agentId, completedToolName);
157723
159249
  officeManager.pushRecentAction(officeId, agentId, completedToolName, "completed");
157724
159250
  notifyAgent(agentId, "toolComplete", { toolName: completedToolName });
157725
- if (remaining.length === 0) {
159251
+ const next = nextSubStateAfterToolComplete(remaining);
159252
+ if (next.kind === "idle") {
157726
159253
  const currentStatus = officeManager.getAgentStatus(officeId, agentId);
157727
159254
  if (currentStatus?.subState === "thinking") {
157728
- officeManager.setAgentThinking(officeId, agentId, currentStatus.thinkingDetail ?? "Processing...");
159255
+ officeManager.setAgentThinking(officeId, agentId, currentStatus.thinkingDetail ?? "Processing...", "tool_complete_settling");
157729
159256
  } else {
157730
- officeManager.setAgentReady(officeId, agentId);
159257
+ officeManager.setAgentReady(officeId, agentId, "tool_complete");
157731
159258
  }
157732
- } else if (remaining.some((t) => isAskUserTool(t.name, t.status))) {
157733
- officeManager.setAgentWaiting(officeId, agentId);
159259
+ } else if (next.kind === "waiting") {
159260
+ officeManager.setAgentWaiting(officeId, agentId, "ask_user_race_guard");
157734
159261
  } else {
157735
- const last = remaining[remaining.length - 1];
157736
- officeManager.setAgentThinking(officeId, agentId, `${last.name}`);
159262
+ officeManager.setAgentThinking(officeId, agentId, next.detail, "tool_complete");
157737
159263
  }
157738
159264
  }
157739
159265
  phaserGameRef?.events.emit("agent:status:changed", agentId);
@@ -157752,9 +159278,9 @@ Terminal error: ${error?.message || String(error)}`);
157752
159278
  agentTools.set(agentId, []);
157753
159279
  }
157754
159280
  if (waitingToolActive) {
157755
- officeManager.setAgentWaiting(officeId, agentId);
159281
+ officeManager.setAgentWaiting(officeId, agentId, "turn_end_ask_user_active");
157756
159282
  } else {
157757
- officeManager.setAgentDonePendingAck(officeId, agentId);
159283
+ officeManager.setAgentDonePendingAck(officeId, agentId, "turn_end");
157758
159284
  }
157759
159285
  notifyAgent(agentId, "turnEnd");
157760
159286
  }
@@ -157808,10 +159334,10 @@ Terminal error: ${error?.message || String(error)}`);
157808
159334
  const current = officeManager.getAgentStatus(officeId, agentId);
157809
159335
  if (status === "preloading") {
157810
159336
  if (!current || current.state === "slacking") {
157811
- officeManager.setAgentStarting(officeId, agentId);
159337
+ officeManager.setAgentStarting(officeId, agentId, "preload");
157812
159338
  }
157813
159339
  } else if (status === "ready") {
157814
- officeManager.setAgentReady(officeId, agentId);
159340
+ officeManager.setAgentReady(officeId, agentId, "preload_ready");
157815
159341
  const agentTools = getCurrentAgentTools();
157816
159342
  if (agentTools.has(agentId)) {
157817
159343
  agentTools.set(agentId, []);
@@ -157819,7 +159345,7 @@ Terminal error: ${error?.message || String(error)}`);
157819
159345
  notifyAgent(agentId, "sessionReady");
157820
159346
  } else if (status === "failed") {
157821
159347
  console.warn(`[Office] Preload FAILED for ${agentId}`);
157822
- officeManager.setAgentError(officeId, agentId, "Preload failed");
159348
+ officeManager.setAgentError(officeId, agentId, "Preload failed", "preload_failed");
157823
159349
  notifyAgent(agentId, "sessionError");
157824
159350
  }
157825
159351
  }
@@ -157877,9 +159403,24 @@ Terminal error: ${error?.message || String(error)}`);
157877
159403
  const staleInTurnThinking = Boolean(
157878
159404
  current?.subState === "thinking" && thinkingSince && now - thinkingSince > STALE_IN_TURN_THINKING_TIMEOUT_MS && activeTools.length === 0
157879
159405
  );
157880
- 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") {
157881
159422
  console.warn(`[Office] Agent ${agent.id} stuck in starting for >${STARTING_TIMEOUT_MS / 1e3}s \u2014 transitioning to error`);
157882
- officeManager.setAgentError(officeId, agent.id, "Startup timed out");
159423
+ officeManager.setAgentError(officeId, agent.id, decision.reason);
157883
159424
  changed = true;
157884
159425
  continue;
157885
159426
  }
@@ -158081,7 +159622,7 @@ Terminal error: ${error?.message || String(error)}`);
158081
159622
  setupTerminalClickHandler();
158082
159623
  function onAgentSessionClosed(agentId) {
158083
159624
  const officeId = officeManager.currentOfficeId;
158084
- if (officeId) officeManager.setAgentSlacking(officeId, agentId);
159625
+ if (officeId) officeManager.setAgentSlacking(officeId, agentId, "session_closed");
158085
159626
  phaserGameRef?.events.emit("agent:status:changed", agentId);
158086
159627
  updateTerminalContent();
158087
159628
  updateStatusBar();
@@ -158098,7 +159639,7 @@ Terminal error: ${error?.message || String(error)}`);
158098
159639
  console.log(`[Office] Fleet V-Team office created: ${officeId} (source: ${sourceOfficeId ?? "none"})`);
158099
159640
  if (sourceOfficeId && window.copilotBridge?.transferSession) {
158100
159641
  try {
158101
- const result = await window.copilotBridge.transferSession(sourceOfficeId, officeId, "architect");
159642
+ const result = await window.copilotBridge.transferSession(sourceOfficeId, officeId, ARCHITECT_AGENT_ID);
158102
159643
  console.log(`[Office] Arthur session transfer: ${result.success ? "OK" : result.error ?? "failed"}`);
158103
159644
  } catch (e) {
158104
159645
  console.warn("[Office] Failed to transfer Arthur session:", e);
@@ -158116,7 +159657,7 @@ Terminal error: ${error?.message || String(error)}`);
158116
159657
  console.log(`[Fleet] Created fleet office: ${officeId}`);
158117
159658
  if (window.copilotBridge?.transferSession) {
158118
159659
  try {
158119
- const result = await window.copilotBridge.transferSession(data.sourceOfficeId, officeId, "architect");
159660
+ const result = await window.copilotBridge.transferSession(data.sourceOfficeId, officeId, ARCHITECT_AGENT_ID);
158120
159661
  console.log(`[Fleet] Arthur session transfer: ${result.success ? "OK" : "failed"}`, result);
158121
159662
  } catch (e) {
158122
159663
  console.warn("[Fleet] Failed to transfer Arthur session:", e);
@@ -158190,6 +159731,7 @@ Terminal error: ${error?.message || String(error)}`);
158190
159731
  scene: [BootScene, OfficeScene, MeetingScene]
158191
159732
  });
158192
159733
  phaserGameRef = game;
159734
+ window.__phaserGame = game;
158193
159735
  bindPhaserEventListeners(game);
158194
159736
  }
158195
159737
  function teardownPhaserGame() {
@@ -158201,9 +159743,18 @@ Terminal error: ${error?.message || String(error)}`);
158201
159743
  } catch (error) {
158202
159744
  console.warn("[main] Failed to destroy Phaser game cleanly:", error);
158203
159745
  }
159746
+ delete window.__phaserGame;
158204
159747
  officePanel.innerHTML = "";
158205
159748
  }
158206
159749
  bindOfficePanelListeners();
159750
+ officeManager.onOfficesUpdated = () => {
159751
+ syncActiveRosterForCurrentOffice();
159752
+ renderOfficeTabs();
159753
+ fetchSessionMeta();
159754
+ updateTerminalContent();
159755
+ updateStatusBar();
159756
+ void autoStartCoordinator.tryWarmCurrentOffice();
159757
+ };
158207
159758
  window.addEventListener("focus", () => {
158208
159759
  catchUpStatusViews("window focus");
158209
159760
  });