@github/copilot-sdk-win32-x64 1.0.15 → 1.0.16-preview.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.
@@ -4041,6 +4041,60 @@ import { dirname as dirname2, isAbsolute, join as join2, resolve as resolve2 } f
4041
4041
  // src/sdk/nodejs/dist/generated/rpc.js
4042
4042
  function createServerRpc(connection) {
4043
4043
  return {
4044
+ /** @experimental */
4045
+ environments: {
4046
+ /**
4047
+ * Lists GitHub Mission Control environments visible to the authenticated identity. Does not require a running host and excludes host relay credentials.
4048
+ *
4049
+ * @param params Optional discovery filters supported by GitHub Mission Control.
4050
+ *
4051
+ * @returns Environments visible to the authenticated caller and matching the supplied filters.
4052
+ */
4053
+ list: async (params) => connection.sendRequest("environments.list", params),
4054
+ /**
4055
+ * Gets safe discovery information for a GitHub Mission Control environment without requiring a running host.
4056
+ *
4057
+ * @param params Identify a Mission Control environment to retrieve.
4058
+ *
4059
+ * @returns Safe discovery information for the requested environment.
4060
+ */
4061
+ get: async (params) => connection.sendRequest("environments.get", params),
4062
+ /**
4063
+ * Deletes a user-managed GitHub Mission Control environment. GitHub-managed environments cannot be deleted. Does not stop a running host, which may register again.
4064
+ *
4065
+ * @param params Identify a user-managed Mission Control environment to delete.
4066
+ *
4067
+ * @returns Acknowledgement that the requested environment was deleted.
4068
+ */
4069
+ delete: async (params) => connection.sendRequest("environments.delete", params)
4070
+ },
4071
+ /** @experimental */
4072
+ host: {
4073
+ /**
4074
+ * Publishes an attached resident session for this listener's lifetime without copying it.
4075
+ *
4076
+ * @param params Publishes a resident session attached to the listener's owning connection.
4077
+ *
4078
+ * @returns The existing runtime identity and its resource on the listener.
4079
+ */
4080
+ publishSession: async (params) => connection.sendRequest("host.publishSession", params),
4081
+ /**
4082
+ * Starts a connection-owned AHP host with explicit localServer and/or githubEnvironment transports as a supervised SDK participant.
4083
+ *
4084
+ * @param params Starts a supervised AHP host with at least one explicitly selected transport.
4085
+ *
4086
+ * @returns Listener readiness, returned only after binding and the supervised participant's SDK handshake.
4087
+ */
4088
+ start: async (params) => connection.sendRequest("host.start", params),
4089
+ /**
4090
+ * Stops a listener owned by this SDK connection and joins its cleanup without deleting sessions.
4091
+ *
4092
+ * @param params Stops a connection-owned listener and joins its teardown.
4093
+ *
4094
+ * @returns Empty acknowledgement for a completed host lifecycle operation.
4095
+ */
4096
+ dispose: async (params) => connection.sendRequest("host.dispose", params)
4097
+ },
4044
4098
  /**
4045
4099
  * Checks server responsiveness and returns protocol information.
4046
4100
  *
@@ -4873,6 +4927,45 @@ function createServerRpc(connection) {
4873
4927
  }
4874
4928
  function createInternalServerRpc(connection) {
4875
4929
  return {
4930
+ /** @experimental */
4931
+ host: {
4932
+ /**
4933
+ * Requests app-owned materialization over the owning SDK participant.
4934
+ *
4935
+ * @param params One application-owned session handoff, requested by the supervised hosting participant.
4936
+ *
4937
+ * @returns The resident session the application has materialized on its own connection.
4938
+ */
4939
+ createSession: async (params) => connection.sendRequest("host.createSession", params),
4940
+ /**
4941
+ * Releases app ownership retention after AHP detaches.
4942
+ *
4943
+ * @param params Ends one participation, not the application's session lifetime.
4944
+ *
4945
+ * @returns Empty acknowledgement for a completed host lifecycle operation.
4946
+ */
4947
+ releaseSession: async (params) => connection.sendRequest("host.releaseSession", params),
4948
+ /**
4949
+ * Resolves current authenticated credentials and remote-control policy only for the runtime-owned Mission Control hosting participant.
4950
+ *
4951
+ * @returns Private credentials delivered only to a runtime-owned Mission Control hosting participant.
4952
+ */
4953
+ getEnvironmentCredentials: async () => connection.sendRequest("host.getEnvironmentCredentials", {}),
4954
+ /**
4955
+ * Returns listener settings only to the supervised hosting participant over its SDK connection.
4956
+ *
4957
+ * @returns Normalized listener settings delivered only to the supervised hosting participant.
4958
+ */
4959
+ getConfiguration: async () => connection.sendRequest("host.getConfiguration", {}),
4960
+ /**
4961
+ * Reports a supervised hosting participant's bound AHP endpoint after its SDK handshake.
4962
+ *
4963
+ * @param params Readiness reported by the supervised hosting participant on its own SDK connection.
4964
+ *
4965
+ * @returns Empty acknowledgement for a completed host lifecycle operation.
4966
+ */
4967
+ ready: async (params) => connection.sendRequest("host.ready", params)
4968
+ },
4876
4969
  /**
4877
4970
  * Performs the SDK server connection handshake and validates the optional connection token. Marked internal because this is JSON-RPC transport plumbing invoked automatically by an SDK client's own `connect()` wrapper, not a user-facing method. Stays internal as long as the SDK client owns the handshake; would only become public if the SDK ever exposed the raw schema surface to consumers without a connection wrapper.
4878
4971
  *
@@ -4994,7 +5087,15 @@ function createSessionRpc(connection, sessionId) {
4994
5087
  *
4995
5088
  * @returns Result of attempting to disable sandboxing for the current session.
4996
5089
  */
4997
- disableForSession: async (params) => connection.sendRequest("session.sandbox.disableForSession", { sessionId, ...params })
5090
+ disableForSession: async (params) => connection.sendRequest("session.sandbox.disableForSession", { sessionId, ...params }),
5091
+ /**
5092
+ * Adds the path offered by a pending sandbox escalation permission request's sandboxPathGrant to the session's sandbox policy and approves the request, so the blocked operation re-runs inside the sandbox rather than outside it. The request is rejected unless the exact request is still pending, carries a sandboxPathGrant, and the grant still takes effect under the current managed policy. Does not persist the path; hosts that store sandbox settings save it themselves.
5093
+ *
5094
+ * @param params Request to accept the sandbox path grant offered on an active sandbox escalation permission prompt.
5095
+ *
5096
+ * @returns Result of accepting a sandbox path grant.
5097
+ */
5098
+ grantPathForRequest: async (params) => connection.sendRequest("session.sandbox.grantPathForRequest", { sessionId, ...params })
4998
5099
  },
4999
5100
  /**
5000
5101
  * Aborts the current agent turn.
@@ -6202,7 +6303,15 @@ function createSessionRpc(connection, sessionId) {
6202
6303
  *
6203
6304
  * @returns The selectable model entries and selection ids synthesized for the synchronized BYOK models.
6204
6305
  */
6205
- sync: async (params) => connection.sendRequest("session.provider.sync", { sessionId, ...params })
6306
+ sync: async (params) => connection.sendRequest("session.provider.sync", { sessionId, ...params }),
6307
+ /**
6308
+ * Withdraws named host-managed models from the session's BYOK registry, leaving every other entry untouched. The scoped counterpart to `provider.sync`: a snapshot can only describe entries the caller knows about, so using it to remove one model silently withdraws rows registered by another source, such as a plugin calling `provider.add` at runtime. Naming what to remove leaves unrelated entries alone. Selection ids that are not registered are ignored, so withdrawal is idempotent. A provider is removed only when one of the withdrawn models was the last entry referencing it; a provider that simply has no models, which is the normal state while its rows are supplied by catalog discovery, is left in place.
6309
+ *
6310
+ * @param params Host-managed model selection ids to withdraw from the session's BYOK registry.
6311
+ *
6312
+ * @returns What the withdrawal actually removed from the registry.
6313
+ */
6314
+ withdraw: async (params) => connection.sendRequest("session.provider.withdraw", { sessionId, ...params })
6206
6315
  },
