@codexhost/cli-darwin-x64 0.16.1 → 0.17.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.
@@ -1138,10 +1138,10 @@ function mergeDefs(...defs) {
1138
1138
  function cloneDef(schema) {
1139
1139
  return mergeDefs(schema._zod.def);
1140
1140
  }
1141
- function getElementAtPath(obj, path9) {
1142
- if (!path9)
1141
+ function getElementAtPath(obj, path8) {
1142
+ if (!path8)
1143
1143
  return obj;
1144
- return path9.reduce((acc, key) => acc?.[key], obj);
1144
+ return path8.reduce((acc, key) => acc?.[key], obj);
1145
1145
  }
1146
1146
  function promiseAllObject(promisesObj) {
1147
1147
  const keys = Object.keys(promisesObj);
@@ -1550,11 +1550,11 @@ function explicitlyAborted(x, startIndex = 0) {
1550
1550
  }
1551
1551
  return false;
1552
1552
  }
1553
- function prefixIssues(path9, issues) {
1553
+ function prefixIssues(path8, issues) {
1554
1554
  return issues.map((iss) => {
1555
1555
  var _a3;
1556
1556
  (_a3 = iss).path ?? (_a3.path = []);
1557
- iss.path.unshift(path9);
1557
+ iss.path.unshift(path8);
1558
1558
  return iss;
1559
1559
  });
1560
1560
  }
@@ -1701,16 +1701,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
1701
1701
  }
1702
1702
  function formatError(error51, mapper = (issue2) => issue2.message) {
1703
1703
  const fieldErrors = { _errors: [] };
1704
- const processError = (error52, path9 = []) => {
1704
+ const processError = (error52, path8 = []) => {
1705
1705
  for (const issue2 of error52.issues) {
1706
1706
  if (issue2.code === "invalid_union" && issue2.errors.length) {
1707
- issue2.errors.map((issues) => processError({ issues }, [...path9, ...issue2.path]));
1707
+ issue2.errors.map((issues) => processError({ issues }, [...path8, ...issue2.path]));
1708
1708
  } else if (issue2.code === "invalid_key") {
1709
- processError({ issues: issue2.issues }, [...path9, ...issue2.path]);
1709
+ processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
1710
1710
  } else if (issue2.code === "invalid_element") {
1711
- processError({ issues: issue2.issues }, [...path9, ...issue2.path]);
1711
+ processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
1712
1712
  } else {
1713
- const fullpath = [...path9, ...issue2.path];
1713
+ const fullpath = [...path8, ...issue2.path];
1714
1714
  if (fullpath.length === 0) {
1715
1715
  fieldErrors._errors.push(mapper(issue2));
1716
1716
  } else {
@@ -1737,17 +1737,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
1737
1737
  }
1738
1738
  function treeifyError(error51, mapper = (issue2) => issue2.message) {
1739
1739
  const result = { errors: [] };
1740
- const processError = (error52, path9 = []) => {
1740
+ const processError = (error52, path8 = []) => {
1741
1741
  var _a3, _b;
1742
1742
  for (const issue2 of error52.issues) {
1743
1743
  if (issue2.code === "invalid_union" && issue2.errors.length) {
1744
- issue2.errors.map((issues) => processError({ issues }, [...path9, ...issue2.path]));
1744
+ issue2.errors.map((issues) => processError({ issues }, [...path8, ...issue2.path]));
1745
1745
  } else if (issue2.code === "invalid_key") {
1746
- processError({ issues: issue2.issues }, [...path9, ...issue2.path]);
1746
+ processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
1747
1747
  } else if (issue2.code === "invalid_element") {
1748
- processError({ issues: issue2.issues }, [...path9, ...issue2.path]);
1748
+ processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
1749
1749
  } else {
1750
- const fullpath = [...path9, ...issue2.path];
1750
+ const fullpath = [...path8, ...issue2.path];
1751
1751
  if (fullpath.length === 0) {
1752
1752
  result.errors.push(mapper(issue2));
1753
1753
  continue;
@@ -1779,8 +1779,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
1779
1779
  }
1780
1780
  function toDotPath(_path) {
1781
1781
  const segs = [];
1782
- const path9 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
1783
- for (const seg of path9) {
1782
+ const path8 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
1783
+ for (const seg of path8) {
1784
1784
  if (typeof seg === "number")
1785
1785
  segs.push(`[${seg}]`);
1786
1786
  else if (typeof seg === "symbol")
@@ -14472,13 +14472,13 @@ function resolveRef(ref, ctx) {
14472
14472
  if (!ref.startsWith("#")) {
14473
14473
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
14474
14474
  }
14475
- const path9 = ref.slice(1).split("/").filter(Boolean);
14476
- if (path9.length === 0) {
14475
+ const path8 = ref.slice(1).split("/").filter(Boolean);
14476
+ if (path8.length === 0) {
14477
14477
  return ctx.rootSchema;
14478
14478
  }
14479
14479
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
14480
- if (path9[0] === defsKey) {
14481
- const key = path9[1];
14480
+ if (path8[0] === defsKey) {
14481
+ const key = path8[1];
14482
14482
  if (!key || !ctx.defs[key]) {
14483
14483
  throw new Error(`Reference not found: ${ref}`);
14484
14484
  }
@@ -16620,7 +16620,35 @@ import path4 from "node:path";
16620
16620
 
16621
16621
  // dist/hermes-runtime.js
16622
16622
  import { execFile as execFile2 } from "node:child_process";
16623
+ import { statSync } from "node:fs";
16623
16624
  import { promisify } from "node:util";
16625
+ var launcherPrefixCache = /* @__PURE__ */ new Map();
16626
+ function launcherCacheKey(launcher, environment) {
16627
+ let stat;
16628
+ try {
16629
+ stat = statSync(launcher);
16630
+ } catch {
16631
+ return null;
16632
+ }
16633
+ return [
16634
+ launcher,
16635
+ stat.mtimeMs,
16636
+ stat.size,
16637
+ environment.HERMES_HOME ?? "",
16638
+ environment.PATH ?? ""
16639
+ ].join("\0");
16640
+ }
16641
+ function timeitPassthrough(script) {
16642
+ return [
16643
+ "-n",
16644
+ "1",
16645
+ "-r",
16646
+ "1",
16647
+ "-s",
16648
+ `exec(${JSON.stringify(script)}, {}); import sys, io; sys.stdout = io.StringIO()`,
16649
+ "pass"
16650
+ ];
16651
+ }
16624
16652
  function legacyHermesPythonCommand(python, script) {
16625
16653
  return {
16626
16654
  command: python,
@@ -16636,22 +16664,12 @@ ${script}`
16636
16664
  };
16637
16665
  }
16638
16666
  async function nativeHermesPythonCommand(launcher, script, environment, timeoutMs = 2e4, platform = process.platform) {
16639
- const invocation = commandInvocation(launcher, [
16640
- "--print-runtime-command",
16641
- "--module",
16642
- "timeit",
16643
- "--",
16644
- "-n",
16645
- "1",
16646
- "-r",
16647
- "1",
16648
- "-s",
16649
- // timeit is a stdlib entry point supported by the native launcher. After
16650
- // the operation returns, redirect its timing footer, preserving normal
16651
- // native cleanup and exit status without contaminating JSON/RPC stdout.
16652
- `exec(${JSON.stringify(script)}, {}); import sys, io; sys.stdout = io.StringIO()`,
16653
- "pass"
16654
- ], environment, platform);
16667
+ const passthrough = timeitPassthrough(script);
16668
+ const cacheKey = launcherCacheKey(launcher, environment);
16669
+ const cached2 = cacheKey !== null ? launcherPrefixCache.get(cacheKey) : void 0;
16670
+ if (cached2 !== void 0 && cached2.length > 0 && cached2[0])
16671
+ return { command: cached2[0], arguments: [...cached2.slice(1), ...passthrough] };
16672
+ const invocation = commandInvocation(launcher, ["--print-runtime-command", "--module", "timeit", "--", ...passthrough], environment, platform);
16655
16673
  let stdout;
16656
16674
  try {
16657
16675
  ({ stdout } = await promisify(execFile2)(invocation.command, invocation.arguments, {
@@ -16671,6 +16689,9 @@ async function nativeHermesPythonCommand(launcher, script, environment, timeoutM
16671
16689
  if (!Array.isArray(value) || value.length === 0 || !value.every((arg) => typeof arg === "string") || !value[0]) {
16672
16690
  throw new Error("Hermes launcher returned an invalid runtime command");
16673
16691
  }
16692
+ const passthroughStart = value.length - passthrough.length;
16693
+ if (cacheKey !== null && passthroughStart >= 1 && value.slice(passthroughStart).every((arg, index) => arg === passthrough[index]))
16694
+ launcherPrefixCache.set(cacheKey, value.slice(0, passthroughStart));
16674
16695
  return { command: value[0], arguments: value.slice(1) };
16675
16696
  }
16676
16697
  async function hermesPythonCommand(runtime, script, environment, timeoutMs = 2e4) {
@@ -16983,9 +17004,6 @@ function catalogModelsFromInventory(inventory) {
16983
17004
  }
16984
17005
 
16985
17006
  // dist/gateway-delegation.js
16986
- import { mkdtemp as mkdtemp2, writeFile as writeFile2, rm as rm2 } from "node:fs/promises";
16987
- import os2 from "node:os";
16988
- import path5 from "node:path";
16989
17007
  var HERMES_DELEGATION_GUIDANCE = `This Session runs inside codexhost.
16990
17008
  When the user authorizes cross-Harness delegation, discover the executable named by CODEXHOST_CLI_PATH through your native terminal tool. Read its --help and harness list, then use delegate start and thread send/read/wait/cancel as documented. Prefer --format compact. Preserve CODEXHOST_RUNTIME_ENDPOINT, CODEXHOST_RUNTIME_TOKEN and CODEXHOST_THREAD_ID in these calls: they identify the Runtime and parent Thread. Never print their values or substitute another executable. Native Hermes delegate_task remains available for Hermes subagents.`;
16991
17009
  var required2 = [
@@ -16996,48 +17014,40 @@ var required2 = [
16996
17014
  ];
16997
17015
  async function prepareGatewayDelegation(environment) {
16998
17016
  if (!required2.every((key) => environment[key]))
16999
- return { environment, bootstrap: "", dispose: async () => {
17017
+ return { environment, bootstrap: "", guidance: null, dispose: async () => {
17000
17018
  } };
17001
- const directory = await mkdtemp2(path5.join(os2.tmpdir(), "codexhost-hermes-delegation-"));
17002
- const file2 = path5.join(directory, "SKILL.md");
17003
- try {
17004
- await writeFile2(file2, `---
17005
- name: delegation
17006
- description: Discover authorized codexhost agent collaboration.
17007
- ---
17008
-
17009
- ${HERMES_DELEGATION_GUIDANCE}
17010
- `, { mode: 384 });
17011
- return {
17012
- environment: {
17013
- ...environment,
17014
- HERMES_TUI_SKILLS: [environment.HERMES_TUI_SKILLS, "codexhost-runtime:delegation"].filter(Boolean).join(",")
17015
- },
17016
- // Reserve RPC stdout before native plugin imports. The registration is
17017
- // process-local; no plugin is installed and no user config is changed.
17018
- //
17019
- // ``hermes_bootstrap`` must run before ``tui_gateway.server`` is imported:
17020
- // the gateway spawn uses ``python -I``, so PYTHONPATH and the launcher's
17021
- // activation are stripped. Hermes's own entry point (``tui_gateway/entry.py``)
17022
- // imports this module first to put the selected dependency environment on
17023
- // ``sys.path``; skipping it makes bare third-party imports (e.g.
17024
- // ``hermes_yaml`` -> ``ruamel``) fail on interpreters whose site-packages
17025
- // do not already carry Hermes's dependencies. ``hermes_bootstrap`` is
17026
- // import-safe here for the same reason entry.py relies on it: stdlib-only
17027
- // at import time, so nothing can shadow it from the launch directory.
17028
- bootstrap: `import hermes_bootstrap
17019
+ return {
17020
+ environment,
17021
+ // Reserve RPC stdout before native plugin imports.
17022
+ //
17023
+ // ``hermes_bootstrap`` must run before ``tui_gateway.server`` is imported:
17024
+ // the gateway spawn uses ``python -I``, so PYTHONPATH and the launcher's
17025
+ // activation are stripped. Hermes's own entry point (``tui_gateway/entry.py``)
17026
+ // imports this module first to put the selected dependency environment on
17027
+ // ``sys.path``; skipping it makes bare third-party imports (e.g.
17028
+ // ``hermes_yaml`` -> ``ruamel``) fail on interpreters whose site-packages
17029
+ // do not already carry Hermes's dependencies. ``hermes_bootstrap`` is
17030
+ // import-safe here for the same reason entry.py relies on it: stdlib-only
17031
+ // at import time, so nothing can shadow it from the launch directory.
17032
+ bootstrap: `import sys
17033
+ import hermes_bootstrap
17034
+ _stdout = sys.stdout # tui_gateway.server redirects sys.stdout to sys.stderr at import (stdio-entry protection); this bridge owns stdout as its RPC channel to the host
17029
17035
  from tui_gateway import server
17030
- from pathlib import Path
17031
- from hermes_cli.plugins import PluginContext, PluginManifest, get_plugin_manager
17032
- PluginContext(PluginManifest(name="codexhost-runtime"), get_plugin_manager()).register_skill(
17033
- "delegation", Path(${JSON.stringify(file2)}))
17036
+ sys.stdout = _stdout
17034
17037
  `,
17035
- dispose: () => rm2(directory, { recursive: true, force: true })
17036
- };
17037
- } catch (error51) {
17038
- await rm2(directory, { recursive: true, force: true });
17039
- throw error51;
17040
- }
17038
+ guidance: `${HERMES_DELEGATION_GUIDANCE}
17039
+
17040
+ Runtime identity for this Thread:
17041
+
17042
+ - CODEXHOST_CLI_PATH: ${environment.CODEXHOST_CLI_PATH}
17043
+ - CODEXHOST_RUNTIME_ENDPOINT: ${environment.CODEXHOST_RUNTIME_ENDPOINT}
17044
+ - CODEXHOST_RUNTIME_TOKEN: ${environment.CODEXHOST_RUNTIME_TOKEN}
17045
+ - CODEXHOST_THREAD_ID: ${environment.CODEXHOST_THREAD_ID}
17046
+
17047
+ (end of codexhost delegation note; the user's message follows)`,
17048
+ dispose: async () => {
17049
+ }
17050
+ };
17041
17051
  }
17042
17052
 
17043
17053
  // dist/hermes-transport.js
@@ -17071,8 +17081,87 @@ function gatewayRecord(value) {
17071
17081
  function gatewayString(value) {
17072
17082
  return typeof value === "string" ? value : "";
17073
17083
  }
17074
- var GATEWAY_LAUNCH = `import runpy
17075
- runpy.run_module('tui_gateway.entry', run_name='__main__')`;
17084
+ var GATEWAY_LAUNCH = `import asyncio
17085
+ import sys
17086
+
17087
+
17088
+ async def _bridge():
17089
+ from hermes_constants import get_hermes_home
17090
+ from hermes_cli.gateway_client import _session_ticket, gateway_ws_target
17091
+ from hermes_cli.gateway_runtime import (
17092
+ attachable,
17093
+ ensure_gateway_runtime,
17094
+ gateway_profile_home,
17095
+ )
17096
+ from gateway.session_contract import CANONICAL_GATEWAY_PROTOCOL
17097
+ from websockets.asyncio.client import connect
17098
+
17099
+ home = gateway_profile_home(get_hermes_home())
17100
+ result = await asyncio.to_thread(ensure_gateway_runtime, home, idle_exit=True)
17101
+ if not attachable(result):
17102
+ detail = f" ({result.detail})" if result.detail else ""
17103
+ raise SystemExit(
17104
+ f"[bridge-exit] gateway not attachable: {result.state} "
17105
+ f"{result.reason_code or 'not ready'}{detail}"
17106
+ )
17107
+ endpoint = result.endpoint
17108
+ ticket = await asyncio.to_thread(_session_ticket, home, endpoint)
17109
+ url, protocols = gateway_ws_target(endpoint, ticket)
17110
+
17111
+ async with connect(
17112
+ url, subprotocols=protocols, open_timeout=10, max_size=8 * 1024 * 1024, proxy=None
17113
+ ) as ws:
17114
+ if protocols and ws.subprotocol != CANONICAL_GATEWAY_PROTOCOL:
17115
+ raise SystemExit("[bridge-exit] gateway protocol mismatch; update/restart required")
17116
+
17117
+ loop = asyncio.get_running_loop()
17118
+ # A full prompt rides on ONE JSON-RPC line; StreamReader's default 64 KiB
17119
+ # line limit aborts the bridge on large pastes (LimitOverrunError), so the
17120
+ # stdin side matches the WebSocket side's 8 MiB frame ceiling.
17121
+ reader = asyncio.StreamReader(limit=8 * 1024 * 1024)
17122
+ await loop.connect_read_pipe(
17123
+ lambda: asyncio.StreamReaderProtocol(reader), sys.stdin
17124
+ )
17125
+
17126
+ async def pump_in():
17127
+ async for raw in reader:
17128
+ line = raw.decode("utf-8", "replace").strip()
17129
+ if line:
17130
+ await ws.send(line)
17131
+
17132
+ async def pump_out():
17133
+ async for raw in ws:
17134
+ if isinstance(raw, bytes):
17135
+ raw = raw.decode("utf-8", "replace")
17136
+ sys.stdout.write(raw + "\\n")
17137
+ sys.stdout.flush()
17138
+
17139
+ pump_out_task = asyncio.create_task(pump_out())
17140
+ pump_in_task = asyncio.create_task(pump_in())
17141
+ # stdin EOF (host closed us) or socket close (gateway went away): end both.
17142
+ done, _pending = await asyncio.wait(
17143
+ {pump_in_task, pump_out_task}, return_when=asyncio.FIRST_COMPLETED
17144
+ )
17145
+ for task in (pump_in_task, pump_out_task):
17146
+ task.cancel()
17147
+ for task in done:
17148
+ if task.cancelled():
17149
+ continue
17150
+ exc = task.exception()
17151
+ if exc is not None:
17152
+ raise exc
17153
+
17154
+
17155
+ # No __main__ guard: the native launcher spawns this through exec(script, {})
17156
+ # whose fresh globals lack __name__; top-level code runs in both the legacy
17157
+ # (-c) and native (exec) spawn forms.
17158
+ try:
17159
+ asyncio.run(_bridge())
17160
+ except SystemExit:
17161
+ raise
17162
+ except Exception as exc:
17163
+ raise SystemExit(f"[bridge-exit] bridge failed: {type(exc).__name__}: {exc}")
17164
+ `;
17076
17165
  var HermesGatewayTransport = class _HermesGatewayTransport {
17077
17166
  runtime;
17078
17167
  cwd;
@@ -17099,6 +17188,18 @@ var HermesGatewayTransport = class _HermesGatewayTransport {
17099
17188
  this.environment = environment;
17100
17189
  this.timeoutMs = timeoutMs;
17101
17190
  }
17191
+ /** Model-facing delegation guidance for this Thread, or null without delegation. */
17192
+ get delegationGuidance() {
17193
+ return this.#delegation?.guidance ?? null;
17194
+ }
17195
+ /** Whether start() already ran (a warm transport skips prepare/start on reuse). */
17196
+ get started() {
17197
+ return this.#startRequested;
17198
+ }
17199
+ /** Whether the bridge child is gone; a closed transport cannot be reused. */
17200
+ get closed() {
17201
+ return this.#closed;
17202
+ }
17102
17203
  static async probe(executable, cwd, environment) {
17103
17204
  const override = environment.CODEXHOST_HERMES_GATEWAY_PYTHON;
17104
17205
  if (override) {
@@ -17611,7 +17712,7 @@ async function resolveHermesSessionCandidate(input) {
17611
17712
  }
17612
17713
 
17613
17714
  // dist/command.js
17614
- import path6 from "node:path";
17715
+ import path5 from "node:path";
17615
17716
  var HermesExecutableError = class extends Error {
17616
17717
  code = "HERMES_NOT_FOUND";
17617
17718
  };
@@ -17634,7 +17735,62 @@ function resolveHermesExecutable(input = {}) {
17634
17735
  });
17635
17736
  if (!resolution)
17636
17737
  throw new HermesExecutableError("Hermes CLI is not installed");
17637
- return targetPath(platform).isAbsolute(resolution.executable) ? resolution.executable : path6.resolve(resolution.executable);
17738
+ return targetPath(platform).isAbsolute(resolution.executable) ? resolution.executable : path5.resolve(resolution.executable);
17739
+ }
17740
+
17741
+ // dist/gateway-transport-pool.js
17742
+ function sameRuntime(a, b) {
17743
+ if (typeof a === "string" || typeof b === "string")
17744
+ return a === b;
17745
+ return a.launcher === b.launcher;
17746
+ }
17747
+ var ENVIRONMENT_IDENTITY_KEYS = ["HERMES_HOME", "HERMES_TUI_SKILLS"];
17748
+ function environmentIdentity(environment) {
17749
+ return ENVIRONMENT_IDENTITY_KEYS.map((key) => `${key}=${environment[key] ?? ""}`).join("\0");
17750
+ }
17751
+ function sameBridge(transport, runtime, cwd, environment) {
17752
+ return sameRuntime(transport.runtime, runtime) && transport.cwd === cwd && environmentIdentity(transport.environment) === environmentIdentity(environment);
17753
+ }
17754
+ var GatewayTransportPool = class {
17755
+ #idle = [];
17756
+ #capacity;
17757
+ #closed = false;
17758
+ constructor(capacity = 2) {
17759
+ this.#capacity = Math.max(1, capacity);
17760
+ }
17761
+ /** A healthy started bridge equivalent to a fresh spawn for these inputs, or null. */
17762
+ acquire(runtime, cwd, environment) {
17763
+ while (this.#idle.length > 0) {
17764
+ const index = this.#idle.findIndex((transport2) => sameBridge(transport2, runtime, cwd, environment));
17765
+ const transport = index >= 0 ? this.#idle.splice(index, 1)[0] : void 0;
17766
+ if (transport === void 0)
17767
+ return null;
17768
+ if (!transport.closed && transport.started)
17769
+ return transport;
17770
+ void transport.close().catch(() => void 0);
17771
+ }
17772
+ return null;
17773
+ }
17774
+ /** Return a bridge after its session settled; unhealthy or late ones are discarded. */
17775
+ release(transport) {
17776
+ if (transport.closed || this.#closed || this.#idle.length >= this.#capacity) {
17777
+ void transport.close().catch(() => void 0);
17778
+ return;
17779
+ }
17780
+ this.#idle.push(transport);
17781
+ }
17782
+ async close() {
17783
+ this.#closed = true;
17784
+ const transports = this.#idle;
17785
+ this.#idle = [];
17786
+ await Promise.all(transports.map((transport) => transport.close().catch(() => void 0)));
17787
+ }
17788
+ };
17789
+ async function startGatewayTransport(runtime, cwd, environment, timeoutMs) {
17790
+ const transport = new HermesGatewayTransport(runtime, cwd, environment, timeoutMs);
17791
+ await transport.prepareSession();
17792
+ await transport.start();
17793
+ return transport;
17638
17794
  }
17639
17795
 
17640
17796
  // dist/gateway-history.js
@@ -17724,21 +17880,24 @@ def capture(db):
17724
17880
  boundaries[str(row['id'])] = {'count': end, 'digest': digest(active[:end])}
17725
17881
  return exported, {'rows': rows, 'derivable': derivable, 'boundaries': boundaries, 'physicalSessionId': sid}
17726
17882
 
17883
+ def ensure_session_row(db):
17884
+ if db.get_session(sid) is None:
17885
+ from hermes_constants import parse_reasoning_effort
17886
+ initial = {}
17887
+ if isinstance(p.get('provider'), str) and p['provider']: initial['provider'] = p['provider']
17888
+ if isinstance(p.get('model'), str) and p['model']: initial['model'] = p['model']
17889
+ if isinstance(p.get('reasoningEffort'), str):
17890
+ reasoning = parse_reasoning_effort(p['reasoningEffort'])
17891
+ if reasoning is not None: initial['reasoning_config'] = reasoning
17892
+ if isinstance(p.get('yolo'), bool): initial['yolo_mode'] = p['yolo']
17893
+ db.create_session(session_id=sid, source='cli', cwd=p['cwd'], model=p.get('model'), model_config=initial)
17894
+ return {'created': True}
17895
+
17727
17896
  try:
17728
17897
  if p['operation'] == 'ensure':
17729
17898
  db = SessionDB()
17730
17899
  try:
17731
- if db.get_session(sid) is None:
17732
- from hermes_constants import parse_reasoning_effort
17733
- initial = {}
17734
- if isinstance(p.get('provider'), str) and p['provider']: initial['provider'] = p['provider']
17735
- if isinstance(p.get('model'), str) and p['model']: initial['model'] = p['model']
17736
- if isinstance(p.get('reasoningEffort'), str):
17737
- reasoning = parse_reasoning_effort(p['reasoningEffort'])
17738
- if reasoning is not None: initial['reasoning_config'] = reasoning
17739
- if isinstance(p.get('yolo'), bool): initial['yolo_mode'] = p['yolo']
17740
- db.create_session(session_id=sid, source='cli', cwd=p['cwd'], model=p.get('model'), model_config=initial)
17741
- result = {'created': True}
17900
+ result = ensure_session_row(db)
17742
17901
  finally: db.close()
17743
17902
  elif p['operation'] == 'discard':
17744
17903
  child = p['derivedSessionId']
@@ -18106,28 +18265,6 @@ var HermesUsage = class {
18106
18265
  }
18107
18266
  };
18108
18267
 
18109
- // dist/gateway-configuration.js
18110
- import { execFile as execFile4 } from "node:child_process";
18111
- import { promisify as promisify3 } from "node:util";
18112
- async function resolveGatewayModel(transport, modelId) {
18113
- if (/\s/u.test(modelId) || modelId.startsWith("-"))
18114
- throw new Error("Invalid Hermes Model identifier");
18115
- const command = await hermesPythonCommand(transport.runtime, `import json
18116
- from hermes_cli.models import parse_model_input
18117
- p,m=parse_model_input(${JSON.stringify(modelId)}, '')
18118
- print(json.dumps({'provider':p,'model':m}))`, { ...process.env, ...transport.environment });
18119
- const result = await promisify3(execFile4)(command.command, command.arguments, {
18120
- cwd: transport.cwd,
18121
- env: { ...process.env, ...transport.environment },
18122
- timeout: 2e4,
18123
- maxBuffer: 1024 * 1024
18124
- });
18125
- const value = JSON.parse(result.stdout.trim());
18126
- if (!value || typeof value !== "object" || !("provider" in value) || !("model" in value) || typeof value.provider !== "string" || typeof value.model !== "string")
18127
- throw new Error("Hermes returned an invalid Model choice");
18128
- return { provider: value.provider, model: value.model };
18129
- }
18130
-
18131
18268
  // dist/gateway-session-transport.js
18132
18269
  var hermesGatewayThinkingOptions = [
18133
18270
  "none",
@@ -18159,14 +18296,14 @@ function gatewayDiff(text2) {
18159
18296
  if (!text2 || text2.length > 1024 * 1024)
18160
18297
  return [];
18161
18298
  const result = [];
18162
- let path9 = "";
18299
+ let path8 = "";
18163
18300
  let inHunk = false;
18164
18301
  let oldLines = [], newLines = [];
18165
18302
  const flush = () => {
18166
- if (path9)
18303
+ if (path8)
18167
18304
  result.push({
18168
18305
  type: "diff",
18169
- path: path9,
18306
+ path: path8,
18170
18307
  oldText: oldLines.join("\n"),
18171
18308
  newText: newLines.join("\n")
18172
18309
  });
@@ -18179,24 +18316,24 @@ function gatewayDiff(text2) {
18179
18316
  if (header) {
18180
18317
  flush();
18181
18318
  inHunk = false;
18182
- path9 = (header[2] === "/dev/null" ? header[1] ?? "" : header[2] ?? "").replace(/^[ab]\//u, "");
18319
+ path8 = (header[2] === "/dev/null" ? header[1] ?? "" : header[2] ?? "").replace(/^[ab]\//u, "");
18183
18320
  continue;
18184
18321
  }
18185
18322
  if (line.startsWith("--- ") && !inHunk) {
18186
18323
  flush();
18187
- path9 = line.slice(4).replace(/^a\//u, "");
18324
+ path8 = line.slice(4).replace(/^a\//u, "");
18188
18325
  } else if (line.startsWith("+++ ") && !inHunk) {
18189
18326
  const next = line.slice(4);
18190
18327
  if (next !== "/dev/null")
18191
- path9 = next.replace(/^b\//u, "");
18328
+ path8 = next.replace(/^b\//u, "");
18192
18329
  } else if (line.startsWith("@@")) {
18193
18330
  inHunk = true;
18194
18331
  continue;
18195
- } else if (path9 && line.startsWith("-"))
18332
+ } else if (path8 && line.startsWith("-"))
18196
18333
  oldLines.push(line.slice(1));
18197
- else if (path9 && line.startsWith("+"))
18334
+ else if (path8 && line.startsWith("+"))
18198
18335
  newLines.push(line.slice(1));
18199
- else if (path9 && line.startsWith(" ")) {
18336
+ else if (path8 && line.startsWith(" ")) {
18200
18337
  oldLines.push(line.slice(1));
18201
18338
  newLines.push(line.slice(1));
18202
18339
  }
@@ -18220,7 +18357,7 @@ var HermesGatewaySessionTransport = class {
18220
18357
  return this.#commands;
18221
18358
  }
18222
18359
  async getCommands() {
18223
- const params = { session_id: this.sessionId, cwd: this.transport.cwd };
18360
+ const params = { session_id: this.sessionId };
18224
18361
  const [response, catalog] = await Promise.all([
18225
18362
  this.transport.request("complete.slash", { ...params, text: "/" }, this.transport.timeoutMs, false),
18226
18363
  this.transport.request("commands.catalog", params, this.transport.timeoutMs, false)
@@ -18245,13 +18382,29 @@ var HermesGatewaySessionTransport = class {
18245
18382
  history;
18246
18383
  #active = null;
18247
18384
  #requests = /* @__PURE__ */ new Map();
18385
+ /** Open approval server requests by their payload request_id, for approval.cancelled. */
18386
+ #approvalRequests = /* @__PURE__ */ new Map();
18387
+ /** Pending approval prompts adopted from the create/resume snapshot. */
18388
+ #adoptedPrompts = [];
18248
18389
  #closed = false;
18249
18390
  #info;
18250
- constructor(transport, sessionId, nativeSessionId, info) {
18391
+ #revision = null;
18392
+ #generation = null;
18393
+ #subscriptionId = null;
18394
+ /** Thread's delegation guidance, injected as api_text on the first turn only. */
18395
+ #delegationGuidance;
18396
+ constructor(transport, sessionId, nativeSessionId, info, bookkeeping = {}, delegationGuidance = null) {
18251
18397
  this.transport = transport;
18252
18398
  this.sessionId = sessionId;
18253
18399
  this.nativeSessionId = nativeSessionId;
18254
18400
  this.#info = info;
18401
+ this.#delegationGuidance = delegationGuidance;
18402
+ if (typeof bookkeeping.revision === "number")
18403
+ this.#revision = bookkeeping.revision;
18404
+ if (typeof bookkeeping.generation === "number")
18405
+ this.#generation = bookkeeping.generation;
18406
+ if (typeof bookkeeping.subscriptionId === "string")
18407
+ this.#subscriptionId = bookkeeping.subscriptionId;
18255
18408
  this.history = new HermesGatewayHistory({
18256
18409
  python: transport.runtime,
18257
18410
  cwd: transport.cwd,
@@ -18312,17 +18465,24 @@ var HermesGatewaySessionTransport = class {
18312
18465
  return optionId;
18313
18466
  }
18314
18467
  async setModel(modelId) {
18315
- const choice = await resolveGatewayModel(this.transport, modelId);
18316
- const result = await this.transport.request("config.set", {
18317
- key: "model",
18318
- value: choice.provider ? `${choice.model} --provider ${choice.provider}` : modelId,
18319
- scope: "session",
18320
- session_id: this.sessionId
18468
+ const revision = this.#revision;
18469
+ const generation = this.#generation;
18470
+ if (revision === null || generation === null)
18471
+ throw new Error("Hermes gateway Session revision is not known yet");
18472
+ const result = await this.transport.request("session.mutate", {
18473
+ session_id: this.sessionId,
18474
+ request_id: `codexhost-model-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
18475
+ expected_revision: revision,
18476
+ expected_generation: generation,
18477
+ operation: "model",
18478
+ payload: { model: modelId }
18321
18479
  });
18322
- if (result.confirm_required === true)
18323
- throw new Error(gatewayString(result.confirm_message) || "Hermes requires confirmation for this Model");
18324
- const actualModel = gatewayString(this.#info.model);
18325
- const actualProvider = gatewayString(this.#info.provider);
18480
+ if (typeof result.revision === "number")
18481
+ this.#revision = result.revision;
18482
+ if (typeof result.execution_generation === "number")
18483
+ this.#generation = result.execution_generation;
18484
+ const actualModel = gatewayString(result.model);
18485
+ const actualProvider = gatewayString(result.provider);
18326
18486
  if (modelId !== actualModel && modelId !== `${actualProvider}:${actualModel}`)
18327
18487
  throw new Error("Hermes did not confirm the requested Model");
18328
18488
  }
@@ -18332,11 +18492,11 @@ var HermesGatewaySessionTransport = class {
18332
18492
  const result = await this.transport.request("config.set", {
18333
18493
  key: "yolo",
18334
18494
  value: modeId === "dont_ask" ? "on" : "off",
18335
- scope: "session",
18336
18495
  session_id: this.sessionId
18337
18496
  });
18338
- if (result.value !== (modeId === "dont_ask" ? "1" : "0") || this.#info.yolo !== (modeId === "dont_ask"))
18497
+ if (result.value !== (modeId === "dont_ask" ? "1" : "0"))
18339
18498
  throw new Error("Hermes effective approval policy differs from the requested mode; check native global approvals policy");
18499
+ this.#info = { ...this.#info, yolo: modeId === "dont_ask" };
18340
18500
  }
18341
18501
  nativeCommandName(text2) {
18342
18502
  return hermesGatewayCommandName(text2, this.#commands);
@@ -18359,9 +18519,11 @@ var HermesGatewaySessionTransport = class {
18359
18519
  reject: rejectResult,
18360
18520
  streamed: "",
18361
18521
  reasoning: "",
18362
- toolIds: /* @__PURE__ */ new Set()
18522
+ toolIds: /* @__PURE__ */ new Set(),
18523
+ admissionId: null
18363
18524
  };
18364
18525
  this.#active = active;
18526
+ void this.#adoptedPrompts.splice(0).map((prompt) => void this.#respondAdoptedApproval(prompt, active).catch(() => void 0));
18365
18527
  const commandText = text2.trim();
18366
18528
  const nativeCommand = this.nativeCommandName(text2);
18367
18529
  try {
@@ -18400,12 +18562,21 @@ var HermesGatewaySessionTransport = class {
18400
18562
  emit({ type: "agent.text", text: response.output });
18401
18563
  active.resolve({ stopReason: "end_turn" });
18402
18564
  } else {
18565
+ const guidance = this.#delegationGuidance;
18566
+ this.#delegationGuidance = null;
18403
18567
  const submitted = await this.transport.request("prompt.submit", {
18404
18568
  session_id: this.sessionId,
18405
- text: text2
18569
+ text: text2,
18570
+ submission_id: `codexhost-turn-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
18571
+ clarify_batch: true,
18572
+ ...guidance ? { api_text: `${guidance}
18573
+
18574
+ ${text2}` } : {}
18406
18575
  });
18407
- if (submitted.status !== "streaming")
18408
- this.#fault(new Error(`Hermes did not start an exclusive turn (${String(submitted.status)})`));
18576
+ if (submitted.status !== "queued" && submitted.status !== "started")
18577
+ this.#fault(new Error(`Hermes did not accept the turn (${String(submitted.status)})`));
18578
+ else if (active.admissionId === null && typeof submitted.admission_id === "string")
18579
+ active.admissionId = submitted.admission_id;
18409
18580
  }
18410
18581
  } catch (error51) {
18411
18582
  active.reject(error51 instanceof Error ? error51 : new Error(String(error51)));
@@ -18421,7 +18592,12 @@ var HermesGatewaySessionTransport = class {
18421
18592
  }
18422
18593
  }
18423
18594
  async cancel() {
18424
- await this.transport.request("session.interrupt", { session_id: this.sessionId });
18595
+ if (this.#generation === null)
18596
+ return;
18597
+ await this.transport.request("session.interrupt", {
18598
+ session_id: this.sessionId,
18599
+ execution_generation: this.#generation
18600
+ });
18425
18601
  }
18426
18602
  async close() {
18427
18603
  if (this.#closed)
@@ -18432,10 +18608,13 @@ var HermesGatewaySessionTransport = class {
18432
18608
  this.#requests.clear();
18433
18609
  this.#active?.reject(new Error("Hermes gateway Session closed"));
18434
18610
  try {
18435
- await this.transport.request("session.close", { session_id: this.sessionId });
18611
+ if (this.#subscriptionId !== null)
18612
+ await this.transport.request("session.detach", {
18613
+ session_id: this.sessionId,
18614
+ subscription_id: this.#subscriptionId
18615
+ });
18436
18616
  } catch {
18437
18617
  }
18438
- await this.transport.close();
18439
18618
  }
18440
18619
  #fault(error51) {
18441
18620
  if (this.#closed)
@@ -18455,6 +18634,10 @@ var HermesGatewaySessionTransport = class {
18455
18634
  const payload = gatewayRecord(event.payload);
18456
18635
  if (event.type === "session.info") {
18457
18636
  this.#info = payload;
18637
+ if (typeof payload.revision === "number")
18638
+ this.#revision = payload.revision;
18639
+ if (typeof payload.execution_generation === "number")
18640
+ this.#generation = payload.execution_generation;
18458
18641
  return;
18459
18642
  }
18460
18643
  if (event.type === "session.usage") {
@@ -18466,12 +18649,26 @@ var HermesGatewaySessionTransport = class {
18466
18649
  controller?.abort(payload.reason === "timeout" ? "expired" : "cancelled");
18467
18650
  return;
18468
18651
  }
18652
+ if (event.type === "approval.cancelled") {
18653
+ const ids = Array.isArray(payload.request_ids) ? payload.request_ids.filter((id) => typeof id === "string") : [];
18654
+ for (const id of ids) {
18655
+ this.#approvalRequests.get(id)?.abort("cancelled");
18656
+ this.#approvalRequests.delete(id);
18657
+ }
18658
+ return;
18659
+ }
18469
18660
  const active = this.#active;
18470
18661
  if (!active) {
18471
18662
  if (event.type === "error")
18472
18663
  this.#fault(new Error(gatewayString(payload.message)));
18473
18664
  return;
18474
18665
  }
18666
+ const eventAdmission = gatewayString(event.admission_id);
18667
+ const eventType = gatewayString(event.type);
18668
+ if (eventAdmission && active.admissionId && eventAdmission !== active.admissionId) {
18669
+ if (eventType.startsWith("message.") || eventType.startsWith("reasoning."))
18670
+ return;
18671
+ }
18475
18672
  const text2 = gatewayString(payload.text);
18476
18673
  if (event.type === "message.delta") {
18477
18674
  active.streamed += text2;
@@ -18602,27 +18799,80 @@ var HermesGatewaySessionTransport = class {
18602
18799
  label: choice === "session" ? "Allow for this session" : "Always allow"
18603
18800
  }
18604
18801
  ] : choice === "deny" ? [{ id: "deny", effect: "deny", label: "Deny" }] : []);
18605
- const response = await active.permission({
18606
- signal: controller.signal,
18607
- title: gatewayString(params.description) || gatewayString(params.command),
18608
- description: gatewayString(params.command) || gatewayString(params.description),
18609
- options
18610
- });
18611
- if (!controller.signal.aborted)
18612
- this.transport.respond(id, {
18613
- choice: response ?? "deny"
18802
+ const payloadRequestId = gatewayString(params.request_id);
18803
+ if (payloadRequestId)
18804
+ this.#approvalRequests.set(payloadRequestId, controller);
18805
+ try {
18806
+ const response = await active.permission({
18807
+ signal: controller.signal,
18808
+ title: gatewayString(params.description) || gatewayString(params.command),
18809
+ description: gatewayString(params.command) || gatewayString(params.description),
18810
+ options
18614
18811
  });
18812
+ if (!controller.signal.aborted)
18813
+ this.transport.respond(id, {
18814
+ choice: response ?? "deny"
18815
+ });
18816
+ } finally {
18817
+ if (payloadRequestId)
18818
+ this.#approvalRequests.delete(payloadRequestId);
18819
+ }
18615
18820
  } else
18616
18821
  this.transport.rejectRequest(id);
18617
18822
  } finally {
18618
18823
  this.#requests.delete(id);
18619
18824
  }
18620
18825
  }
18826
+ /** Keep approval prompts from the create/resume snapshot for the next turn. */
18827
+ adoptPendingPrompts(prompts) {
18828
+ if (!Array.isArray(prompts))
18829
+ return;
18830
+ for (const entry of prompts) {
18831
+ const prompt = gatewayRecord(entry);
18832
+ if (gatewayString(prompt.kind) === "approval" && gatewayString(prompt.prompt_id))
18833
+ this.#adoptedPrompts.push(prompt);
18834
+ }
18835
+ }
18836
+ /**
18837
+ * Answer a snapshot-adopted approval through canonical `approval.respond`.
18838
+ * Clarify prompts are deliberately not answered here: a live clarify
18839
+ * question is served by the `clarify` server request (batch mode), and the
18840
+ * gateway re-issues still-open server requests to an attaching client.
18841
+ */
18842
+ async #respondAdoptedApproval(prompt, active) {
18843
+ if (this.#closed)
18844
+ return;
18845
+ const promptId = gatewayString(prompt.prompt_id);
18846
+ const generation = prompt.execution_generation;
18847
+ const choices = Array.isArray(prompt.choices) ? prompt.choices.filter((choice) => typeof choice === "string") : ["once", "deny"];
18848
+ const options = choices.flatMap((choice) => choice === "once" ? [{ id: "once", effect: "allowOnce", label: "Allow once" }] : choice === "session" || choice === "always" ? [
18849
+ {
18850
+ id: choice,
18851
+ effect: choice === "session" ? "allowForSession" : "allowAlways",
18852
+ label: choice === "session" ? "Allow for this session" : "Always allow"
18853
+ }
18854
+ ] : choice === "deny" ? [{ id: "deny", effect: "deny", label: "Deny" }] : []);
18855
+ const response = await active.permission({
18856
+ signal: new AbortController().signal,
18857
+ title: gatewayString(prompt.description) || gatewayString(prompt.command) || "Hermes approval requested before this Session attached",
18858
+ description: gatewayString(prompt.command) || gatewayString(prompt.description),
18859
+ options
18860
+ });
18861
+ if (this.#closed)
18862
+ return;
18863
+ if (typeof generation === "number")
18864
+ await this.transport.request("approval.respond", {
18865
+ session_id: this.sessionId,
18866
+ execution_generation: generation,
18867
+ prompt_id: promptId,
18868
+ choice: response ?? "deny"
18869
+ }, this.transport.timeoutMs, false);
18870
+ }
18621
18871
  };
18622
18872
 
18623
18873
  // dist/usage-statistics.js
18624
- import os3 from "node:os";
18625
- import path7 from "node:path";
18874
+ import os2 from "node:os";
18875
+ import path6 from "node:path";
18626
18876
  import { lstat } from "node:fs/promises";
18627
18877
 
18628
18878
  // ../../harness-adapter/dist/usage-statistics.js
@@ -18682,9 +18932,9 @@ function usageEntryFromRequest(request, occurredAtMs) {
18682
18932
 
18683
18933
  // dist/usage-statistics.js
18684
18934
  function hermesStateDatabasePath(environment) {
18685
- const home = environment.HOME || environment.USERPROFILE || os3.homedir();
18935
+ const home = environment.HOME || environment.USERPROFILE || os2.homedir();
18686
18936
  const configured = environment.HERMES_HOME;
18687
- return path7.resolve(configured ? path7.join(configured, "state.db") : path7.join(home, ".hermes", "state.db"));
18937
+ return path6.resolve(configured ? path6.join(configured, "state.db") : path6.join(home, ".hermes", "state.db"));
18688
18938
  }
18689
18939
  async function fingerprint(file2) {
18690
18940
  const info = await lstat(file2).catch(() => null);
@@ -18766,7 +19016,7 @@ function createHermesUsageStatistics(environment) {
18766
19016
  }
18767
19017
 
18768
19018
  // dist/gateway-open.js
18769
- import path8 from "node:path";
19019
+ import path7 from "node:path";
18770
19020
  import { realpath } from "node:fs/promises";
18771
19021
 
18772
19022
  // dist/hermes-session.js
@@ -18976,16 +19226,16 @@ var Diff = class {
18976
19226
  }
18977
19227
  }
18978
19228
  }
18979
- addToPath(path9, added, removed, oldPosInc, options) {
18980
- const last = path9.lastComponent;
19229
+ addToPath(path8, added, removed, oldPosInc, options) {
19230
+ const last = path8.lastComponent;
18981
19231
  if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
18982
19232
  return {
18983
- oldPos: path9.oldPos + oldPosInc,
19233
+ oldPos: path8.oldPos + oldPosInc,
18984
19234
  lastComponent: { count: last.count + 1, added, removed, previousComponent: last.previousComponent }
18985
19235
  };
18986
19236
  } else {
18987
19237
  return {
18988
- oldPos: path9.oldPos + oldPosInc,
19238
+ oldPos: path8.oldPos + oldPosInc,
18989
19239
  lastComponent: { count: 1, added, removed, previousComponent: last }
18990
19240
  };
18991
19241
  }
@@ -19300,8 +19550,8 @@ function hermesFileChanges(update) {
19300
19550
  for (const block of update.content ?? []) {
19301
19551
  if (block.type !== "diff")
19302
19552
  continue;
19303
- const { path: path9, oldText, newText } = block;
19304
- if (!path9.trim() || /[\0\r\n]/u.test(path9) || typeof newText !== "string")
19553
+ const { path: path8, oldText, newText } = block;
19554
+ if (!path8.trim() || /[\0\r\n]/u.test(path8) || typeof newText !== "string")
19305
19555
  continue;
19306
19556
  if (oldText === newText)
19307
19557
  continue;
@@ -19309,10 +19559,10 @@ function hermesFileChanges(update) {
19309
19559
  if (bytes > MAX_DIFF_BYTES || changes.length === MAX_DIFFS)
19310
19560
  return [];
19311
19561
  changes.push({
19312
- path: path9,
19562
+ path: path8,
19313
19563
  kind: "update",
19314
19564
  diffScope: "fragment",
19315
- unifiedDiff: createTwoFilesPatch(path9, path9, oldText ?? "", newText, "", "", {
19565
+ unifiedDiff: createTwoFilesPatch(path8, path8, oldText ?? "", newText, "", "", {
19316
19566
  context: 3
19317
19567
  })
19318
19568
  });
@@ -20117,7 +20367,7 @@ ${reason}` }
20117
20367
 
20118
20368
  // dist/gateway-open.js
20119
20369
  async function sameDirectory(left, right) {
20120
- return await realpath(left).catch(() => path8.resolve(left)) === await realpath(right).catch(() => path8.resolve(right));
20370
+ return await realpath(left).catch(() => path7.resolve(left)) === await realpath(right).catch(() => path7.resolve(right));
20121
20371
  }
20122
20372
  var gatewayCapabilities = {
20123
20373
  configuration: {
@@ -20179,8 +20429,10 @@ async function openGatewaySession(input, transport, onSettle, inheritedPermissio
20179
20429
  let nativeRef = source;
20180
20430
  let sourceHistory;
20181
20431
  try {
20182
- await transport.prepareSession();
20183
- await transport.start();
20432
+ if (!transport.started) {
20433
+ await transport.prepareSession();
20434
+ await transport.start();
20435
+ }
20184
20436
  if (source && (input.kind === "fork" || input.kind === "rollbackLastTurn")) {
20185
20437
  sourceHistory = new HermesGatewayHistory({
20186
20438
  python: transport.runtime,
@@ -20190,34 +20442,33 @@ async function openGatewaySession(input, transport, onSettle, inheritedPermissio
20190
20442
  });
20191
20443
  nativeRef = await sourceHistory.derive(input.kind === "fork" ? { checkpoint: input.checkpoint } : { rollbackLastTurn: true });
20192
20444
  }
20193
- const choice = model2 ? await resolveGatewayModel(transport, model2) : null;
20194
- const response = await transport.request(nativeRef ? "session.resume" : "session.create", nativeRef ? { session_id: nativeRef.nativeSessionId, eager_build: true, omit_messages: true } : {
20195
- cwd: input.cwd,
20196
- close_on_disconnect: true,
20197
- ...choice ? choice : {},
20198
- ...input.kind === "create" && input.thinkingOptionId ? { reasoning_effort: input.thinkingOptionId } : {}
20199
- });
20445
+ const response = await transport.request(
20446
+ nativeRef ? "session.resume" : "session.create",
20447
+ // Canonical wire (hermes-gateway-v1, CanonicalCreateParams/CanonicalResumeParams):
20448
+ // no close_on_disconnect / eager_build keys, and the effort key is "reasoning".
20449
+ nativeRef ? { session_id: nativeRef.nativeSessionId, omit_messages: true } : {
20450
+ cwd: input.cwd,
20451
+ ...model2 ? { model: model2 } : {},
20452
+ ...input.kind === "create" && input.thinkingOptionId ? { reasoning: input.thinkingOptionId } : {}
20453
+ }
20454
+ );
20200
20455
  const runtimeId = gatewayString(response.session_id);
20201
20456
  const storedId = nativeRef?.nativeSessionId || gatewayString(response.stored_session_id);
20202
20457
  if (!runtimeId || !storedId)
20203
20458
  throw new Error("Hermes gateway did not return Session identity");
20204
- let info = gatewayRecord(response.info);
20205
- if (info.lazy === true)
20206
- info = await transport.waitForSession(runtimeId);
20459
+ const info = gatewayRecord(response.info);
20207
20460
  const nativeCwd = gatewayString(info.cwd);
20208
20461
  if (nativeCwd && !await sameDirectory(nativeCwd, input.cwd))
20209
20462
  throw new HermesGatewayHistoryError("unsupported", "Hermes resumed a different native working directory");
20210
- const bridge = new HermesGatewaySessionTransport(transport, runtimeId, storedId, info);
20211
- if (!nativeRef)
20212
- await bridge.history.ensureCreated({
20213
- cwd: input.cwd,
20214
- model: gatewayString(info.model),
20215
- provider: gatewayString(info.provider),
20216
- reasoningEffort: gatewayString(info.reasoning_effort)
20217
- });
20218
- const physicalId = await bridge.history.resolvePhysicalSessionId();
20219
- if (gatewayString(info.stored_session_id) !== physicalId)
20220
- throw new Error("Hermes live Session and persisted history identities differ");
20463
+ const bridge = new HermesGatewaySessionTransport(transport, runtimeId, storedId, info, {
20464
+ revision: response.revision,
20465
+ generation: response.execution_generation,
20466
+ subscriptionId: response.subscription_id
20467
+ }, transport.delegationGuidance);
20468
+ bridge.adoptPendingPrompts(response.prompts);
20469
+ if (nativeRef) {
20470
+ await bridge.history.resolvePhysicalSessionId();
20471
+ }
20221
20472
  if (model2)
20222
20473
  await bridge.setModel(model2);
20223
20474
  if (input.kind !== "fork" && input.thinkingOptionId)
@@ -20269,6 +20520,7 @@ var HermesAdapter = class {
20269
20520
  #importReads = /* @__PURE__ */ new Map();
20270
20521
  #closed = false;
20271
20522
  #gatewayTransports = /* @__PURE__ */ new Set();
20523
+ #gatewayPool = new GatewayTransportPool(2);
20272
20524
  #gatewayProbes = /* @__PURE__ */ new Map();
20273
20525
  #openingNativeIds = /* @__PURE__ */ new Set();
20274
20526
  usageStatistics;
@@ -20384,9 +20636,10 @@ var HermesAdapter = class {
20384
20636
  return failure("sessionBusy", "This Hermes Session already has an owner", true);
20385
20637
  if (ref)
20386
20638
  this.#openingNativeIds.add(ref.nativeSessionId);
20387
- const transport = new HermesGatewayTransport(runtime, input.cwd, environment, this.#options.commandTimeoutMs);
20388
- this.#gatewayTransports.add(transport);
20639
+ let transport;
20389
20640
  try {
20641
+ transport = this.#gatewayPool.acquire(runtime, input.cwd, environment) ?? await startGatewayTransport(runtime, input.cwd, environment, this.#options.commandTimeoutMs);
20642
+ this.#gatewayTransports.add(transport);
20390
20643
  let inheritedMode;
20391
20644
  if (input.kind === "fork") {
20392
20645
  const source = [...this.#sessions].find((s) => s.initialState.nativeRef?.nativeSessionId === input.sourceRef.nativeSessionId);
@@ -20397,6 +20650,7 @@ var HermesAdapter = class {
20397
20650
  const session = await openGatewaySession(input, transport, (settled) => {
20398
20651
  this.#sessions.delete(settled);
20399
20652
  this.#gatewayTransports.delete(transport);
20653
+ this.#gatewayPool.release(transport);
20400
20654
  }, inheritedMode);
20401
20655
  if (this.#closed) {
20402
20656
  await session.close();
@@ -20405,8 +20659,10 @@ var HermesAdapter = class {
20405
20659
  this.#sessions.add(session);
20406
20660
  return { ok: true, value: session };
20407
20661
  } catch (error51) {
20408
- await transport.close().catch(() => void 0);
20409
- this.#gatewayTransports.delete(transport);
20662
+ if (transport) {
20663
+ await transport.close().catch(() => void 0);
20664
+ this.#gatewayTransports.delete(transport);
20665
+ }
20410
20666
  return failure(error51 instanceof HermesGatewayHistoryError ? error51.code : "nativeFailure", error51 instanceof Error ? error51.message : "Hermes gateway open failed");
20411
20667
  } finally {
20412
20668
  if (ref)
@@ -20430,6 +20686,7 @@ var HermesAdapter = class {
20430
20686
  await Promise.all(sessions.map((session) => session.close().catch(() => void 0)));
20431
20687
  await Promise.all([...this.#gatewayTransports].map((transport) => transport.close()));
20432
20688
  this.#gatewayTransports.clear();
20689
+ await this.#gatewayPool.close();
20433
20690
  }
20434
20691
  #effectiveEnvironment(environment) {
20435
20692
  return { ...this.#options.environment ?? process.env, ...environment ?? {} };