copilotoffice 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3044,8 +3044,8 @@ 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 path5 = require("path");
3048
- var os4 = require("os");
3047
+ var path7 = require("path");
3048
+ var os5 = require("os");
3049
3049
  var crypto_1 = require("crypto");
3050
3050
  var net_1 = require("net");
3051
3051
  var api_1 = require_api();
@@ -3180,9 +3180,9 @@ var require_main = __commonJS({
3180
3180
  }
3181
3181
  let result;
3182
3182
  if (XDG_RUNTIME_DIR) {
3183
- result = path5.join(XDG_RUNTIME_DIR, `vscode-ipc-${randomSuffix}.sock`);
3183
+ result = path7.join(XDG_RUNTIME_DIR, `vscode-ipc-${randomSuffix}.sock`);
3184
3184
  } else {
3185
- result = path5.join(os4.tmpdir(), `vscode-${randomSuffix}.sock`);
3185
+ result = path7.join(os5.tmpdir(), `vscode-${randomSuffix}.sock`);
3186
3186
  }
3187
3187
  const limit = safeIpcPathLengths.get(process.platform);
3188
3188
  if (limit !== void 0 && result.length > limit) {
@@ -6441,40 +6441,40 @@ function normalizeSqliteParams(params) {
6441
6441
  }
6442
6442
  function createSessionFsAdapter(provider) {
6443
6443
  return {
6444
- readFile: async ({ path: path5 }) => {
6444
+ readFile: async ({ path: path7 }) => {
6445
6445
  try {
6446
- const content = await provider.readFile(path5);
6446
+ const content = await provider.readFile(path7);
6447
6447
  return { content };
6448
6448
  } catch (err) {
6449
6449
  return { content: "", error: toSessionFsError(err) };
6450
6450
  }
6451
6451
  },
6452
- writeFile: async ({ path: path5, content, mode }) => {
6452
+ writeFile: async ({ path: path7, content, mode }) => {
6453
6453
  try {
6454
- await provider.writeFile(path5, content, mode);
6454
+ await provider.writeFile(path7, content, mode);
6455
6455
  return void 0;
6456
6456
  } catch (err) {
6457
6457
  return toSessionFsError(err);
6458
6458
  }
6459
6459
  },
6460
- appendFile: async ({ path: path5, content, mode }) => {
6460
+ appendFile: async ({ path: path7, content, mode }) => {
6461
6461
  try {
6462
- await provider.appendFile(path5, content, mode);
6462
+ await provider.appendFile(path7, content, mode);
6463
6463
  return void 0;
6464
6464
  } catch (err) {
6465
6465
  return toSessionFsError(err);
6466
6466
  }
6467
6467
  },
6468
- exists: async ({ path: path5 }) => {
6468
+ exists: async ({ path: path7 }) => {
6469
6469
  try {
6470
- return { exists: await provider.exists(path5) };
6470
+ return { exists: await provider.exists(path7) };
6471
6471
  } catch {
6472
6472
  return { exists: false };
6473
6473
  }
6474
6474
  },
6475
- stat: async ({ path: path5 }) => {
6475
+ stat: async ({ path: path7 }) => {
6476
6476
  try {
6477
- return await provider.stat(path5);
6477
+ return await provider.stat(path7);
6478
6478
  } catch (err) {
6479
6479
  return {
6480
6480
  isFile: false,
@@ -6486,33 +6486,33 @@ function createSessionFsAdapter(provider) {
6486
6486
  };
6487
6487
  }
6488
6488
  },
6489
- mkdir: async ({ path: path5, recursive, mode }) => {
6489
+ mkdir: async ({ path: path7, recursive, mode }) => {
6490
6490
  try {
6491
- await provider.mkdir(path5, recursive ?? false, mode);
6491
+ await provider.mkdir(path7, recursive ?? false, mode);
6492
6492
  return void 0;
6493
6493
  } catch (err) {
6494
6494
  return toSessionFsError(err);
6495
6495
  }
6496
6496
  },
6497
- readdir: async ({ path: path5 }) => {
6497
+ readdir: async ({ path: path7 }) => {
6498
6498
  try {
6499
- const entries = await provider.readdir(path5);
6499
+ const entries = await provider.readdir(path7);
6500
6500
  return { entries };
6501
6501
  } catch (err) {
6502
6502
  return { entries: [], error: toSessionFsError(err) };
6503
6503
  }
6504
6504
  },
6505
- readdirWithTypes: async ({ path: path5 }) => {
6505
+ readdirWithTypes: async ({ path: path7 }) => {
6506
6506
  try {
6507
- const entries = await provider.readdirWithTypes(path5);
6507
+ const entries = await provider.readdirWithTypes(path7);
6508
6508
  return { entries };
6509
6509
  } catch (err) {
6510
6510
  return { entries: [], error: toSessionFsError(err) };
6511
6511
  }
6512
6512
  },
6513
- rm: async ({ path: path5, recursive, force }) => {
6513
+ rm: async ({ path: path7, recursive, force }) => {
6514
6514
  try {
6515
- await provider.rm(path5, recursive ?? false, force ?? false);
6515
+ await provider.rm(path7, recursive ?? false, force ?? false);
6516
6516
  return void 0;
6517
6517
  } catch (err) {
6518
6518
  return toSessionFsError(err);
@@ -9367,9 +9367,9 @@ var init_dist = __esm({
9367
9367
  });
9368
9368
 
9369
9369
  // electron/terminal/server.ts
9370
- var path4 = __toESM(require("path"));
9371
- var os3 = __toESM(require("os"));
9372
- var fs3 = __toESM(require("fs"));
9370
+ var path6 = __toESM(require("path"));
9371
+ var os4 = __toESM(require("os"));
9372
+ var fs5 = __toESM(require("fs"));
9373
9373
  var crypto3 = __toESM(require("crypto"));
9374
9374
  var import_child_process2 = require("child_process");
9375
9375
 
@@ -9566,6 +9566,53 @@ function formatToolStatus(toolName, args) {
9566
9566
  return `Using ${toolName}`;
9567
9567
  }
9568
9568
  }
9569
+ function normalizeAskUserArgs(args) {
9570
+ const a = args ?? {};
9571
+ const questionRaw = a.question ?? a.prompt ?? a.message ?? a.text ?? "";
9572
+ const question = typeof questionRaw === "string" ? questionRaw : String(questionRaw ?? "");
9573
+ const rawOptions = a.options ?? a.choices ?? a.answers ?? a.selections ?? [];
9574
+ const options = [];
9575
+ if (Array.isArray(rawOptions)) {
9576
+ for (const opt of rawOptions) {
9577
+ if (typeof opt === "string") {
9578
+ options.push({ text: opt });
9579
+ } else if (opt && typeof opt === "object") {
9580
+ const o = opt;
9581
+ const text = o.text ?? o.label ?? o.value ?? o.name ?? "";
9582
+ options.push({ text: typeof text === "string" ? text : String(text ?? "") });
9583
+ }
9584
+ }
9585
+ }
9586
+ const freeformRaw = a.freeform ?? a.allowFreeform ?? a.allowFreeText ?? a.allowCustom ?? a.freeText ?? false;
9587
+ const freeform = Boolean(freeformRaw);
9588
+ return { question, options, freeform };
9589
+ }
9590
+ function buildAskUserRelay(event, backendName) {
9591
+ const d = event.data ?? {};
9592
+ if (event.type === "user_input.requested") {
9593
+ const options = Array.isArray(d.choices) ? d.choices.map((c) => ({
9594
+ text: typeof c === "string" ? c : String(c?.text ?? c?.label ?? c?.value ?? "")
9595
+ })) : [];
9596
+ return {
9597
+ toolId: String(d.toolCallId ?? ""),
9598
+ requestId: String(d.requestId ?? ""),
9599
+ question: String(d.question ?? ""),
9600
+ options,
9601
+ freeform: Boolean(d.allowFreeform)
9602
+ };
9603
+ }
9604
+ if (event.type === "tool.execution_start" && d.toolName === "ask_user" && backendName === "node-pty") {
9605
+ const norm = normalizeAskUserArgs(d.arguments);
9606
+ return {
9607
+ toolId: String(d.toolCallId ?? ""),
9608
+ requestId: "",
9609
+ question: norm.question,
9610
+ options: norm.options,
9611
+ freeform: norm.freeform
9612
+ };
9613
+ }
9614
+ return null;
9615
+ }
9569
9616
 
9570
9617
  // electron/terminal/event-source.ts
9571
9618
  var EVENT_METADATA_KEYS = /* @__PURE__ */ new Set(["type", "id", "timestamp", "parentId"]);
@@ -9642,23 +9689,232 @@ var FileWatcherEventSourceFactory = class {
9642
9689
 
9643
9690
  // electron/terminal/terminal-backend.ts
9644
9691
  var import_child_process = require("child_process");
9692
+ var os3 = __toESM(require("os"));
9693
+ var path4 = __toESM(require("path"));
9694
+
9695
+ // electron/terminal/custom-agents.ts
9696
+ var fs2 = __toESM(require("fs"));
9645
9697
  var os2 = __toESM(require("os"));
9646
9698
  var path2 = __toESM(require("path"));
9699
+ function parseAgentFrontmatter(content) {
9700
+ const normalized = content.replace(/\r\n/g, "\n");
9701
+ if (!normalized.startsWith("---\n") && normalized !== "---") {
9702
+ return { prompt: content.trim() };
9703
+ }
9704
+ const end = normalized.indexOf("\n---", 3);
9705
+ if (end === -1) {
9706
+ return { prompt: content.trim() };
9707
+ }
9708
+ const fmBlock = normalized.slice(4, end);
9709
+ const afterFence = normalized.indexOf("\n", end + 1);
9710
+ const prompt = (afterFence === -1 ? "" : normalized.slice(afterFence + 1)).trim();
9711
+ const raw = parseFrontmatterBlock(fmBlock);
9712
+ const out = { prompt };
9713
+ if (typeof raw.name === "string") out.name = raw.name;
9714
+ if (typeof raw.displayName === "string") out.displayName = raw.displayName;
9715
+ if (typeof raw.description === "string") out.description = raw.description;
9716
+ if (typeof raw.model === "string") out.model = raw.model;
9717
+ if (typeof raw.infer === "boolean") out.infer = raw.infer;
9718
+ if (Array.isArray(raw.tools)) out.tools = raw.tools.map(String);
9719
+ if (Array.isArray(raw.skills)) out.skills = raw.skills.map(String);
9720
+ return out;
9721
+ }
9722
+ function parseFrontmatterBlock(block) {
9723
+ const lines = block.split("\n");
9724
+ const result = {};
9725
+ let i = 0;
9726
+ const keyLine = /^([A-Za-z_][A-Za-z0-9_-]*):(.*)$/;
9727
+ while (i < lines.length) {
9728
+ const line = lines[i];
9729
+ if (!line.trim() || /^\s*#/.test(line)) {
9730
+ i++;
9731
+ continue;
9732
+ }
9733
+ const m = keyLine.exec(line);
9734
+ if (!m || /^\s/.test(line)) {
9735
+ i++;
9736
+ continue;
9737
+ }
9738
+ const key = m[1];
9739
+ const rest = m[2].trim();
9740
+ if (rest === ">" || rest === "|" || rest === ">-" || rest === "|-") {
9741
+ const folded = rest[0] === ">";
9742
+ const collected = [];
9743
+ i++;
9744
+ while (i < lines.length && (lines[i].trim() === "" || /^\s/.test(lines[i]))) {
9745
+ collected.push(lines[i].replace(/^\s+/, ""));
9746
+ i++;
9747
+ }
9748
+ while (collected.length && collected[collected.length - 1] === "") collected.pop();
9749
+ result[key] = folded ? collected.join(" ").replace(/\s+/g, " ").trim() : collected.join("\n");
9750
+ continue;
9751
+ }
9752
+ if (rest.startsWith("[") && rest.endsWith("]")) {
9753
+ result[key] = rest.slice(1, -1).split(",").map((s) => unquote(s.trim())).filter((s) => s.length > 0);
9754
+ i++;
9755
+ continue;
9756
+ }
9757
+ if (rest === "") {
9758
+ const items = [];
9759
+ let j = i + 1;
9760
+ while (j < lines.length && (lines[j].trim() === "" || /^\s/.test(lines[j]))) {
9761
+ const item = /^\s*-\s+(.*)$/.exec(lines[j]);
9762
+ if (item) items.push(unquote(item[1].trim()));
9763
+ else if (lines[j].trim() === "") {
9764
+ } else break;
9765
+ j++;
9766
+ }
9767
+ if (items.length > 0) {
9768
+ result[key] = items;
9769
+ i = j;
9770
+ continue;
9771
+ }
9772
+ result[key] = "";
9773
+ i++;
9774
+ continue;
9775
+ }
9776
+ result[key] = coerceScalar(unquote(rest));
9777
+ i++;
9778
+ }
9779
+ return result;
9780
+ }
9781
+ function unquote(s) {
9782
+ if (s.startsWith('"') && s.endsWith('"') || s.startsWith("'") && s.endsWith("'")) {
9783
+ return s.slice(1, -1);
9784
+ }
9785
+ return s;
9786
+ }
9787
+ function coerceScalar(s) {
9788
+ if (s === "true") return true;
9789
+ if (s === "false") return false;
9790
+ return s;
9791
+ }
9792
+ function toCustomAgentConfig(parsed, fallbackName) {
9793
+ const name = parsed.name || fallbackName;
9794
+ if (!name || !parsed.prompt) return null;
9795
+ const config = {
9796
+ name,
9797
+ prompt: parsed.prompt
9798
+ };
9799
+ if (parsed.displayName) config.displayName = parsed.displayName;
9800
+ if (parsed.description) config.description = parsed.description;
9801
+ if (parsed.tools && parsed.tools.length > 0) config.tools = parsed.tools;
9802
+ if (parsed.model) config.model = parsed.model;
9803
+ if (typeof parsed.infer === "boolean") config.infer = parsed.infer;
9804
+ if (parsed.skills && parsed.skills.length > 0) config.skills = parsed.skills;
9805
+ return config;
9806
+ }
9807
+ function resolveCopilotHome(env = process.env, homeDir = os2.homedir()) {
9808
+ const override = env.COPILOT_HOME?.trim();
9809
+ if (override) return override;
9810
+ return path2.join(homeDir, ".copilot");
9811
+ }
9812
+ function customAgentDirs(cwd, copilotHome = resolveCopilotHome()) {
9813
+ const dirs = [path2.join(copilotHome, "agents")];
9814
+ if (cwd) {
9815
+ dirs.push(path2.join(cwd, ".github", "agents"));
9816
+ }
9817
+ return dirs;
9818
+ }
9819
+ function loadCustomAgents(cwd, copilotHome = resolveCopilotHome()) {
9820
+ const byName = /* @__PURE__ */ new Map();
9821
+ for (const dir of customAgentDirs(cwd, copilotHome)) {
9822
+ let entries;
9823
+ try {
9824
+ entries = fs2.readdirSync(dir);
9825
+ } catch {
9826
+ continue;
9827
+ }
9828
+ for (const entry of entries) {
9829
+ if (!entry.toLowerCase().endsWith(".agent.md")) continue;
9830
+ const full = path2.join(dir, entry);
9831
+ let content;
9832
+ try {
9833
+ content = fs2.readFileSync(full, "utf8");
9834
+ } catch {
9835
+ continue;
9836
+ }
9837
+ const fallbackName = entry.slice(0, -".agent.md".length);
9838
+ const config = toCustomAgentConfig(parseAgentFrontmatter(content), fallbackName);
9839
+ if (config && !byName.has(config.name)) {
9840
+ byName.set(config.name, config);
9841
+ }
9842
+ }
9843
+ }
9844
+ return [...byName.values()];
9845
+ }
9846
+
9847
+ // electron/terminal/custom-skills.ts
9848
+ var fs3 = __toESM(require("fs"));
9849
+ var path3 = __toESM(require("path"));
9850
+ function skillDirs(cwd, copilotHome = resolveCopilotHome()) {
9851
+ const dirs = [];
9852
+ if (cwd) {
9853
+ dirs.push(path3.join(cwd, ".github", "skills"));
9854
+ }
9855
+ dirs.push(path3.join(copilotHome, "skills"));
9856
+ return dirs;
9857
+ }
9858
+ function resolveSkillDirectories(cwd, copilotHome = resolveCopilotHome()) {
9859
+ const out = [];
9860
+ for (const dir of skillDirs(cwd, copilotHome)) {
9861
+ try {
9862
+ if (fs3.statSync(dir).isDirectory()) {
9863
+ out.push(dir);
9864
+ }
9865
+ } catch {
9866
+ }
9867
+ }
9868
+ return out;
9869
+ }
9870
+
9871
+ // electron/terminal/terminal-backend.ts
9872
+ var pendingUserInput = /* @__PURE__ */ new Map();
9873
+ function makeUserInputHandler(sessionId) {
9874
+ return (_request, ctx) => new Promise((resolve) => {
9875
+ const scope = sessionId || ctx?.sessionId || "";
9876
+ const existing = pendingUserInput.get(scope);
9877
+ if (existing) {
9878
+ console.warn(
9879
+ `[terminal-backend] makeUserInputHandler: replacing an UNRESOLVED pending user-input for session="${scope}" (its promise will never resolve)`
9880
+ );
9881
+ }
9882
+ pendingUserInput.set(scope, { resolve, sessionId: scope });
9883
+ });
9884
+ }
9885
+ function handlePendingUserInput(sessionId, answer) {
9886
+ const entry = pendingUserInput.get(sessionId);
9887
+ if (!entry) {
9888
+ console.warn(
9889
+ `[terminal-backend] handlePendingUserInput: no pending user-input for session="${sessionId}" (already resolved or unknown) \u2014 no-op`
9890
+ );
9891
+ return false;
9892
+ }
9893
+ pendingUserInput.delete(sessionId);
9894
+ entry.resolve({ answer: answer.answer, wasFreeform: answer.wasFreeform });
9895
+ return true;
9896
+ }
9897
+ function clearPendingUserInputForSession(sessionId) {
9898
+ return pendingUserInput.delete(sessionId) ? 1 : 0;
9899
+ }
9900
+ function answerTransport(proc) {
9901
+ return typeof proc.submitPrompt === "function" ? "sdk" : "keystroke";
9902
+ }
9647
9903
  function splitPathEntries(pathValue) {
9648
- return pathValue.split(path2.delimiter).filter(Boolean);
9904
+ return pathValue.split(path4.delimiter).filter(Boolean);
9649
9905
  }
9650
9906
  function normalizeEntry(entry) {
9651
- return path2.normalize(entry).replace(/[\\\/]+$/, "");
9907
+ return path4.normalize(entry).replace(/[\\\/]+$/, "");
9652
9908
  }
9653
9909
  function getRepoNodeModulesBin(repoRoot) {
9654
- return normalizeEntry(path2.join(repoRoot, "node_modules", ".bin"));
9910
+ return normalizeEntry(path4.join(repoRoot, "node_modules", ".bin"));
9655
9911
  }
9656
9912
  function isRepoNodeModulesBin(entry, repoRoot) {
9657
9913
  return normalizeEntry(entry).toLowerCase() === getRepoNodeModulesBin(repoRoot).toLowerCase();
9658
9914
  }
9659
9915
  function sanitizeCopilotPath(pathValue, repoRoot) {
9660
9916
  if (!pathValue) return "";
9661
- return splitPathEntries(pathValue).filter((entry) => !isRepoNodeModulesBin(entry, repoRoot)).join(path2.delimiter);
9917
+ return splitPathEntries(pathValue).filter((entry) => !isRepoNodeModulesBin(entry, repoRoot)).join(path4.delimiter);
9662
9918
  }
9663
9919
  function resolveBundledCopilotCliPath() {
9664
9920
  try {
@@ -9677,9 +9933,9 @@ function resolveCopilotCliPath(repoRoot, pathValue) {
9677
9933
  const sanitizedPath = sanitizeCopilotPath(pathValue, repoRoot);
9678
9934
  const env = { ...process.env, PATH: sanitizedPath };
9679
9935
  try {
9680
- const command = os2.platform() === "win32" ? "where.exe copilot" : "which -a copilot";
9936
+ const command = os3.platform() === "win32" ? "where.exe copilot" : "which -a copilot";
9681
9937
  const output = (0, import_child_process.execSync)(command, { encoding: "utf8", env, stdio: ["ignore", "pipe", "ignore"] });
9682
- const candidates = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).filter((candidate) => !candidate.toLowerCase().includes(`${path2.sep}node_modules${path2.sep}.bin${path2.sep}copilot`.toLowerCase()));
9938
+ const candidates = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).filter((candidate) => !candidate.toLowerCase().includes(`${path4.sep}node_modules${path4.sep}.bin${path4.sep}copilot`.toLowerCase()));
9683
9939
  return candidates[0] || null;
9684
9940
  } catch {
9685
9941
  return null;
@@ -9731,7 +9987,7 @@ var NodePtyProcess = class {
9731
9987
  }
9732
9988
  kill() {
9733
9989
  try {
9734
- if (os2.platform() === "win32") {
9990
+ if (os3.platform() === "win32") {
9735
9991
  try {
9736
9992
  (0, import_child_process.execSync)(`taskkill /T /F /PID ${this.proc.pid}`, { stdio: "ignore" });
9737
9993
  } catch {
@@ -9986,7 +10242,21 @@ var CopilotSdkBackend = class _CopilotSdkBackend {
9986
10242
  const sharedConfig = {
9987
10243
  streaming: true,
9988
10244
  workingDirectory: options.cwd,
9989
- onPermissionRequest: this.approveAll ?? (async () => ({ kind: "approved" }))
10245
+ // Inject the user's custom agents (~/.copilot/agents + <cwd>/.github/agents).
10246
+ // SDK-created sessions don't auto-discover them the way the TUI does, so
10247
+ // without this "New Session" loses every custom agent. See ./custom-agents.
10248
+ customAgents: loadCustomAgents(options.cwd),
10249
+ // Inject the user's skills too (~/.copilot/skills + <cwd>/.github/skills).
10250
+ // SDK-created sessions don't auto-discover them (enableConfigDiscovery
10251
+ // defaults to false), so without this the model never loads any skill even
10252
+ // though the hosted TUI's `/` menu still lists them. See ./custom-skills.
10253
+ enableSkills: true,
10254
+ skillDirectories: resolveSkillDirectories(options.cwd),
10255
+ onPermissionRequest: this.approveAll ?? (async () => ({ kind: "approved" })),
10256
+ // spec 015 prerequisite (forStdio path): register the user-input handler so
10257
+ // the model is told `ask_user` is available and Teams/local answers can
10258
+ // resolve the pending interaction late. See makeUserInputHandler.
10259
+ onUserInputRequest: makeUserInputHandler(options.sessionId)
9990
10260
  };
9991
10261
  try {
9992
10262
  return await client.resumeSession(options.sessionId, sharedConfig);
@@ -10081,7 +10351,7 @@ var UiServerHostRuntime = class {
10081
10351
  this.status = "stopped";
10082
10352
  clearTimeout(this.listeningTimeout);
10083
10353
  try {
10084
- if (os2.platform() === "win32") {
10354
+ if (os3.platform() === "win32") {
10085
10355
  try {
10086
10356
  (0, import_child_process.execSync)(`taskkill /T /F /PID ${this.proc.pid}`, { stdio: "ignore" });
10087
10357
  } catch {
@@ -10120,7 +10390,20 @@ var ControlPlaneClient = class {
10120
10390
  const sharedConfig = {
10121
10391
  streaming: true,
10122
10392
  workingDirectory: cwd,
10123
- onPermissionRequest
10393
+ // Inject the user's custom agents (~/.copilot/agents + <cwd>/.github/agents)
10394
+ // so SDK-created ("New Session") sessions expose them like the TUI does.
10395
+ customAgents: loadCustomAgents(cwd),
10396
+ // Inject the user's skills too (~/.copilot/skills + <cwd>/.github/skills)
10397
+ // so SDK-created ("New Session") sessions load them like the TUI does.
10398
+ // See ./custom-skills.
10399
+ enableSkills: true,
10400
+ skillDirectories: resolveSkillDirectories(cwd),
10401
+ onPermissionRequest,
10402
+ // spec 015 prerequisite: advertise `ask_user` (requestUserInput: true) and
10403
+ // provide the late-resolvable answer channel. Without this the model refuses
10404
+ // to call ask_user. The relay of the question itself rides the normal event
10405
+ // stream (user_input.requested → server watcherCallback).
10406
+ onUserInputRequest: makeUserInputHandler(sessionId)
10124
10407
  };
10125
10408
  try {
10126
10409
  return await client.resumeSession(sessionId, sharedConfig);
@@ -10320,8 +10603,8 @@ var UiServerBackend = class _UiServerBackend {
10320
10603
  }
10321
10604
  };
10322
10605
  function createSdkCliLaunchConfig(cliPath) {
10323
- if (os2.platform() === "win32" && /\.(bat|cmd)$/i.test(cliPath)) {
10324
- const commandProcessor = process.env.ComSpec || path2.join(process.env.WINDIR || "C:\\Windows", "System32", "cmd.exe");
10606
+ if (os3.platform() === "win32" && /\.(bat|cmd)$/i.test(cliPath)) {
10607
+ const commandProcessor = process.env.ComSpec || path4.join(process.env.WINDIR || "C:\\Windows", "System32", "cmd.exe");
10325
10608
  return {
10326
10609
  cliPath: commandProcessor,
10327
10610
  cliArgs: ["/c", cliPath]
@@ -10361,6 +10644,12 @@ function hasActiveViewer(ck, maps) {
10361
10644
  function shouldForwardSharedHostData(ck, foreground) {
10362
10645
  return foreground === void 0 || foreground === ck;
10363
10646
  }
10647
+ function foregroundAfterStart(currentForeground, startingCk) {
10648
+ return currentForeground ?? startingCk;
10649
+ }
10650
+ function shouldReassertForeground(intendedForeground, startingCk) {
10651
+ return intendedForeground !== void 0 && intendedForeground !== startingCk;
10652
+ }
10364
10653
 
10365
10654
  // electron/terminal/session-repair.ts
10366
10655
  var crypto = __toESM(require("crypto"));
@@ -10391,12 +10680,12 @@ function repairDuplicateSessionIds(officeId, data, options = {}) {
10391
10680
 
10392
10681
  // electron/terminal/pty-registry.ts
10393
10682
  var crypto2 = __toESM(require("crypto"));
10394
- var fs2 = __toESM(require("fs"));
10395
- var path3 = __toESM(require("path"));
10396
- var DEFAULT_REGISTRY_FILE = path3.join(process.cwd(), ".data", "pty-pids.json");
10683
+ var fs4 = __toESM(require("fs"));
10684
+ var path5 = __toESM(require("path"));
10685
+ var DEFAULT_REGISTRY_FILE = path5.join(process.cwd(), ".data", "pty-pids.json");
10397
10686
  function readPtyRegistry(file = DEFAULT_REGISTRY_FILE) {
10398
10687
  try {
10399
- const raw = fs2.readFileSync(file, "utf8");
10688
+ const raw = fs4.readFileSync(file, "utf8");
10400
10689
  const parsed = JSON.parse(raw);
10401
10690
  if (!Array.isArray(parsed)) return [];
10402
10691
  return parsed.filter(
@@ -10408,10 +10697,10 @@ function readPtyRegistry(file = DEFAULT_REGISTRY_FILE) {
10408
10697
  }
10409
10698
  function writePtyRegistry(records, file = DEFAULT_REGISTRY_FILE) {
10410
10699
  try {
10411
- fs2.mkdirSync(path3.dirname(file), { recursive: true });
10700
+ fs4.mkdirSync(path5.dirname(file), { recursive: true });
10412
10701
  const tmp = `${file}.${process.pid}.${crypto2.randomBytes(4).toString("hex")}.tmp`;
10413
- fs2.writeFileSync(tmp, JSON.stringify(records, null, 2));
10414
- fs2.renameSync(tmp, file);
10702
+ fs4.writeFileSync(tmp, JSON.stringify(records, null, 2));
10703
+ fs4.renameSync(tmp, file);
10415
10704
  } catch {
10416
10705
  }
10417
10706
  }
@@ -10466,13 +10755,13 @@ function normalizePromptText(s) {
10466
10755
  var MAX_BUFFER_BYTES = 512 * 1024;
10467
10756
  var agentScrollbackBuffers = /* @__PURE__ */ new Map();
10468
10757
  var agentScrollbackBytes = /* @__PURE__ */ new Map();
10469
- var DATA_DIR = path4.join(process.cwd(), ".data");
10470
- var OLD_SESSION_FILE = path4.join(DATA_DIR, "copilot-office-sessions.json");
10471
- var OFFICES_FILE = path4.join(DATA_DIR, "copilot-offices.json");
10758
+ var DATA_DIR = path6.join(process.cwd(), ".data");
10759
+ var OLD_SESSION_FILE = path6.join(DATA_DIR, "copilot-office-sessions.json");
10760
+ var OFFICES_FILE = path6.join(DATA_DIR, "copilot-offices.json");
10472
10761
  var officeSessions = /* @__PURE__ */ new Map();
10473
10762
  var hasAutoTitled = /* @__PURE__ */ new Set();
10474
10763
  function getSessionFile(officeId) {
10475
- return path4.join(DATA_DIR, `${officeId}.sessions.json`);
10764
+ return path6.join(DATA_DIR, `${officeId}.sessions.json`);
10476
10765
  }
10477
10766
  function getOfficeSession(officeId) {
10478
10767
  let data = officeSessions.get(officeId);
@@ -10513,9 +10802,9 @@ function appendToScrollback(agentId, data) {
10513
10802
  }
10514
10803
  async function loadOfficeSessionFile(officeId) {
10515
10804
  try {
10516
- await fs3.promises.mkdir(DATA_DIR, { recursive: true });
10805
+ await fs5.promises.mkdir(DATA_DIR, { recursive: true });
10517
10806
  const filePath = getSessionFile(officeId);
10518
- const raw = await fs3.promises.readFile(filePath, "utf8").catch(() => null);
10807
+ const raw = await fs5.promises.readFile(filePath, "utf8").catch(() => null);
10519
10808
  const data = getOfficeSession(officeId);
10520
10809
  if (raw) {
10521
10810
  const parsed = JSON.parse(raw);
@@ -10545,27 +10834,27 @@ async function loadOfficeSessionFile(officeId) {
10545
10834
  }
10546
10835
  async function saveOfficeSessionFile(officeId) {
10547
10836
  try {
10548
- await fs3.promises.mkdir(DATA_DIR, { recursive: true });
10837
+ await fs5.promises.mkdir(DATA_DIR, { recursive: true });
10549
10838
  const data = getOfficeSession(officeId);
10550
10839
  const json = {
10551
10840
  current: Object.fromEntries(data.sessionIds),
10552
10841
  history: Object.fromEntries(data.sessionHistory),
10553
10842
  metadata: Object.fromEntries(data.sessionMeta)
10554
10843
  };
10555
- await fs3.promises.writeFile(getSessionFile(officeId), JSON.stringify(json, null, 2));
10844
+ await fs5.promises.writeFile(getSessionFile(officeId), JSON.stringify(json, null, 2));
10556
10845
  } catch (e) {
10557
10846
  console.error(`[TermServer] Failed to save sessions for ${officeId}:`, e);
10558
10847
  }
10559
10848
  }
10560
10849
  async function createEmptySessionFile(officeId) {
10561
- await fs3.promises.mkdir(DATA_DIR, { recursive: true });
10850
+ await fs5.promises.mkdir(DATA_DIR, { recursive: true });
10562
10851
  const filePath = getSessionFile(officeId);
10563
10852
  try {
10564
- await fs3.promises.access(filePath, fs3.constants.F_OK);
10853
+ await fs5.promises.access(filePath, fs5.constants.F_OK);
10565
10854
  await loadOfficeSessionFile(officeId);
10566
10855
  } catch {
10567
10856
  const empty = { current: {}, history: {}, metadata: {} };
10568
- await fs3.promises.writeFile(filePath, JSON.stringify(empty, null, 2));
10857
+ await fs5.promises.writeFile(filePath, JSON.stringify(empty, null, 2));
10569
10858
  getOfficeSession(officeId);
10570
10859
  console.log(`[TermServer] Created empty session file for ${officeId}: ${filePath}`);
10571
10860
  }
@@ -10585,19 +10874,19 @@ async function migrateGlobalSessionFile() {
10585
10874
  try {
10586
10875
  const migratedMarker = OLD_SESSION_FILE + ".migrated";
10587
10876
  try {
10588
- await fs3.promises.access(migratedMarker, fs3.constants.F_OK);
10877
+ await fs5.promises.access(migratedMarker, fs5.constants.F_OK);
10589
10878
  return;
10590
10879
  } catch {
10591
10880
  }
10592
10881
  let oldRaw = null;
10593
10882
  try {
10594
- oldRaw = await fs3.promises.readFile(OLD_SESSION_FILE, "utf8");
10883
+ oldRaw = await fs5.promises.readFile(OLD_SESSION_FILE, "utf8");
10595
10884
  } catch {
10596
10885
  return;
10597
10886
  }
10598
10887
  let firstOfficeId = "office-0";
10599
10888
  try {
10600
- const officesRaw = await fs3.promises.readFile(OFFICES_FILE, "utf8");
10889
+ const officesRaw = await fs5.promises.readFile(OFFICES_FILE, "utf8");
10601
10890
  const officesData = JSON.parse(officesRaw);
10602
10891
  if (Array.isArray(officesData.offices) && officesData.offices.length > 0) {
10603
10892
  firstOfficeId = officesData.offices[0].id || "office-0";
@@ -10606,22 +10895,22 @@ async function migrateGlobalSessionFile() {
10606
10895
  }
10607
10896
  const firstOfficeFile = getSessionFile(firstOfficeId);
10608
10897
  try {
10609
- await fs3.promises.access(firstOfficeFile, fs3.constants.F_OK);
10898
+ await fs5.promises.access(firstOfficeFile, fs5.constants.F_OK);
10610
10899
  } catch {
10611
- await fs3.promises.writeFile(firstOfficeFile, oldRaw);
10900
+ await fs5.promises.writeFile(firstOfficeFile, oldRaw);
10612
10901
  console.log(`[TermServer] Migrated global sessions to ${firstOfficeFile}`);
10613
10902
  }
10614
- await fs3.promises.rename(OLD_SESSION_FILE, migratedMarker);
10903
+ await fs5.promises.rename(OLD_SESSION_FILE, migratedMarker);
10615
10904
  console.log(`[TermServer] Renamed old session file to ${migratedMarker}`);
10616
10905
  } catch (e) {
10617
10906
  console.error("[TermServer] Migration error:", e);
10618
10907
  }
10619
10908
  }
10620
10909
  async function loadAllOfficeSessions() {
10621
- await fs3.promises.mkdir(DATA_DIR, { recursive: true });
10910
+ await fs5.promises.mkdir(DATA_DIR, { recursive: true });
10622
10911
  await migrateGlobalSessionFile();
10623
10912
  try {
10624
- const files = await fs3.promises.readdir(DATA_DIR);
10913
+ const files = await fs5.promises.readdir(DATA_DIR);
10625
10914
  for (const file of files) {
10626
10915
  const match = file.match(/^(.+)\.sessions\.json$/);
10627
10916
  if (match) {
@@ -10746,12 +11035,12 @@ async function startTerminalForAgentImpl(officeId, agentId, workingDir, cols, ro
10746
11035
  }
10747
11036
  }
10748
11037
  const terminalKey = ck;
10749
- const shell = os3.platform() === "win32" ? "powershell.exe" : "bash";
11038
+ const shell = os4.platform() === "win32" ? "powershell.exe" : "bash";
10750
11039
  let cwd = process.cwd();
10751
11040
  if (workingDir) {
10752
- const customPath = path4.isAbsolute(workingDir) ? workingDir : path4.join(process.cwd(), workingDir);
11041
+ const customPath = path6.isAbsolute(workingDir) ? workingDir : path6.join(process.cwd(), workingDir);
10753
11042
  try {
10754
- await fs3.promises.access(customPath, fs3.constants.F_OK);
11043
+ await fs5.promises.access(customPath, fs5.constants.F_OK);
10755
11044
  cwd = customPath;
10756
11045
  } catch {
10757
11046
  }
@@ -10810,7 +11099,7 @@ async function startTerminalForAgentImpl(officeId, agentId, workingDir, cols, ro
10810
11099
  agentToTerminal.set(ck, terminalKey);
10811
11100
  if (activeBackend.name === "ui-server") {
10812
11101
  const intendedCk = officeForegroundCk.get(officeId);
10813
- if (intendedCk && intendedCk !== ck) {
11102
+ if (shouldReassertForeground(intendedCk, ck)) {
10814
11103
  const fgKey = agentToTerminal.get(intendedCk);
10815
11104
  const fgProc = fgKey ? ptyProcesses.get(fgKey) : null;
10816
11105
  if (fgProc && typeof fgProc.process.setForeground === "function") {
@@ -10874,6 +11163,22 @@ async function startTerminalForAgentImpl(officeId, agentId, workingDir, cols, ro
10874
11163
  const d = event.data;
10875
11164
  console.log(`[TermServer] Forwarding tool_start for ${ck}: ${d.toolName}`);
10876
11165
  send({ type: "copilot-tool-start", agentId, toolName: d.toolName, toolId: d.toolCallId, status: formatToolStatus(d.toolName, d.arguments) });
11166
+ const askRelay = buildAskUserRelay(event, activeBackend.name);
11167
+ if (askRelay) {
11168
+ send({ type: "copilot-ask-user", agentId, toolId: askRelay.toolId, requestId: askRelay.requestId, question: askRelay.question, options: askRelay.options, freeform: askRelay.freeform });
11169
+ }
11170
+ } else if (event.type === "user_input.requested") {
11171
+ const askRelay = buildAskUserRelay(event, activeBackend.name);
11172
+ if (askRelay) {
11173
+ console.log(`[TermServer] Forwarding ask_user (user_input.requested) for ${ck}: requestId=${askRelay.requestId}, ${askRelay.options.length} option(s)`);
11174
+ send({ type: "copilot-tool-start", agentId, toolName: "ask_user", toolId: askRelay.toolId, status: "Waiting for your answer" });
11175
+ send({ type: "copilot-ask-user", agentId, toolId: askRelay.toolId, requestId: askRelay.requestId, question: askRelay.question, options: askRelay.options, freeform: askRelay.freeform });
11176
+ }
11177
+ } else if (event.type === "user_input.completed") {
11178
+ const d = event.data ?? {};
11179
+ const completedRequestId = d.requestId != null ? String(d.requestId) : "";
11180
+ console.log(`[TermServer] Forwarding ask_user complete (user_input.completed) for ${ck}: requestId=${completedRequestId}`);
11181
+ send({ type: "copilot-ask-user-complete", agentId, requestId: completedRequestId });
10877
11182
  } else if (event.type === "tool.execution_complete") {
10878
11183
  const d = event.data;
10879
11184
  console.log(`[TermServer] Forwarding tool_complete for ${ck}: ${d.toolCallId}`);
@@ -10951,8 +11256,8 @@ async function startTerminalForAgentImpl(officeId, agentId, workingDir, cols, ro
10951
11256
  pendingData = "";
10952
11257
  };
10953
11258
  const isSharedHostBackend = activeBackend.name === "ui-server";
10954
- if (isSharedHostBackend && !officeForegroundCk.has(officeId)) {
10955
- officeForegroundCk.set(officeId, ck);
11259
+ if (isSharedHostBackend) {
11260
+ officeForegroundCk.set(officeId, foregroundAfterStart(officeForegroundCk.get(officeId), ck));
10956
11261
  }
10957
11262
  proc.onData((data) => {
10958
11263
  if (isSharedHostBackend && !shouldForwardSharedHostData(ck, officeForegroundCk.get(officeId))) {
@@ -10995,6 +11300,10 @@ async function startTerminalForAgentImpl(officeId, agentId, workingDir, cols, ro
10995
11300
  w.stop();
10996
11301
  agentWatchers.delete(ck);
10997
11302
  }
11303
+ const dropped = clearPendingUserInputForSession(sessionId);
11304
+ if (dropped > 0) {
11305
+ console.log(`[TermServer] Cleared ${dropped} pending ask_user interaction(s) for exited session ${sessionId} (${ck})`);
11306
+ }
10998
11307
  });
10999
11308
  if (!shellOnlyMode && activeBackend.name === "node-pty") {
11000
11309
  setTimeout(() => {
@@ -11051,6 +11360,29 @@ async function handleMessage(msg) {
11051
11360
  }
11052
11361
  break;
11053
11362
  }
11363
+ case "submit-answer": {
11364
+ const key = getTerminalKey(msg.officeId, msg.agentId);
11365
+ const proc = key ? ptyProcesses.get(key) : null;
11366
+ if (proc) {
11367
+ agentForwardKeys.add(compositeKey(msg.officeId, msg.agentId));
11368
+ const backendProc = proc.process;
11369
+ if (answerTransport(backendProc) === "sdk") {
11370
+ const resolved = handlePendingUserInput(proc.sessionId, { answer: msg.answer, wasFreeform: msg.wasFreeform });
11371
+ if (!resolved) {
11372
+ console.warn(`[TermServer] submit-answer: no pending user-input for session="${proc.sessionId}" (answerRequestId="${msg.answerRequestId ?? ""}", ${compositeKey(msg.officeId, msg.agentId)}) \u2014 no-op (already resolved or unknown)`);
11373
+ }
11374
+ send({ type: "response", requestId: msg.requestId, result: { success: resolved, error: resolved ? void 0 : "no pending user-input to resolve" } });
11375
+ } else {
11376
+ submitViaKeystrokes(backendProc, msg.answer, key);
11377
+ send({ type: "response", requestId: msg.requestId, result: { success: true } });
11378
+ }
11379
+ } else {
11380
+ const ck = compositeKey(msg.officeId, msg.agentId);
11381
+ console.log(`[TermServer] SUBMIT-ANSWER FAILED \u2014 no PTY for ${ck}`);
11382
+ send({ type: "response", requestId: msg.requestId, result: { success: false, error: `No PTY for ${ck}` } });
11383
+ }
11384
+ break;
11385
+ }
11054
11386
  case "set-agent-forwarding": {
11055
11387
  const ck = compositeKey(msg.officeId, msg.agentId);
11056
11388
  if (msg.enabled) agentForwardKeys.add(ck);
@@ -11181,9 +11513,9 @@ async function handleMessage(msg) {
11181
11513
  const ptyProc = termKey ? ptyProcesses.get(termKey) : null;
11182
11514
  let cwd = process.cwd();
11183
11515
  if (ptyProc?.workingDir) {
11184
- const customPath = path4.join(process.cwd(), ptyProc.workingDir);
11516
+ const customPath = path6.join(process.cwd(), ptyProc.workingDir);
11185
11517
  try {
11186
- await fs3.promises.access(customPath, fs3.constants.F_OK);
11518
+ await fs5.promises.access(customPath, fs5.constants.F_OK);
11187
11519
  cwd = customPath;
11188
11520
  } catch {
11189
11521
  }
@@ -11352,7 +11684,7 @@ async function handleMessage(msg) {
11352
11684
  case "delete-office-session": {
11353
11685
  const filePath = getSessionFile(msg.officeId);
11354
11686
  try {
11355
- await fs3.promises.unlink(filePath);
11687
+ await fs5.promises.unlink(filePath);
11356
11688
  officeSessions.delete(msg.officeId);
11357
11689
  console.log(`[TermServer] Deleted session file for ${msg.officeId}`);
11358
11690
  } catch {