6207
6316
  /** @experimental */
6208
6317
  options: {
@@ -7455,6 +7564,11 @@ function registerClientSessionApiHandlers(connection, getHandlers) {
7455
7564
  });
7456
7565
  }
7457
7566
  function registerClientGlobalApiHandlers(connection, handlers) {
7567
+ connection.onNotification("host.exited", async (params) => {
7568
+ const handler = handlers.host;
7569
+ if (!handler) return;
7570
+ await handler.exited(params);
7571
+ });
7458
7572
  connection.onRequest("extensionLaunchProvider.resolve", async (params) => {
7459
7573
  const handler = handlers.extensionLaunchProvider;
7460
7574
  if (!handler) throw new Error("No extensionLaunchProvider client-global handler registered");
@@ -10568,6 +10682,42 @@ function assertWorkflowStepResult(value, key) {
10568
10682
  });
10569
10683
  }
10570
10684
 
10685
+ // src/sdk/nodejs/dist/host.js
10686
+ var AhpHost = class {
10687
+ /** @internal */
10688
+ constructor(info, disposeHost, publishHostSession) {
10689
+ this.disposeHost = disposeHost;
10690
+ this.publishHostSession = publishHostSession;
10691
+ this.hostId = info.hostId;
10692
+ this.url = info.url;
10693
+ this.environmentId = info.environmentId;
10694
+ this.token = info.token;
10695
+ this.pid = info.pid;
10696
+ }
10697
+ disposeHost;
10698
+ publishHostSession;
10699
+ hostId;
10700
+ /** Local WebSocket URL. Absent when only the GitHub environment is enabled. */
10701
+ url;
10702
+ /** Mission Control environment ID, when GitHub hosting is enabled. */
10703
+ environmentId;
10704
+ /** Connection token, when required by the listener. Treat this value as a secret. */
10705
+ token;
10706
+ /** Legacy separate host process ID. Absent for in-process listeners; use dispose() to stop. */
10707
+ pid;
10708
+ /** Ask the runtime to stop the listener and await its cleanup, on every call. */
10709
+ dispose() {
10710
+ return this.disposeHost();
10711
+ }
10712
+ /** Publish an existing session attached to this client, until this listener stops. */
10713
+ publishSession(sessionId) {
10714
+ return this.publishHostSession(sessionId);
10715
+ }
10716
+ async [Symbol.asyncDispose]() {
10717
+ await this.dispose();
10718
+ }
10719
+ };
10720
+
10571
10721
  // src/sdk/nodejs/dist/runtimeArtifacts.js
10572
10722
  import {
10573
10723
  chmodSync,
@@ -10914,6 +11064,57 @@ var ToolSet = class {
10914
11064
  // src/sdk/nodejs/dist/client.js
10915
11065
  var MIN_PROTOCOL_VERSION = 3;
10916
11066
  var RUNTIME_SHUTDOWN_TIMEOUT_MS = 1e4;
11067
+ function createMessageConnection(reader, writer) {
11068
+ let dispatch;
11069
+ let finishDrain;
11070
+ let drained;
11071
+ let disposed = false;
11072
+ const barrier = { jsonrpc: "2.0", method: "$/sdkDrain" };
11073
+ const connection = (0, import_node3.createMessageConnection)(
11074
+ {
11075
+ onError: reader.onError,
11076
+ onClose: reader.onClose,
11077
+ onPartialMessage: reader.onPartialMessage,
11078
+ listen: (callback) => {
11079
+ dispatch = callback;
11080
+ return reader.listen(callback);
11081
+ },
11082
+ dispose: () => reader.dispose()
11083
+ },
11084
+ writer,
11085
+ void 0,
11086
+ {
11087
+ messageStrategy: {
11088
+ handleMessage: (message, next) => {
11089
+ if (message === barrier) {
11090
+ finishDrain?.();
11091
+ } else {
11092
+ next(message);
11093
+ }
11094
+ }
11095
+ }
11096
+ }
11097
+ );
11098
+ connection.onDispose(() => {
11099
+ disposed = true;
11100
+ finishDrain?.();
11101
+ });
11102
+ return Object.assign(connection, {
11103
+ drain: () => {
11104
+ if (!drained) {
11105
+ drained = new Promise((resolve22) => {
11106
+ finishDrain = resolve22;
11107
+ if (disposed || !dispatch) {
11108
+ resolve22();
11109
+ } else {
11110
+ dispatch(barrier);
11111
+ }
11112
+ });
11113
+ }
11114
+ return drained;
11115
+ }
11116
+ });
11117
+ }
10917
11118
  async function withTimeout(promise, timeoutMs, message) {
10918
11119
  let timeout;
10919
11120
  try {
@@ -11100,6 +11301,9 @@ var CopilotClient = class _CopilotClient {
11100
11301
  /** Shared in-flight start; concurrent callers await it instead of spawning another CLI. */
11101
11302
  startPromise = null;
11102
11303
  sessions = /* @__PURE__ */ new Map();
11304
+ hostExitCallbacks = /* @__PURE__ */ new Map();
11305
+ hostSessionFactories = /* @__PURE__ */ new Map();
11306
+ hostHandoffs = /* @__PURE__ */ new Map();
11103
11307
  stderrBuffer = "";
11104
11308
  // Captures CLI stderr for error messages
11105
11309
  /** Resolved connection mode chosen in the constructor. */
@@ -11391,6 +11595,11 @@ var CopilotClient = class _CopilotClient {
11391
11595
  }
11392
11596
  setupClientGlobalHandlers() {
11393
11597
  const handlers = {};
11598
+ handlers.host = {
11599
+ exited: async (exit) => {
11600
+ this.handleHostExit(exit);
11601
+ }
11602
+ };
11394
11603
  handlers.extensionLaunchProvider = this.extensionLaunchProvider;
11395
11604
  if (this.requestHandler) {
11396
11605
  this.requestAdapter = createCopilotRequestAdapter(this.requestHandler, () => {
@@ -11544,7 +11753,8 @@ var CopilotClient = class _CopilotClient {
11544
11753
  * 1. Closes all active sessions (releases in-memory resources)
11545
11754
  * 2. Requests runtime shutdown for SDK-owned CLI processes
11546
11755
  * 3. Closes the JSON-RPC connection
11547
- * 4. Terminates the CLI server process (if spawned by this client)
11756
+ * 4. Signals EOF to an owned stdio process and waits for host cleanup, then
11757
+ * terminates the process if it does not exit within the shutdown timeout
11548
11758
  *
11549
11759
  * Note: session data on disk is preserved, so sessions can be resumed later.
11550
11760
  * To permanently remove session data before stopping, call
@@ -11662,7 +11872,23 @@ var CopilotClient = class _CopilotClient {
11662
11872
  }
11663
11873
  if (this.cliProcess && !this.isExternalServer) {
11664
11874
  const child = this.cliProcess;
11665
- this.cliProcess = null;
11875
+ if (this.connectionConfig.kind === "stdio" && child.stdin && child.exitCode == null && child.signalCode == null) {
11876
+ const gracefulExitStart = Date.now();
11877
+ try {
11878
+ child.stdin.end();
11879
+ const exited = await waitForChildExit(child, RUNTIME_SHUTDOWN_TIMEOUT_MS);
11880
+ this.logDebugTiming(
11881
+ exited ? "CopilotClient.stop graceful stdio exit complete" : "CopilotClient.stop graceful stdio exit timed out; terminating child",
11882
+ gracefulExitStart
11883
+ );
11884
+ } catch (error) {
11885
+ errors.push(
11886
+ new Error(
11887
+ `Failed to close CLI stdin: ${error instanceof Error ? error.message : String(error)}`
11888
+ )
11889
+ );
11890
+ }
11891
+ }
11666
11892
  try {
11667
11893
  if (child.exitCode == null && child.signalCode == null) {
11668
11894
  child.kill();
@@ -11680,6 +11906,10 @@ var CopilotClient = class _CopilotClient {
11680
11906
  `Failed to kill CLI process: ${error instanceof Error ? error.message : String(error)}`
11681
11907
  )
11682
11908
  );
11909
+ } finally {
11910
+ if (this.cliProcess === child) {
11911
+ this.cliProcess = null;
11912
+ }
11683
11913
  }
11684
11914
  }
11685
11915
  if (this.ffiHost) {
@@ -11703,6 +11933,7 @@ var CopilotClient = class _CopilotClient {
11703
11933
  this.runtimePort = null;
11704
11934
  this.stderrBuffer = "";
11705
11935
  this.processExitPromise = null;
11936
+ this.disconnectHosts();
11706
11937
  return errors;
11707
11938
  }
11708
11939
  /**
@@ -11747,6 +11978,7 @@ var CopilotClient = class _CopilotClient {
11747
11978
  */
11748
11979
  async forceStop() {
11749
11980
  this.forceStopping = true;
11981
+ this.disconnectHosts();
11750
11982
  for (const session of this.sessions.values()) {
11751
11983
  session._markDisconnected();
11752
11984
  }
@@ -12046,6 +12278,7 @@ var CopilotClient = class _CopilotClient {
12046
12278
  const response = await this.connection.sendRequest("session.create", {
12047
12279
  ...await getTraceContext(this.onGetTraceContext),
12048
12280
  model: config.model,
12281
+ allowedModels: config.allowedModels,
12049
12282
  sessionId: localSessionId,
12050
12283
  clientName: config.clientName,
12051
12284
  reasoningEffort: config.reasoningEffort,
@@ -12176,6 +12409,11 @@ var CopilotClient = class _CopilotClient {
12176
12409
  }
12177
12410
  throw e;
12178
12411
  }
12412
+ for (const entry of this.hostHandoffs.values()) {
12413
+ if (entry.requestedSessionId === session.sessionId) {
12414
+ entry.configs.set(session, config);
12415
+ }
12416
+ }
12179
12417
  return session;
12180
12418
  }
12181
12419
  /**
@@ -12282,6 +12520,7 @@ var CopilotClient = class _CopilotClient {
12282
12520
  }
12283
12521
  try {
12284
12522
  const response = await this.connection.sendRequest("session.resume", {
12523
+ allowedModels: config.allowedModels,
12285
12524
  ...await getTraceContext(this.onGetTraceContext),
12286
12525
  sessionId,
12287
12526
  clientName: config.clientName,
@@ -12410,8 +12649,205 @@ var CopilotClient = class _CopilotClient {
12410
12649
  }
12411
12650
  throw e;
12412
12651
  }
12652
+ for (const entry of this.hostHandoffs.values()) {
12653
+ if (entry.requestedSessionId === session.sessionId) {
12654
+ entry.configs.set(session, { ...config, sessionId });
12655
+ }
12656
+ }
12413
12657
  return session;
12414
12658
  }
12659
+ /**
12660
+ * Start a complete AHP listener hosted in-process by this runtime.
12661
+ *
12662
+ * This connection owns the host. Disposing it or disconnecting the client
12663
+ * stops the listener without deleting sessions. The host uses a separate
12664
+ * SDK connection to the same runtime, not another runtime process.
12665
+ * Only one AHP host may own the catalog in an effective Copilot home
12666
+ * at a time. Other SDK clients and sessions remain usable in that home.
12667
+ * Select localServer, githubEnvironment, or both. An empty localServer
12668
+ * selects 127.0.0.1 on an available port. `onExit` runs at most once; owner disconnection cannot
12669
+ * acknowledge listener cleanup over the disconnected transport.
12670
+ *
12671
+ * @experimental
12672
+ */
12673
+ async startAhpHost(options) {
12674
+ if (!options?.localServer && !options?.githubEnvironment) {
12675
+ throw new Error("At least one of localServer or githubEnvironment is required");
12676
+ }
12677
+ if (this.state !== "connected") {
12678
+ await this.start();
12679
+ }
12680
+ const rpc = this.rpc;
12681
+ const hostId = randomUUID();
12682
+ const {
12683
+ localServer,
12684
+ githubEnvironment,
12685
+ onExit,
12686
+ createSession,
12687
+ resumeSession,
12688
+ onSessionReleased
12689
+ } = options;
12690
+ if (createSession || resumeSession) {
12691
+ this.hostSessionFactories.set(hostId, {
12692
+ createSession,
12693
+ resumeSession,
12694
+ onSessionReleased
12695
+ });
12696
+ }
12697
+ if (onExit) {
12698
+ this.hostExitCallbacks.set(hostId, onExit);
12699
+ }
12700
+ try {
12701
+ const info = await rpc.host.start({
12702
+ hostId,
12703
+ localServer,
12704
+ githubEnvironment,
12705
+ sessionFactory: createSession ? true : void 0,
12706
+ resumeFactory: resumeSession ? true : void 0
12707
+ });
12708
+ return new AhpHost(
12709
+ info,
12710
+ async () => {
12711
+ await rpc.host.dispose({ hostId });
12712
+ },
12713
+ (sessionId) => rpc.host.publishSession({ hostId, sessionId })
12714
+ );
12715
+ } catch (error) {
12716
+ this.hostExitCallbacks.delete(hostId);
12717
+ this.releaseHostSessions(hostId);
12718
+ throw error;
12719
+ }
12720
+ }
12721
+ handleHostExit(exit) {
12722
+ this.releaseHostSessions(exit.hostId);
12723
+ const onExit = this.hostExitCallbacks.get(exit.hostId);
12724
+ if (onExit) {
12725
+ this.hostExitCallbacks.delete(exit.hostId);
12726
+ const reportError = (error) => {
12727
+ console.error("AHP host exit callback failed", { hostId: exit.hostId, error });
12728
+ };
12729
+ try {
12730
+ void Promise.resolve(onExit(exit)).catch(reportError);
12731
+ } catch (error) {
12732
+ reportError(error);
12733
+ }
12734
+ }
12735
+ }
12736
+ disconnectHosts() {
12737
+ for (const hostId of /* @__PURE__ */ new Set([
12738
+ ...this.hostExitCallbacks.keys(),
12739
+ ...this.hostSessionFactories.keys()
12740
+ ])) {
12741
+ this.handleHostExit({
12742
+ hostId,
12743
+ reason: "ownerDisconnected",
12744
+ error: "Owner connection closed; runtime cleanup cannot be acknowledged on this connection."
12745
+ });
12746
+ }
12747
+ }
12748
+ releaseHostSessions(hostId) {
12749
+ this.hostSessionFactories.delete(hostId);
12750
+ for (const [handoffId, entry] of this.hostHandoffs) {
12751
+ if (entry.hostId === hostId) this.releaseHostSession(handoffId);
12752
+ }
12753
+ }
12754
+ releaseHostSession(handoffId) {
12755
+ const entry = this.hostHandoffs.get(handoffId);
12756
+ if (!entry) return;
12757
+ this.hostHandoffs.delete(handoffId);
12758
+ entry.released = true;
12759
+ entry.configs.clear();
12760
+ entry.controller.abort();
12761
+ if (entry.session) this.notifyHostSessionReleased(entry);
12762
+ }
12763
+ notifyHostSessionReleased(entry) {
12764
+ const { session, onReleased } = entry;
12765
+ entry.session = void 0;
12766
+ if (session && onReleased) {
12767
+ try {
12768
+ void Promise.resolve(onReleased(session)).catch(() => {
12769
+ console.error("AHP session release callback failed");
12770
+ });
12771
+ } catch {
12772
+ console.error("AHP session release callback failed");
12773
+ }
12774
+ }
12775
+ }
12776
+ async createHostSession(params) {
12777
+ const options = this.hostSessionFactories.get(params.hostId);
12778
+ if (!(params.resume ? options?.resumeSession : options?.createSession) || this.hostHandoffs.has(params.handoffId)) {
12779
+ throw new Error("AHP session factory is unavailable or handoff already exists");
12780
+ }
12781
+ const entry = {
12782
+ hostId: params.hostId,
12783
+ requestedSessionId: params.config.sessionId,
12784
+ controller: new AbortController(),
12785
+ configs: /* @__PURE__ */ new Map(),
12786
+ released: false,
12787
+ onReleased: options.onSessionReleased
12788
+ };
12789
+ this.hostHandoffs.set(params.handoffId, entry);
12790
+ const { configDir, ...config } = params.config;
12791
+ if (configDir !== void 0) config.configDirectory = configDir;
12792
+ const expected = structuredClone(config);
12793
+ const cancelled = new Promise((_resolve, reject) => {
12794
+ entry.controller.signal.addEventListener(
12795
+ "abort",
12796
+ () => {
12797
+ reject(new Error("AHP session handoff ended"));
12798
+ },
12799
+ { once: true }
12800
+ );
12801
+ });
12802
+ const materialized = Promise.resolve().then(() => {
12803
+ if (params.resume) {
12804
+ const { sessionId, ...resumeConfig } = config;
12805
+ if (!sessionId) throw new Error("AHP resume requires sessionId");
12806
+ return options.resumeSession({
12807
+ sessionId,
12808
+ config: resumeConfig,
12809
+ signal: entry.controller.signal
12810
+ });
12811
+ }
12812
+ return options.createSession({
12813
+ config,
12814
+ signal: entry.controller.signal
12815
+ });
12816
+ }).then((session) => {
12817
+ entry.session = session;
12818
+ const actual = entry.configs.get(session);
12819
+ entry.configs.clear();
12820
+ if (entry.released) {
12821
+ this.notifyHostSessionReleased(entry);
12822
+ throw new Error("AHP session handoff ended");
12823
+ }
12824
+ if (this.sessions.get(session.sessionId) !== session || session.sessionId !== expected.sessionId) {
12825
+ throw new Error(
12826
+ "AHP callback must return the requested session from this client"
12827
+ );
12828
+ }
12829
+ const contains = (value, required) => {
12830
+ if (required && typeof required === "object" && !Array.isArray(required)) {
12831
+ return !!value && typeof value === "object" && Object.entries(required).every(
12832
+ ([key, child]) => contains(value[key], child)
12833
+ );
12834
+ }
12835
+ return JSON.stringify(value) === JSON.stringify(required);
12836
+ };
12837
+ if ((!params.resume || actual !== void 0) && !contains(actual, expected)) {
12838
+ throw new Error(
12839
+ "AHP callback must preserve the supplied session configuration"
12840
+ );
12841
+ }
12842
+ return { sessionId: session.sessionId };
12843
+ });
12844
+ try {
12845
+ return await Promise.race([materialized, cancelled]);
12846
+ } catch (error) {
12847
+ this.releaseHostSession(params.handoffId);
12848
+ throw error;
12849
+ }
12850
+ }
12415
12851
  /**
12416
12852
  * Sends a ping request to the server to verify connectivity.
12417
12853
  *
@@ -12951,8 +13387,13 @@ stderr: ${stderrOutput}`
12951
13387
  case "inprocess":
12952
13388
  return this.connectViaFfi();
12953
13389
  case "tcp":
12954
- case "uri":
12955
13390
  return this.connectViaTcp();
13391
+ case "uri": {
13392
+ const { host, port } = this.parseCliUrl(this.connectionConfig.url);
13393
+ this.actualHost = host;
13394
+ this.runtimePort = port;
13395
+ return this.connectViaTcp();
13396
+ }
12956
13397
  }
12957
13398
  }
12958
13399
  /** Starts the in-process FFI runtime with SDK-managed typed options. */
@@ -13011,7 +13452,7 @@ stderr: ${stderrOutput}`
13011
13452
  throw new Error("In-process FFI runtime host not started");
13012
13453
  }
13013
13454
  this.messageWriter = new TeardownResilientStreamMessageWriter(this.ffiHost.sendStream);
13014
- this.connection = (0, import_node3.createMessageConnection)(
13455
+ this.connection = createMessageConnection(
13015
13456
  new import_node3.StreamMessageReader(this.ffiHost.receiveStream),
13016
13457
  this.messageWriter
13017
13458
  );
@@ -13065,7 +13506,7 @@ stderr: ${stderrOutput}` : ""}`
13065
13506
  }
13066
13507
  });
13067
13508
  this.messageWriter = new TeardownResilientStreamMessageWriter(this.cliProcess.stdin);
13068
- this.connection = (0, import_node3.createMessageConnection)(
13509
+ this.connection = createMessageConnection(
13069
13510
  new import_node3.StreamMessageReader(this.cliProcess.stdout),
13070
13511
  this.messageWriter
13071
13512
  );
@@ -13080,7 +13521,7 @@ stderr: ${stderrOutput}` : ""}`
13080
13521
  throw new Error("CLI child process was unexpectedly started in parent process mode");
13081
13522
  }
13082
13523
  this.messageWriter = new TeardownResilientStreamMessageWriter(process.stdout);
13083
- this.connection = (0, import_node3.createMessageConnection)(
13524
+ this.connection = createMessageConnection(
13084
13525
  new import_node3.StreamMessageReader(process.stdin),
13085
13526
  this.messageWriter
13086
13527
  );
@@ -13103,7 +13544,7 @@ stderr: ${stderrOutput}` : ""}`
13103
13544
  this.socket.connect(this.runtimePort, this.actualHost, () => {
13104
13545
  clearTimeout(connectionTimeout);
13105
13546
  this.messageWriter = new TeardownResilientStreamMessageWriter(this.socket);
13106
- this.connection = (0, import_node3.createMessageConnection)(
13547
+ this.connection = createMessageConnection(
13107
13548
  new import_node3.StreamMessageReader(this.socket),
13108
13549
  this.messageWriter
13109
13550
  );
@@ -13160,6 +13601,18 @@ stderr: ${stderrOutput}` : ""}`
13160
13601
  (params, token) => confirmation.confirm(params, token)
13161
13602
  );
13162
13603
  }
13604
+ this.connection.onRequest(
13605
+ "host.materializeSession",
13606
+ (params) => this.createHostSession(params)
13607
+ );
13608
+ this.connection.onNotification(
13609
+ "host.sessionReleased",
13610
+ (params) => {
13611
+ if (this.hostHandoffs.get(params.handoffId)?.hostId === params.hostId) {
13612
+ this.releaseHostSession(params.handoffId);
13613
+ }
13614
+ }
13615
+ );
13163
13616
  this.connection.onRequest(
13164
13617
  "hooks.invoke",
13165
13618
  async (params) => {
@@ -13167,20 +13620,27 @@ stderr: ${stderrOutput}` : ""}`
13167
13620
  }
13168
13621
  );
13169
13622
  const connection = this.connection;
13623
+ const messageWriter = this.messageWriter;
13170
13624
  const markDisconnected = () => {
13171
13625
  if (this.connection !== connection) {
13626
+ connection.dispose();
13172
13627
  return;
13173
13628
  }
13174
13629
  this.connectionClosed = true;
13175
13630
  this.state = "disconnected";
13631
+ this.disconnectHosts();
13176
13632
  for (const session of this.sessions.values()) {
13177
13633
  session._markDisconnected();
13178
13634
  }
13179
13635
  this.sessions.clear();
13180
13636
  this.githubTokenProviders.clear();
13181
13637
  this.requestAdapter?.cancelPending();
13638
+ connection.dispose();
13182
13639
  };
13183
- this.connection.onClose(markDisconnected);
13640
+ this.connection.onClose(() => {
13641
+ if (messageWriter) messageWriter.suppressWriteErrors = true;
13642
+ void connection.drain().then(markDisconnected);
13643
+ });
13184
13644
  this.connection.onError(() => {
13185
13645
  if (this.connection === connection) {
13186
13646
  this.state = "disconnected";