@github/copilot-language-server 1.561.0 → 1.563.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.
@@ -22,7 +22,7 @@ __export(cliVersion_exports, {
22
22
  COPILOT_CLI_VERSION: () => COPILOT_CLI_VERSION
23
23
  });
24
24
  module.exports = __toCommonJS(cliVersion_exports);
25
- const COPILOT_CLI_VERSION = "1.0.96";
25
+ const COPILOT_CLI_VERSION = "1.0.103";
26
26
  const COPILOT_CLI_USE_NPM_PACKAGE = false;
27
27
  // Annotate the CommonJS export names for ESM import in node:
28
28
  0 && (module.exports = {
@@ -293,6 +293,8 @@ class CopilotClient {
293
293
  sessions = /* @__PURE__ */ new Map();
294
294
  hostExitCallbacks = /* @__PURE__ */ new Map();
295
295
  hostSessionFactories = /* @__PURE__ */ new Map();
296
+ hostAuthenticators = /* @__PURE__ */ new Map();
297
+ hostAuthentications = /* @__PURE__ */ new Map();
296
298
  hostHandoffs = /* @__PURE__ */ new Map();
297
299
  stderrBuffer = "";
298
300
  // Captures CLI stderr for error messages
@@ -497,6 +499,7 @@ class CopilotClient {
497
499
  const connArgs = conn.kind === "stdio" || conn.kind === "tcp" ? conn.args ?? [] : [];
498
500
  this.connectionExtraArgs = [...connArgs];
499
501
  this.options = {
502
+ entra: options.entra ? { ...options.entra } : void 0,
500
503
  workingDirectory: options.workingDirectory ?? process.cwd(),
501
504
  logLevel: options.logLevel,
502
505
  gitHubToken: options.gitHubToken,
@@ -505,7 +508,6 @@ class CopilotClient {
505
508
  telemetry: options.telemetry,
506
509
  baseDirectory: options.baseDirectory,
507
510
  sessionIdleTimeoutSeconds: options.sessionIdleTimeoutSeconds ?? 0,
508
- enableRemoteSessions: options.enableRemoteSessions ?? false,
509
511
  mode: options.mode ?? "copilot-cli",
510
512
  clientInfo: options.clientInfo
511
513
  };
@@ -620,6 +622,16 @@ class CopilotClient {
620
622
  handlers.gitHubToken = {
621
623
  getToken: (params) => this.acquireGitHubToken(params)
622
624
  };
625
+ handlers.uiThread = {
626
+ run: async ({ actionId }) => {
627
+ const connection = this.connectionConfig;
628
+ const host = this.ffiHost;
629
+ if (connection.kind !== "inprocess" || !connection.runOnUiThread || !host) {
630
+ throw new Error("No in-process runOnUiThread handler is configured.");
631
+ }
632
+ await connection.runOnUiThread(() => host.runUiAction(actionId));
633
+ }
634
+ };
623
635
  this.clientGlobalHandlers = handlers;
624
636
  }
625
637
  async acquireGitHubToken(params) {
@@ -780,11 +792,19 @@ class CopilotClient {
780
792
  const sessionId = session.sessionId;
781
793
  let lastError = null;
782
794
  for (let attempt = 1; attempt <= 3; attempt++) {
795
+ if (this.connectionClosed) {
796
+ lastError = null;
797
+ break;
798
+ }
783
799
  try {
784
800
  await session.disconnect();
785
801
  lastError = null;
786
802
  break;
787
803
  } catch (error) {
804
+ if (this.connectionClosed) {
805
+ lastError = null;
806
+ break;
807
+ }
788
808
  lastError = error instanceof Error ? error : new Error(String(error));
789
809
  if (attempt < 3) {
790
810
  const delay = 100 * Math.pow(2, attempt - 1);
@@ -970,8 +990,10 @@ class CopilotClient {
970
990
  * - Clears all sessions immediately without destroying them
971
991
  * - Force closes the connection
972
992
  * - Sends SIGKILL to the CLI process (if spawned by this client)
993
+ * - Waits for the spawned CLI process to exit
973
994
  *
974
995
  * @returns A promise that resolves when the force stop is complete
996
+ * @throws If the spawned CLI process cannot be terminated
975
997
  *
976
998
  * @example
977
999
  * ```typescript
@@ -1018,13 +1040,6 @@ class CopilotClient {
1018
1040
  }
1019
1041
  this.socket = null;
1020
1042
  }
1021
- if (this.cliProcess && !this.isExternalServer) {
1022
- try {
1023
- this.cliProcess.kill("SIGKILL");
1024
- } catch {
1025
- }
1026
- this.cliProcess = null;
1027
- }
1028
1043
  if (this.ffiHost) {
1029
1044
  const host = this.ffiHost;
1030
1045
  this.ffiHost = null;
@@ -1041,6 +1056,20 @@ class CopilotClient {
1041
1056
  this.runtimePort = null;
1042
1057
  this.stderrBuffer = "";
1043
1058
  this.processExitPromise = null;
1059
+ if (this.cliProcess && !this.isExternalServer) {
1060
+ const child = this.cliProcess;
1061
+ if (child.exitCode == null && child.signalCode == null) {
1062
+ child.kill("SIGKILL");
1063
+ if (!await waitForChildExit(child, RUNTIME_SHUTDOWN_TIMEOUT_MS)) {
1064
+ throw new Error(
1065
+ `Timed out waiting for CLI process to exit after SIGKILL: ${RUNTIME_SHUTDOWN_TIMEOUT_MS}ms`
1066
+ );
1067
+ }
1068
+ }
1069
+ if (this.cliProcess === child) {
1070
+ this.cliProcess = null;
1071
+ }
1072
+ }
1044
1073
  }
1045
1074
  /**
1046
1075
  * Creates a new conversation session with the Copilot CLI.
@@ -1299,6 +1328,7 @@ class CopilotClient {
1299
1328
  model: config.model,
1300
1329
  allowedModels: config.allowedModels,
1301
1330
  sessionId: localSessionId,
1331
+ name: config.name,
1302
1332
  clientName: config.clientName,
1303
1333
  reasoningEffort: config.reasoningEffort,
1304
1334
  reasoningSummary: config.reasoningSummary,
@@ -1312,7 +1342,8 @@ class CopilotClient {
1312
1342
  skipPermission: tool.skipPermission,
1313
1343
  defer: tool.defer,
1314
1344
  metadata: tool.metadata,
1315
- isTerminal: tool.isTerminal
1345
+ isTerminal: tool.isTerminal,
1346
+ namespace: tool.namespace
1316
1347
  })),
1317
1348
  toolSearch: config.toolSearch,
1318
1349
  imageGeneration: config.imageGeneration,
@@ -1339,6 +1370,7 @@ class CopilotClient {
1339
1370
  enableCitations: config.enableCitations,
1340
1371
  enableFileChangeTracking: config.enableFileChangeTracking,
1341
1372
  sessionLimits: config.sessionLimits,
1373
+ autopilot: config.autopilot,
1342
1374
  modelCapabilities: config.modelCapabilities,
1343
1375
  largeOutput: toWireLargeOutput(config.largeOutput),
1344
1376
  requestPermission: !!config.onPermissionRequest,
@@ -1351,6 +1383,7 @@ class CopilotClient {
1351
1383
  requestAutoModeSwitch: !!config.onAutoModeSwitchRequest,
1352
1384
  hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
1353
1385
  workingDirectory: config.workingDirectory,
1386
+ trustWorkingDirectory: config.trustWorkingDirectory,
1354
1387
  additionalDirectories: config.additionalDirectories,
1355
1388
  streaming: config.streaming,
1356
1389
  includeSubAgentStreamingEvents: config.includeSubAgentStreamingEvents ?? true,
@@ -1377,20 +1410,23 @@ class CopilotClient {
1377
1410
  enableSkills: config.enableSkills,
1378
1411
  skillDirectories: config.skillDirectories,
1379
1412
  ...config.skillProvider ? { hasSkillProvider: true } : {},
1413
+ agentDirectories: config.agentDirectories,
1414
+ hookDirectories: config.hookDirectories,
1380
1415
  pluginDirectories: config.pluginDirectories,
1381
1416
  instructionDirectories: config.instructionDirectories,
1382
1417
  disabledSkills: config.disabledSkills,
1383
1418
  disabledMcpServers: config.disabledMcpServers,
1419
+ sandboxConfig: config.sandboxConfig,
1384
1420
  infiniteSessions: config.infiniteSessions,
1385
1421
  memory: config.memory,
1386
1422
  gitHubToken: config.gitHubToken,
1387
1423
  gitHubTokenProviderRegistrationId,
1388
- remoteSession: config.remoteSession,
1389
1424
  cloud: config.cloud,
1390
1425
  featureFlags: config.featureFlags,
1391
1426
  expAssignments: config.expAssignments,
1392
1427
  enableManagedSettings: config.enableManagedSettings,
1393
1428
  enforceManagedModelDefaults: config.enforceManagedModelDefaults,
1429
+ interactiveUser: config.interactiveUser,
1394
1430
  managedSettings: config.managedSettings
1395
1431
  });
1396
1432
  const {
@@ -1581,6 +1617,7 @@ class CopilotClient {
1581
1617
  enableCitations: config.enableCitations,
1582
1618
  enableFileChangeTracking: config.enableFileChangeTracking,
1583
1619
  sessionLimits: config.sessionLimits,
1620
+ autopilot: config.autopilot,
1584
1621
  tools: config.tools?.map((tool) => ({
1585
1622
  name: tool.name,
1586
1623
  description: tool.description,
@@ -1589,7 +1626,8 @@ class CopilotClient {
1589
1626
  skipPermission: tool.skipPermission,
1590
1627
  defer: tool.defer,
1591
1628
  metadata: tool.metadata,
1592
- isTerminal: tool.isTerminal
1629
+ isTerminal: tool.isTerminal,
1630
+ namespace: tool.namespace
1593
1631
  })),
1594
1632
  toolSearch: config.toolSearch,
1595
1633
  imageGeneration: config.imageGeneration,
@@ -1645,22 +1683,25 @@ class CopilotClient {
1645
1683
  agent: config.agent,
1646
1684
  skillDirectories: config.skillDirectories,
1647
1685
  ...config.skillProvider ? { hasSkillProvider: true } : {},
1686
+ agentDirectories: config.agentDirectories,
1687
+ hookDirectories: config.hookDirectories,
1648
1688
  pluginDirectories: config.pluginDirectories,
1649
1689
  instructionDirectories: config.instructionDirectories,
1650
1690
  disabledSkills: config.disabledSkills,
1651
1691
  disabledMcpServers: config.disabledMcpServers,
1692
+ sandboxConfig: config.sandboxConfig,
1652
1693
  infiniteSessions: config.infiniteSessions,
1653
1694
  memory: config.memory,
1654
1695
  disableResume: config.suppressResumeEvent,
1655
1696
  continuePendingWork: config.continuePendingWork,
1656
1697
  gitHubToken: config.gitHubToken,
1657
1698
  gitHubTokenProviderRegistrationId,
1658
- remoteSession: config.remoteSession,
1659
1699
  openCanvases: config.openCanvases,
1660
1700
  featureFlags: config.featureFlags,
1661
1701
  expAssignments: config.expAssignments,
1662
1702
  enableManagedSettings: config.enableManagedSettings,
1663
1703
  enforceManagedModelDefaults: config.enforceManagedModelDefaults,
1704
+ interactiveUser: config.interactiveUser,
1664
1705
  managedSettings: config.managedSettings,
1665
1706
  ...extensionOptions?.requestedEnvironmentVariables ? {
1666
1707
  requestedEnvironmentVariables: extensionOptions.requestedEnvironmentVariables
@@ -1724,6 +1765,18 @@ class CopilotClient {
1724
1765
  * @experimental
1725
1766
  */
1726
1767
  async startAhpHost(options) {
1768
+ const authentication = options?.authentication;
1769
+ if (!authentication || !["github", "none", "custom"].includes(authentication.mode)) {
1770
+ throw new Error("An explicit authentication mode is required");
1771
+ }
1772
+ if (authentication.mode === "custom" && (typeof authentication.requireIdentity !== "string" || !authentication.requireIdentity.trim() || typeof authentication.authenticate !== "function")) {
1773
+ throw new Error(
1774
+ "Custom authentication requires a nonempty requireIdentity and authenticate callback"
1775
+ );
1776
+ }
1777
+ if (authentication.mode !== "github" && options.githubEnvironment) {
1778
+ throw new Error("githubEnvironment requires github authentication");
1779
+ }
1727
1780
  if (!options?.localServer && !options?.githubEnvironment) {
1728
1781
  throw new Error("At least one of localServer or githubEnvironment is required");
1729
1782
  }
@@ -1732,8 +1785,12 @@ class CopilotClient {
1732
1785
  }
1733
1786
  const rpc = this.rpc;
1734
1787
  const hostId = (0, import_node_crypto.randomUUID)();
1788
+ if (authentication.mode === "custom") {
1789
+ this.hostAuthenticators.set(hostId, { ...authentication });
1790
+ }
1735
1791
  const {
1736
1792
  computeId,
1793
+ allowCreatingNewSessions,
1737
1794
  localServer,
1738
1795
  githubEnvironment,
1739
1796
  onExit,
@@ -1745,7 +1802,11 @@ class CopilotClient {
1745
1802
  this.hostSessionFactories.set(hostId, {
1746
1803
  createSession,
1747
1804
  resumeSession,
1748
- onSessionReleased
1805
+ onSessionReleased,
1806
+ policy: {
1807
+ mode: authentication.mode,
1808
+ requireIdentity: authentication.mode === "custom" ? authentication.requireIdentity : void 0
1809
+ }
1749
1810
  });
1750
1811
  }
1751
1812
  if (onExit) {
@@ -1754,11 +1815,13 @@ class CopilotClient {
1754
1815
  try {
1755
1816
  const info = await rpc.host.start({
1756
1817
  hostId,
1818
+ authentication: authentication.mode === "custom" ? { mode: "custom", requireIdentity: authentication.requireIdentity } : { mode: authentication.mode },
1757
1819
  computeId,
1758
1820
  localServer,
1759
1821
  githubEnvironment,
1760
1822
  sessionFactory: createSession ? true : void 0,
1761
- resumeFactory: resumeSession ? true : void 0
1823
+ resumeFactory: resumeSession ? true : void 0,
1824
+ allowCreatingNewSessions
1762
1825
  });
1763
1826
  return new import_host.AhpHost(
1764
1827
  info,
@@ -1766,7 +1829,14 @@ class CopilotClient {
1766
1829
  await rpc.host.dispose({ hostId });
1767
1830
  },
1768
1831
  (sessionId) => rpc.host.publishSession({ hostId, sessionId }),
1769
- () => rpc.host.listSessions({ hostId })
1832
+ () => rpc.host.listSessions({ hostId }),
1833
+ (sessionId) => rpc.host.unpublishSession({ hostId, sessionId }),
1834
+ async (allowed) => {
1835
+ await rpc.host.setSessionCreationPolicy({
1836
+ hostId,
1837
+ allowCreatingNewSessions: allowed
1838
+ });
1839
+ }
1770
1840
  );
1771
1841
  } catch (error) {
1772
1842
  this.hostExitCallbacks.delete(hostId);
@@ -1792,7 +1862,8 @@ class CopilotClient {
1792
1862
  disconnectHosts() {
1793
1863
  for (const hostId of /* @__PURE__ */ new Set([
1794
1864
  ...this.hostExitCallbacks.keys(),
1795
- ...this.hostSessionFactories.keys()
1865
+ ...this.hostSessionFactories.keys(),
1866
+ ...this.hostAuthenticators.keys()
1796
1867
  ])) {
1797
1868
  this.handleHostExit({
1798
1869
  hostId,
@@ -1802,6 +1873,13 @@ class CopilotClient {
1802
1873
  }
1803
1874
  }
1804
1875
  releaseHostSessions(hostId) {
1876
+ this.hostAuthenticators.delete(hostId);
1877
+ for (const [requestId, entry] of this.hostAuthentications) {
1878
+ if (entry.hostId === hostId) {
1879
+ this.hostAuthentications.delete(requestId);
1880
+ entry.controller.abort();
1881
+ }
1882
+ }
1805
1883
  this.hostSessionFactories.delete(hostId);
1806
1884
  for (const [handoffId, entry] of this.hostHandoffs) {
1807
1885
  if (entry.hostId === hostId) this.releaseHostSession(handoffId);
@@ -1830,10 +1908,19 @@ class CopilotClient {
1830
1908
  }
1831
1909
  }
1832
1910
  async createHostSession(params) {
1911
+ const authentication = params.authentication;
1912
+ if (!authentication) {
1913
+ throw new Error("AHP session handoff authentication is required");
1914
+ }
1833
1915
  const options = this.hostSessionFactories.get(params.hostId);
1834
1916
  if (!(params.resume ? options?.resumeSession : options?.createSession) || this.hostHandoffs.has(params.handoffId)) {
1835
1917
  throw new Error("AHP session factory is unavailable or handoff already exists");
1836
1918
  }
1919
+ const policy = options.policy;
1920
+ const identity = authentication.identity;
1921
+ if (authentication.mode !== policy.mode || (authentication.mode === "none" ? identity !== void 0 : typeof identity !== "string" || !identity.trim()) || policy.mode === "custom" && identity !== policy.requireIdentity) {
1922
+ throw new Error("AHP trusted authentication does not match host policy");
1923
+ }
1837
1924
  const entry = {
1838
1925
  hostId: params.hostId,
1839
1926
  requestedSessionId: params.config.sessionId,
@@ -1860,12 +1947,14 @@ class CopilotClient {
1860
1947
  const { sessionId, ...resumeConfig } = config;
1861
1948
  if (!sessionId) throw new Error("AHP resume requires sessionId");
1862
1949
  return options.resumeSession({
1950
+ authentication,
1863
1951
  sessionId,
1864
1952
  config: resumeConfig,
1865
1953
  signal: entry.controller.signal
1866
1954
  });
1867
1955
  }
1868
1956
  return options.createSession({
1957
+ authentication,
1869
1958
  config,
1870
1959
  signal: entry.controller.signal
1871
1960
  });
@@ -2012,6 +2101,7 @@ class CopilotClient {
2012
2101
  let serverVersion;
2013
2102
  try {
2014
2103
  const connectParams = {
2104
+ entra: this.options.entra,
2015
2105
  token: this.effectiveConnectionToken,
2016
2106
  supportedTaskKinds: ["agent", "client", "shell"]
2017
2107
  };
@@ -2026,6 +2116,9 @@ class CopilotClient {
2026
2116
  serverVersion = result.protocolVersion;
2027
2117
  } catch (err) {
2028
2118
  if (err instanceof import_node.ResponseError && (err.code === import_node.ErrorCodes.MethodNotFound || err.message === "Unhandled method connect")) {
2119
+ if (this.options.entra) {
2120
+ throw new Error("This runtime does not support Entra client configuration.");
2121
+ }
2029
2122
  serverVersion = (await raceAgainstExit(this.ping())).protocolVersion;
2030
2123
  } else {
2031
2124
  throw err;
@@ -2312,9 +2405,6 @@ class CopilotClient {
2312
2405
  this.options.sessionIdleTimeoutSeconds.toString()
2313
2406
  );
2314
2407
  }
2315
- if (this.options.enableRemoteSessions) {
2316
- args.push("--remote");
2317
- }
2318
2408
  const envWithoutNodeDebug = this.buildRuntimeEnv();
2319
2409
  if (!this.resolvedCliPath) {
2320
2410
  throw new Error(
@@ -2426,7 +2516,13 @@ stderr: ${stderrOutput}`
2426
2516
  this.cliStartTimeout = setTimeout(() => {
2427
2517
  if (!resolved) {
2428
2518
  resolved = true;
2429
- reject(new Error("Timeout waiting for CLI server to start"));
2519
+ reject(
2520
+ new Error(
2521
+ `Timeout waiting for CLI server to start
2522
+ stdout: ${stdout.trim().slice(-8192) || "(empty)"}
2523
+ stderr: ${this.stderrBuffer.trim().slice(-8192) || "(empty)"}`
2524
+ )
2525
+ );
2430
2526
  }
2431
2527
  }, 3e4);
2432
2528
  });
@@ -2494,9 +2590,6 @@ stderr: ${stderrOutput}`
2494
2590
  if (this.options.sessionIdleTimeoutSeconds > 0) {
2495
2591
  args.push("--session-idle-timeout", this.options.sessionIdleTimeoutSeconds.toString());
2496
2592
  }
2497
- if (this.options.enableRemoteSessions) {
2498
- args.push("--remote");
2499
- }
2500
2593
  if (cliLoginSyncIntegrationId) {
2501
2594
  args.push(`--cli-login-sync-integration-id=${cliLoginSyncIntegrationId}`);
2502
2595
  }
@@ -2519,6 +2612,9 @@ stderr: ${stderrOutput}`
2519
2612
  );
2520
2613
  this.attachConnectionHandlers();
2521
2614
  this.connection.listen();
2615
+ this.ffiHost.configureUiThread(
2616
+ this.connectionConfig.kind === "inprocess" && this.connectionConfig.runOnUiThread !== void 0
2617
+ );
2522
2618
  }
2523
2619
  static getNapiPrebuildsFolder(entrypoint) {
2524
2620
  const arch = process.arch;
@@ -2561,6 +2657,9 @@ stderr: ${stderrOutput}`
2561
2657
  stderr: ${stderrOutput}` : ""}`
2562
2658
  );
2563
2659
  this.logDebug(`stdin pipe error: ${reason}`);
2660
+ if (this.messageWriter) {
2661
+ this.messageWriter.suppressWriteErrors = true;
2662
+ }
2564
2663
  try {
2565
2664
  this.connection?.dispose();
2566
2665
  } catch {
@@ -2666,6 +2765,49 @@ stderr: ${stderrOutput}` : ""}`
2666
2765
  "host.materializeSession",
2667
2766
  (params) => this.createHostSession(params)
2668
2767
  );
2768
+ this.connection.onRequest(
2769
+ "host.validateAuthentication",
2770
+ async (params) => {
2771
+ const authenticator = this.hostAuthenticators.get(params.hostId);
2772
+ if (!authenticator || this.hostAuthentications.has(params.requestId)) {
2773
+ throw new Error("AHP authenticator is unavailable or request already exists");
2774
+ }
2775
+ const entry = { hostId: params.hostId, controller: new AbortController() };
2776
+ this.hostAuthentications.set(params.requestId, entry);
2777
+ const signal = entry.controller.signal;
2778
+ const cancelled = new Promise((_resolve, reject) => {
2779
+ signal.addEventListener(
2780
+ "abort",
2781
+ () => reject(new Error("AHP authentication ended")),
2782
+ { once: true }
2783
+ );
2784
+ });
2785
+ try {
2786
+ const result = await Promise.race([
2787
+ Promise.resolve().then(
2788
+ () => authenticator.authenticate({ token: params.token, signal })
2789
+ ),
2790
+ cancelled
2791
+ ]);
2792
+ if (signal.aborted || this.hostAuthenticators.get(params.hostId) !== authenticator) {
2793
+ throw new Error("AHP authentication ended");
2794
+ }
2795
+ return result;
2796
+ } finally {
2797
+ if (this.hostAuthentications.get(params.requestId) === entry) {
2798
+ this.hostAuthentications.delete(params.requestId);
2799
+ }
2800
+ entry.controller.abort();
2801
+ }
2802
+ }
2803
+ );
2804
+ this.connection.onNotification(
2805
+ "host.authenticationCancelled",
2806
+ (params) => {
2807
+ const entry = this.hostAuthentications.get(params.requestId);
2808
+ if (entry?.hostId === params.hostId) entry.controller.abort();
2809
+ }
2810
+ );
2669
2811
  this.connection.onNotification(
2670
2812
  "host.sessionReleased",
2671
2813
  (params) => {
@@ -98,7 +98,7 @@ class CopilotWebSocketForwarder extends CopilotWebSocketHandler {
98
98
  constructor(context) {
99
99
  super(context);
100
100
  }
101
- sendRequestMessage(data) {
101
+ sendRequestMessage(data, _ctx) {
102
102
  if (this.#upstream?.readyState !== WebSocket.OPEN) {
103
103
  return;
104
104
  }
@@ -182,6 +182,7 @@ class CopilotRequestHandler {
182
182
  requestId: exchange.requestId,
183
183
  sessionId: exchange.sessionId,
184
184
  agentId: exchange.agentId,
185
+ agentInvocationId: exchange.agentInvocationId,
185
186
  parentAgentId: exchange.parentAgentId,
186
187
  interactionType: exchange.interactionType,
187
188
  transport: exchange.transport,
@@ -209,7 +210,10 @@ class CopilotRequestHandler {
209
210
  let cancelled;
210
211
  const clientSettled = (async () => {
211
212
  for await (const chunk of exchange.requestBody) {
212
- await handler.sendRequestMessage(decodeFrame(chunk));
213
+ await handler.sendRequestMessage(decodeFrame(chunk.chunk ?? new Uint8Array()), {
214
+ ...ctx,
215
+ agentInvocationId: chunk.agentInvocationId
216
+ });
213
217
  }
214
218
  return "client-complete";
215
219
  })().catch((err) => {
@@ -309,7 +313,7 @@ function routeChunk(exchange, params) {
309
313
  return;
310
314
  }
311
315
  if (params.data && params.data.length > 0) {
312
- exchange.pushChunk(decodeChunkData(params.data, !!params.binary));
316
+ exchange.pushChunk(decodeChunkData(params.data, !!params.binary), params.agentInvocationId);
313
317
  }
314
318
  if (params.end) {
315
319
  exchange.pushEnd();
@@ -319,6 +323,7 @@ class CopilotRequestExchange {
319
323
  requestId;
320
324
  sessionId;
321
325
  agentId;
326
+ agentInvocationId;
322
327
  parentAgentId;
323
328
  interactionType;
324
329
  method = "GET";
@@ -341,6 +346,7 @@ class CopilotRequestExchange {
341
346
  setContext(params) {
342
347
  this.sessionId = params.sessionId;
343
348
  this.agentId = params.agentId;
349
+ this.agentInvocationId = params.agentInvocationId;
344
350
  this.parentAgentId = params.parentAgentId;
345
351
  this.interactionType = params.interactionType;
346
352
  this.method = params.method;
@@ -361,8 +367,8 @@ class CopilotRequestExchange {
361
367
  return this.#cancelled;
362
368
  }
363
369
  // --- Request body feed (driven by the adapter as chunk frames arrive) ---
364
- pushChunk(chunk) {
365
- this.#push({ chunk });
370
+ pushChunk(chunk, agentInvocationId) {
371
+ this.#push({ chunk, agentInvocationId });
366
372
  }
367
373
  pushEnd() {
368
374
  this.#push({ end: true });
@@ -405,7 +411,7 @@ class CopilotRequestExchange {
405
411
  this.#drained = true;
406
412
  return { value: void 0, done: true };
407
413
  }
408
- return { value: item.chunk ?? new Uint8Array(), done: false };
414
+ return { value: item, done: false };
409
415
  }
410
416
  })
411
417
  };
@@ -517,9 +523,11 @@ async function buildFetchRequest(exchange) {
517
523
  async function drainAsync(stream) {
518
524
  const parts = [];
519
525
  let total = 0;
520
- for await (const chunk of stream) {
521
- parts.push(chunk);
522
- total += chunk.byteLength;
526
+ for await (const item of stream) {
527
+ if (item.chunk) {
528
+ parts.push(item.chunk);
529
+ total += item.chunk.byteLength;
530
+ }
523
531
  }
524
532
  if (parts.length === 0) {
525
533
  return new Uint8Array(0);
@@ -80,6 +80,15 @@ function loadLibrary(libraryPath) {
80
80
  "size_t"
81
81
  ]),
82
82
  connectionClose: lib.func(`${SYMBOL_PREFIX}connection_close`, "bool", ["uint32"]),
83
+ connectionSetUiThreadHandler: lib.func(
84
+ `${SYMBOL_PREFIX}connection_set_ui_thread_handler`,
85
+ "bool",
86
+ ["uint32", "bool"]
87
+ ),
88
+ connectionRunUiAction: lib.func(`${SYMBOL_PREFIX}connection_run_ui_action`, "bool", [
89
+ "uint32",
90
+ "uint32"
91
+ ]),
83
92
  outboundCallbackType
84
93
  };
85
94
  loadedLibraryPath = libraryPath;
@@ -230,6 +239,21 @@ class FfiRuntimeHost {
230
239
  throw new Error("Failed to write a frame to the in-process runtime connection.");
231
240
  }
232
241
  }
242
+ configureUiThread(enabled) {
243
+ if (this.disposed || !this.lib.connectionSetUiThreadHandler(this.connectionId, enabled)) {
244
+ throw new Error("Failed to configure the in-process UI dispatcher.");
245
+ }
246
+ }
247
+ runUiAction(actionId) {
248
+ if (!Number.isInteger(actionId) || actionId < 0 || actionId > 4294967295) {
249
+ throw new RangeError("UI action ID must be an unsigned 32-bit integer.");
250
+ }
251
+ if (this.disposed || !this.lib.connectionRunUiAction(this.connectionId, actionId)) {
252
+ throw new Error(
253
+ "Native UI action is retired, already executed, or not running on the process main thread."
254
+ );
255
+ }
256
+ }
233
257
  /**
234
258
  * Native outbound (server→client) callback. koffi delivers it on the JS event loop
235
259
  * via a threadsafe function, so the frame is decoded and written straight to