copilotoffice 1.1.1 → 2.1.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.
@@ -3044,7 +3044,7 @@ var require_main = __commonJS({
3044
3044
  exports2.createMessageConnection = exports2.createServerSocketTransport = exports2.createClientSocketTransport = exports2.createServerPipeTransport = exports2.createClientPipeTransport = exports2.generateRandomPipeName = exports2.StreamMessageWriter = exports2.StreamMessageReader = exports2.SocketMessageWriter = exports2.SocketMessageReader = exports2.PortMessageWriter = exports2.PortMessageReader = exports2.IPCMessageWriter = exports2.IPCMessageReader = void 0;
3045
3045
  var ril_1 = require_ril();
3046
3046
  ril_1.default.install();
3047
- var path4 = require("path");
3047
+ var path5 = require("path");
3048
3048
  var os4 = require("os");
3049
3049
  var crypto_1 = require("crypto");
3050
3050
  var net_1 = require("net");
@@ -3180,9 +3180,9 @@ var require_main = __commonJS({
3180
3180
  }
3181
3181
  let result;
3182
3182
  if (XDG_RUNTIME_DIR) {
3183
- result = path4.join(XDG_RUNTIME_DIR, `vscode-ipc-${randomSuffix}.sock`);
3183
+ result = path5.join(XDG_RUNTIME_DIR, `vscode-ipc-${randomSuffix}.sock`);
3184
3184
  } else {
3185
- result = path4.join(os4.tmpdir(), `vscode-${randomSuffix}.sock`);
3185
+ result = path5.join(os4.tmpdir(), `vscode-${randomSuffix}.sock`);
3186
3186
  }
3187
3187
  const limit = safeIpcPathLengths.get(process.platform);
3188
3188
  if (limit !== void 0 && result.length > limit) {
@@ -5067,10 +5067,10 @@ var init_dist = __esm({
5067
5067
  });
5068
5068
 
5069
5069
  // electron/terminal/server.ts
5070
- var path3 = __toESM(require("path"));
5070
+ var path4 = __toESM(require("path"));
5071
5071
  var os3 = __toESM(require("os"));
5072
- var fs2 = __toESM(require("fs"));
5073
- var crypto = __toESM(require("crypto"));
5072
+ var fs3 = __toESM(require("fs"));
5073
+ var crypto3 = __toESM(require("crypto"));
5074
5074
  var import_child_process2 = require("child_process");
5075
5075
 
5076
5076
  // electron/terminal/events-watcher.ts
@@ -5407,14 +5407,7 @@ var CopilotSdkProcess = class {
5407
5407
  this.emitPrompt();
5408
5408
  continue;
5409
5409
  }
5410
- this.promptPending = true;
5411
- this.queuedSend = this.queuedSend.then(async () => {
5412
- await this.session.send({ prompt, mode: "enqueue" });
5413
- }).catch((error) => {
5414
- this.emitData(`\x1B[31m[SDK send failed: ${String(error)}]\x1B[0m\r
5415
- `);
5416
- this.emitPrompt();
5417
- });
5410
+ this.enqueuePrompt(prompt);
5418
5411
  continue;
5419
5412
  }
5420
5413
  if (ch === "\b" || ch === "\x7F") {
@@ -5428,6 +5421,34 @@ var CopilotSdkProcess = class {
5428
5421
  this.emitData(ch);
5429
5422
  }
5430
5423
  }
5424
+ /**
5425
+ * Submit a complete prompt directly to the SDK session, bypassing the
5426
+ * line-editor. Handles multi-line prompts atomically (no premature submit on
5427
+ * embedded newlines) and echoes the prompt so it appears in the terminal as
5428
+ * if typed. This is the robust path used by programmatic drivers (e.g. Teams
5429
+ * remote dispatch) instead of racing keystrokes through `write()`.
5430
+ */
5431
+ submitPrompt(text, label) {
5432
+ if (this.closed) return;
5433
+ const prompt = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").trim();
5434
+ if (!prompt) return;
5435
+ this.lineBuffer = "";
5436
+ const tag = label ? `\x1B[2;36m[${label}]\x1B[0m ` : "";
5437
+ this.emitData(`${tag}${prompt.replace(/\n/g, "\r\n")}\r
5438
+ `);
5439
+ this.enqueuePrompt(prompt);
5440
+ }
5441
+ /** Queue a prompt for the SDK session, serialized after any in-flight send. */
5442
+ enqueuePrompt(prompt) {
5443
+ this.promptPending = true;
5444
+ this.queuedSend = this.queuedSend.then(async () => {
5445
+ await this.session.send({ prompt, mode: "enqueue" });
5446
+ }).catch((error) => {
5447
+ this.emitData(`\x1B[31m[SDK send failed: ${String(error)}]\x1B[0m\r
5448
+ `);
5449
+ this.emitPrompt();
5450
+ });
5451
+ }
5431
5452
  resize(_cols, _rows) {
5432
5453
  }
5433
5454
  onData(callback) {
@@ -5595,25 +5616,127 @@ function createSdkCliLaunchConfig(cliPath) {
5595
5616
  return { cliPath, cliArgs: [] };
5596
5617
  }
5597
5618
 
5619
+ // electron/terminal/agent-viewers.ts
5620
+ function addAgentViewer(ck, maps) {
5621
+ maps.activeAgentViewers.add(ck);
5622
+ const termKey = maps.agentToTerminal.get(ck);
5623
+ if (termKey && termKey !== ck) {
5624
+ maps.activeAgentViewers.add(termKey);
5625
+ return { aliasKey: termKey };
5626
+ }
5627
+ return { aliasKey: null };
5628
+ }
5629
+ function removeAgentViewer(ck, maps) {
5630
+ maps.activeAgentViewers.delete(ck);
5631
+ const termKey = maps.agentToTerminal.get(ck);
5632
+ if (termKey && termKey !== ck) {
5633
+ maps.activeAgentViewers.delete(termKey);
5634
+ return { aliasKey: termKey };
5635
+ }
5636
+ return { aliasKey: null };
5637
+ }
5638
+ function hasActiveViewer(ck, maps) {
5639
+ if (maps.activeAgentViewers.has(ck)) return true;
5640
+ for (const [alias, termKey] of maps.agentToTerminal) {
5641
+ if (termKey === ck && maps.activeAgentViewers.has(alias)) return true;
5642
+ }
5643
+ return false;
5644
+ }
5645
+
5646
+ // electron/terminal/session-repair.ts
5647
+ var crypto = __toESM(require("crypto"));
5648
+ function repairDuplicateSessionIds(officeId, data, options = {}) {
5649
+ const logger = options.logger ?? { warn: (msg) => console.warn(msg) };
5650
+ const mintId = options.mintId ?? (() => crypto.randomUUID());
5651
+ const seen = /* @__PURE__ */ new Map();
5652
+ let repaired = false;
5653
+ for (const [agentId, sessionId] of data.sessionIds.entries()) {
5654
+ const firstAgent = seen.get(sessionId);
5655
+ if (firstAgent === void 0) {
5656
+ seen.set(sessionId, agentId);
5657
+ continue;
5658
+ }
5659
+ let fresh = mintId();
5660
+ while (seen.has(fresh) || data.sessionIds.get(agentId) === fresh) {
5661
+ fresh = mintId();
5662
+ }
5663
+ data.sessionIds.set(agentId, fresh);
5664
+ seen.set(fresh, agentId);
5665
+ logger.warn(
5666
+ `[TermServer] Repaired duplicate sessionId for officeId=${officeId} agentId=${agentId} from=${sessionId} to=${fresh}`
5667
+ );
5668
+ repaired = true;
5669
+ }
5670
+ return repaired;
5671
+ }
5672
+
5673
+ // electron/terminal/pty-registry.ts
5674
+ var crypto2 = __toESM(require("crypto"));
5675
+ var fs2 = __toESM(require("fs"));
5676
+ var path3 = __toESM(require("path"));
5677
+ var DEFAULT_REGISTRY_FILE = path3.join(process.cwd(), ".data", "pty-pids.json");
5678
+ function readPtyRegistry(file = DEFAULT_REGISTRY_FILE) {
5679
+ try {
5680
+ const raw = fs2.readFileSync(file, "utf8");
5681
+ const parsed = JSON.parse(raw);
5682
+ if (!Array.isArray(parsed)) return [];
5683
+ return parsed.filter(
5684
+ (r) => !!r && typeof r.pid === "number" && Number.isFinite(r.pid)
5685
+ );
5686
+ } catch {
5687
+ return [];
5688
+ }
5689
+ }
5690
+ function writePtyRegistry(records, file = DEFAULT_REGISTRY_FILE) {
5691
+ try {
5692
+ fs2.mkdirSync(path3.dirname(file), { recursive: true });
5693
+ const tmp = `${file}.${process.pid}.${crypto2.randomBytes(4).toString("hex")}.tmp`;
5694
+ fs2.writeFileSync(tmp, JSON.stringify(records, null, 2));
5695
+ fs2.renameSync(tmp, file);
5696
+ } catch {
5697
+ }
5698
+ }
5699
+ function registerPty(record, file = DEFAULT_REGISTRY_FILE) {
5700
+ const records = readPtyRegistry(file).filter((r) => r.pid !== record.pid);
5701
+ records.push(record);
5702
+ writePtyRegistry(records, file);
5703
+ }
5704
+ function unregisterPty(pid, file = DEFAULT_REGISTRY_FILE) {
5705
+ const records = readPtyRegistry(file);
5706
+ const next = records.filter((r) => r.pid !== pid);
5707
+ if (next.length !== records.length) {
5708
+ writePtyRegistry(next, file);
5709
+ }
5710
+ }
5711
+
5598
5712
  // electron/terminal/server.ts
5599
5713
  var ptyProcesses = /* @__PURE__ */ new Map();
5714
+ var DEBUG_COLD_START = process.env.COPILOT_OFFICE_DEBUG_COLD_START === "1";
5600
5715
  var agentToTerminal = /* @__PURE__ */ new Map();
5601
5716
  var activeAgentViewers = /* @__PURE__ */ new Set();
5717
+ var agentForwardKeys = /* @__PURE__ */ new Set();
5718
+ var lastPtyDataAt = /* @__PURE__ */ new Map();
5719
+ var viewerMaps = { activeAgentViewers, agentToTerminal };
5602
5720
  var agentWatchers = /* @__PURE__ */ new Map();
5603
5721
  var terminalBackend = null;
5604
5722
  var eventSourceFactory = new FileWatcherEventSourceFactory();
5605
5723
  var agentReadyState = /* @__PURE__ */ new Map();
5606
5724
  var agentInTurn = /* @__PURE__ */ new Map();
5725
+ var userMessageSeq = /* @__PURE__ */ new Map();
5726
+ var lastUserMessageText = /* @__PURE__ */ new Map();
5727
+ function normalizePromptText(s) {
5728
+ return s.replace(/\s+/g, " ").trim();
5729
+ }
5607
5730
  var MAX_BUFFER_BYTES = 512 * 1024;
5608
5731
  var agentScrollbackBuffers = /* @__PURE__ */ new Map();
5609
5732
  var agentScrollbackBytes = /* @__PURE__ */ new Map();
5610
- var DATA_DIR = path3.join(process.cwd(), ".data");
5611
- var OLD_SESSION_FILE = path3.join(DATA_DIR, "copilot-office-sessions.json");
5612
- var OFFICES_FILE = path3.join(DATA_DIR, "copilot-offices.json");
5733
+ var DATA_DIR = path4.join(process.cwd(), ".data");
5734
+ var OLD_SESSION_FILE = path4.join(DATA_DIR, "copilot-office-sessions.json");
5735
+ var OFFICES_FILE = path4.join(DATA_DIR, "copilot-offices.json");
5613
5736
  var officeSessions = /* @__PURE__ */ new Map();
5614
5737
  var hasAutoTitled = /* @__PURE__ */ new Set();
5615
5738
  function getSessionFile(officeId) {
5616
- return path3.join(DATA_DIR, `${officeId}.sessions.json`);
5739
+ return path4.join(DATA_DIR, `${officeId}.sessions.json`);
5617
5740
  }
5618
5741
  function getOfficeSession(officeId) {
5619
5742
  let data = officeSessions.get(officeId);
@@ -5626,12 +5749,8 @@ function getOfficeSession(officeId) {
5626
5749
  function compositeKey(officeId, agentId) {
5627
5750
  return `${officeId}:${agentId}`;
5628
5751
  }
5629
- function hasActiveViewer(ck) {
5630
- if (activeAgentViewers.has(ck)) return true;
5631
- for (const [alias, termKey] of agentToTerminal) {
5632
- if (termKey === ck && activeAgentViewers.has(alias)) return true;
5633
- }
5634
- return false;
5752
+ function hasActiveViewer2(ck) {
5753
+ return hasActiveViewer(ck, viewerMaps);
5635
5754
  }
5636
5755
  function send(msg) {
5637
5756
  if (process.send) {
@@ -5655,9 +5774,9 @@ function appendToScrollback(agentId, data) {
5655
5774
  }
5656
5775
  async function loadOfficeSessionFile(officeId) {
5657
5776
  try {
5658
- await fs2.promises.mkdir(DATA_DIR, { recursive: true });
5777
+ await fs3.promises.mkdir(DATA_DIR, { recursive: true });
5659
5778
  const filePath = getSessionFile(officeId);
5660
- const raw = await fs2.promises.readFile(filePath, "utf8").catch(() => null);
5779
+ const raw = await fs3.promises.readFile(filePath, "utf8").catch(() => null);
5661
5780
  const data = getOfficeSession(officeId);
5662
5781
  if (raw) {
5663
5782
  const parsed = JSON.parse(raw);
@@ -5675,6 +5794,10 @@ async function loadOfficeSessionFile(officeId) {
5675
5794
  data.sessionMeta = /* @__PURE__ */ new Map();
5676
5795
  await saveOfficeSessionFile(officeId);
5677
5796
  }
5797
+ const repaired = repairDuplicateSessionIds(officeId, data);
5798
+ if (repaired) {
5799
+ await saveOfficeSessionFile(officeId);
5800
+ }
5678
5801
  console.log(`[TermServer] Loaded sessions for ${officeId}: ${data.sessionIds.size} current, ${data.sessionHistory.size} history`);
5679
5802
  }
5680
5803
  } catch (e) {
@@ -5683,27 +5806,27 @@ async function loadOfficeSessionFile(officeId) {
5683
5806
  }
5684
5807
  async function saveOfficeSessionFile(officeId) {
5685
5808
  try {
5686
- await fs2.promises.mkdir(DATA_DIR, { recursive: true });
5809
+ await fs3.promises.mkdir(DATA_DIR, { recursive: true });
5687
5810
  const data = getOfficeSession(officeId);
5688
5811
  const json = {
5689
5812
  current: Object.fromEntries(data.sessionIds),
5690
5813
  history: Object.fromEntries(data.sessionHistory),
5691
5814
  metadata: Object.fromEntries(data.sessionMeta)
5692
5815
  };
5693
- await fs2.promises.writeFile(getSessionFile(officeId), JSON.stringify(json, null, 2));
5816
+ await fs3.promises.writeFile(getSessionFile(officeId), JSON.stringify(json, null, 2));
5694
5817
  } catch (e) {
5695
5818
  console.error(`[TermServer] Failed to save sessions for ${officeId}:`, e);
5696
5819
  }
5697
5820
  }
5698
5821
  async function createEmptySessionFile(officeId) {
5699
- await fs2.promises.mkdir(DATA_DIR, { recursive: true });
5822
+ await fs3.promises.mkdir(DATA_DIR, { recursive: true });
5700
5823
  const filePath = getSessionFile(officeId);
5701
5824
  try {
5702
- await fs2.promises.access(filePath, fs2.constants.F_OK);
5825
+ await fs3.promises.access(filePath, fs3.constants.F_OK);
5703
5826
  await loadOfficeSessionFile(officeId);
5704
5827
  } catch {
5705
5828
  const empty = { current: {}, history: {}, metadata: {} };
5706
- await fs2.promises.writeFile(filePath, JSON.stringify(empty, null, 2));
5829
+ await fs3.promises.writeFile(filePath, JSON.stringify(empty, null, 2));
5707
5830
  getOfficeSession(officeId);
5708
5831
  console.log(`[TermServer] Created empty session file for ${officeId}: ${filePath}`);
5709
5832
  }
@@ -5723,19 +5846,19 @@ async function migrateGlobalSessionFile() {
5723
5846
  try {
5724
5847
  const migratedMarker = OLD_SESSION_FILE + ".migrated";
5725
5848
  try {
5726
- await fs2.promises.access(migratedMarker, fs2.constants.F_OK);
5849
+ await fs3.promises.access(migratedMarker, fs3.constants.F_OK);
5727
5850
  return;
5728
5851
  } catch {
5729
5852
  }
5730
5853
  let oldRaw = null;
5731
5854
  try {
5732
- oldRaw = await fs2.promises.readFile(OLD_SESSION_FILE, "utf8");
5855
+ oldRaw = await fs3.promises.readFile(OLD_SESSION_FILE, "utf8");
5733
5856
  } catch {
5734
5857
  return;
5735
5858
  }
5736
5859
  let firstOfficeId = "office-0";
5737
5860
  try {
5738
- const officesRaw = await fs2.promises.readFile(OFFICES_FILE, "utf8");
5861
+ const officesRaw = await fs3.promises.readFile(OFFICES_FILE, "utf8");
5739
5862
  const officesData = JSON.parse(officesRaw);
5740
5863
  if (Array.isArray(officesData.offices) && officesData.offices.length > 0) {
5741
5864
  firstOfficeId = officesData.offices[0].id || "office-0";
@@ -5744,22 +5867,22 @@ async function migrateGlobalSessionFile() {
5744
5867
  }
5745
5868
  const firstOfficeFile = getSessionFile(firstOfficeId);
5746
5869
  try {
5747
- await fs2.promises.access(firstOfficeFile, fs2.constants.F_OK);
5870
+ await fs3.promises.access(firstOfficeFile, fs3.constants.F_OK);
5748
5871
  } catch {
5749
- await fs2.promises.writeFile(firstOfficeFile, oldRaw);
5872
+ await fs3.promises.writeFile(firstOfficeFile, oldRaw);
5750
5873
  console.log(`[TermServer] Migrated global sessions to ${firstOfficeFile}`);
5751
5874
  }
5752
- await fs2.promises.rename(OLD_SESSION_FILE, migratedMarker);
5875
+ await fs3.promises.rename(OLD_SESSION_FILE, migratedMarker);
5753
5876
  console.log(`[TermServer] Renamed old session file to ${migratedMarker}`);
5754
5877
  } catch (e) {
5755
5878
  console.error("[TermServer] Migration error:", e);
5756
5879
  }
5757
5880
  }
5758
5881
  async function loadAllOfficeSessions() {
5759
- await fs2.promises.mkdir(DATA_DIR, { recursive: true });
5882
+ await fs3.promises.mkdir(DATA_DIR, { recursive: true });
5760
5883
  await migrateGlobalSessionFile();
5761
5884
  try {
5762
- const files = await fs2.promises.readdir(DATA_DIR);
5885
+ const files = await fs3.promises.readdir(DATA_DIR);
5763
5886
  for (const file of files) {
5764
5887
  const match = file.match(/^(.+)\.sessions\.json$/);
5765
5888
  if (match) {
@@ -5777,6 +5900,46 @@ function getTerminalKey(officeId, agentId) {
5777
5900
  if (ptyProcesses.has(ck)) return ck;
5778
5901
  return null;
5779
5902
  }
5903
+ function submitViaKeystrokes(proc, prompt, ck) {
5904
+ const text = prompt.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
5905
+ const safeWrite = (data) => {
5906
+ try {
5907
+ proc.write(data);
5908
+ } catch {
5909
+ }
5910
+ lastPtyDataAt.set(ck, Date.now());
5911
+ };
5912
+ const waitForIdle = (quietMs, capMs) => new Promise((resolve) => {
5913
+ const start = Date.now();
5914
+ const tick = () => {
5915
+ const last = lastPtyDataAt.get(ck) ?? 0;
5916
+ if (Date.now() - last >= quietMs || Date.now() - start >= capMs) resolve();
5917
+ else setTimeout(tick, 50);
5918
+ };
5919
+ tick();
5920
+ });
5921
+ void (async () => {
5922
+ await waitForIdle(300, 3e3);
5923
+ safeWrite("");
5924
+ safeWrite(`\x1B[200~${text}\x1B[201~`);
5925
+ await waitForIdle(250, 2e3);
5926
+ const delay = (ms) => new Promise((r) => setTimeout(r, ms));
5927
+ const want = normalizePromptText(text);
5928
+ const baseline = userMessageSeq.get(ck) ?? 0;
5929
+ const accepted = () => (userMessageSeq.get(ck) ?? 0) > baseline && lastUserMessageText.get(ck) === want;
5930
+ const MAX_ATTEMPTS = 12;
5931
+ const POLL_MS = 500;
5932
+ for (let attempt = 0; attempt < MAX_ATTEMPTS && !accepted(); attempt++) {
5933
+ safeWrite("\r");
5934
+ for (let waited = 0; waited < POLL_MS && !accepted(); waited += 50) {
5935
+ await delay(50);
5936
+ }
5937
+ }
5938
+ if (!accepted()) {
5939
+ console.warn(`[TermServer] submitViaKeystrokes: prompt not confirmed accepted after ${MAX_ATTEMPTS} Enter attempts for ${ck} \u2014 it may not have submitted.`);
5940
+ }
5941
+ })();
5942
+ }
5780
5943
  function killAllPtyProcesses() {
5781
5944
  console.log(`[TermServer] Killing ${ptyProcesses.size} PTY processes`);
5782
5945
  ptyProcesses.forEach((proc) => killPtyProcess(proc));
@@ -5787,9 +5950,15 @@ function killAllPtyProcesses() {
5787
5950
  }
5788
5951
  function killPtyProcess(proc) {
5789
5952
  proc.process.kill();
5953
+ unregisterPty(proc.pid);
5790
5954
  }
5955
+ var yoloEnabled = false;
5956
+ var additionalParams = "";
5791
5957
  var pendingPreseededPrompts = /* @__PURE__ */ new Map();
5792
5958
  async function startTerminalForAgent(officeId, agentId, workingDir, cols, rows, preseededPrompt, launchMode = "copilot") {
5959
+ if (process.env.COPILOT_E2E === "1") {
5960
+ launchMode = "shell";
5961
+ }
5793
5962
  if (!terminalBackend || !terminalBackend.isAvailable()) {
5794
5963
  return { success: false, error: "terminal backend not available" };
5795
5964
  }
@@ -5806,7 +5975,7 @@ async function startTerminalForAgent(officeId, agentId, workingDir, cols, rows,
5806
5975
  const officeData = getOfficeSession(officeId);
5807
5976
  let sessionId = officeData.sessionIds.get(agentId);
5808
5977
  if (!sessionId) {
5809
- sessionId = crypto.randomUUID();
5978
+ sessionId = crypto3.randomUUID();
5810
5979
  officeData.sessionIds.set(agentId, sessionId);
5811
5980
  await saveOfficeSessionFile(officeId);
5812
5981
  console.log(`[TermServer] New session GUID for ${ck}: ${sessionId}`);
@@ -5817,9 +5986,9 @@ async function startTerminalForAgent(officeId, agentId, workingDir, cols, rows,
5817
5986
  const shell = os3.platform() === "win32" ? "powershell.exe" : "bash";
5818
5987
  let cwd = process.cwd();
5819
5988
  if (workingDir) {
5820
- const customPath = path3.isAbsolute(workingDir) ? workingDir : path3.join(process.cwd(), workingDir);
5989
+ const customPath = path4.isAbsolute(workingDir) ? workingDir : path4.join(process.cwd(), workingDir);
5821
5990
  try {
5822
- await fs2.promises.access(customPath, fs2.constants.F_OK);
5991
+ await fs3.promises.access(customPath, fs3.constants.F_OK);
5823
5992
  cwd = customPath;
5824
5993
  } catch {
5825
5994
  }
@@ -5847,6 +6016,9 @@ async function startTerminalForAgent(officeId, agentId, workingDir, cols, rows,
5847
6016
  workingDir
5848
6017
  });
5849
6018
  agentToTerminal.set(ck, terminalKey);
6019
+ if (terminalBackend.name === "node-pty") {
6020
+ registerPty({ pid: proc.pid, agentId, sessionId, startedAt: Date.now() });
6021
+ }
5850
6022
  if (!shellOnlyMode) {
5851
6023
  send({ type: "terminal-preload-status", agentId, status: "preloading" });
5852
6024
  }
@@ -5905,8 +6077,16 @@ async function startTerminalForAgent(officeId, agentId, workingDir, cols, rows,
5905
6077
  console.log(`[TermServer] Forwarding turn_start for ${ck}`);
5906
6078
  send({ type: "copilot-turn-start", agentId });
5907
6079
  } else if (event.type === "user.message") {
6080
+ userMessageSeq.set(ck, (userMessageSeq.get(ck) ?? 0) + 1);
6081
+ let rawUserText = "";
6082
+ {
6083
+ const d = event.data ?? {};
6084
+ const raw = d.content || d.message || d.text || d.input || d.prompt || d.body || "";
6085
+ rawUserText = String(raw);
6086
+ lastUserMessageText.set(ck, normalizePromptText(rawUserText));
6087
+ }
5908
6088
  console.log(`[TermServer] Forwarding user_message for ${ck}, data keys: ${JSON.stringify(Object.keys(event.data || {}))}`);
5909
- send({ type: "copilot-user-message", agentId });
6089
+ send({ type: "copilot-user-message", agentId, text: rawUserText });
5910
6090
  const existing = officeData.sessionMeta.get(agentId);
5911
6091
  const existingTitle = typeof existing?.title === "string" ? existing.title.trim() : "";
5912
6092
  if (existingTitle) {
@@ -5939,8 +6119,10 @@ async function startTerminalForAgent(officeId, agentId, workingDir, cols, rows,
5939
6119
  console.log(`[TermServer] Subagent FAILED for ${ck}: ${d.agentName ?? "unknown"} (toolCallId: ${d.toolCallId ?? "?"}, error: ${d.error ?? "unknown"})`);
5940
6120
  }
5941
6121
  const isFleetCriticalEvent = event.type.startsWith("subagent.") || event.type === "system.notification" || event.type === "tool.execution_start" && event.data?.toolName === "task";
5942
- if (isFleetCriticalEvent || hasActiveViewer(ck)) {
6122
+ if (isFleetCriticalEvent || hasActiveViewer2(ck)) {
5943
6123
  send({ type: "copilot-event", agentId, event });
6124
+ } else if (agentForwardKeys.has(ck)) {
6125
+ send({ type: "copilot-event", agentId, event, mainOnly: true });
5944
6126
  } else {
5945
6127
  console.warn(`[TermServer] Dropped copilot-event ${event.type} for ${ck} \u2014 no active viewer (viewers: [${[...activeAgentViewers].join(", ")}])`);
5946
6128
  }
@@ -5952,18 +6134,23 @@ async function startTerminalForAgent(officeId, agentId, workingDir, cols, rows,
5952
6134
  let flushTimer = null;
5953
6135
  const flushData = () => {
5954
6136
  flushTimer = null;
5955
- if (pendingData && hasActiveViewer(ck)) {
6137
+ if (pendingData && hasActiveViewer2(ck)) {
5956
6138
  send({ type: "terminal-data", agentId, data: pendingData });
5957
6139
  }
5958
6140
  pendingData = "";
5959
6141
  };
5960
6142
  proc.onData((data) => {
6143
+ lastPtyDataAt.set(ck, Date.now());
5961
6144
  appendToScrollback(ck, data);
5962
- if (!shellOnlyMode && terminalBackend?.name === "node-pty" && !hasSignalledReady && data.includes("Environment loaded")) {
5963
- console.log(`[TermServer] Primary ready signal for ${ck}: "Environment loaded" detected`);
5964
- signalReady();
6145
+ if (!shellOnlyMode && terminalBackend?.name === "node-pty" && !hasSignalledReady) {
6146
+ const hasLegacyReadyMarker = data.includes("Environment loaded");
6147
+ const hasInteractiveFooter = data.includes("/ commands") || data.includes("? help");
6148
+ if (hasLegacyReadyMarker || hasInteractiveFooter) {
6149
+ console.log(`[TermServer] Ready signal for ${ck}: PTY marker detected`);
6150
+ signalReady();
6151
+ }
5965
6152
  }
5966
- if (!hasActiveViewer(ck)) return;
6153
+ if (!hasActiveViewer2(ck)) return;
5967
6154
  pendingData += data;
5968
6155
  if (pendingData.length >= MAX_PENDING_BYTES) {
5969
6156
  if (flushTimer) clearTimeout(flushTimer);
@@ -5974,12 +6161,16 @@ async function startTerminalForAgent(officeId, agentId, workingDir, cols, rows,
5974
6161
  });
5975
6162
  proc.onExit(({ exitCode }) => {
5976
6163
  send({ type: "terminal-exit", agentId, exitCode });
6164
+ unregisterPty(proc.pid);
5977
6165
  ptyProcesses.delete(terminalKey);
5978
6166
  activeAgentViewers.delete(ck);
5979
6167
  agentScrollbackBuffers.delete(ck);
5980
6168
  agentScrollbackBytes.delete(ck);
5981
6169
  agentReadyState.delete(ck);
5982
6170
  agentInTurn.delete(ck);
6171
+ lastPtyDataAt.delete(ck);
6172
+ userMessageSeq.delete(ck);
6173
+ lastUserMessageText.delete(ck);
5983
6174
  const w = agentWatchers.get(ck);
5984
6175
  if (w) {
5985
6176
  w.stop();
@@ -5988,8 +6179,10 @@ async function startTerminalForAgent(officeId, agentId, workingDir, cols, rows,
5988
6179
  });
5989
6180
  if (!shellOnlyMode && terminalBackend.name === "node-pty") {
5990
6181
  setTimeout(() => {
5991
- console.log(`[TermServer] Starting copilot --session-id for ${ck}: ${sessionId}`);
5992
- proc.write(`copilot --session-id ${sessionId}\r`);
6182
+ const yoloFlag = yoloEnabled ? " --yolo" : "";
6183
+ const extraParams = additionalParams ? ` ${additionalParams}` : "";
6184
+ console.log(`[TermServer] Starting copilot --session-id for ${ck}: ${sessionId}${yoloEnabled ? " (yolo)" : ""}${additionalParams ? ` (params: ${additionalParams})` : ""}`);
6185
+ proc.write(`copilot --session-id=${sessionId}${yoloFlag}${extraParams}\r`);
5993
6186
  }, 500);
5994
6187
  }
5995
6188
  return { success: true, pid: proc.pid, sessionId };
@@ -6020,12 +6213,47 @@ async function handleMessage(msg) {
6020
6213
  }
6021
6214
  break;
6022
6215
  }
6216
+ case "submit-prompt": {
6217
+ const key = getTerminalKey(msg.officeId, msg.agentId);
6218
+ const proc = key ? ptyProcesses.get(key) : null;
6219
+ if (proc) {
6220
+ agentForwardKeys.add(compositeKey(msg.officeId, msg.agentId));
6221
+ const backendProc = proc.process;
6222
+ if (typeof backendProc.submitPrompt === "function") {
6223
+ backendProc.submitPrompt(msg.prompt, msg.label);
6224
+ } else {
6225
+ submitViaKeystrokes(backendProc, msg.prompt, key);
6226
+ }
6227
+ send({ type: "response", requestId: msg.requestId, result: { success: true } });
6228
+ } else {
6229
+ const ck = compositeKey(msg.officeId, msg.agentId);
6230
+ console.log(`[TermServer] SUBMIT-PROMPT FAILED \u2014 no PTY for ${ck}`);
6231
+ send({ type: "response", requestId: msg.requestId, result: { success: false, error: `No PTY for ${ck}` } });
6232
+ }
6233
+ break;
6234
+ }
6235
+ case "set-agent-forwarding": {
6236
+ const ck = compositeKey(msg.officeId, msg.agentId);
6237
+ if (msg.enabled) agentForwardKeys.add(ck);
6238
+ else agentForwardKeys.delete(ck);
6239
+ break;
6240
+ }
6023
6241
  case "resize": {
6024
6242
  const key = getTerminalKey(msg.officeId, msg.agentId);
6025
6243
  const proc = key ? ptyProcesses.get(key) : null;
6026
6244
  if (proc) proc.process.resize(msg.cols, msg.rows);
6027
6245
  break;
6028
6246
  }
6247
+ case "set-yolo": {
6248
+ yoloEnabled = msg.enabled;
6249
+ console.log(`[TermServer] YOLO mode ${yoloEnabled ? "ENABLED" : "disabled"}`);
6250
+ break;
6251
+ }
6252
+ case "set-additional-params": {
6253
+ additionalParams = (msg.params ?? "").trim();
6254
+ console.log(`[TermServer] Additional params ${additionalParams ? `set: ${additionalParams}` : "cleared"}`);
6255
+ break;
6256
+ }
6029
6257
  case "kill": {
6030
6258
  const ck = compositeKey(msg.officeId, msg.agentId);
6031
6259
  const key = getTerminalKey(msg.officeId, msg.agentId);
@@ -6058,11 +6286,9 @@ async function handleMessage(msg) {
6058
6286
  case "attach": {
6059
6287
  const ck = compositeKey(msg.officeId, msg.agentId);
6060
6288
  console.log(`[TermServer] Attaching viewer for ${ck}`);
6061
- activeAgentViewers.add(ck);
6062
- const termKey = agentToTerminal.get(ck);
6063
- if (termKey && termKey !== ck) {
6064
- activeAgentViewers.add(termKey);
6065
- console.log(`[TermServer] Also marking original key ${termKey} as active viewer (transferred session)`);
6289
+ const { aliasKey } = addAgentViewer(ck, viewerMaps);
6290
+ if (aliasKey) {
6291
+ console.log(`[TermServer] Also marking original key ${aliasKey} as active viewer (transferred session)`);
6066
6292
  }
6067
6293
  const chunks = agentScrollbackBuffers.get(ck) || [];
6068
6294
  const rawScrollback = chunks.join("");
@@ -6072,11 +6298,7 @@ async function handleMessage(msg) {
6072
6298
  case "detach": {
6073
6299
  const ck = compositeKey(msg.officeId, msg.agentId);
6074
6300
  console.log(`[TermServer] Detaching viewer for ${ck}`);
6075
- activeAgentViewers.delete(ck);
6076
- const termKey = agentToTerminal.get(ck);
6077
- if (termKey && termKey !== ck) {
6078
- activeAgentViewers.delete(termKey);
6079
- }
6301
+ removeAgentViewer(ck, viewerMaps);
6080
6302
  break;
6081
6303
  }
6082
6304
  case "exists": {
@@ -6093,6 +6315,15 @@ async function handleMessage(msg) {
6093
6315
  const officeData = getOfficeSession(msg.officeId);
6094
6316
  const current = officeData.sessionIds.get(msg.agentId);
6095
6317
  const changed = !!normalized && current !== normalized;
6318
+ if (changed && normalized) {
6319
+ for (const [otherAgent, otherSid] of officeData.sessionIds) {
6320
+ if (otherAgent !== msg.agentId && otherSid === normalized) {
6321
+ console.warn(`[TermServer] Rejected set-session-id ${normalized} for ${compositeKey(msg.officeId, msg.agentId)} \u2014 already in use by ${otherAgent}`);
6322
+ send({ type: "response", requestId: msg.requestId, result: { success: false, error: "sessionId already in use by another agent in this office" } });
6323
+ return;
6324
+ }
6325
+ }
6326
+ }
6096
6327
  if (changed) {
6097
6328
  archiveSessionId(msg.officeId, msg.agentId);
6098
6329
  officeData.sessionIds.set(msg.agentId, normalized);
@@ -6119,15 +6350,18 @@ async function handleMessage(msg) {
6119
6350
  const ptyProc = termKey ? ptyProcesses.get(termKey) : null;
6120
6351
  let cwd = process.cwd();
6121
6352
  if (ptyProc?.workingDir) {
6122
- const customPath = path3.join(process.cwd(), ptyProc.workingDir);
6353
+ const customPath = path4.join(process.cwd(), ptyProc.workingDir);
6123
6354
  try {
6124
- await fs2.promises.access(customPath, fs2.constants.F_OK);
6355
+ await fs3.promises.access(customPath, fs3.constants.F_OK);
6125
6356
  cwd = customPath;
6126
6357
  } catch {
6127
6358
  }
6128
6359
  }
6129
6360
  try {
6130
- (0, import_child_process2.spawn)("wt", ["-d", cwd, "copilot", "--session-id", sid], { detached: true, stdio: "ignore" }).unref();
6361
+ const wtArgs = ["-d", cwd, "copilot", "--session-id", sid];
6362
+ if (yoloEnabled) wtArgs.push("--yolo");
6363
+ if (additionalParams) wtArgs.push(...additionalParams.split(/\s+/).filter(Boolean));
6364
+ (0, import_child_process2.spawn)("wt", wtArgs, { detached: true, stdio: "ignore" }).unref();
6131
6365
  send({ type: "response", requestId: msg.requestId, result: { success: true } });
6132
6366
  } catch (error) {
6133
6367
  send({ type: "response", requestId: msg.requestId, result: { success: false, error: String(error) } });
@@ -6159,7 +6393,7 @@ async function handleMessage(msg) {
6159
6393
  hasAutoTitled.delete(ck);
6160
6394
  send({ type: "session-meta-updated", agentId: msg.agentId, meta: { title: "" } });
6161
6395
  archiveSessionId(msg.officeId, msg.agentId);
6162
- const newSessionId = crypto.randomUUID();
6396
+ const newSessionId = crypto3.randomUUID();
6163
6397
  officeDataReset.sessionIds.set(msg.agentId, newSessionId);
6164
6398
  await saveOfficeSessionFile(msg.officeId);
6165
6399
  console.log(`[TermServer] Reset session for ${ck}: new GUID ${newSessionId}`);
@@ -6210,7 +6444,7 @@ async function handleMessage(msg) {
6210
6444
  }
6211
6445
  officeData.sessionMeta.clear();
6212
6446
  for (const agentId of officeData.sessionIds.keys()) {
6213
- officeData.sessionIds.set(agentId, crypto.randomUUID());
6447
+ officeData.sessionIds.set(agentId, crypto3.randomUUID());
6214
6448
  }
6215
6449
  await saveOfficeSessionFile(officeId);
6216
6450
  console.log(`[TermServer] All sessions reset for ${officeId}`);
@@ -6266,7 +6500,15 @@ async function handleMessage(msg) {
6266
6500
  }
6267
6501
  case "get-all-session-meta": {
6268
6502
  const officeData = getOfficeSession(msg.officeId);
6269
- send({ type: "response", requestId: msg.requestId, result: Object.fromEntries(officeData.sessionMeta) });
6503
+ const merged = {};
6504
+ for (const [agentId, meta] of officeData.sessionMeta.entries()) {
6505
+ const sessionId = officeData.sessionIds.get(agentId);
6506
+ merged[agentId] = sessionId ? { ...meta, sessionId } : { ...meta };
6507
+ }
6508
+ for (const [agentId, sessionId] of officeData.sessionIds.entries()) {
6509
+ if (!merged[agentId]) merged[agentId] = { title: "", sessionId };
6510
+ }
6511
+ send({ type: "response", requestId: msg.requestId, result: merged });
6270
6512
  break;
6271
6513
  }
6272
6514
  case "create-office-session": {
@@ -6277,7 +6519,7 @@ async function handleMessage(msg) {
6277
6519
  case "delete-office-session": {
6278
6520
  const filePath = getSessionFile(msg.officeId);
6279
6521
  try {
6280
- await fs2.promises.unlink(filePath);
6522
+ await fs3.promises.unlink(filePath);
6281
6523
  officeSessions.delete(msg.officeId);
6282
6524
  console.log(`[TermServer] Deleted session file for ${msg.officeId}`);
6283
6525
  } catch {