@codexhost/cli-darwin-x64 0.13.1 → 0.13.3

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.
@@ -4295,9 +4295,11 @@ async function runDelegationCli(input) {
4295
4295
  "INVALID_ARGUMENT",
4296
4296
  "--host conflicts with the Thread reference"
4297
4297
  );
4298
+ const caller = command === "read" ? callerThreadId(environment) : void 0;
4298
4299
  const body = {
4299
4300
  ...reference,
4300
4301
  ...hostId2 ? { hostId: hostId2 } : {},
4302
+ ...caller ? { callerThreadId: normalizeThreadId(caller) } : {},
4301
4303
  view,
4302
4304
  ...value(parsed2, "--cursor") ? { cursor: value(parsed2, "--cursor") } : {},
4303
4305
  ...value(parsed2, "--limit") ? { limit: positiveInteger(value(parsed2, "--limit"), "--limit", MAX_LIMIT) } : {},
@@ -4434,6 +4436,7 @@ import {
4434
4436
  realpath,
4435
4437
  stat,
4436
4438
  readdir as readdir2,
4439
+ unlink,
4437
4440
  writeFile as writeFile3
4438
4441
  } from "node:fs/promises";
4439
4442
  import { homedir } from "node:os";
@@ -19943,6 +19946,7 @@ config(en_default());
19943
19946
  var REMOTE_THREAD_REPLY_MAX_BYTES = 16 * 1024 * 1024;
19944
19947
  var REMOTE_THREAD_CONTROL_TIMEOUT_MS = 4e4;
19945
19948
  var DELEGATION_READ_METHOD = "codexhost/thread/delegation-read";
19949
+ var THREAD_REFERENCE_SOURCE_HOST_PARAM = "codexhostSourceHostId";
19946
19950
  var delegationReadParamsSchema = external_exports.strictObject({
19947
19951
  threadId: external_exports.string().min(1).max(1024),
19948
19952
  view: external_exports.enum(["result", "messages"]),
@@ -20023,6 +20027,15 @@ var harnessDisplaySetSchema = external_exports.strictObject({
20023
20027
  initializeOnly: external_exports.boolean().optional()
20024
20028
  });
20025
20029
 
20030
+ // packages/shared-contracts/dist/harness-presentation.js
20031
+ var localeSchema = external_exports.string().max(64).regex(/^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/u);
20032
+ var displayText = (maximum) => external_exports.string().min(1).max(maximum).refine((text) => text.trim().length > 0);
20033
+ var harnessLocalizationsSchema = external_exports.record(localeSchema, external_exports.object({
20034
+ label: displayText(256).optional(),
20035
+ description: displayText(1024).optional()
20036
+ }).strict()).refine((entries) => Object.keys(entries).length <= 16, "Too many locales");
20037
+ var harnessLocalizedTextSchema = external_exports.record(localeSchema, displayText(4096)).refine((entries) => typeof entries.en === "string", "English fallback is required").refine((entries) => Object.keys(entries).length <= 16, "Too many locales");
20038
+
20026
20039
  // packages/shared-contracts/dist/model-price-overrides.js
20027
20040
  var MODEL_PRICE_OVERRIDES_GET_METHOD = "codexhost/usage/model-prices/get";
20028
20041
  var MODEL_PRICE_OVERRIDES_SET_METHOD = "codexhost/usage/model-prices/set";
@@ -20372,8 +20385,24 @@ var threadUsageSnapshotSchema = external_exports.object({
20372
20385
  }
20373
20386
  });
20374
20387
  var usagePercentSchema = external_exports.number().finite().min(0).max(100);
20388
+ var accountCreditsPeriodSchema = external_exports.enum([
20389
+ "weekly",
20390
+ "monthly",
20391
+ "five_hour",
20392
+ "seven_day",
20393
+ "unknown"
20394
+ ]);
20395
+ var accountCreditsScopeSchema = external_exports.object({
20396
+ id: external_exports.string().min(1).max(256),
20397
+ label: external_exports.string().min(1).max(256),
20398
+ localizations: harnessLocalizationsSchema.optional()
20399
+ }).strict();
20375
20400
  var accountCreditsProductUsageSchema = external_exports.object({
20376
20401
  product: external_exports.string().min(1),
20402
+ /** Native period and scope, never inferred from the display name. */
20403
+ periodType: accountCreditsPeriodSchema.optional(),
20404
+ scope: accountCreditsScopeSchema.optional(),
20405
+ localizations: harnessLocalizationsSchema.optional(),
20377
20406
  used: external_exports.number().finite().nonnegative().optional(),
20378
20407
  // Zero is a real empty cap, not an absent field.
20379
20408
  limit: external_exports.number().finite().nonnegative().optional(),
@@ -20393,9 +20422,11 @@ var accountCreditsSnapshotSchema = external_exports.object({
20393
20422
  unit: external_exports.string().min(1).max(32).optional(),
20394
20423
  /** Native label when the primary limit is scoped to a model or product group. */
20395
20424
  label: external_exports.string().min(1).optional(),
20425
+ scope: accountCreditsScopeSchema.optional(),
20426
+ localizations: harnessLocalizationsSchema.optional(),
20396
20427
  usedPercent: usagePercentSchema,
20397
20428
  resetsAt: external_exports.string().min(1).optional(),
20398
- periodType: external_exports.enum(["weekly", "monthly", "five_hour", "seven_day", "unknown"]),
20429
+ periodType: accountCreditsPeriodSchema,
20399
20430
  productUsage: external_exports.array(accountCreditsProductUsageSchema).min(1).optional(),
20400
20431
  resetCredits: accountResetCreditsSchema.optional()
20401
20432
  }).strict();
@@ -20406,7 +20437,9 @@ var threadUsageInspectionParamsSchema = external_exports.object({
20406
20437
  var threadUsageInspectionSchema = external_exports.object({
20407
20438
  threadId: hostThreadIdSchema,
20408
20439
  usage: threadUsageSnapshotSchema.nullable(),
20409
- accountCredits: accountCreditsSnapshotSchema.optional()
20440
+ accountCredits: accountCreditsSnapshotSchema.optional(),
20441
+ /** Whether this source can provide cached account quota independently of usage. */
20442
+ accountCreditsSupported: external_exports.boolean().optional()
20410
20443
  }).strict();
20411
20444
 
20412
20445
  // packages/shared-contracts/dist/harness-accounts.js
@@ -20533,7 +20566,8 @@ var harnessPermissionModeSchema = external_exports.object({
20533
20566
  id: harnessPermissionModeIdSchema,
20534
20567
  label: nonBlankTextSchema.max(HARNESS_PERMISSION_MODE_LABEL_MAX_LENGTH),
20535
20568
  description: nonBlankTextSchema.max(HARNESS_PERMISSION_MODE_DESCRIPTION_MAX_LENGTH).optional(),
20536
- dangerous: external_exports.boolean().optional()
20569
+ dangerous: external_exports.boolean().optional(),
20570
+ localizations: harnessLocalizationsSchema.optional()
20537
20571
  }).strict();
20538
20572
  var harnessPermissionModeCatalogSchema = external_exports.object({
20539
20573
  modes: external_exports.array(harnessPermissionModeSchema).min(1).max(HARNESS_PERMISSION_MODE_CATALOG_MAX_LENGTH),
@@ -20957,6 +20991,8 @@ var harnessInstallationStateSchema = external_exports.object({
20957
20991
  // Adapter-owned diagnostic text; UI uses messageCode rather than displaying it verbatim.
20958
20992
  message: external_exports.string().max(2048).optional(),
20959
20993
  messageCode: external_exports.string().min(1).max(128).optional(),
20994
+ /** Safe plugin-owned explanation, distinct from raw diagnostic message. */
20995
+ notice: harnessLocalizedTextSchema.optional(),
20960
20996
  latestVersionKind: external_exports.enum(["unknown", "tracking-branch"]).optional()
20961
20997
  }).strict();
20962
20998
 
@@ -21087,6 +21123,7 @@ var harnessCommandDescriptorSchema = external_exports.object({
21087
21123
  invocation: commandInvocationSchema,
21088
21124
  label: commandLabelSchema,
21089
21125
  description: commandDescriptionSchema.optional(),
21126
+ localizations: harnessLocalizationsSchema.optional(),
21090
21127
  argumentMode: external_exports.enum(["none", "text"]),
21091
21128
  /**
21092
21129
  * Native distinction reported by the Harness. Omitted when the Harness does
@@ -21467,14 +21504,40 @@ var RuntimeMaintenance = class {
21467
21504
  options2.environment.CODEXHOST_DATA_DIR ?? path7.join(home, ".codexhost", "remote", "data"),
21468
21505
  "remote-update.json"
21469
21506
  );
21507
+ this.#cancelPath = path7.join(path7.dirname(this.#statusPath), "remote-update.cancel");
21470
21508
  }
21471
21509
  options;
21472
21510
  #running;
21473
21511
  #statusPath;
21512
+ #cancelPath;
21474
21513
  #update = { phase: "idle", targetVersion: null, error: null };
21475
21514
  #blocked = false;
21515
+ #watch = null;
21476
21516
  #starting = null;
21477
21517
  #startingVersion = null;
21518
+ get #startTimeoutMs() {
21519
+ return this.options.updaterStartTimeoutMs ?? UPDATER_START_TIMEOUT_MS;
21520
+ }
21521
+ #ensureWatching() {
21522
+ if (this.#watch) return;
21523
+ this.#watch = setInterval(() => {
21524
+ void this.status().catch(() => void 0).then(() => {
21525
+ if (!this.#blocked && this.#watch) {
21526
+ clearInterval(this.#watch);
21527
+ this.#watch = null;
21528
+ }
21529
+ });
21530
+ }, UPDATER_WATCH_INTERVAL_MS);
21531
+ this.#watch.unref();
21532
+ }
21533
+ async #recordStartFailure(version2, failure) {
21534
+ await writeFile3(this.#cancelPath, JSON.stringify({ targetVersion: version2 }), { mode: 384 });
21535
+ await writeFile3(
21536
+ this.#statusPath,
21537
+ JSON.stringify({ phase: "failed", targetVersion: version2, error: failure }),
21538
+ { mode: 384 }
21539
+ );
21540
+ }
21478
21541
  #sshPending = /* @__PURE__ */ new Set();
21479
21542
  async setupSsh(input) {
21480
21543
  if (this.options.remote) throw new Error("SSH installation must run from the local computer");
@@ -21535,7 +21598,7 @@ var RuntimeMaintenance = class {
21535
21598
  installation(this.options.runtimePath, this.options.environment),
21536
21599
  this.#resources()
21537
21600
  ]);
21538
- if (this.#update.phase === "idle" || this.#blocked) {
21601
+ if (this.#update.phase === "idle" || this.#blocked || this.#update.phase === "failed") {
21539
21602
  try {
21540
21603
  const value2 = JSON.parse(await readFile5(this.#statusPath, "utf8"));
21541
21604
  const parsed2 = runtimeStatusSchema.shape.update.parse(value2);
@@ -21551,15 +21614,22 @@ var RuntimeMaintenance = class {
21551
21614
  }
21552
21615
  } else {
21553
21616
  const written = (await stat(this.#statusPath)).mtimeMs;
21554
- alive = Date.now() - written < UPDATER_START_TIMEOUT_MS * 2;
21617
+ alive = Date.now() - written < this.#startTimeoutMs * 2;
21555
21618
  }
21556
21619
  if (!alive) {
21557
21620
  parsed2.phase = "failed";
21558
21621
  parsed2.error = "The previous remote update was interrupted; retry the update";
21559
21622
  }
21560
21623
  }
21561
- if (this.#update === observedUpdate) this.#update = parsed2;
21562
- if (["failed", "succeeded"].includes(this.#update.phase)) this.#blocked = false;
21624
+ if (this.#update === observedUpdate) {
21625
+ this.#update = parsed2;
21626
+ if (["installing", "restarting"].includes(this.#update.phase)) {
21627
+ this.#blocked = true;
21628
+ this.#ensureWatching();
21629
+ } else if (["failed", "succeeded"].includes(this.#update.phase)) {
21630
+ this.#blocked = false;
21631
+ }
21632
+ }
21563
21633
  } catch {
21564
21634
  }
21565
21635
  }
@@ -21631,6 +21701,7 @@ var RuntimeMaintenance = class {
21631
21701
  await chmod2(helper, 448);
21632
21702
  const request = path7.join(work, "request.json");
21633
21703
  this.#update = { phase: "installing", targetVersion: version2, error: null };
21704
+ await unlink(this.#cancelPath).catch(() => void 0);
21634
21705
  await writeFile3(this.#statusPath, JSON.stringify(this.#update), { mode: 384 });
21635
21706
  await writeFile3(
21636
21707
  request,
@@ -21649,35 +21720,38 @@ var RuntimeMaintenance = class {
21649
21720
  );
21650
21721
  const starter = spawn3(
21651
21722
  "/bin/sh",
21652
- ["-c", '"$0" remote --request "$1" </dev/null >/dev/null 2>&1 &', helper, request],
21653
- { detached: true, stdio: "ignore", env: this.options.environment }
21723
+ ["-c", '"$0" remote --request "$1" </dev/null >/dev/null 2>&1 & echo $!', helper, request],
21724
+ { detached: true, stdio: ["ignore", "pipe", "ignore"], env: this.options.environment }
21654
21725
  );
21726
+ let pidOutput = "";
21727
+ starter.stdout?.on("data", (chunk) => {
21728
+ pidOutput += typeof chunk === "string" ? chunk : chunk.toString("utf8");
21729
+ });
21655
21730
  await new Promise((resolve, reject) => {
21656
21731
  starter.once("error", reject);
21657
21732
  starter.once(
21658
- "exit",
21733
+ "close",
21659
21734
  (code) => code === 0 ? resolve() : reject(new Error("Remote updater could not be started"))
21660
21735
  );
21661
21736
  });
21662
- const deadline = Date.now() + UPDATER_START_TIMEOUT_MS;
21737
+ const helperPid = Number.parseInt(pidOutput.trim(), 10);
21738
+ if (!Number.isSafeInteger(helperPid) || helperPid <= 0) {
21739
+ throw new Error("Remote updater could not be started");
21740
+ }
21741
+ const deadline = Date.now() + this.#startTimeoutMs;
21663
21742
  while (!await this.#helperReported()) {
21664
21743
  if (Date.now() >= deadline) {
21744
+ try {
21745
+ process.kill(helperPid);
21746
+ } catch {
21747
+ }
21665
21748
  const failure = "Remote updater did not start";
21666
- await writeFile3(
21667
- this.#statusPath,
21668
- JSON.stringify({ phase: "failed", targetVersion: version2, error: failure }),
21669
- { mode: 384 }
21670
- );
21749
+ await this.#recordStartFailure(version2, failure);
21671
21750
  throw new Error(failure);
21672
21751
  }
21673
21752
  await new Promise((resolve) => setTimeout(resolve, 50));
21674
21753
  }
21675
- const watch = setInterval(() => {
21676
- void this.status().catch(() => void 0).then(() => {
21677
- if (!this.#blocked) clearInterval(watch);
21678
- });
21679
- }, UPDATER_WATCH_INTERVAL_MS);
21680
- watch.unref();
21754
+ this.#ensureWatching();
21681
21755
  }
21682
21756
  async #helperReported() {
21683
21757
  try {
@@ -21691,6 +21765,7 @@ var RuntimeMaintenance = class {
21691
21765
 
21692
21766
  // packages/host-runtime/src/run-host-runtime.ts
21693
21767
  import { randomBytes as randomBytes4 } from "node:crypto";
21768
+ import { statSync } from "node:fs";
21694
21769
  import path37 from "node:path";
21695
21770
  import { homedir as homedir4 } from "node:os";
21696
21771
  import { fileURLToPath as fileURLToPath2 } from "node:url";
@@ -24964,6 +25039,16 @@ function rewriteDelegationMentionInput(input, skill) {
24964
25039
  return rewritten;
24965
25040
  }
24966
25041
 
25042
+ // packages/harness-adapter/dist/account-credits.js
25043
+ function parseHarnessAccountCredits(value2) {
25044
+ if (!value2 || typeof value2 !== "object" || Array.isArray(value2))
25045
+ return null;
25046
+ const publicValue = { ...value2 };
25047
+ delete publicValue.fetchedAt;
25048
+ const parsed2 = accountCreditsSnapshotSchema.safeParse(publicValue);
25049
+ return parsed2.success ? parsed2.data : null;
25050
+ }
25051
+
24967
25052
  // packages/harness-adapter/dist/output-channel.js
24968
25053
  var HarnessOutputChannel = class {
24969
25054
  outputs;
@@ -27063,6 +27148,7 @@ function projectCodexRateLimitsToCredits(usage, resetCredits) {
27063
27148
  const resetsAtUnix = hasFiveHour ? usage.planFiveHourResetsAtUnix : usage.planSevenDayResetsAtUnix;
27064
27149
  const secondary = hasFiveHour && hasSevenDay && usage.planSevenDayUsedPercent !== void 0 ? {
27065
27150
  product: "7-day window",
27151
+ periodType: "seven_day",
27066
27152
  usagePercent: usage.planSevenDayUsedPercent,
27067
27153
  ...usage.planSevenDayResetsAtUnix !== void 0 ? { resetsAt: isoFromUnix(usage.planSevenDayResetsAtUnix) } : {}
27068
27154
  } : void 0;
@@ -31745,6 +31831,16 @@ var ExternalThreadIdleRelease = class {
31745
31831
  const state = this.#activity.get(thread);
31746
31832
  if (state) state.outputFailed = true;
31747
31833
  }
31834
+ /** After a successful Session revival, allow idle release again. */
31835
+ clearOutputFailed(thread) {
31836
+ const state = this.#activity.get(thread);
31837
+ if (state) {
31838
+ state.outputFailed = false;
31839
+ state.lastActivity = Date.now();
31840
+ } else {
31841
+ this.touch(thread);
31842
+ }
31843
+ }
31748
31844
  failure(thread) {
31749
31845
  return this.#activity.get(thread)?.closeFailed ? CLOSE_FAILURE : void 0;
31750
31846
  }
@@ -31893,6 +31989,80 @@ var ExternalThreadIdleRelease = class {
31893
31989
  }
31894
31990
  };
31895
31991
 
31992
+ // packages/host-runtime/src/external-thread-lifecycle.ts
31993
+ var RELEASE_TIMEOUT_MS = 6e4;
31994
+ var ExternalSessionReleaseError = class extends Error {
31995
+ constructor(cause) {
31996
+ super(
31997
+ "External Session shutdown could not be confirmed. Retry to confirm release; recovery is blocked until the old Session closes and its output drains. If the Harness cannot retry shutdown, stop residual processes and restart Codex Desktop.",
31998
+ { cause }
31999
+ );
32000
+ this.name = "ExternalSessionReleaseError";
32001
+ }
32002
+ };
32003
+ var ExternalThreadLifecycle = class {
32004
+ #pending = /* @__PURE__ */ new Map();
32005
+ #closing = /* @__PURE__ */ new WeakMap();
32006
+ #releasing = /* @__PURE__ */ new Map();
32007
+ busy(id2) {
32008
+ return this.#pending.has(id2) || this.#releasing.has(id2);
32009
+ }
32010
+ pending() {
32011
+ return [...this.#pending];
32012
+ }
32013
+ run(id2, operation) {
32014
+ const previous = this.#pending.get(id2) ?? Promise.resolve();
32015
+ const task = previous.then(async () => {
32016
+ const releasing = this.#releasing.get(id2);
32017
+ if (releasing) await this.release(id2, releasing.session, releasing.output);
32018
+ return operation();
32019
+ });
32020
+ const settled = task.then(
32021
+ () => void 0,
32022
+ () => void 0
32023
+ );
32024
+ this.#pending.set(id2, settled);
32025
+ void settled.then(() => {
32026
+ if (this.#pending.get(id2) === settled) this.#pending.delete(id2);
32027
+ });
32028
+ return task;
32029
+ }
32030
+ /** Call inside the Thread's lifecycle operation. A timeout never cancels the owned close. */
32031
+ async release(id2, session, output) {
32032
+ this.#releasing.set(id2, { session, output });
32033
+ let closing = this.#closing.get(session);
32034
+ if (!closing) {
32035
+ closing = Promise.resolve().then(() => session.close());
32036
+ this.#closing.set(session, closing);
32037
+ const attempt = closing;
32038
+ void closing.catch(() => {
32039
+ if (this.#closing.get(session) === attempt) this.#closing.delete(session);
32040
+ });
32041
+ }
32042
+ let timeout;
32043
+ try {
32044
+ await Promise.race([
32045
+ closing.then(() => output),
32046
+ new Promise((_, reject) => {
32047
+ timeout = setTimeout(
32048
+ () => reject(new Error("Session shutdown timed out")),
32049
+ RELEASE_TIMEOUT_MS
32050
+ );
32051
+ timeout.unref();
32052
+ })
32053
+ ]);
32054
+ this.#releasing.delete(id2);
32055
+ } catch (error51) {
32056
+ throw new ExternalSessionReleaseError(error51);
32057
+ } finally {
32058
+ if (timeout) clearTimeout(timeout);
32059
+ }
32060
+ }
32061
+ async drain() {
32062
+ while (this.#pending.size) await Promise.all(this.#pending.values());
32063
+ }
32064
+ };
32065
+
31896
32066
  // packages/host-runtime/src/usage-metering.ts
31897
32067
  var TOKENS_PER_PRICE_UNIT = 1e6;
31898
32068
  var UsageMeter = class {
@@ -32175,6 +32345,11 @@ var ExternalThreadOpenError = class extends Error {
32175
32345
  function errorMessage3(error51) {
32176
32346
  return error51 instanceof Error ? error51.message : String(error51);
32177
32347
  }
32348
+ function recoveryError(error51) {
32349
+ if (error51 instanceof ExternalThreadOpenError) return error51.rpcError;
32350
+ if (error51 instanceof ExternalSessionReleaseError) return { code: -32075, message: error51.message };
32351
+ return { code: -32076, message: "External Thread recovery failed; retry the request" };
32352
+ }
32178
32353
  var ExternalThreadRuntime = class {
32179
32354
  idleRelease;
32180
32355
  #adapters;
@@ -32182,7 +32357,8 @@ var ExternalThreadRuntime = class {
32182
32357
  #diagnose;
32183
32358
  #environment;
32184
32359
  #repository;
32185
- #restores = /* @__PURE__ */ new Map();
32360
+ #lifecycle = new ExternalThreadLifecycle();
32361
+ #hasRunningDescendants;
32186
32362
  #subagentRunning;
32187
32363
  #threads = /* @__PURE__ */ new Map();
32188
32364
  constructor(input) {
@@ -32192,12 +32368,13 @@ var ExternalThreadRuntime = class {
32192
32368
  this.#consumeOutputs = input.consumeOutputs;
32193
32369
  this.#diagnose = input.diagnose;
32194
32370
  this.#subagentRunning = input.subagentRunning ?? (() => false);
32371
+ this.#hasRunningDescendants = input.hasRunningDescendants ?? (() => false);
32195
32372
  this.idleRelease = new ExternalThreadIdleRelease({
32196
32373
  threads: () => this.values(),
32197
32374
  get: (id2) => this.get(id2),
32198
32375
  remove: (id2) => this.remove(id2),
32199
32376
  queue: input.idleRelease?.queue ?? new DesktopRequestQueue(),
32200
- canRelease: input.idleRelease?.canRelease ?? (() => false),
32377
+ canRelease: (thread) => !thread.sessionUnavailable && !this.#lifecycle.busy(thread.id) && (input.idleRelease?.canRelease(thread) ?? false),
32201
32378
  ...input.idleRelease?.onClosed ? { onClosed: input.idleRelease.onClosed } : {},
32202
32379
  diagnose: input.diagnose
32203
32380
  });
@@ -32213,7 +32390,45 @@ var ExternalThreadRuntime = class {
32213
32390
  }
32214
32391
  clear() {
32215
32392
  this.#threads.clear();
32216
- this.#restores.clear();
32393
+ }
32394
+ drain() {
32395
+ return this.#lifecycle.drain();
32396
+ }
32397
+ #withLifecycle(threadId2, operation) {
32398
+ return this.#lifecycle.run(threadId2, async () => {
32399
+ await this.idleRelease.waitForClose(threadId2);
32400
+ return operation();
32401
+ });
32402
+ }
32403
+ markSessionUnavailable(thread) {
32404
+ thread.sessionUnavailable = true;
32405
+ }
32406
+ #idleForRestore(thread) {
32407
+ if (thread.running || thread.activeTurnId !== null) return false;
32408
+ if (thread.session.hasBackgroundWork?.()) return false;
32409
+ if (this.#hasRunningDescendants(thread.id)) return false;
32410
+ return true;
32411
+ }
32412
+ async #dropIdleSession(thread) {
32413
+ if (this.#threads.get(thread.id) !== thread) return;
32414
+ thread.sessionUnavailable = true;
32415
+ await this.#lifecycle.release(thread.id, thread.session, thread.outputTask);
32416
+ if (this.#threads.get(thread.id) !== thread) return;
32417
+ await this.#retireSubagents(thread);
32418
+ if (this.#threads.get(thread.id) === thread) this.#threads.delete(thread.id);
32419
+ }
32420
+ /** Called without awaiting from the output consumer: release itself drains that consumer. */
32421
+ async discardUnavailableIdle(thread) {
32422
+ const session = thread.session;
32423
+ try {
32424
+ await this.#withLifecycle(thread.id, async () => {
32425
+ if (this.#threads.get(thread.id) !== thread || thread.session !== session || !thread.sessionUnavailable || !this.#idleForRestore(thread))
32426
+ return;
32427
+ await this.#dropIdleSession(thread);
32428
+ });
32429
+ } catch (error51) {
32430
+ this.#diagnose(error51);
32431
+ }
32217
32432
  }
32218
32433
  register(input) {
32219
32434
  const harnessId = input.record.harnessId;
@@ -32258,7 +32473,8 @@ var ExternalThreadRuntime = class {
32258
32473
  ephemeralTurnIds: /* @__PURE__ */ new Set(),
32259
32474
  persistenceError: null,
32260
32475
  projectedTerminalTurnId: null,
32261
- ignoredInteractionIds: /* @__PURE__ */ new Set()
32476
+ ignoredInteractionIds: /* @__PURE__ */ new Set(),
32477
+ sessionUnavailable: false
32262
32478
  };
32263
32479
  this.idleRelease.touch(externalThread);
32264
32480
  externalThread.outputTask = this.#consumeOutputs(externalThread);
@@ -32285,11 +32501,12 @@ var ExternalThreadRuntime = class {
32285
32501
  } catch (error51) {
32286
32502
  this.#diagnose(error51);
32287
32503
  }
32288
- await this.#retireSubagents(current.id);
32504
+ await this.#retireSubagents(current, true);
32289
32505
  this.#threads.delete(current.id);
32290
32506
  return this.register(input);
32291
32507
  }
32292
- async #retireSubagents(parentId) {
32508
+ async #retireSubagents(parent, bestEffort = false) {
32509
+ const parentId = parent.id;
32293
32510
  const ancestors = /* @__PURE__ */ new Map();
32294
32511
  const descendant = async (record4) => {
32295
32512
  const visited = /* @__PURE__ */ new Set();
@@ -32306,24 +32523,35 @@ var ExternalThreadRuntime = class {
32306
32523
  }
32307
32524
  return false;
32308
32525
  };
32309
- for (const [id2, restoring] of [...this.#restores]) {
32310
- if (await descendant(await this.#repository.find(id2))) {
32311
- await restoring.catch(this.#diagnose);
32312
- }
32526
+ for (const [id2, pending] of this.#lifecycle.pending()) {
32527
+ if (id2 !== parentId && await descendant(await this.#repository.find(id2))) await pending;
32313
32528
  }
32314
- for (const child of this.#threads.values()) {
32529
+ for (const child of [...this.#threads.values()]) {
32315
32530
  if (!await descendant(child.record)) continue;
32316
- this.#threads.delete(child.id);
32317
- try {
32318
- await child.session.close();
32319
- await child.outputTask;
32320
- } catch (error51) {
32321
- this.#diagnose(error51);
32322
- }
32531
+ const retire = async () => {
32532
+ if (this.#threads.get(parentId) !== parent || this.#threads.get(child.id) !== child) return;
32533
+ if (bestEffort) {
32534
+ this.#threads.delete(child.id);
32535
+ await child.session.close();
32536
+ await child.outputTask;
32537
+ } else {
32538
+ child.sessionUnavailable = true;
32539
+ await this.#lifecycle.release(child.id, child.session, child.outputTask);
32540
+ if (this.#threads.get(child.id) === child) this.#threads.delete(child.id);
32541
+ }
32542
+ };
32543
+ if (bestEffort) await retire().catch(this.#diagnose);
32544
+ else await this.#withLifecycle(child.id, retire);
32323
32545
  }
32324
32546
  }
32325
32547
  async locate(threadId2) {
32326
- await this.idleRelease.waitForClose(threadId2);
32548
+ try {
32549
+ return await this.#withLifecycle(threadId2, () => this.#locate(threadId2));
32550
+ } catch (error51) {
32551
+ return { kind: "error", error: recoveryError(error51) };
32552
+ }
32553
+ }
32554
+ async #locate(threadId2) {
32327
32555
  const loaded = this.#threads.get(threadId2);
32328
32556
  if (loaded) {
32329
32557
  const failure = this.idleRelease.failure(loaded);
@@ -32349,37 +32577,61 @@ var ExternalThreadRuntime = class {
32349
32577
  return { kind: "external", record: record4, thread: null };
32350
32578
  }
32351
32579
  async resolve(threadId2) {
32352
- const location = await this.locate(threadId2);
32353
- if (location.kind !== "external") return location;
32354
- if (location.thread) {
32355
- this.idleRelease.touch(location.thread);
32356
- return { kind: "external", thread: location.thread, historyFresh: false };
32357
- }
32358
- const { record: record4 } = location;
32359
- let restoring = this.#restores.get(threadId2);
32360
- if (!restoring) {
32361
- const restored = this.#threads.get(threadId2);
32362
- if (restored) {
32363
- this.idleRelease.touch(restored);
32364
- return { kind: "external", thread: restored, historyFresh: false };
32365
- }
32366
- restoring = this.#restore(record4).finally(() => {
32367
- this.#restores.delete(threadId2);
32580
+ try {
32581
+ return await this.#withLifecycle(threadId2, async () => {
32582
+ const location = await this.#locate(threadId2);
32583
+ if (location.kind !== "external") return location;
32584
+ if (location.thread) {
32585
+ if (location.thread.sessionUnavailable && this.#idleForRestore(location.thread)) {
32586
+ await this.#dropIdleSession(location.thread);
32587
+ const current = this.#threads.get(threadId2);
32588
+ if (current) return { kind: "external", thread: current, historyFresh: false };
32589
+ } else {
32590
+ this.idleRelease.touch(location.thread);
32591
+ return { kind: "external", thread: location.thread, historyFresh: false };
32592
+ }
32593
+ }
32594
+ return {
32595
+ kind: "external",
32596
+ thread: await this.#restore(location.record),
32597
+ historyFresh: true
32598
+ };
32368
32599
  });
32369
- this.#restores.set(threadId2, restoring);
32600
+ } catch (error51) {
32601
+ return { kind: "error", error: recoveryError(error51) };
32370
32602
  }
32603
+ }
32604
+ async refresh(thread) {
32371
32605
  try {
32372
- return { kind: "external", thread: await restoring, historyFresh: true };
32606
+ return await this.#withLifecycle(thread.id, () => this.#refresh(thread));
32373
32607
  } catch (error51) {
32608
+ return recoveryError(error51);
32609
+ }
32610
+ }
32611
+ async #refresh(thread) {
32612
+ if (this.#threads.get(thread.id) !== thread) {
32374
32613
  return {
32375
- kind: "error",
32376
- error: error51 instanceof ExternalThreadOpenError ? error51.rpcError : { code: -32076, message: "External Thread recovery failed" }
32614
+ code: -32076,
32615
+ message: "External Session changed during recovery; retry the request"
32377
32616
  };
32378
32617
  }
32379
- }
32380
- async refresh(thread) {
32381
- const snapshot2 = await thread.session.readSnapshot();
32382
- if (!snapshot2.ok) return mapExternalThreadHarnessError(snapshot2.error, "read");
32618
+ const failure = this.idleRelease.failure(thread);
32619
+ if (failure) return { code: -32075, message: failure };
32620
+ if (thread.sessionUnavailable && this.#idleForRestore(thread)) {
32621
+ const revived = await this.#reviveIdleSession(thread);
32622
+ if (revived) return revived;
32623
+ }
32624
+ let snapshot2 = await thread.session.readSnapshot();
32625
+ if (!snapshot2.ok) {
32626
+ if (snapshot2.error.code === "invalidState" && this.#idleForRestore(thread) && this.#threads.get(thread.id) === thread) {
32627
+ const revived = await this.#reviveIdleSession(thread);
32628
+ if (revived) return revived;
32629
+ snapshot2 = await thread.session.readSnapshot();
32630
+ if (!snapshot2.ok) return mapExternalThreadHarnessError(snapshot2.error, "read");
32631
+ } else {
32632
+ return mapExternalThreadHarnessError(snapshot2.error, "read");
32633
+ }
32634
+ }
32383
32635
  try {
32384
32636
  const aligned = await this.#repository.alignSnapshot(thread.record, snapshot2.value);
32385
32637
  thread.record = aligned.record;
@@ -32396,6 +32648,122 @@ var ExternalThreadRuntime = class {
32396
32648
  return { code: -32081, message: "External Thread history could not be persisted" };
32397
32649
  }
32398
32650
  }
32651
+ async #reviveIdleSession(thread) {
32652
+ thread.sessionUnavailable = true;
32653
+ const harnessId = thread.record.harnessId;
32654
+ const adapter = this.#adapters.get(harnessId);
32655
+ if (!adapter || !thread.record.nativeSessionRef) {
32656
+ await this.#dropIdleSession(thread);
32657
+ return { code: -32077, message: "External Harness is unavailable" };
32658
+ }
32659
+ await this.#lifecycle.release(thread.id, thread.session, thread.outputTask);
32660
+ await this.#retireSubagents(thread);
32661
+ if (this.#threads.get(thread.id) !== thread) {
32662
+ return { code: -32076, message: "External Thread recovery failed" };
32663
+ }
32664
+ const restoredSelection = decodeExternalTransportSelection(
32665
+ harnessId,
32666
+ thread.record.transportModelId
32667
+ );
32668
+ const opened = await adapter.open({
32669
+ kind: "resume",
32670
+ cwd: thread.record.cwd,
32671
+ environment: { ...this.#environment, [DELEGATION_THREAD_ID_ENV]: thread.record.hostThreadId },
32672
+ nativeRef: thread.record.nativeSessionRef,
32673
+ knownTurnRefs: thread.record.turnMappings.map(({ nativeTurnRef }) => nativeTurnRef),
32674
+ ...restoredSelection?.model ? { model: restoredSelection.model } : {},
32675
+ ...restoredSelection?.thinkingOptionId ? { thinkingOptionId: restoredSelection.thinkingOptionId } : {},
32676
+ ...restoredSelection?.permissionModeId ? { permissionModeId: restoredSelection.permissionModeId } : {}
32677
+ });
32678
+ if (!opened.ok) {
32679
+ await this.#dropIdleSession(thread);
32680
+ return mapExternalThreadHarnessError(opened.error, "resume");
32681
+ }
32682
+ const session = opened.value;
32683
+ if (this.#threads.get(thread.id) !== thread) {
32684
+ await this.#lifecycle.release(thread.id, session, Promise.resolve());
32685
+ return { code: -32076, message: "External Thread recovery failed" };
32686
+ }
32687
+ try {
32688
+ const snapshot2 = await session.readSnapshot();
32689
+ if (!snapshot2.ok) {
32690
+ await this.#lifecycle.release(thread.id, session, Promise.resolve());
32691
+ await this.#dropIdleSession(thread);
32692
+ return mapExternalThreadHarnessError(snapshot2.error, "read");
32693
+ }
32694
+ let aligned = await this.#repository.alignSnapshot(thread.record, snapshot2.value);
32695
+ if (this.#threads.get(thread.id) !== thread) {
32696
+ await this.#lifecycle.release(thread.id, session, Promise.resolve());
32697
+ return { code: -32076, message: "External Thread recovery failed" };
32698
+ }
32699
+ const restoredState = snapshot2.value.state;
32700
+ const effectiveModel = restoredState ? restoredState.effectiveModel : restoredSelection?.model;
32701
+ const effectiveThinkingOptionId = restoredState ? restoredState.effectiveThinkingOptionId : restoredSelection?.thinkingOptionId;
32702
+ const effectivePermissionModeId = restoredState ? restoredState.effectivePermissionModeId : restoredSelection?.permissionModeId;
32703
+ let transportModelId = aligned.record.transportModelId;
32704
+ if (restoredState && effectiveModel) {
32705
+ const liveSelection = {
32706
+ model: effectiveModel,
32707
+ ...effectiveThinkingOptionId ? { thinkingOptionId: effectiveThinkingOptionId } : {},
32708
+ ...effectivePermissionModeId ? { permissionModeId: effectivePermissionModeId } : {}
32709
+ };
32710
+ transportModelId = encodeExternalTransportSelection(harnessId, liveSelection);
32711
+ if (transportModelId !== aligned.record.transportModelId) {
32712
+ try {
32713
+ aligned = {
32714
+ ...aligned,
32715
+ record: await this.#repository.setTransportModelId(
32716
+ aligned.record.hostThreadId,
32717
+ transportModelId
32718
+ )
32719
+ };
32720
+ } catch (error51) {
32721
+ this.#diagnose(error51);
32722
+ }
32723
+ }
32724
+ }
32725
+ if (this.#threads.get(thread.id) !== thread) {
32726
+ await this.#lifecycle.release(thread.id, session, Promise.resolve());
32727
+ return { code: -32076, message: "External Thread recovery failed" };
32728
+ }
32729
+ const initialState = restoredState ?? session.initialState;
32730
+ const observerState = {
32731
+ ...initialState,
32732
+ ...effectiveModel ? { effectiveModel } : {},
32733
+ ...effectiveThinkingOptionId ? { effectiveThinkingOptionId } : {},
32734
+ ...effectivePermissionModeId ? { effectivePermissionModeId } : {}
32735
+ };
32736
+ thread.session = session;
32737
+ thread.record = aligned.record;
32738
+ thread.turns = aligned.turns;
32739
+ thread.historyHydrated = true;
32740
+ thread.sessionUnavailable = false;
32741
+ thread.persistenceError = null;
32742
+ thread.transportModelId = transportModelId;
32743
+ if (effectiveModel) thread.requestedModel = effectiveModel;
32744
+ if (effectiveThinkingOptionId) thread.requestedThinkingOptionId = effectiveThinkingOptionId;
32745
+ if (effectivePermissionModeId) thread.requestedPermissionModeId = effectivePermissionModeId;
32746
+ thread.stateObserver = new SessionStateObserver(observerState);
32747
+ thread.latestUsage = session.initialUsage;
32748
+ thread.usageMeter = new UsageMeter();
32749
+ thread.usageTurnId = null;
32750
+ thread.thread = externalThreadValue({
32751
+ record: aligned.record,
32752
+ turns: aligned.turns,
32753
+ sessionId: thread.sessionId,
32754
+ running: thread.running
32755
+ });
32756
+ thread.outputTask = this.#consumeOutputs(thread);
32757
+ this.idleRelease.clearOutputFailed(thread);
32758
+ return null;
32759
+ } catch (error51) {
32760
+ if (error51 instanceof ExternalSessionReleaseError) throw error51;
32761
+ await this.#lifecycle.release(thread.id, session, Promise.resolve());
32762
+ this.#diagnose(error51);
32763
+ await this.#dropIdleSession(thread);
32764
+ return { code: -32081, message: "External Thread history could not be persisted" };
32765
+ }
32766
+ }
32399
32767
  async persistTerminalIdentity(thread, event) {
32400
32768
  if (thread.persistenceError) return thread.persistenceError;
32401
32769
  if (!event.nativeTurnRef) {
@@ -32534,7 +32902,8 @@ var ExternalThreadRuntime = class {
32534
32902
  transportModelId
32535
32903
  });
32536
32904
  } catch (error51) {
32537
- await session.close().catch(() => void 0);
32905
+ if (error51 instanceof ExternalSessionReleaseError) throw error51;
32906
+ await this.#lifecycle.release(record4.hostThreadId, session, Promise.resolve());
32538
32907
  throw error51;
32539
32908
  }
32540
32909
  }
@@ -35467,7 +35836,9 @@ function hasValidSessionUsage(value2) {
35467
35836
  }
35468
35837
  function isAdapter(value2) {
35469
35838
  if (!value2 || typeof value2 !== "object") return false;
35470
- return hasValidSessionUsage(value2) && ["inspect", "open", "close"].every((key) => typeof Reflect.get(value2, key) === "function");
35839
+ return hasValidSessionUsage(value2) && ["inspect", "open", "close"].every((key) => typeof Reflect.get(value2, key) === "function") && ["credits", "refreshCredits"].every(
35840
+ (key) => Reflect.get(value2, key) === void 0 || typeof Reflect.get(value2, key) === "function"
35841
+ );
35471
35842
  }
35472
35843
  function isUsageAdapter(value2) {
35473
35844
  if (!value2 || typeof value2 !== "object" || typeof Reflect.get(value2, "close") !== "function")
@@ -37335,6 +37706,16 @@ async function moveThreadSection(input) {
37335
37706
  }
37336
37707
  }
37337
37708
 
37709
+ // packages/host-runtime/src/external-thread-official-defaults.ts
37710
+ var EMPTY_RESULTS = /* @__PURE__ */ new Map([
37711
+ ["thread/attachment/list", () => ({ data: [], nextCursor: null })],
37712
+ ["thread/queue/list", () => ({ data: [], nextCursor: null })],
37713
+ ["thread/settings/update", () => ({})]
37714
+ ]);
37715
+ function externalThreadEmptyOfficialResult(method) {
37716
+ return EMPTY_RESULTS.get(method)?.();
37717
+ }
37718
+
37338
37719
  // packages/host-runtime/src/observed-session-usage.ts
37339
37720
  var ObservedSessionUsage = class {
37340
37721
  constructor(providers, prices, diagnose) {
@@ -37503,16 +37884,6 @@ function officialThreadBusy(thread) {
37503
37884
  const latestTurn = turns.at(-1);
37504
37885
  return isRecord18(latestTurn) && (latestTurn.status === "inProgress" || latestTurn.status === "running");
37505
37886
  }
37506
- function isCreditsAdapter(adapter) {
37507
- return typeof adapter.credits === "function";
37508
- }
37509
- function projectAccountCredits(value2) {
37510
- if (!isRecord18(value2)) return null;
37511
- const rest = { ...value2 };
37512
- delete rest.fetchedAt;
37513
- const parsed2 = accountCreditsSnapshotSchema.safeParse(rest);
37514
- return parsed2.success ? parsed2.data : null;
37515
- }
37516
37887
  function errorMessage4(error51) {
37517
37888
  return error51 instanceof Error ? error51.message : String(error51);
37518
37889
  }
@@ -37684,6 +38055,7 @@ var AppServerHost = class {
37684
38055
  #launchSettings;
37685
38056
  #accountInspections = new HarnessAccountInspectionCache();
37686
38057
  #externalRuntime;
38058
+ #executionHosts = /* @__PURE__ */ new WeakMap();
37687
38059
  #externalPrewarms = new ExternalThreadPrewarms();
37688
38060
  #externalSteering = new ExternalTurnSteering();
37689
38061
  #liveCommandCache = new LiveCommandCatalogCache();
@@ -37818,6 +38190,7 @@ var AppServerHost = class {
37818
38190
  consumeOutputs: (thread) => this.#consumeHarnessOutputs(thread),
37819
38191
  diagnose: (error51) => this.#diagnose(error51),
37820
38192
  subagentRunning: (threadId2) => this.#subagentThreadStatuses.get(threadId2) === "active",
38193
+ hasRunningDescendants: (threadId2) => this.#hasRunningSubagents(threadId2),
37821
38194
  idleRelease: {
37822
38195
  queue: this.#desktopRequests,
37823
38196
  onClosed: async (thread) => {
@@ -37876,7 +38249,11 @@ var AppServerHost = class {
37876
38249
  wait: (input) => this.#waitForPlugins().then(() => this.#delegationCoordinator.wait(input)),
37877
38250
  list: (input) => this.#waitForPlugins().then(() => this.#delegationCoordinator.list(input)),
37878
38251
  canHandleStart: (input) => this.#canHandleDelegationStart(input),
37879
- ownsThread: (threadId2) => this.#ownsDelegationThread(threadId2)
38252
+ ownsThread: (threadId2) => this.#ownsDelegationThread(threadId2),
38253
+ executionHostId: (threadId2) => {
38254
+ const thread = this.#externalRuntime.get(threadId2);
38255
+ return thread ? this.#executionHosts.get(thread) : void 0;
38256
+ }
37880
38257
  });
37881
38258
  this.#unregisterDelegationApi = typeof unregisterDelegationApi === "function" ? unregisterDelegationApi : void 0;
37882
38259
  }
@@ -38013,6 +38390,7 @@ var AppServerHost = class {
38013
38390
  await Promise.allSettled(threads.map(({ session }) => session.close()));
38014
38391
  await Promise.allSettled(threads.map(({ outputTask }) => outputTask));
38015
38392
  await this.#closeAdapters();
38393
+ await this.#externalRuntime.drain();
38016
38394
  for (const pending of [...this.#pendingDesktopApprovals.values()]) {
38017
38395
  await this.#resolveDesktopApproval(pending.interaction.interactionId).catch(
38018
38396
  () => void 0
@@ -38942,8 +39320,9 @@ var AppServerHost = class {
38942
39320
  const location = await this.#locateExternalThread(request.params.threadId);
38943
39321
  if (await this.#writeResolutionError(request, location)) return;
38944
39322
  if (location.kind === "external") {
39323
+ const emptyResult = externalThreadEmptyOfficialResult(request.method);
38945
39324
  await this.#writer.json(
38946
- rpcError(request, -32076, `External Thread does not support ${request.method}`)
39325
+ emptyResult ? rpcEnvelope(request, { result: emptyResult }) : rpcError(request, -32076, `External Thread does not support ${request.method}`)
38947
39326
  );
38948
39327
  return;
38949
39328
  }
@@ -39999,6 +40378,7 @@ var AppServerHost = class {
39999
40378
  const result2 = threadUsageInspectionSchema.parse({
40000
40379
  threadId: params.data.threadId,
40001
40380
  usage: await this.#observedSessionUsage.read(params.data.threadId),
40381
+ accountCreditsSupported: true,
40002
40382
  ...accountCredits ? { accountCredits } : {}
40003
40383
  });
40004
40384
  await this.#writer.json(rpcEnvelope(request, { result: jsonValueSchema.parse(result2) }));
@@ -40006,11 +40386,17 @@ var AppServerHost = class {
40006
40386
  }
40007
40387
  if (params.data.refresh === "exact") void resolution.thread.session.refreshUsage?.();
40008
40388
  const adapter = this.#externalAdapters.get(resolution.thread.harnessId);
40009
- if (adapter && isCreditsAdapter(adapter)) void adapter.refreshCredits?.();
40010
- const credits = adapter && isCreditsAdapter(adapter) ? projectAccountCredits(adapter.credits()) : null;
40389
+ if (adapter?.credits && adapter.refreshCredits)
40390
+ void Promise.resolve().then(() => adapter.refreshCredits?.()).catch(() => void 0);
40391
+ let credits = null;
40392
+ try {
40393
+ credits = parseHarnessAccountCredits(adapter?.credits?.());
40394
+ } catch {
40395
+ }
40011
40396
  const result = threadUsageInspectionSchema.parse({
40012
40397
  threadId: params.data.threadId,
40013
40398
  usage: await this.#threadUsage(resolution.thread),
40399
+ accountCreditsSupported: typeof adapter?.credits === "function",
40014
40400
  ...credits ? { accountCredits: credits } : {}
40015
40401
  });
40016
40402
  await this.#writer.json(rpcEnvelope(request, { result: jsonValueSchema.parse(result) }));
@@ -40206,7 +40592,7 @@ var AppServerHost = class {
40206
40592
  this.#pendingExternalCommandRequests.delete(thread.id);
40207
40593
  }
40208
40594
  }
40209
- async #beginExternalCommand(thread, descriptor, arguments_2, requestedTurnId) {
40595
+ async #beginExternalCommand(thread, descriptor, arguments_2, requestedTurnId, sourceHostId) {
40210
40596
  const commands = thread.session.commands;
40211
40597
  if (!commands) {
40212
40598
  throw new ExternalCommandError(-32078, "External Harness does not expose commands");
@@ -40235,6 +40621,7 @@ var AppServerHost = class {
40235
40621
  thread.ephemeralTurnIds.add(turnId);
40236
40622
  if (descriptor.invocation === "/compact") this.#manualCompactionTurns.set(thread, turnId);
40237
40623
  else this.#manualCompactionTurns.delete(thread);
40624
+ const restoreExecutionHost = this.#setExecutionHost(thread, sourceHostId);
40238
40625
  try {
40239
40626
  if (thread.unsubmittedPrewarm && await this.#externalRuntime.submitPrewarm(thread)) {
40240
40627
  await this.#notifyExternalThreadStarted(thread.thread);
@@ -40247,6 +40634,7 @@ var AppServerHost = class {
40247
40634
  if (!result.ok) throw new ExternalCommandError(-32073, result.error.message);
40248
40635
  return { turnId: result.value.turnId, turn: projection.projector.pendingTurn(), gate };
40249
40636
  } catch (error51) {
40637
+ restoreExecutionHost();
40250
40638
  thread.running = false;
40251
40639
  thread.activeTurnId = null;
40252
40640
  thread.projectedTurns.delete(turnId);
@@ -41026,7 +41414,8 @@ var AppServerHost = class {
41026
41414
  thread,
41027
41415
  text,
41028
41416
  void 0,
41029
- typeof params.clientUserMessageId === "string" ? params.clientUserMessageId : void 0
41417
+ typeof params.clientUserMessageId === "string" ? params.clientUserMessageId : void 0,
41418
+ params[THREAD_REFERENCE_SOURCE_HOST_PARAM]
41030
41419
  );
41031
41420
  try {
41032
41421
  await this.#writer.json(rpcEnvelope(request, { result: { turn: started.turn } }));
@@ -41052,7 +41441,8 @@ var AppServerHost = class {
41052
41441
  thread,
41053
41442
  text,
41054
41443
  assertActive,
41055
- typeof requestObject(request).clientUserMessageId === "string" ? requestObject(request).clientUserMessageId : void 0
41444
+ typeof requestObject(request).clientUserMessageId === "string" ? requestObject(request).clientUserMessageId : void 0,
41445
+ requestObject(request)[THREAD_REFERENCE_SOURCE_HOST_PARAM]
41056
41446
  )
41057
41447
  );
41058
41448
  try {
@@ -41073,12 +41463,12 @@ var AppServerHost = class {
41073
41463
  }
41074
41464
  }
41075
41465
  /** Keep command restoration and dispatch identical for start and steer submissions. */
41076
- async #beginExternalInputTurn(thread, inputText, assertActive, clientUserMessageId) {
41466
+ async #beginExternalInputTurn(thread, inputText, assertActive, clientUserMessageId, sourceHostId) {
41077
41467
  const text = restoreHarnessCommandMentions(inputText);
41078
41468
  const commands = thread.session.commands;
41079
41469
  if (!commands || !isExternalCommandCandidate(text)) {
41080
41470
  assertActive?.();
41081
- return this.#beginExternalTurn(thread, text, clientUserMessageId);
41471
+ return this.#beginExternalTurn(thread, text, clientUserMessageId, sourceHostId);
41082
41472
  }
41083
41473
  this.#pendingExternalCommandRequests.add(thread.id);
41084
41474
  try {
@@ -41089,9 +41479,15 @@ var AppServerHost = class {
41089
41479
  assertActive?.();
41090
41480
  if (!command) {
41091
41481
  this.#pendingExternalCommandRequests.delete(thread.id);
41092
- return await this.#beginExternalTurn(thread, text, clientUserMessageId);
41482
+ return await this.#beginExternalTurn(thread, text, clientUserMessageId, sourceHostId);
41093
41483
  }
41094
- return await this.#beginExternalCommand(thread, command.descriptor, command.arguments);
41484
+ return await this.#beginExternalCommand(
41485
+ thread,
41486
+ command.descriptor,
41487
+ command.arguments,
41488
+ void 0,
41489
+ sourceHostId
41490
+ );
41095
41491
  } catch (error51) {
41096
41492
  if (error51 instanceof ExternalCommandError || error51 instanceof ExternalSteerError) throw error51;
41097
41493
  this.#diagnose(error51);
@@ -41103,7 +41499,20 @@ var AppServerHost = class {
41103
41499
  this.#pendingExternalCommandRequests.delete(thread.id);
41104
41500
  }
41105
41501
  }
41106
- async #beginExternalTurn(thread, text, clientUserMessageId) {
41502
+ /** Apply context before native execution; a rejected start restores the prior identity. */
41503
+ #setExecutionHost(thread, sourceHostId) {
41504
+ const previousHostId = this.#executionHosts.get(thread);
41505
+ if (typeof sourceHostId === "string" && sourceHostId.length > 0 && sourceHostId.length <= 1024 && !/[\u0000-\u0020\u007f]/u.test(sourceHostId)) {
41506
+ this.#executionHosts.set(thread, sourceHostId);
41507
+ } else {
41508
+ this.#executionHosts.delete(thread);
41509
+ }
41510
+ return () => {
41511
+ if (previousHostId === void 0) this.#executionHosts.delete(thread);
41512
+ else this.#executionHosts.set(thread, previousHostId);
41513
+ };
41514
+ }
41515
+ async #beginExternalTurn(thread, text, clientUserMessageId, sourceHostId) {
41107
41516
  if (this.#closeRequested || this.#externalRuntime.get(thread.id) !== thread) {
41108
41517
  throw new ExternalSteerError(-32073, "External Thread is no longer available");
41109
41518
  }
@@ -41130,6 +41539,7 @@ var AppServerHost = class {
41130
41539
  thread.projectedTerminalTurnId = null;
41131
41540
  thread.projectedTurns.set(turnId, projection);
41132
41541
  thread.responseGates.set(turnId, gate);
41542
+ const restoreExecutionHost = this.#setExecutionHost(thread, sourceHostId);
41133
41543
  try {
41134
41544
  if (thread.unsubmittedPrewarm && await this.#externalRuntime.submitPrewarm(thread)) {
41135
41545
  await this.#notifyExternalThreadStarted(thread.thread);
@@ -41142,6 +41552,7 @@ var AppServerHost = class {
41142
41552
  if (!result.ok) throw new ExternalSteerError(-32073, result.error.message);
41143
41553
  return { turnId, turn: projection.projector.pendingTurn(), gate };
41144
41554
  } catch (error51) {
41555
+ restoreExecutionHost();
41145
41556
  thread.running = false;
41146
41557
  thread.activeTurnId = null;
41147
41558
  thread.projectedTurns.delete(turnId);
@@ -41370,7 +41781,11 @@ var AppServerHost = class {
41370
41781
  if (event.type === "session.faulted") {
41371
41782
  this.#externalSteering.fault(thread.id, new Error(event.error.message));
41372
41783
  thread.stateObserver.fault(new Error(event.error.message));
41784
+ this.#externalRuntime.markSessionUnavailable(thread);
41373
41785
  this.#diagnose(`${thread.harnessId} Harness Session faulted: ${event.error.message}`);
41786
+ if (!thread.running && !thread.activeTurnId && !this.#hasRunningSubagents(thread.id) && !thread.session.hasBackgroundWork?.()) {
41787
+ void this.#externalRuntime.discardUnavailableIdle(thread);
41788
+ }
41374
41789
  return;
41375
41790
  }
41376
41791
  if (event.type === "turn.autonomous.started") {
@@ -41499,6 +41914,11 @@ var AppServerHost = class {
41499
41914
  return { ...subagent, subagentId: record4.hostThreadId };
41500
41915
  }
41501
41916
  async #refreshOpenSubagentThread(threadId2, terminal3 = true) {
41917
+ await this.#desktopRequests.run(threadId2, async () => {
41918
+ await this.#refreshOpenSubagentThreadBody(threadId2, terminal3);
41919
+ });
41920
+ }
41921
+ async #refreshOpenSubagentThreadBody(threadId2, terminal3) {
41502
41922
  const child = this.#externalRuntime.get(threadId2);
41503
41923
  if (!child) return;
41504
41924
  const previousItems = new Map(
@@ -43523,15 +43943,28 @@ var DelegationControlRegistry = class {
43523
43943
  return (await this.#registrationForThread(input.threadId)).cancel(input);
43524
43944
  }
43525
43945
  async read(input) {
43526
- if (input.hostId !== void 0 && input.hostId !== "local") {
43527
- if (!input.hostId || !this.#remoteRead)
43946
+ const { callerThreadId: callerThreadId2, ...read } = input;
43947
+ if (read.hostId !== void 0 && read.hostId !== "local") {
43948
+ if (typeof callerThreadId2 === "string" && callerThreadId2) {
43949
+ const callers = await this.#matching(
43950
+ (registration) => registration.executionHostId?.(callerThreadId2) === read.hostId && registration.ownsThread(callerThreadId2)
43951
+ );
43952
+ if (callers.length > 1)
43953
+ only(callers, "Calling Thread has ambiguous execution Host context");
43954
+ if (callers.length === 1) {
43955
+ const { hostId: hostId2, ...local } = read;
43956
+ const snapshot2 = await (await this.#registrationForThread(local.threadId)).read(local);
43957
+ return { ...snapshot2, hostId: hostId2 };
43958
+ }
43959
+ }
43960
+ if (!read.hostId || !this.#remoteRead)
43528
43961
  throw new DelegationControlError(
43529
43962
  "RUNTIME_UNREACHABLE",
43530
43963
  "Remote Thread reading is unavailable"
43531
43964
  );
43532
- return this.#remoteRead(input);
43965
+ return this.#remoteRead(read);
43533
43966
  }
43534
- return (await this.#registrationForThread(input.threadId)).read(input);
43967
+ return (await this.#registrationForThread(read.threadId)).read(read);
43535
43968
  }
43536
43969
  async wait(input) {
43537
43970
  if (input.hostId !== void 0 && input.hostId !== "local")
@@ -45555,9 +45988,21 @@ function requiredRuntimeConfiguration(environment) {
45555
45988
  if (!stockCodexPath) throw new Error(`${STOCK_CODEX_PATH_ENV} is required`);
45556
45989
  return { stockCodexPath };
45557
45990
  }
45558
- function delegationCliEnvironment(environment) {
45559
- const cliPath = environment[DELEGATION_CLI_PATH_ENV] ?? environment[UPDATE_RUNTIME_ENV.launcherExecutable];
45560
- const nodePath2 = environment[UPDATE_RUNTIME_ENV.npmNodePath];
45991
+ function delegationCliEnvironment(environment, hostRuntimePath) {
45992
+ let packagedCli;
45993
+ if (hostRuntimePath && path37.isAbsolute(hostRuntimePath)) {
45994
+ const candidate = path37.resolve(
45995
+ path37.dirname(hostRuntimePath),
45996
+ "../bin",
45997
+ process.platform === "win32" ? "codexhost.exe" : "codexhost"
45998
+ );
45999
+ try {
46000
+ if (statSync(candidate).isFile()) packagedCli = candidate;
46001
+ } catch {
46002
+ }
46003
+ }
46004
+ const cliPath = environment[DELEGATION_CLI_PATH_ENV] ?? environment[UPDATE_RUNTIME_ENV.launcherExecutable] ?? packagedCli;
46005
+ const nodePath2 = environment[DELEGATION_CLI_NODE_PATH_ENV] ?? environment[UPDATE_RUNTIME_ENV.npmNodePath] ?? (packagedCli && cliPath === packagedCli ? process.execPath : void 0);
45561
46006
  return {
45562
46007
  ...cliPath ? { [DELEGATION_CLI_PATH_ENV]: cliPath } : {},
45563
46008
  ...nodePath2 && path37.isAbsolute(nodePath2) ? { [DELEGATION_CLI_NODE_PATH_ENV]: nodePath2 } : {}
@@ -45575,7 +46020,7 @@ async function prepareDelegationRuntime(input) {
45575
46020
  const server = await startDelegationControlServer({ token, api: registry2, watchApi: registry2 });
45576
46021
  const environment = {
45577
46022
  ...input.environment,
45578
- ...delegationCliEnvironment(input.environment),
46023
+ ...delegationCliEnvironment(input.environment, input.hostRuntimePath),
45579
46024
  [DELEGATION_RUNTIME_ENDPOINT_ENV]: server.endpoint,
45580
46025
  [DELEGATION_RUNTIME_TOKEN_ENV]: token
45581
46026
  };
@@ -45650,6 +46095,7 @@ async function runHostRuntime(input) {
45650
46095
  const environment = remoteControlPlan?.environment ?? input.environment;
45651
46096
  return prepareDelegationRuntime({
45652
46097
  environment,
46098
+ ...hostRuntimePath ? { hostRuntimePath } : {},
45653
46099
  createHost: async (delegationEnvironment, onDelegationApi, registry2) => {
45654
46100
  const official = await prepareLocalCodex({
45655
46101
  stockCodexPath,
@@ -45758,6 +46204,7 @@ async function runHostRuntime(input) {
45758
46204
  if (!listenUrl) throw new Error("Remote app-server listener URL is unavailable");
45759
46205
  return prepareDelegationRuntime({
45760
46206
  environment: input.environment,
46207
+ ...hostRuntimePath ? { hostRuntimePath } : {},
45761
46208
  createHost: async (delegationEnvironment, _onDelegationApi, registry2) => {
45762
46209
  const socketPath = remoteAppServerSocketPath(delegationEnvironment, listenUrl);
45763
46210
  const officialPlan = createRemoteOfficialAppServerPlan(input.arguments, socketPath);
@@ -47463,8 +47910,11 @@ async function startHarnessBrokerServer(input) {
47463
47910
  retireWhenIdle = false;
47464
47911
  if (request.method === "adapter.credits") {
47465
47912
  harnessAccountListParamsSchema.parse(request.params);
47466
- const adapter = input.adapter;
47467
- return accountCreditsSnapshotSchema.nullable().parse(adapter.credits?.() ?? null);
47913
+ const value2 = input.adapter.credits?.();
47914
+ const credits = parseHarnessAccountCredits(value2);
47915
+ if (value2 != null && !credits)
47916
+ throw new Error("Invalid account credits snapshot");
47917
+ return credits;
47468
47918
  }
47469
47919
  if (request.method === "adapter.inspectAccount") {
47470
47920
  harnessAccountListParamsSchema.parse(request.params);
@@ -47991,7 +48441,7 @@ import {
47991
48441
  readdirSync,
47992
48442
  renameSync,
47993
48443
  rmSync,
47994
- statSync,
48444
+ statSync as statSync2,
47995
48445
  writeFileSync
47996
48446
  } from "node:fs";
47997
48447
  import os12 from "node:os";
@@ -48021,7 +48471,7 @@ function pruneRuntimeLogs(input) {
48021
48471
  if (!Number.isSafeInteger(pid)) continue;
48022
48472
  try {
48023
48473
  const filePath = path42.join(input.directory, entry.name);
48024
- const stats = statSync(filePath);
48474
+ const stats = statSync2(filePath);
48025
48475
  files.push({
48026
48476
  filePath,
48027
48477
  pid,