@codexhost/cli-darwin-x64 0.1.0-test.1 → 0.1.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.
@@ -4,6 +4,9 @@ var __export = (target, all) => {
4
4
  __defProp(target, name, { get: all[name], enumerable: true });
5
5
  };
6
6
 
7
+ // packages/host-runtime/src/release-main.ts
8
+ import { fileURLToPath } from "node:url";
9
+
7
10
  // node_modules/zod/v4/classic/external.js
8
11
  var external_exports = {};
9
12
  __export(external_exports, {
@@ -726,9 +729,9 @@ function floatSafeRemainder(val, step) {
726
729
  return ratio - roundedRatio;
727
730
  }
728
731
  var EVALUATING = /* @__PURE__ */ Symbol("evaluating");
729
- function defineLazy(object2, key, getter) {
732
+ function defineLazy(object3, key, getter) {
730
733
  let value = void 0;
731
- Object.defineProperty(object2, key, {
734
+ Object.defineProperty(object3, key, {
732
735
  get() {
733
736
  if (value === EVALUATING) {
734
737
  return void 0;
@@ -740,7 +743,7 @@ function defineLazy(object2, key, getter) {
740
743
  return value;
741
744
  },
742
745
  set(v2) {
743
- Object.defineProperty(object2, key, {
746
+ Object.defineProperty(object3, key, {
744
747
  value: v2
745
748
  // configurable: true,
746
749
  });
@@ -770,10 +773,10 @@ function mergeDefs(...defs) {
770
773
  function cloneDef(schema) {
771
774
  return mergeDefs(schema._zod.def);
772
775
  }
773
- function getElementAtPath(obj, path8) {
774
- if (!path8)
776
+ function getElementAtPath(obj, path11) {
777
+ if (!path11)
775
778
  return obj;
776
- return path8.reduce((acc, key) => acc?.[key], obj);
779
+ return path11.reduce((acc, key) => acc?.[key], obj);
777
780
  }
778
781
  function promiseAllObject(promisesObj) {
779
782
  const keys = Object.keys(promisesObj);
@@ -1182,11 +1185,11 @@ function explicitlyAborted(x2, startIndex = 0) {
1182
1185
  }
1183
1186
  return false;
1184
1187
  }
1185
- function prefixIssues(path8, issues) {
1188
+ function prefixIssues(path11, issues) {
1186
1189
  return issues.map((iss) => {
1187
1190
  var _a4;
1188
1191
  (_a4 = iss).path ?? (_a4.path = []);
1189
- iss.path.unshift(path8);
1192
+ iss.path.unshift(path11);
1190
1193
  return iss;
1191
1194
  });
1192
1195
  }
@@ -1333,16 +1336,16 @@ function flattenError(error52, mapper = (issue2) => issue2.message) {
1333
1336
  }
1334
1337
  function formatError(error52, mapper = (issue2) => issue2.message) {
1335
1338
  const fieldErrors = { _errors: [] };
1336
- const processError = (error53, path8 = []) => {
1339
+ const processError = (error53, path11 = []) => {
1337
1340
  for (const issue2 of error53.issues) {
1338
1341
  if (issue2.code === "invalid_union" && issue2.errors.length) {
1339
- issue2.errors.map((issues) => processError({ issues }, [...path8, ...issue2.path]));
1342
+ issue2.errors.map((issues) => processError({ issues }, [...path11, ...issue2.path]));
1340
1343
  } else if (issue2.code === "invalid_key") {
1341
- processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
1344
+ processError({ issues: issue2.issues }, [...path11, ...issue2.path]);
1342
1345
  } else if (issue2.code === "invalid_element") {
1343
- processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
1346
+ processError({ issues: issue2.issues }, [...path11, ...issue2.path]);
1344
1347
  } else {
1345
- const fullpath = [...path8, ...issue2.path];
1348
+ const fullpath = [...path11, ...issue2.path];
1346
1349
  if (fullpath.length === 0) {
1347
1350
  fieldErrors._errors.push(mapper(issue2));
1348
1351
  } else {
@@ -1369,17 +1372,17 @@ function formatError(error52, mapper = (issue2) => issue2.message) {
1369
1372
  }
1370
1373
  function treeifyError(error52, mapper = (issue2) => issue2.message) {
1371
1374
  const result = { errors: [] };
1372
- const processError = (error53, path8 = []) => {
1375
+ const processError = (error53, path11 = []) => {
1373
1376
  var _a4, _b2;
1374
1377
  for (const issue2 of error53.issues) {
1375
1378
  if (issue2.code === "invalid_union" && issue2.errors.length) {
1376
- issue2.errors.map((issues) => processError({ issues }, [...path8, ...issue2.path]));
1379
+ issue2.errors.map((issues) => processError({ issues }, [...path11, ...issue2.path]));
1377
1380
  } else if (issue2.code === "invalid_key") {
1378
- processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
1381
+ processError({ issues: issue2.issues }, [...path11, ...issue2.path]);
1379
1382
  } else if (issue2.code === "invalid_element") {
1380
- processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
1383
+ processError({ issues: issue2.issues }, [...path11, ...issue2.path]);
1381
1384
  } else {
1382
- const fullpath = [...path8, ...issue2.path];
1385
+ const fullpath = [...path11, ...issue2.path];
1383
1386
  if (fullpath.length === 0) {
1384
1387
  result.errors.push(mapper(issue2));
1385
1388
  continue;
@@ -1411,8 +1414,8 @@ function treeifyError(error52, mapper = (issue2) => issue2.message) {
1411
1414
  }
1412
1415
  function toDotPath(_path) {
1413
1416
  const segs = [];
1414
- const path8 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
1415
- for (const seg of path8) {
1417
+ const path11 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
1418
+ for (const seg of path11) {
1416
1419
  if (typeof seg === "number")
1417
1420
  segs.push(`[${seg}]`);
1418
1421
  else if (typeof seg === "symbol")
@@ -14104,13 +14107,13 @@ function resolveRef(ref, ctx) {
14104
14107
  if (!ref.startsWith("#")) {
14105
14108
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
14106
14109
  }
14107
- const path8 = ref.slice(1).split("/").filter(Boolean);
14108
- if (path8.length === 0) {
14110
+ const path11 = ref.slice(1).split("/").filter(Boolean);
14111
+ if (path11.length === 0) {
14109
14112
  return ctx.rootSchema;
14110
14113
  }
14111
14114
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
14112
- if (path8[0] === defsKey) {
14113
- const key = path8[1];
14115
+ if (path11[0] === defsKey) {
14116
+ const key = path11[1];
14114
14117
  if (!key || !ctx.defs[key]) {
14115
14118
  throw new Error(`Reference not found: ${ref}`);
14116
14119
  }
@@ -14650,6 +14653,50 @@ var threadPermissionModeSelectParamsSchema = external_exports.object({
14650
14653
  permissionModeId: harnessPermissionModeIdSchema
14651
14654
  }).strict();
14652
14655
 
14656
+ // packages/shared-contracts/dist/thread-usage.js
14657
+ var nonNegativeSafeIntegerSchema = external_exports.number().int().safe().nonnegative();
14658
+ var finiteNonNegativeNumberSchema = external_exports.number().finite().nonnegative();
14659
+ var cacheHitRatePercentSchema = external_exports.number().finite().min(0).max(100);
14660
+ var threadUsageSnapshotSchema = external_exports.object({
14661
+ inputTokens: nonNegativeSafeIntegerSchema.optional(),
14662
+ cachedInputTokens: nonNegativeSafeIntegerSchema.optional(),
14663
+ cacheWriteInputTokens: nonNegativeSafeIntegerSchema.optional(),
14664
+ outputTokens: nonNegativeSafeIntegerSchema.optional(),
14665
+ reasoningOutputTokens: nonNegativeSafeIntegerSchema.optional(),
14666
+ totalTokens: nonNegativeSafeIntegerSchema.optional(),
14667
+ totalCostUsd: finiteNonNegativeNumberSchema.optional(),
14668
+ cacheHitRatePercent: cacheHitRatePercentSchema.optional(),
14669
+ contextWindowTokens: nonNegativeSafeIntegerSchema.optional(),
14670
+ contextUsedTokens: nonNegativeSafeIntegerSchema.optional()
14671
+ }).strict().superRefine((usage, context) => {
14672
+ if (Object.keys(usage).length === 0) {
14673
+ context.addIssue({ code: "custom", message: "Thread Usage must contain a reliable field" });
14674
+ }
14675
+ const hasContextUsed = usage.contextUsedTokens !== void 0;
14676
+ const hasContextWindow = usage.contextWindowTokens !== void 0;
14677
+ if (hasContextUsed !== hasContextWindow) {
14678
+ context.addIssue({
14679
+ code: "custom",
14680
+ message: "Thread Usage context fields must be provided together",
14681
+ path: [hasContextUsed ? "contextWindowTokens" : "contextUsedTokens"]
14682
+ });
14683
+ }
14684
+ if (usage.contextWindowTokens === 0) {
14685
+ context.addIssue({
14686
+ code: "custom",
14687
+ message: "Thread Usage contextWindowTokens must be greater than zero",
14688
+ path: ["contextWindowTokens"]
14689
+ });
14690
+ }
14691
+ });
14692
+ var threadUsageInspectionParamsSchema = external_exports.object({
14693
+ threadId: hostThreadIdSchema
14694
+ }).strict();
14695
+ var threadUsageInspectionSchema = external_exports.object({
14696
+ threadId: hostThreadIdSchema,
14697
+ usage: threadUsageSnapshotSchema.nullable()
14698
+ }).strict();
14699
+
14653
14700
  // packages/shared-contracts/dist/harness-models.js
14654
14701
  var HARNESS_MODEL_REF_MAX_LENGTH = 512;
14655
14702
  var HARNESS_MODEL_LABEL_MAX_LENGTH = 256;
@@ -14740,7 +14787,8 @@ var harnessModelCatalogSchema = external_exports.object({
14740
14787
  });
14741
14788
  var harnessHistoryCapabilitiesSchema = external_exports.object({
14742
14789
  fork: external_exports.boolean(),
14743
- forkAcrossCwd: external_exports.boolean()
14790
+ forkAcrossCwd: external_exports.boolean(),
14791
+ rollbackLastTurn: external_exports.boolean()
14744
14792
  }).strict().refine((history) => history.fork || !history.forkAcrossCwd, {
14745
14793
  path: ["forkAcrossCwd"],
14746
14794
  message: "Cross-cwd Fork requires exact history Fork support"
@@ -14822,6 +14870,7 @@ var externalThreadInspectionSchema = external_exports.object({
14822
14870
  availableThinkingOptions: harnessThinkingOptionsSchema.optional(),
14823
14871
  effectivePermissionModeId: harnessPermissionModeIdSchema.optional(),
14824
14872
  history: harnessHistoryCapabilitiesSchema,
14873
+ usage: threadUsageSnapshotSchema.optional(),
14825
14874
  locked: external_exports.literal(true)
14826
14875
  }).strict();
14827
14876
  var threadInspectionSchema = external_exports.discriminatedUnion("owner", [
@@ -14950,6 +14999,45 @@ var nativeCheckpointRefV1RuntimeSchema = external_exports.strictObject({
14950
14999
  var nativeCheckpointRefV1Schema = nativeCheckpointRefV1RuntimeSchema;
14951
15000
  var nativeCheckpointRefSchema = nativeCheckpointRefV1Schema;
14952
15001
 
15002
+ // packages/shared-contracts/dist/updates.js
15003
+ var UPDATE_ERROR_MAX_LENGTH = 500;
15004
+ var UPDATE_SEMVER_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/u;
15005
+ var updateSemanticVersionSchema = external_exports.string().regex(UPDATE_SEMVER_PATTERN);
15006
+ var updateInstallationSchema = external_exports.enum(["npm", "windows-installer", "macos-dmg"]);
15007
+ var updatePhaseSchema = external_exports.enum([
15008
+ "prepared",
15009
+ "waiting-for-exit",
15010
+ "installing",
15011
+ "restarting",
15012
+ "succeeded",
15013
+ "failed"
15014
+ ]);
15015
+ var updateStatusSchema = external_exports.strictObject({
15016
+ version: updateSemanticVersionSchema,
15017
+ installation: updateInstallationSchema,
15018
+ phase: updatePhaseSchema,
15019
+ updatedAt: external_exports.number().int().nonnegative(),
15020
+ error: external_exports.string().min(1).max(UPDATE_ERROR_MAX_LENGTH).nullable()
15021
+ });
15022
+ var updateEmptyParamsSchema = external_exports.strictObject({});
15023
+ var githubReleaseNotesUrlSchema = external_exports.string().max(300).regex(/^https:\/\/github\.com\/BytePioneer-AI\/codex-host\/releases\/tag\/v[0-9A-Za-z.+-]+$/u, "release notes URL must identify a codexhost GitHub Release");
15024
+ var updateCheckResultSchema = external_exports.strictObject({
15025
+ currentVersion: updateSemanticVersionSchema,
15026
+ latestVersion: updateSemanticVersionSchema.nullable(),
15027
+ updateAvailable: external_exports.boolean(),
15028
+ installationAvailable: external_exports.boolean(),
15029
+ releaseNotes: external_exports.string().min(1).max(2e4).nullable(),
15030
+ releaseNotesUrl: githubReleaseNotesUrlSchema.nullable(),
15031
+ status: updateStatusSchema.nullable(),
15032
+ error: external_exports.string().min(1).max(UPDATE_ERROR_MAX_LENGTH).nullable()
15033
+ });
15034
+ var updateStartResultSchema = external_exports.strictObject({
15035
+ status: updateStatusSchema
15036
+ });
15037
+ var updateStatusResultSchema = external_exports.strictObject({
15038
+ status: updateStatusSchema.nullable()
15039
+ });
15040
+
14953
15041
  // packages/shared-contracts/dist/index.js
14954
15042
  var workspaceContractVersionSchema = external_exports.literal(WORKSPACE_CONTRACT_VERSION);
14955
15043
 
@@ -15057,7 +15145,11 @@ var tokenFields = [
15057
15145
  "contextWindowTokens",
15058
15146
  "contextUsedTokens"
15059
15147
  ];
15060
- var usageFields = /* @__PURE__ */ new Set([...tokenFields, "totalCostUsd"]);
15148
+ var usageFields = /* @__PURE__ */ new Set([
15149
+ ...tokenFields,
15150
+ "totalCostUsd",
15151
+ "cacheHitRatePercent"
15152
+ ]);
15061
15153
  function isRecord(value) {
15062
15154
  return typeof value === "object" && value !== null && !Array.isArray(value);
15063
15155
  }
@@ -15081,6 +15173,9 @@ function parseHostUsage(value) {
15081
15173
  if (value.totalCostUsd !== void 0 && (typeof value.totalCostUsd !== "number" || !Number.isFinite(value.totalCostUsd) || value.totalCostUsd < 0)) {
15082
15174
  throw new Error("Harness Usage 'totalCostUsd' must be a finite non-negative number");
15083
15175
  }
15176
+ if (value.cacheHitRatePercent !== void 0 && (typeof value.cacheHitRatePercent !== "number" || !Number.isFinite(value.cacheHitRatePercent) || value.cacheHitRatePercent < 0 || value.cacheHitRatePercent > 100)) {
15177
+ throw new Error("Harness Usage 'cacheHitRatePercent' must be between 0 and 100");
15178
+ }
15084
15179
  const hasContextUsed = value.contextUsedTokens !== void 0;
15085
15180
  const hasContextWindow = value.contextWindowTokens !== void 0;
15086
15181
  if (hasContextUsed !== hasContextWindow) {
@@ -42652,7 +42747,7 @@ var ClaudeHarnessSession = class {
42652
42747
  selectThinkingOption: true,
42653
42748
  selectPermissionMode: true
42654
42749
  },
42655
- history: { fork: true, forkAcrossCwd: false }
42750
+ history: { fork: true, forkAcrossCwd: false, rollbackLastTurn: false }
42656
42751
  };
42657
42752
  initialState;
42658
42753
  initialUsage = null;
@@ -42719,7 +42814,7 @@ var ClaudeHarnessSession = class {
42719
42814
  };
42720
42815
  }
42721
42816
  if (!this.#statePublished)
42722
- return { ok: true, value: { turns: [] } };
42817
+ return { ok: true, value: { turns: [], state: this.#state } };
42723
42818
  this.#readingHistory = true;
42724
42819
  try {
42725
42820
  let messages;
@@ -42746,7 +42841,10 @@ var ClaudeHarnessSession = class {
42746
42841
  };
42747
42842
  }
42748
42843
  try {
42749
- return { ok: true, value: mapClaudeSnapshot(messages, this.#sessionId) };
42844
+ return {
42845
+ ok: true,
42846
+ value: { ...mapClaudeSnapshot(messages, this.#sessionId), state: this.#state }
42847
+ };
42750
42848
  } catch {
42751
42849
  return {
42752
42850
  ok: false,
@@ -43649,7 +43747,7 @@ var ClaudeCodeAdapter = class {
43649
43747
  selectThinkingOption: false,
43650
43748
  selectPermissionMode: snapshot.canSelectPermissionMode
43651
43749
  },
43652
- history: { fork: true, forkAcrossCwd: false }
43750
+ history: { fork: true, forkAcrossCwd: false, rollbackLastTurn: false }
43653
43751
  }
43654
43752
  };
43655
43753
  }
@@ -43664,7 +43762,7 @@ var ClaudeCodeAdapter = class {
43664
43762
  selectThinkingOption: true,
43665
43763
  selectPermissionMode: snapshot.canSelectPermissionMode
43666
43764
  },
43667
- history: { fork: true, forkAcrossCwd: false }
43765
+ history: { fork: true, forkAcrossCwd: false, rollbackLastTurn: false }
43668
43766
  }
43669
43767
  };
43670
43768
  } catch (error52) {
@@ -43702,6 +43800,16 @@ var ClaudeCodeAdapter = class {
43702
43800
  }
43703
43801
  };
43704
43802
  }
43803
+ if (input.kind === "rollbackLastTurn") {
43804
+ return {
43805
+ ok: false,
43806
+ error: {
43807
+ code: "unsupported",
43808
+ message: "Claude Code does not support exact last-Turn rollback",
43809
+ retryable: false
43810
+ }
43811
+ };
43812
+ }
43705
43813
  let requestedThinkingOptionId = CLAUDE_DEFAULT_THINKING_OPTION_ID;
43706
43814
  if (input.kind === "create" && input.thinkingOptionId) {
43707
43815
  try {
@@ -44246,6 +44354,11 @@ function mapPiSnapshot(history, state) {
44246
44354
  }
44247
44355
  return { turns };
44248
44356
  }
44357
+ function resolvePiLastTurnBoundary(history) {
44358
+ const users = activePiEntries(history).filter((entry) => messageRole(entry) === "user");
44359
+ const last = users.at(-1);
44360
+ return last ? { lastUserEntryId: last.id, sourceTurnCount: users.length } : null;
44361
+ }
44249
44362
  function resolvePiForkBoundary(history, checkpointId) {
44250
44363
  const active = activePiEntries(history);
44251
44364
  const users = active.filter((entry) => messageRole(entry) === "user");
@@ -44258,6 +44371,58 @@ function resolvePiForkBoundary(history, checkpointId) {
44258
44371
  };
44259
44372
  }
44260
44373
 
44374
+ // packages/adapters/pi/dist/pi-last-turn-rollback.js
44375
+ function modelFromState(state) {
44376
+ if (state.provider === null && state.modelId === null)
44377
+ return null;
44378
+ if (state.provider === null || state.modelId === null) {
44379
+ throw new Error("Pi state contains a partial Model identity");
44380
+ }
44381
+ return { provider: state.provider, id: state.modelId };
44382
+ }
44383
+ async function rollbackPiLastTurn(transport, sourceSessionId, cwd) {
44384
+ const startupState = transport.state;
44385
+ const startupSessionId = startupState.sessionId;
44386
+ const currentModel = modelFromState(startupState);
44387
+ const currentThinking = startupState.thinkingLevel;
44388
+ const copiedHistory = await transport.getEntries();
44389
+ const boundary = resolvePiLastTurnBoundary(copiedHistory);
44390
+ if (!boundary)
44391
+ return { ok: false, reason: "empty" };
44392
+ const sourceSnapshot = mapPiSnapshot(copiedHistory, {
44393
+ sessionId: startupSessionId,
44394
+ model: currentModel
44395
+ });
44396
+ let state = await transport.fork(boundary.lastUserEntryId);
44397
+ if (state.sessionId === sourceSessionId || state.sessionId === startupSessionId) {
44398
+ throw new Error("Pi last-Turn rollback did not create a distinct Native Session");
44399
+ }
44400
+ if (!samePiModel(modelFromState(state), currentModel)) {
44401
+ if (!currentModel)
44402
+ throw new Error("Pi last-Turn rollback changed the current Model");
44403
+ state = await transport.selectModel(currentModel);
44404
+ }
44405
+ if (state.thinkingLevel !== currentThinking) {
44406
+ if (!currentThinking)
44407
+ throw new Error("Pi last-Turn rollback changed current Thinking");
44408
+ state = await transport.selectThinkingOption(currentThinking);
44409
+ }
44410
+ if (!samePiModel(modelFromState(state), currentModel) || state.thinkingLevel !== currentThinking) {
44411
+ throw new Error("Pi last-Turn rollback could not restore current Model and Thinking");
44412
+ }
44413
+ const snapshot = mapPiSnapshot(await transport.getEntries(), {
44414
+ sessionId: state.sessionId,
44415
+ model: modelFromState(state)
44416
+ });
44417
+ const expectedTurnKeys = sourceSnapshot.turns.slice(0, -1).map((turn) => turn.nativeTurnRef.nativeTurnKey);
44418
+ const actualTurnKeys = snapshot.turns.map((turn) => turn.nativeTurnRef.nativeTurnKey);
44419
+ if (snapshot.turns.length !== boundary.sourceTurnCount - 1 || actualTurnKeys.some((key, index) => key !== expectedTurnKeys[index])) {
44420
+ throw new Error("Pi last-Turn rollback did not produce the exact retained history prefix");
44421
+ }
44422
+ await transport.verifySessionCwd(cwd);
44423
+ return { ok: true };
44424
+ }
44425
+
44261
44426
  // packages/adapters/pi/dist/pi-rpc-session.js
44262
44427
  import { spawn as spawn2, spawnSync } from "node:child_process";
44263
44428
  import { randomUUID as randomUUID2 } from "node:crypto";
@@ -44268,6 +44433,29 @@ import path5 from "node:path";
44268
44433
  function isRecord8(value) {
44269
44434
  return typeof value === "object" && value !== null && !Array.isArray(value);
44270
44435
  }
44436
+ function nonNegativeSafeInteger(value) {
44437
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
44438
+ }
44439
+ function optionalPiCacheHitRatePercent(value) {
44440
+ if (!isRecord8(value) || value.role !== "assistant" || !isRecord8(value.usage))
44441
+ return null;
44442
+ const input = nonNegativeSafeInteger(value.usage.input);
44443
+ const cacheRead = nonNegativeSafeInteger(value.usage.cacheRead);
44444
+ const cacheWrite = nonNegativeSafeInteger(value.usage.cacheWrite);
44445
+ if (input === null || cacheRead === null || cacheWrite === null)
44446
+ return null;
44447
+ const promptTokens = input + cacheRead + cacheWrite;
44448
+ return promptTokens > 0 ? cacheRead / promptTokens * 100 : null;
44449
+ }
44450
+ function latestPiCacheHitRatePercent(history) {
44451
+ let latest = null;
44452
+ for (const entry of activePiEntries(history)) {
44453
+ if (entry.type === "message" && isRecord8(entry.message) && entry.message.role === "assistant") {
44454
+ latest = optionalPiCacheHitRatePercent(entry.message);
44455
+ }
44456
+ }
44457
+ return latest;
44458
+ }
44271
44459
  function responseData(response, operation) {
44272
44460
  if (!isRecord8(response.data)) {
44273
44461
  throw new Error(`Pi RPC ${operation} response has no data`);
@@ -44564,13 +44752,13 @@ function piRpcProcessCommand(options, dependencies = {}) {
44564
44752
  const command = platform === "win32" ? resolveWindowsCommand(selectedCommand, options.environment, dependencies.isFile ?? isRegularFile) : selectedCommand;
44565
44753
  const sessionArguments = options.forkSessionFile ? ["--fork", options.forkSessionFile] : options.sessionFile ? ["--session", options.sessionFile] : [];
44566
44754
  const modelArguments = options.model ? ["--provider", options.model.provider, "--model", options.model.id] : [];
44567
- const arguments_ = ["--mode", "rpc", ...modelArguments, ...sessionArguments];
44755
+ const arguments_2 = ["--mode", "rpc", ...modelArguments, ...sessionArguments];
44568
44756
  const extension = path5.win32.extname(command).toLowerCase();
44569
44757
  if (platform !== "win32" || ![".cmd", ".bat"].includes(extension)) {
44570
- return { command, arguments: arguments_, windowsVerbatimArguments: false };
44758
+ return { command, arguments: arguments_2, windowsVerbatimArguments: false };
44571
44759
  }
44572
44760
  const quote = (value) => `"${value.replaceAll("%", "%%").replaceAll('"', '""')}"`;
44573
- const commandLine = [command, ...arguments_].map(quote).join(" ");
44761
+ const commandLine = [command, ...arguments_2].map(quote).join(" ");
44574
44762
  return {
44575
44763
  command: options.environment?.ComSpec ?? options.environment?.COMSPEC ?? "cmd.exe",
44576
44764
  arguments: ["/d", "/v:off", "/s", "/c", `"${commandLine}"`],
@@ -44602,6 +44790,7 @@ var PiRpcSession = class {
44602
44790
  #failed = false;
44603
44791
  #pending = /* @__PURE__ */ new Map();
44604
44792
  #state = null;
44793
+ #latestCacheHitRatePercent;
44605
44794
  constructor(options, processAdapter = nodeProcessAdapter) {
44606
44795
  if (options.sessionFile && options.forkSessionFile) {
44607
44796
  throw new Error("Pi RPC cannot combine Session resume and Fork startup");
@@ -44682,7 +44871,9 @@ var PiRpcSession = class {
44682
44871
  }
44683
44872
  async getSessionUsage() {
44684
44873
  try {
44685
- return parsePiSessionUsage(await this.#send("get_session_stats", {}));
44874
+ const usage = parsePiSessionUsage(await this.#send("get_session_stats", {}));
44875
+ await this.#ensureLatestCacheHitRate();
44876
+ return this.#withLatestCacheHitRate(usage);
44686
44877
  } catch (error52) {
44687
44878
  if (!(error52 instanceof PiRpcUnsupportedCommandError))
44688
44879
  throw error52;
@@ -44691,9 +44882,23 @@ var PiRpcSession = class {
44691
44882
  if (!this.#state || observedState.sessionId !== this.#state.sessionId || observedState.provider !== this.#state.provider || observedState.modelId !== this.#state.modelId) {
44692
44883
  throw new Error("Pi RPC Usage fallback does not match the confirmed Session state");
44693
44884
  }
44694
- return parsePiStateContextUsage(response);
44885
+ await this.#ensureLatestCacheHitRate();
44886
+ const usage = parsePiStateContextUsage(response);
44887
+ return usage ? this.#withLatestCacheHitRate(usage) : null;
44695
44888
  }
44696
44889
  }
44890
+ async #ensureLatestCacheHitRate() {
44891
+ if (this.#latestCacheHitRatePercent !== void 0)
44892
+ return;
44893
+ try {
44894
+ this.#latestCacheHitRatePercent = latestPiCacheHitRatePercent(parseSessionHistory(await this.#send("get_entries", {})));
44895
+ } catch {
44896
+ this.#latestCacheHitRatePercent = null;
44897
+ }
44898
+ }
44899
+ #withLatestCacheHitRate(usage) {
44900
+ return this.#latestCacheHitRatePercent === null || this.#latestCacheHitRatePercent === void 0 ? usage : parseHostUsage({ ...usage, cacheHitRatePercent: this.#latestCacheHitRatePercent });
44901
+ }
44697
44902
  async fork(entryId) {
44698
44903
  if (entryId.length === 0)
44699
44904
  throw new Error("Pi Fork Entry ID must not be empty");
@@ -45190,12 +45395,14 @@ var PiRpcSession = class {
45190
45395
  const finalText = assistantText2(value);
45191
45396
  const finalReasoning = assistantReasoning2(value);
45192
45397
  const failure2 = assistantFailure(value);
45398
+ const cacheHitRatePercent = optionalPiCacheHitRatePercent(value);
45193
45399
  if (finalText === null || finalReasoning === null || failure2 === void 0)
45194
45400
  return;
45195
45401
  if (active.assistantMessageId === null && finalText === active.lastFinalizedMessageText && finalReasoning === active.lastFinalizedMessageReasoning && !active.sawStreamedMessageText && !active.sawStreamedMessageReasoning) {
45196
45402
  return;
45197
45403
  }
45198
45404
  active.failure = failure2;
45405
+ this.#latestCacheHitRatePercent = cacheHitRatePercent;
45199
45406
  const messageId = this.#ensureAssistantMessage(active, value);
45200
45407
  if (!active.sawStreamedMessageReasoning && finalReasoning.length > 0) {
45201
45408
  active.reasoningMessageOpen = true;
@@ -45449,8 +45656,8 @@ function numberField(value, key) {
45449
45656
  const field = value[key];
45450
45657
  return typeof field === "number" || field === null ? field : void 0;
45451
45658
  }
45452
- function stripDiffPrefix(path8) {
45453
- return path8.startsWith("a/") || path8.startsWith("b/") ? path8.slice(2) : path8;
45659
+ function stripDiffPrefix(path11) {
45660
+ return path11.startsWith("a/") || path11.startsWith("b/") ? path11.slice(2) : path11;
45454
45661
  }
45455
45662
  function reliableFileChange(result) {
45456
45663
  if (!isRecord11(result) || !isRecord11(result.details) || typeof result.details.patch !== "string") {
@@ -45469,10 +45676,10 @@ function reliableFileChange(result) {
45469
45676
  const oldFile = file2.oldFileName;
45470
45677
  const newFile = file2.newFileName;
45471
45678
  const kind = oldFile === "/dev/null" ? "add" : newFile === "/dev/null" ? "delete" : "update";
45472
- const path8 = stripDiffPrefix(kind === "delete" ? oldFile : newFile);
45473
- if (!path8 || path8 === "/dev/null")
45679
+ const path11 = stripDiffPrefix(kind === "delete" ? oldFile : newFile);
45680
+ if (!path11 || path11 === "/dev/null")
45474
45681
  return null;
45475
- return [{ path: path8, kind, unifiedDiff: patch }];
45682
+ return [{ path: path11, kind, unifiedDiff: patch }];
45476
45683
  }
45477
45684
  function delay3(milliseconds) {
45478
45685
  return new Promise((resolve2) => setTimeout(resolve2, milliseconds));
@@ -45516,7 +45723,7 @@ var PiHarnessSession = class {
45516
45723
  selectThinkingOption: options.supportsThinkingSelection,
45517
45724
  selectPermissionMode: false
45518
45725
  },
45519
- history: { fork: true, forkAcrossCwd: true }
45726
+ history: { fork: true, forkAcrossCwd: true, rollbackLastTurn: true }
45520
45727
  };
45521
45728
  this.#transport = options.startedTransport ?? null;
45522
45729
  this.initialState = options.startedTransport ? harnessStateFromPi(options.startedTransport.state, options.startedThinkingLevels ?? null) : {};
@@ -45547,10 +45754,13 @@ var PiHarnessSession = class {
45547
45754
  const history = await transport.getEntries();
45548
45755
  return {
45549
45756
  ok: true,
45550
- value: mapPiSnapshot(history, {
45551
- sessionId: transport.state.sessionId,
45552
- model: nativeModelForHistory(transport.state)
45553
- })
45757
+ value: {
45758
+ ...mapPiSnapshot(history, {
45759
+ sessionId: transport.state.sessionId,
45760
+ model: nativeModelForHistory(transport.state)
45761
+ }),
45762
+ state: this.#state
45763
+ }
45554
45764
  };
45555
45765
  } catch (error52) {
45556
45766
  return { ok: false, error: normalizedError(error52, "nativeFailure") };
@@ -46461,7 +46671,7 @@ var PiAdapter = class {
46461
46671
  selectThinkingOption: thinkingLevels !== null,
46462
46672
  selectPermissionMode: false
46463
46673
  },
46464
- history: { fork: true, forkAcrossCwd: true }
46674
+ history: { fork: true, forkAcrossCwd: true, rollbackLastTurn: true }
46465
46675
  }
46466
46676
  };
46467
46677
  } catch (error52) {
@@ -46566,7 +46776,7 @@ var PiAdapter = class {
46566
46776
  retryable: false
46567
46777
  });
46568
46778
  }
46569
- } else {
46779
+ } else if (input.kind === "fork") {
46570
46780
  const checkpoint = nativeCheckpointRefSchema.parse(input.checkpoint);
46571
46781
  const startupSessionId = transport.state.sessionId;
46572
46782
  if (startupSessionId === sourceRef.nativeSessionId) {
@@ -46596,6 +46806,15 @@ var PiAdapter = class {
46596
46806
  throw new Error("Pi Fork derived history does not match the requested Checkpoint");
46597
46807
  }
46598
46808
  await transport.verifySessionCwd(input.cwd);
46809
+ } else {
46810
+ const rolledBack = await rollbackPiLastTurn(transport, sourceRef.nativeSessionId, input.cwd);
46811
+ if (!rolledBack.ok) {
46812
+ throw new PiAdapterFaultError({
46813
+ code: "invalidState",
46814
+ message: "Pi Native Session has no Turn to roll back",
46815
+ retryable: false
46816
+ });
46817
+ }
46599
46818
  }
46600
46819
  const startedThinkingLevels = await transport.getAvailableThinkingLevels();
46601
46820
  this.#thinkingSelectionSupported = startedThinkingLevels !== null;
@@ -46682,6 +46901,7 @@ import { randomUUID as randomUUID7 } from "node:crypto";
46682
46901
  import nodePath from "node:path";
46683
46902
 
46684
46903
  // packages/mapping-store/dist/mapping-store.js
46904
+ import { execFileSync } from "node:child_process";
46685
46905
  import { randomUUID as randomUUID4 } from "node:crypto";
46686
46906
  import { constants } from "node:fs";
46687
46907
  import { copyFile, mkdir, open as open2, readFile, readdir, rename, rm as rm2, stat } from "node:fs/promises";
@@ -46721,15 +46941,15 @@ var storedThreadRecordV1Schema = external_exports.object({
46721
46941
  turnMappings: external_exports.array(storedTurnMappingV1Schema),
46722
46942
  createdAt: isoDateSchema,
46723
46943
  updatedAt: isoDateSchema
46724
- }).strict().superRefine((record2, context) => {
46725
- if (record2.state === "ready" && !record2.nativeSessionRef) {
46944
+ }).strict().superRefine((record3, context) => {
46945
+ if (record3.state === "ready" && !record3.nativeSessionRef) {
46726
46946
  context.addIssue({
46727
46947
  code: "custom",
46728
46948
  path: ["nativeSessionRef"],
46729
46949
  message: "Ready Thread must have a Native Session Ref"
46730
46950
  });
46731
46951
  }
46732
- if (record2.nativeSessionRef && record2.nativeSessionRef.harnessId !== record2.harnessId) {
46952
+ if (record3.nativeSessionRef && record3.nativeSessionRef.harnessId !== record3.harnessId) {
46733
46953
  context.addIssue({
46734
46954
  code: "custom",
46735
46955
  path: ["nativeSessionRef", "harnessId"],
@@ -46738,7 +46958,7 @@ var storedThreadRecordV1Schema = external_exports.object({
46738
46958
  }
46739
46959
  const hostTurnIds = /* @__PURE__ */ new Set();
46740
46960
  const nativeTurnKeys = /* @__PURE__ */ new Set();
46741
- for (const [index, mapping] of record2.turnMappings.entries()) {
46961
+ for (const [index, mapping] of record3.turnMappings.entries()) {
46742
46962
  if (hostTurnIds.has(mapping.hostTurnId)) {
46743
46963
  context.addIssue({
46744
46964
  code: "custom",
@@ -46762,14 +46982,14 @@ var storedThreadRecordV1Schema = external_exports.object({
46762
46982
  ]) {
46763
46983
  if (!ref)
46764
46984
  continue;
46765
- if (ref.harnessId !== record2.harnessId) {
46985
+ if (ref.harnessId !== record3.harnessId) {
46766
46986
  context.addIssue({
46767
46987
  code: "custom",
46768
46988
  path: ["turnMappings", index, name, "harnessId"],
46769
46989
  message: "Turn Ref Harness must match the Thread Harness"
46770
46990
  });
46771
46991
  }
46772
- if (record2.nativeSessionRef && ref.nativeSessionId !== record2.nativeSessionRef.nativeSessionId) {
46992
+ if (record3.nativeSessionRef && ref.nativeSessionId !== record3.nativeSessionRef.nativeSessionId) {
46773
46993
  context.addIssue({
46774
46994
  code: "custom",
46775
46995
  path: ["turnMappings", index, name, "nativeSessionId"],
@@ -46789,8 +47009,8 @@ var MappingStoreError = class extends Error {
46789
47009
  this.name = "MappingStoreError";
46790
47010
  }
46791
47011
  };
46792
- function cloneRecord(record2) {
46793
- return JSON.parse(JSON.stringify(record2));
47012
+ function cloneRecord(record3) {
47013
+ return JSON.parse(JSON.stringify(record3));
46794
47014
  }
46795
47015
  function nativeSessionKey(ref) {
46796
47016
  return `${ref.harnessId}\0${ref.nativeSessionId}`;
@@ -46815,13 +47035,62 @@ async function exists(file2) {
46815
47035
  throw error52;
46816
47036
  }
46817
47037
  }
46818
- function processAlive(pid) {
47038
+ function processIdentity(pid) {
46819
47039
  try {
46820
47040
  process.kill(pid, 0);
46821
- return true;
46822
47041
  } catch (error52) {
46823
- return systemErrorCode(error52) === "EPERM";
47042
+ if (systemErrorCode(error52) !== "EPERM")
47043
+ return null;
47044
+ }
47045
+ if (process.platform !== "win32") {
47046
+ return { executablePath: null, startedAt: null };
47047
+ }
47048
+ try {
47049
+ const query = [
47050
+ "$ErrorActionPreference = 'Stop'",
47051
+ `$process = Get-Process -Id ${pid}`,
47052
+ "$process | Select-Object Path, StartTime | ConvertTo-Json -Compress"
47053
+ ].join("; ");
47054
+ const result = execFileSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", query], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], windowsHide: true }).trim();
47055
+ if (!result)
47056
+ return { executablePath: null, startedAt: null };
47057
+ const parsed = JSON.parse(result);
47058
+ const executablePath = typeof parsed.Path === "string" ? parsed.Path : null;
47059
+ const startedAt = typeof parsed.StartTime === "string" ? Date.parse(parsed.StartTime) : NaN;
47060
+ return { executablePath, startedAt: Number.isFinite(startedAt) ? startedAt : null };
47061
+ } catch {
47062
+ return { executablePath: null, startedAt: null };
47063
+ }
47064
+ }
47065
+ function normalizeExecutablePath(value) {
47066
+ return process.platform === "win32" ? value.replaceAll("/", "\\").toLowerCase() : value;
47067
+ }
47068
+ function isNodeExecutable(value) {
47069
+ const normalized = normalizeExecutablePath(value);
47070
+ return normalized.endsWith("\\node.exe") || normalized.endsWith("/node");
47071
+ }
47072
+ function lockOwnerIsLive(lock) {
47073
+ if (typeof lock.pid !== "number")
47074
+ return false;
47075
+ const identity = processIdentity(lock.pid);
47076
+ if (!identity)
47077
+ return false;
47078
+ if (process.platform !== "win32")
47079
+ return true;
47080
+ if (identity.executablePath && lock.executablePath) {
47081
+ if (normalizeExecutablePath(identity.executablePath) !== normalizeExecutablePath(lock.executablePath)) {
47082
+ return false;
47083
+ }
47084
+ } else if (identity.executablePath && !lock.executablePath && !isNodeExecutable(identity.executablePath)) {
47085
+ return false;
47086
+ }
47087
+ if (lock.processStartedAt && identity.startedAt !== null) {
47088
+ const expected = Date.parse(lock.processStartedAt);
47089
+ if (Number.isFinite(expected) && Math.abs(identity.startedAt - expected) > 5e3) {
47090
+ return false;
47091
+ }
46824
47092
  }
47093
+ return true;
46825
47094
  }
46826
47095
  var MappingStore = class {
46827
47096
  #backupsDirectory;
@@ -46865,13 +47134,13 @@ var MappingStore = class {
46865
47134
  for (const name of names) {
46866
47135
  const primary = path6.join(this.#threadsDirectory, name);
46867
47136
  const backup = path6.join(this.#backupsDirectory, name);
46868
- let record2 = null;
47137
+ let record3 = null;
46869
47138
  try {
46870
- record2 = await this.#readRecord(primary, name);
47139
+ record3 = await this.#readRecord(primary, name);
46871
47140
  } catch (primaryError) {
46872
47141
  try {
46873
- record2 = await this.#readRecord(backup, name);
46874
- await this.#replaceFile(primary, record2, false);
47142
+ record3 = await this.#readRecord(backup, name);
47143
+ await this.#replaceFile(primary, record3, false);
46875
47144
  } catch (backupError) {
46876
47145
  const quarantine = path6.join(this.#quarantineDirectory, `${name}.${this.#now().getTime()}.invalid`);
46877
47146
  await rename(primary, quarantine).catch(() => void 0);
@@ -46880,12 +47149,12 @@ var MappingStore = class {
46880
47149
  continue;
46881
47150
  }
46882
47151
  }
46883
- if (record2.state === "creating" && !record2.nativeSessionRef) {
47152
+ if (record3.state === "creating" && !record3.nativeSessionRef) {
46884
47153
  await rm2(primary, { force: true });
46885
47154
  await rm2(backup, { force: true });
46886
47155
  continue;
46887
47156
  }
46888
- this.#records.set(record2.hostThreadId, record2);
47157
+ this.#records.set(record3.hostThreadId, record3);
46889
47158
  }
46890
47159
  this.#rebuildIndexes();
46891
47160
  this.#initialized = true;
@@ -46896,8 +47165,8 @@ var MappingStore = class {
46896
47165
  }
46897
47166
  async getThread(hostThreadId) {
46898
47167
  this.#requireInitialized();
46899
- const record2 = this.#records.get(hostThreadId);
46900
- return record2 ? cloneRecord(record2) : null;
47168
+ const record3 = this.#records.get(hostThreadId);
47169
+ return record3 ? cloneRecord(record3) : null;
46901
47170
  }
46902
47171
  async listThreads() {
46903
47172
  this.#requireInitialized();
@@ -46917,7 +47186,7 @@ var MappingStore = class {
46917
47186
  throw new MappingStoreError("DUPLICATE_CREATE_REQUEST", "Create request already identifies another Thread");
46918
47187
  }
46919
47188
  const timestamp = this.#now().toISOString();
46920
- const record2 = storedThreadRecordV1Schema.parse({
47189
+ const record3 = storedThreadRecordV1Schema.parse({
46921
47190
  formatVersion: 1,
46922
47191
  revision: 1,
46923
47192
  hostThreadId: input.hostThreadId,
@@ -46935,8 +47204,8 @@ var MappingStore = class {
46935
47204
  createdAt: timestamp,
46936
47205
  updatedAt: timestamp
46937
47206
  });
46938
- await this.#writeNew(record2);
46939
- return cloneRecord(record2);
47207
+ await this.#writeNew(record3);
47208
+ return cloneRecord(record3);
46940
47209
  }
46941
47210
  async commitReady(input) {
46942
47211
  return this.#update(input.hostThreadId, (current) => ({
@@ -46959,6 +47228,18 @@ var MappingStore = class {
46959
47228
  };
46960
47229
  });
46961
47230
  }
47231
+ async replaceReadySessionAfterLastTurn(input) {
47232
+ return this.#update(input.hostThreadId, (current) => {
47233
+ if (current.state !== "ready" || !current.nativeSessionRef || current.nativeSessionRef.nativeSessionId === input.nativeSessionRef.nativeSessionId || input.turnMappings.length !== current.turnMappings.length - 1 || input.turnMappings.some(({ hostTurnId }, index) => hostTurnId !== current.turnMappings[index]?.hostTurnId)) {
47234
+ throw new MappingStoreError("MAPPING_CONFLICT", "Last-Turn Session replacement must retain the exact shorter Host Turn prefix");
47235
+ }
47236
+ return {
47237
+ ...current,
47238
+ nativeSessionRef: input.nativeSessionRef,
47239
+ turnMappings: input.turnMappings
47240
+ };
47241
+ });
47242
+ }
46962
47243
  async upsertTurnMappings(hostThreadId, mappings) {
46963
47244
  return this.#update(hostThreadId, (current) => ({
46964
47245
  ...current,
@@ -47005,8 +47286,8 @@ var MappingStore = class {
47005
47286
  }
47006
47287
  async removeProvisional(hostThreadId) {
47007
47288
  this.#requireInitialized();
47008
- const record2 = this.#records.get(hostThreadId);
47009
- if (record2?.state === "ready") {
47289
+ const record3 = this.#records.get(hostThreadId);
47290
+ if (record3?.state === "ready") {
47010
47291
  throw new MappingStoreError("MAPPING_CONFLICT", "Ready Thread cannot be removed as provisional");
47011
47292
  }
47012
47293
  await this.#remove(hostThreadId);
@@ -47097,25 +47378,25 @@ var MappingStore = class {
47097
47378
  }
47098
47379
  return merged;
47099
47380
  }
47100
- async #writeNew(record2) {
47101
- this.#validateGlobal(record2, null);
47102
- await this.#replaceFile(this.#recordPath(record2.hostThreadId), record2, false);
47103
- this.#records.set(record2.hostThreadId, record2);
47381
+ async #writeNew(record3) {
47382
+ this.#validateGlobal(record3, null);
47383
+ await this.#replaceFile(this.#recordPath(record3.hostThreadId), record3, false);
47384
+ this.#records.set(record3.hostThreadId, record3);
47104
47385
  this.#rebuildIndexes();
47105
47386
  }
47106
- async #replaceFile(target, record2, preserveBackup) {
47387
+ async #replaceFile(target, record3, preserveBackup) {
47107
47388
  const temp = `${target}.tmp-${randomUUID4()}`;
47108
47389
  let handle = null;
47109
47390
  try {
47110
- await this.#beforeReplace?.(cloneRecord(record2));
47391
+ await this.#beforeReplace?.(cloneRecord(record3));
47111
47392
  handle = await open2(temp, "wx", constants.S_IRUSR | constants.S_IWUSR);
47112
- await handle.writeFile(`${JSON.stringify(record2, null, 2)}
47393
+ await handle.writeFile(`${JSON.stringify(record3, null, 2)}
47113
47394
  `, "utf8");
47114
47395
  await handle.sync();
47115
47396
  await handle.close();
47116
47397
  handle = null;
47117
47398
  if (preserveBackup && await exists(target)) {
47118
- await copyFile(target, this.#backupPath(record2.hostThreadId));
47399
+ await copyFile(target, this.#backupPath(record3.hostThreadId));
47119
47400
  }
47120
47401
  await rename(temp, target);
47121
47402
  } catch (error52) {
@@ -47150,7 +47431,9 @@ var MappingStore = class {
47150
47431
  const lock = {
47151
47432
  pid: process.pid,
47152
47433
  instanceId: this.#instanceId,
47153
- startedAt: this.#now().toISOString()
47434
+ startedAt: this.#now().toISOString(),
47435
+ executablePath: process.execPath,
47436
+ processStartedAt: new Date(Date.now() - process.uptime() * 1e3).toISOString()
47154
47437
  };
47155
47438
  await handle.writeFile(`${JSON.stringify(lock)}
47156
47439
  `, "utf8");
@@ -47169,7 +47452,7 @@ var MappingStore = class {
47169
47452
  existing = JSON.parse(await readFile(this.#lockPath, "utf8"));
47170
47453
  } catch {
47171
47454
  }
47172
- if (typeof existing.pid === "number" && processAlive(existing.pid)) {
47455
+ if (typeof existing.pid === "number" && lockOwnerIsLive(existing)) {
47173
47456
  throw new MappingStoreError("STORE_LOCKED", "Another codexhost process owns Mapping Store");
47174
47457
  }
47175
47458
  await rename(this.#lockPath, `${this.#lockPath}.stale-${this.#now().getTime()}`).catch(() => void 0);
@@ -47181,18 +47464,18 @@ var MappingStore = class {
47181
47464
  });
47182
47465
  }
47183
47466
  }
47184
- #validateGlobal(record2, replacing) {
47185
- const duplicateCreate = this.#createRequests.get(record2.createRequestId);
47467
+ #validateGlobal(record3, replacing) {
47468
+ const duplicateCreate = this.#createRequests.get(record3.createRequestId);
47186
47469
  if (duplicateCreate && duplicateCreate !== replacing) {
47187
47470
  throw new MappingStoreError("DUPLICATE_CREATE_REQUEST", "Create request is already stored");
47188
47471
  }
47189
- if (record2.nativeSessionRef) {
47190
- const duplicateSession = this.#nativeSessions.get(nativeSessionKey(record2.nativeSessionRef));
47472
+ if (record3.nativeSessionRef) {
47473
+ const duplicateSession = this.#nativeSessions.get(nativeSessionKey(record3.nativeSessionRef));
47191
47474
  if (duplicateSession && duplicateSession !== replacing) {
47192
47475
  throw new MappingStoreError("DUPLICATE_NATIVE_SESSION", "Native Session is already mapped");
47193
47476
  }
47194
47477
  }
47195
- for (const mapping of record2.turnMappings) {
47478
+ for (const mapping of record3.turnMappings) {
47196
47479
  const duplicateHostTurn = this.#hostTurns.get(mapping.hostTurnId);
47197
47480
  if (duplicateHostTurn && duplicateHostTurn !== replacing) {
47198
47481
  throw new MappingStoreError("MAPPING_CONFLICT", "Host Turn is already mapped");
@@ -47208,14 +47491,14 @@ var MappingStore = class {
47208
47491
  this.#nativeSessions.clear();
47209
47492
  this.#hostTurns.clear();
47210
47493
  this.#nativeTurns.clear();
47211
- for (const record2 of this.#records.values()) {
47212
- this.#validateGlobal(record2, record2.hostThreadId);
47213
- this.#createRequests.set(record2.createRequestId, record2.hostThreadId);
47214
- if (record2.nativeSessionRef) {
47215
- this.#nativeSessions.set(nativeSessionKey(record2.nativeSessionRef), record2.hostThreadId);
47216
- }
47217
- for (const mapping of record2.turnMappings) {
47218
- this.#hostTurns.set(mapping.hostTurnId, record2.hostThreadId);
47494
+ for (const record3 of this.#records.values()) {
47495
+ this.#validateGlobal(record3, record3.hostThreadId);
47496
+ this.#createRequests.set(record3.createRequestId, record3.hostThreadId);
47497
+ if (record3.nativeSessionRef) {
47498
+ this.#nativeSessions.set(nativeSessionKey(record3.nativeSessionRef), record3.hostThreadId);
47499
+ }
47500
+ for (const mapping of record3.turnMappings) {
47501
+ this.#hostTurns.set(mapping.hostTurnId, record3.hostThreadId);
47219
47502
  this.#nativeTurns.set(nativeTurnKey(mapping), mapping.hostTurnId);
47220
47503
  }
47221
47504
  }
@@ -47604,8 +47887,8 @@ function projectItem(item, outcome, defaultCwd, includeCommandOutput = true) {
47604
47887
  return {
47605
47888
  id: item.itemId,
47606
47889
  type: "fileChange",
47607
- changes: item.changes.map(({ path: path8, kind, unifiedDiff }) => ({
47608
- path: path8,
47890
+ changes: item.changes.map(({ path: path11, kind, unifiedDiff }) => ({
47891
+ path: path11,
47609
47892
  kind,
47610
47893
  diff: unifiedDiff
47611
47894
  })),
@@ -48022,8 +48305,8 @@ var CodexTurnProjector = class {
48022
48305
  return messages;
48023
48306
  }
48024
48307
  #fileChangeUpdates(itemId2, changes) {
48025
- const projectedChanges = changes.map(({ path: path8, kind, unifiedDiff }) => ({
48026
- path: path8,
48308
+ const projectedChanges = changes.map(({ path: path11, kind, unifiedDiff }) => ({
48309
+ path: path11,
48027
48310
  kind,
48028
48311
  diff: unifiedDiff
48029
48312
  }));
@@ -48106,13 +48389,13 @@ function decodeThreadForkRequest(request) {
48106
48389
  if (runtimeWorkspaceRoots !== void 0 && runtimeWorkspaceRoots !== null && (!Array.isArray(runtimeWorkspaceRoots) || runtimeWorkspaceRoots.some((root) => typeof root !== "string" || root.length === 0))) {
48107
48390
  throw new Error("thread/fork params.runtimeWorkspaceRoots must be text paths or null");
48108
48391
  }
48109
- const path8 = optionalText(params, "path", { allowEmpty: true });
48392
+ const path11 = optionalText(params, "path", { allowEmpty: true });
48110
48393
  const ephemeral = optionalBoolean(params, "ephemeral");
48111
48394
  return {
48112
48395
  threadId: threadId2,
48113
48396
  ...lastTurnText ? { lastTurnId: hostTurnIdSchema.parse(lastTurnText) } : {},
48114
48397
  ...beforeTurnText ? { beforeTurnId: hostTurnIdSchema.parse(beforeTurnText) } : {},
48115
- ...path8 ? { path: path8 } : {},
48398
+ ...path11 ? { path: path11 } : {},
48116
48399
  ...optionalField(params, "model"),
48117
48400
  ...optionalField(params, "modelProvider"),
48118
48401
  ...optionalField(params, "cwd"),
@@ -48744,11 +49027,11 @@ var ExternalThreadRepository = class {
48744
49027
  removeThread(hostThreadId) {
48745
49028
  return this.store.removeThread(hostThreadId);
48746
49029
  }
48747
- async persistTurn(record2, hostTurnId, nativeTurnRef, nativeCheckpointRef) {
48748
- if (record2.state !== "ready" || !record2.nativeSessionRef || nativeTurnRef.harnessId !== record2.harnessId || nativeTurnRef.nativeSessionId !== record2.nativeSessionRef.nativeSessionId || nativeCheckpointRef && (nativeCheckpointRef.harnessId !== record2.harnessId || nativeCheckpointRef.nativeSessionId !== record2.nativeSessionRef.nativeSessionId)) {
49030
+ async persistTurn(record3, hostTurnId, nativeTurnRef, nativeCheckpointRef) {
49031
+ if (record3.state !== "ready" || !record3.nativeSessionRef || nativeTurnRef.harnessId !== record3.harnessId || nativeTurnRef.nativeSessionId !== record3.nativeSessionRef.nativeSessionId || nativeCheckpointRef && (nativeCheckpointRef.harnessId !== record3.harnessId || nativeCheckpointRef.nativeSessionId !== record3.nativeSessionRef.nativeSessionId)) {
48749
49032
  throw new Error("Live Turn identity does not belong to the stored Thread");
48750
49033
  }
48751
- return this.store.upsertTurnMappings(record2.hostThreadId, [
49034
+ return this.store.upsertTurnMappings(record3.hostThreadId, [
48752
49035
  {
48753
49036
  hostTurnId,
48754
49037
  nativeTurnRef,
@@ -48756,12 +49039,12 @@ var ExternalThreadRepository = class {
48756
49039
  }
48757
49040
  ]);
48758
49041
  }
48759
- async commitDerivedSnapshot(record2, nativeSessionRef, snapshot) {
48760
- if (record2.state !== "creating" || record2.turnMappings.length !== 0) {
49042
+ async commitDerivedSnapshot(record3, nativeSessionRef, snapshot) {
49043
+ if (record3.state !== "creating" || record3.turnMappings.length !== 0) {
48761
49044
  throw new Error("Derived Thread must be provisional before Snapshot commit");
48762
49045
  }
48763
49046
  const mappings = snapshot.turns.map((turn) => {
48764
- if (turn.nativeTurnRef.harnessId !== record2.harnessId || turn.nativeTurnRef.nativeSessionId !== nativeSessionRef.nativeSessionId || turn.checkpoint && (turn.checkpoint.harnessId !== record2.harnessId || turn.checkpoint.nativeSessionId !== nativeSessionRef.nativeSessionId)) {
49047
+ if (turn.nativeTurnRef.harnessId !== record3.harnessId || turn.nativeTurnRef.nativeSessionId !== nativeSessionRef.nativeSessionId || turn.checkpoint && (turn.checkpoint.harnessId !== record3.harnessId || turn.checkpoint.nativeSessionId !== nativeSessionRef.nativeSessionId)) {
48765
49048
  throw new Error("Derived Snapshot identity does not belong to its Native Session");
48766
49049
  }
48767
49050
  return {
@@ -48771,7 +49054,7 @@ var ExternalThreadRepository = class {
48771
49054
  };
48772
49055
  });
48773
49056
  const nextRecord = await this.store.commitReady({
48774
- hostThreadId: record2.hostThreadId,
49057
+ hostThreadId: record3.hostThreadId,
48775
49058
  nativeSessionRef,
48776
49059
  turnMappings: mappings
48777
49060
  });
@@ -48782,7 +49065,7 @@ var ExternalThreadRepository = class {
48782
49065
  if (!mapping) throw new Error("Derived Snapshot mapping is incomplete");
48783
49066
  return projectHistoricalTurn({
48784
49067
  turnId: mapping.hostTurnId,
48785
- cwd: record2.cwd,
49068
+ cwd: record3.cwd,
48786
49069
  snapshot: turn
48787
49070
  });
48788
49071
  })
@@ -48839,8 +49122,41 @@ var ExternalThreadRepository = class {
48839
49122
  })
48840
49123
  };
48841
49124
  }
48842
- async sessionTreeId(record2) {
48843
- let current = record2;
49125
+ async commitLastTurnRollback(current, nativeSessionRef, snapshot) {
49126
+ if (current.state !== "ready" || !current.nativeSessionRef || nativeSessionRef.harnessId !== current.harnessId || nativeSessionRef.nativeSessionId === current.nativeSessionRef.nativeSessionId || snapshot.turns.length !== current.turnMappings.length - 1) {
49127
+ throw new Error("Last-Turn rollback is not an exact ready Session replacement");
49128
+ }
49129
+ const mappings = snapshot.turns.map((turn, index) => {
49130
+ const previous = current.turnMappings[index];
49131
+ if (!previous || turn.nativeTurnRef.harnessId !== current.harnessId || turn.nativeTurnRef.nativeSessionId !== nativeSessionRef.nativeSessionId || turn.checkpoint && (turn.checkpoint.harnessId !== current.harnessId || turn.checkpoint.nativeSessionId !== nativeSessionRef.nativeSessionId)) {
49132
+ throw new Error("Last-Turn rollback Snapshot identity is invalid");
49133
+ }
49134
+ return {
49135
+ hostTurnId: previous.hostTurnId,
49136
+ nativeTurnRef: turn.nativeTurnRef,
49137
+ ...turn.checkpoint ? { nativeCheckpointRef: turn.checkpoint } : {}
49138
+ };
49139
+ });
49140
+ const nextRecord = await this.store.replaceReadySessionAfterLastTurn({
49141
+ hostThreadId: current.hostThreadId,
49142
+ nativeSessionRef,
49143
+ turnMappings: mappings
49144
+ });
49145
+ return {
49146
+ record: nextRecord,
49147
+ turns: snapshot.turns.map((turn, index) => {
49148
+ const mapping = mappings[index];
49149
+ if (!mapping) throw new Error("Last-Turn rollback Snapshot mapping is incomplete");
49150
+ return projectHistoricalTurn({
49151
+ turnId: mapping.hostTurnId,
49152
+ cwd: current.cwd,
49153
+ snapshot: turn
49154
+ });
49155
+ })
49156
+ };
49157
+ }
49158
+ async sessionTreeId(record3) {
49159
+ let current = record3;
48844
49160
  const visited = /* @__PURE__ */ new Set();
48845
49161
  while (current.forkSource) {
48846
49162
  if (visited.has(current.hostThreadId))
@@ -48852,18 +49168,18 @@ var ExternalThreadRepository = class {
48852
49168
  }
48853
49169
  return current.hostThreadId;
48854
49170
  }
48855
- async alignSnapshot(record2, snapshot) {
48856
- const nativeSessionRef = record2.nativeSessionRef;
48857
- if (!nativeSessionRef || record2.state !== "ready") {
49171
+ async alignSnapshot(record3, snapshot) {
49172
+ const nativeSessionRef = record3.nativeSessionRef;
49173
+ if (!nativeSessionRef || record3.state !== "ready") {
48858
49174
  throw new Error("External Thread has no committed Native Session identity");
48859
49175
  }
48860
49176
  for (const turn of snapshot.turns) {
48861
- if (turn.nativeTurnRef.harnessId !== record2.harnessId || turn.nativeTurnRef.nativeSessionId !== nativeSessionRef.nativeSessionId || turn.checkpoint && (turn.checkpoint.harnessId !== record2.harnessId || turn.checkpoint.nativeSessionId !== nativeSessionRef.nativeSessionId)) {
49177
+ if (turn.nativeTurnRef.harnessId !== record3.harnessId || turn.nativeTurnRef.nativeSessionId !== nativeSessionRef.nativeSessionId || turn.checkpoint && (turn.checkpoint.harnessId !== record3.harnessId || turn.checkpoint.nativeSessionId !== nativeSessionRef.nativeSessionId)) {
48862
49178
  throw new Error("External Snapshot identity does not belong to the stored Thread");
48863
49179
  }
48864
49180
  }
48865
49181
  const mappingsByNative = new Map(
48866
- record2.turnMappings.map(
49182
+ record3.turnMappings.map(
48867
49183
  (mapping) => [nativeTurnKey2(mapping.nativeTurnRef), mapping]
48868
49184
  )
48869
49185
  );
@@ -48886,24 +49202,24 @@ var ExternalThreadRepository = class {
48886
49202
  let existingIndex = 0;
48887
49203
  for (const { existing } of aligned) {
48888
49204
  if (!existing) continue;
48889
- if (existing.hostTurnId !== record2.turnMappings[existingIndex]?.hostTurnId) {
49205
+ if (existing.hostTurnId !== record3.turnMappings[existingIndex]?.hostTurnId) {
48890
49206
  throw new Error("Persisted Turn mappings do not match the Native Snapshot order");
48891
49207
  }
48892
49208
  existingIndex += 1;
48893
49209
  }
48894
- if (existingIndex !== record2.turnMappings.length) {
49210
+ if (existingIndex !== record3.turnMappings.length) {
48895
49211
  throw new Error("Persisted Turn mappings do not match the Native Snapshot order");
48896
49212
  }
48897
49213
  const orderedMappings = aligned.map(({ mapping }) => mapping);
48898
- const mappingsChanged = orderedMappings.length !== record2.turnMappings.length || orderedMappings.some((mapping, index) => {
48899
- const persisted = record2.turnMappings[index];
49214
+ const mappingsChanged = orderedMappings.length !== record3.turnMappings.length || orderedMappings.some((mapping, index) => {
49215
+ const persisted = record3.turnMappings[index];
48900
49216
  return !persisted || !sameMapping(mapping, persisted);
48901
49217
  });
48902
- const nextRecord = mappingsChanged ? await this.store.reconcileTurnMappings(record2.hostThreadId, orderedMappings) : record2;
49218
+ const nextRecord = mappingsChanged ? await this.store.reconcileTurnMappings(record3.hostThreadId, orderedMappings) : record3;
48903
49219
  return {
48904
49220
  record: nextRecord,
48905
49221
  turns: aligned.map(
48906
- ({ mapping, snapshot: turn }) => projectHistoricalTurn({ turnId: mapping.hostTurnId, cwd: record2.cwd, snapshot: turn })
49222
+ ({ mapping, snapshot: turn }) => projectHistoricalTurn({ turnId: mapping.hostTurnId, cwd: record3.cwd, snapshot: turn })
48907
49223
  )
48908
49224
  };
48909
49225
  }
@@ -48922,9 +49238,9 @@ function createExternalThreadRecordInput(input) {
48922
49238
  };
48923
49239
  }
48924
49240
  function externalThreadValue(input) {
48925
- const { record: record2 } = input;
48926
- const createdAt = Math.floor(Date.parse(record2.createdAt) / 1e3);
48927
- const updatedAt = Math.floor(Date.parse(record2.updatedAt) / 1e3);
49241
+ const { record: record3 } = input;
49242
+ const createdAt = Math.floor(Date.parse(record3.createdAt) / 1e3);
49243
+ const updatedAt = Math.floor(Date.parse(record3.updatedAt) / 1e3);
48928
49244
  const preview = input.turns.flatMap((turn) => Array.isArray(turn.items) ? turn.items : []).find(
48929
49245
  (item) => typeof item === "object" && item !== null && !Array.isArray(item) && item.type === "userMessage"
48930
49246
  );
@@ -48933,10 +49249,10 @@ function externalThreadValue(input) {
48933
49249
  (part) => typeof part === "object" && part !== null && !Array.isArray(part) && part.type === "text"
48934
49250
  ).map((part) => typeof part.text === "string" ? part.text : "").join("\n") : "";
48935
49251
  return {
48936
- id: record2.hostThreadId,
49252
+ id: record3.hostThreadId,
48937
49253
  sessionId: input.sessionId,
48938
49254
  path: null,
48939
- cwd: record2.cwd,
49255
+ cwd: record3.cwd,
48940
49256
  source: "vscode",
48941
49257
  threadSource: null,
48942
49258
  modelProvider: "codexhost",
@@ -48947,13 +49263,13 @@ function externalThreadValue(input) {
48947
49263
  status: input.loaded === false ? { type: "notLoaded" } : input.running ? { type: "active", activeFlags: [] } : { type: "idle" },
48948
49264
  turns: input.turns,
48949
49265
  preview: previewText,
48950
- name: record2.title || null,
49266
+ name: record3.title || null,
48951
49267
  gitInfo: null,
48952
- forkedFromId: record2.forkSource?.hostThreadId ?? null,
49268
+ forkedFromId: record3.forkSource?.hostThreadId ?? null,
48953
49269
  parentThreadId: null,
48954
- ephemeral: record2.ephemeral,
49270
+ ephemeral: record3.ephemeral,
48955
49271
  canAcceptDirectInput: input.loaded === false ? null : true,
48956
- historyMode: record2.historyMode,
49272
+ historyMode: record3.historyMode,
48957
49273
  isPinned: false,
48958
49274
  agentNickname: null,
48959
49275
  agentRole: null,
@@ -49083,7 +49399,8 @@ async function executeExternalThreadFork(input) {
49083
49399
  session,
49084
49400
  sessionId: source.sessionId,
49085
49401
  thread,
49086
- turns: aligned.turns
49402
+ turns: aligned.turns,
49403
+ ...snapshot.value.state ? { restoredState: snapshot.value.state } : {}
49087
49404
  });
49088
49405
  return {
49089
49406
  ok: true,
@@ -49244,6 +49561,96 @@ function listExternalItems(turns, params) {
49244
49561
  }
49245
49562
 
49246
49563
  // packages/host-runtime/src/external-thread-rollback.ts
49564
+ function sameCurrentConfiguration(current, replacement) {
49565
+ return current.effectiveModel?.id === replacement.effectiveModel?.id && current.effectiveThinkingOptionId === replacement.effectiveThinkingOptionId;
49566
+ }
49567
+ async function executeCurrentLastTurnRollback(input) {
49568
+ const { current, adapters, repository, runtime } = input;
49569
+ if (current.record.turnMappings.length === 0) {
49570
+ return {
49571
+ ok: false,
49572
+ error: { code: -32076, message: "External Thread has no Turn to roll back" }
49573
+ };
49574
+ }
49575
+ const currentNativeRef = current.record.nativeSessionRef;
49576
+ const adapter = adapters.get(current.harnessId);
49577
+ if (!currentNativeRef || !adapter) {
49578
+ return {
49579
+ ok: false,
49580
+ error: { code: -32079, message: "External Native Session is unavailable" }
49581
+ };
49582
+ }
49583
+ let opened;
49584
+ try {
49585
+ opened = await adapter.open({
49586
+ kind: "rollbackLastTurn",
49587
+ cwd: current.cwd,
49588
+ sourceRef: currentNativeRef
49589
+ });
49590
+ } catch {
49591
+ return { ok: false, error: { code: -32076, message: "External Thread rollback failed" } };
49592
+ }
49593
+ if (!opened.ok) {
49594
+ return { ok: false, error: mapExternalThreadHarnessError(opened.error, "fork") };
49595
+ }
49596
+ const session = opened.value;
49597
+ const finalNativeRef = session.initialState.nativeRef;
49598
+ if (!finalNativeRef || finalNativeRef.harnessId !== current.harnessId || finalNativeRef.nativeSessionId === currentNativeRef.nativeSessionId) {
49599
+ await session.close().catch(() => void 0);
49600
+ return {
49601
+ ok: false,
49602
+ error: { code: -32076, message: "External rollback did not create a distinct Session" }
49603
+ };
49604
+ }
49605
+ const snapshot = await session.readSnapshot();
49606
+ if (!snapshot.ok) {
49607
+ await session.close().catch(() => void 0);
49608
+ return { ok: false, error: mapExternalThreadHarnessError(snapshot.error, "read") };
49609
+ }
49610
+ if (snapshot.value.turns.length !== current.record.turnMappings.length - 1) {
49611
+ await session.close().catch(() => void 0);
49612
+ return {
49613
+ ok: false,
49614
+ error: { code: -32080, message: "External rollback did not remove exactly one Turn" }
49615
+ };
49616
+ }
49617
+ const replacementState = snapshot.value.state ?? session.initialState;
49618
+ if (!sameCurrentConfiguration(current.stateObserver.state, replacementState)) {
49619
+ await session.close().catch(() => void 0);
49620
+ return {
49621
+ ok: false,
49622
+ error: { code: -32080, message: "External rollback changed Model or Thinking" }
49623
+ };
49624
+ }
49625
+ let aligned;
49626
+ try {
49627
+ aligned = await repository.commitLastTurnRollback(
49628
+ current.record,
49629
+ finalNativeRef,
49630
+ snapshot.value
49631
+ );
49632
+ } catch {
49633
+ await session.close().catch(() => void 0);
49634
+ return {
49635
+ ok: false,
49636
+ error: { code: -32081, message: "External rollback could not be persisted" }
49637
+ };
49638
+ }
49639
+ const thread = externalThreadValue({
49640
+ record: aligned.record,
49641
+ turns: aligned.turns,
49642
+ sessionId: current.sessionId
49643
+ });
49644
+ await runtime.replace(current, {
49645
+ record: aligned.record,
49646
+ session,
49647
+ sessionId: current.sessionId,
49648
+ thread,
49649
+ turns: aligned.turns,
49650
+ restoredState: replacementState
49651
+ });
49652
+ return { ok: true, thread };
49653
+ }
49247
49654
  async function executeExternalThreadRollback(input) {
49248
49655
  const { derived, rollback, adapters, repository, runtime } = input;
49249
49656
  if (derived.running) {
@@ -49251,6 +49658,14 @@ async function executeExternalThreadRollback(input) {
49251
49658
  }
49252
49659
  const refreshError = await runtime.refresh(derived);
49253
49660
  if (refreshError) return { ok: false, error: refreshError };
49661
+ if (rollback.numTurns === 1 && derived.session.capabilities.history.rollbackLastTurn) {
49662
+ return executeCurrentLastTurnRollback({
49663
+ current: derived,
49664
+ adapters,
49665
+ repository,
49666
+ runtime
49667
+ });
49668
+ }
49254
49669
  const forkSource = derived.record.forkSource;
49255
49670
  if (!forkSource) {
49256
49671
  return {
@@ -49470,9 +49885,10 @@ var ExternalThreadRuntime = class {
49470
49885
  if (!this.#adapters.has(harnessId)) {
49471
49886
  throw new Error(`External Harness '${input.record.harnessId}' is not registered`);
49472
49887
  }
49473
- const effectiveModel = input.requestedModel ?? input.session.initialState.effectiveModel;
49474
- const effectiveThinkingOptionId = input.requestedThinkingOptionId ?? input.session.initialState.effectiveThinkingOptionId;
49475
- const effectivePermissionModeId = input.requestedPermissionModeId ?? input.session.initialState.effectivePermissionModeId;
49888
+ const initialState = input.restoredState ?? input.session.initialState;
49889
+ const effectiveModel = input.requestedModel ?? initialState.effectiveModel;
49890
+ const effectiveThinkingOptionId = input.requestedThinkingOptionId ?? initialState.effectiveThinkingOptionId;
49891
+ const effectivePermissionModeId = input.requestedPermissionModeId ?? initialState.effectivePermissionModeId;
49476
49892
  const externalThread = {
49477
49893
  id: input.record.hostThreadId,
49478
49894
  cwd: input.record.cwd,
@@ -49484,7 +49900,7 @@ var ExternalThreadRuntime = class {
49484
49900
  ...effectivePermissionModeId ? { requestedPermissionModeId: effectivePermissionModeId } : {},
49485
49901
  record: input.record,
49486
49902
  sessionId: input.sessionId,
49487
- stateObserver: new SessionStateObserver(input.session.initialState),
49903
+ stateObserver: new SessionStateObserver(initialState),
49488
49904
  thread: input.thread,
49489
49905
  transportModelId: input.record.transportModelId,
49490
49906
  turns: input.turns,
@@ -49518,23 +49934,23 @@ var ExternalThreadRuntime = class {
49518
49934
  async locate(threadId2) {
49519
49935
  const loaded = this.#threads.get(threadId2);
49520
49936
  if (loaded) return { kind: "external", record: loaded.record, thread: loaded };
49521
- let record2;
49937
+ let record3;
49522
49938
  try {
49523
- record2 = await this.#repository.find(threadId2);
49939
+ record3 = await this.#repository.find(threadId2);
49524
49940
  } catch {
49525
49941
  return {
49526
49942
  kind: "error",
49527
49943
  error: { code: -32081, message: "External Thread ownership could not be read" }
49528
49944
  };
49529
49945
  }
49530
- if (!record2) return { kind: "official" };
49531
- if (record2.state !== "ready" || !record2.nativeSessionRef) {
49946
+ if (!record3) return { kind: "official" };
49947
+ if (record3.state !== "ready" || !record3.nativeSessionRef) {
49532
49948
  return {
49533
49949
  kind: "error",
49534
49950
  error: { code: -32079, message: "External Native Session is unavailable" }
49535
49951
  };
49536
49952
  }
49537
- return { kind: "external", record: record2, thread: null };
49953
+ return { kind: "external", record: record3, thread: null };
49538
49954
  }
49539
49955
  async resolve(threadId2) {
49540
49956
  const location = await this.locate(threadId2);
@@ -49542,12 +49958,12 @@ var ExternalThreadRuntime = class {
49542
49958
  if (location.thread) {
49543
49959
  return { kind: "external", thread: location.thread, historyFresh: false };
49544
49960
  }
49545
- const { record: record2 } = location;
49961
+ const { record: record3 } = location;
49546
49962
  let restoring = this.#restores.get(threadId2);
49547
49963
  if (!restoring) {
49548
49964
  const restored = this.#threads.get(threadId2);
49549
49965
  if (restored) return { kind: "external", thread: restored, historyFresh: false };
49550
- restoring = this.#restore(record2).finally(() => {
49966
+ restoring = this.#restore(record3).finally(() => {
49551
49967
  this.#restores.delete(threadId2);
49552
49968
  });
49553
49969
  this.#restores.set(threadId2, restoring);
@@ -49601,10 +50017,10 @@ var ExternalThreadRuntime = class {
49601
50017
  return failure2;
49602
50018
  }
49603
50019
  }
49604
- async #restore(record2) {
49605
- const harnessId = record2.harnessId;
50020
+ async #restore(record3) {
50021
+ const harnessId = record3.harnessId;
49606
50022
  const adapter = this.#adapters.get(harnessId);
49607
- if (!adapter || !record2.nativeSessionRef) {
50023
+ if (!adapter || !record3.nativeSessionRef) {
49608
50024
  throw new ExternalThreadOpenError({
49609
50025
  code: -32077,
49610
50026
  message: "External Harness is unavailable"
@@ -49612,8 +50028,8 @@ var ExternalThreadRuntime = class {
49612
50028
  }
49613
50029
  const opened = await adapter.open({
49614
50030
  kind: "resume",
49615
- cwd: record2.cwd,
49616
- nativeRef: record2.nativeSessionRef
50031
+ cwd: record3.cwd,
50032
+ nativeRef: record3.nativeSessionRef
49617
50033
  });
49618
50034
  if (!opened.ok) {
49619
50035
  throw new ExternalThreadOpenError(mapExternalThreadHarnessError(opened.error, "resume"));
@@ -49624,7 +50040,7 @@ var ExternalThreadRuntime = class {
49624
50040
  if (!snapshot.ok) {
49625
50041
  throw new ExternalThreadOpenError(mapExternalThreadHarnessError(snapshot.error, "read"));
49626
50042
  }
49627
- const aligned = await this.#repository.alignSnapshot(record2, snapshot.value);
50043
+ const aligned = await this.#repository.alignSnapshot(record3, snapshot.value);
49628
50044
  const sessionId = await this.#repository.sessionTreeId(aligned.record);
49629
50045
  return this.register({
49630
50046
  record: aligned.record,
@@ -49635,7 +50051,8 @@ var ExternalThreadRuntime = class {
49635
50051
  turns: aligned.turns,
49636
50052
  sessionId
49637
50053
  }),
49638
- turns: aligned.turns
50054
+ turns: aligned.turns,
50055
+ ...snapshot.value.state ? { restoredState: snapshot.value.state } : {}
49639
50056
  });
49640
50057
  } catch (error52) {
49641
50058
  await session.close().catch(() => void 0);
@@ -49829,17 +50246,17 @@ function externalAnchor(entry) {
49829
50246
  if (entry.source !== "external") throw new Error("Expected an External Thread list entry");
49830
50247
  return { timestamp: entry.timestamp, threadId: threadId(entry.thread) };
49831
50248
  }
49832
- function includesExternalRecord(record2, query) {
49833
- if (record2.state !== "ready" || !record2.nativeSessionRef) return false;
49834
- if (record2.archived !== query.archived) return false;
49835
- if (query.cwd !== null && !query.cwd.includes(record2.cwd)) return false;
50249
+ function includesExternalRecord(record3, query) {
50250
+ if (record3.state !== "ready" || !record3.nativeSessionRef) return false;
50251
+ if (record3.archived !== query.archived) return false;
50252
+ if (query.cwd !== null && !query.cwd.includes(record3.cwd)) return false;
49836
50253
  if (query.modelProviders !== null && query.modelProviders.length > 0 && !query.modelProviders.includes("codexhost")) {
49837
50254
  return false;
49838
50255
  }
49839
50256
  if (query.sourceKinds !== null && query.sourceKinds.length > 0 && !query.sourceKinds.includes("vscode")) {
49840
50257
  return false;
49841
50258
  }
49842
- if (query.searchTerm !== null && !record2.title.toLowerCase().includes(query.searchTerm.toLowerCase())) {
50259
+ if (query.searchTerm !== null && !record3.title.toLowerCase().includes(query.searchTerm.toLowerCase())) {
49843
50260
  return false;
49844
50261
  }
49845
50262
  if (query.parentThreadId !== null || query.ancestorThreadId !== null) return false;
@@ -49847,47 +50264,47 @@ function includesExternalRecord(record2, query) {
49847
50264
  return true;
49848
50265
  }
49849
50266
  function resolveExternalSessionTreeIds(records) {
49850
- const byId = new Map(records.map((record2) => [record2.hostThreadId, record2]));
50267
+ const byId = new Map(records.map((record3) => [record3.hostThreadId, record3]));
49851
50268
  const resolved = /* @__PURE__ */ new Map();
49852
50269
  const resolve2 = (start) => {
49853
50270
  const cached2 = resolved.get(start.hostThreadId);
49854
50271
  if (cached2) return cached2;
49855
- const path8 = [];
50272
+ const path11 = [];
49856
50273
  const visited = /* @__PURE__ */ new Set();
49857
50274
  let current = start;
49858
50275
  while (true) {
49859
50276
  const known = resolved.get(current.hostThreadId);
49860
50277
  if (known) {
49861
- for (const record2 of path8) resolved.set(record2.hostThreadId, known);
50278
+ for (const record3 of path11) resolved.set(record3.hostThreadId, known);
49862
50279
  return known;
49863
50280
  }
49864
50281
  if (visited.has(current.hostThreadId)) {
49865
50282
  throw new Error("External Thread Fork tree contains a cycle");
49866
50283
  }
49867
50284
  visited.add(current.hostThreadId);
49868
- path8.push(current);
50285
+ path11.push(current);
49869
50286
  const sourceId = current.forkSource?.hostThreadId;
49870
50287
  const source = sourceId ? byId.get(sourceId) : void 0;
49871
50288
  if (!source) {
49872
50289
  const root = current.hostThreadId;
49873
- for (const record2 of path8) resolved.set(record2.hostThreadId, root);
50290
+ for (const record3 of path11) resolved.set(record3.hostThreadId, root);
49874
50291
  return root;
49875
50292
  }
49876
50293
  current = source;
49877
50294
  }
49878
50295
  };
49879
- for (const record2 of records) resolve2(record2);
50296
+ for (const record3 of records) resolve2(record3);
49880
50297
  return resolved;
49881
50298
  }
49882
50299
  function listExternalThreadMetadata(input) {
49883
50300
  if (!input.query.supportsExternal) return { data: [], hasMore: false };
49884
50301
  const sessionIds = resolveExternalSessionTreeIds(input.records);
49885
- const entries = input.records.filter((record2) => includesExternalRecord(record2, input.query)).map((record2) => {
49886
- const runtime = input.runtimeFor(record2.hostThreadId);
49887
- const sessionId = sessionIds.get(record2.hostThreadId);
50302
+ const entries = input.records.filter((record3) => includesExternalRecord(record3, input.query)).map((record3) => {
50303
+ const runtime = input.runtimeFor(record3.hostThreadId);
50304
+ const sessionId = sessionIds.get(record3.hostThreadId);
49888
50305
  if (!sessionId) throw new Error("External Thread Session tree could not be resolved");
49889
50306
  const thread = externalThreadValue({
49890
- record: record2,
50307
+ record: record3,
49891
50308
  turns: [],
49892
50309
  sessionId,
49893
50310
  ...runtime ? { running: runtime.running } : { loaded: false }
@@ -49895,7 +50312,7 @@ function listExternalThreadMetadata(input) {
49895
50312
  return {
49896
50313
  source: "external",
49897
50314
  thread,
49898
- timestamp: input.query.sortKey === "created_at" ? unixTimestamp(record2.createdAt, "createdAt") : unixTimestamp(record2.updatedAt, "updatedAt")
50315
+ timestamp: input.query.sortKey === "created_at" ? unixTimestamp(record3.createdAt, "createdAt") : unixTimestamp(record3.updatedAt, "updatedAt")
49899
50316
  };
49900
50317
  }).sort((left, right) => compareThreadListEntries(left, right, input.query.sortDirection)).filter(
49901
50318
  (entry) => !input.anchor || compareExternalAnchor(entry, input.anchor, input.query.sortDirection) > 0
@@ -49977,7 +50394,7 @@ async function aggregateThreadList(input) {
49977
50394
  let officialIndex = 0;
49978
50395
  let firstOfficialBackwardsCursor;
49979
50396
  let officialRequestCount = 0;
49980
- const externalIds = new Set(input.records.map((record2) => record2.hostThreadId));
50397
+ const externalIds = new Set(input.records.map((record3) => record3.hostThreadId));
49981
50398
  const requestOfficial = async (cursor, limit) => {
49982
50399
  officialRequestCount += 1;
49983
50400
  if (officialRequestCount > MAX_OFFICIAL_PAGE_REQUESTS) {
@@ -50037,7 +50454,7 @@ async function aggregateThreadList(input) {
50037
50454
  let nextOfficialCursor = officialCursor;
50038
50455
  let nextOfficialDone = officialDone;
50039
50456
  const finalOfficialBatch = officialBatch;
50040
- if (finalOfficialBatch) {
50457
+ if (finalOfficialBatch && !officialDone) {
50041
50458
  if (officialIndex === 0) {
50042
50459
  nextOfficialCursor = officialBatchStart;
50043
50460
  nextOfficialDone = false;
@@ -50101,7 +50518,15 @@ function officialEnvironment(source) {
50101
50518
  "CODEXHOST_PI_COMMAND",
50102
50519
  "CODEXHOST_ENABLE_CLAUDE_CODE",
50103
50520
  "CODEXHOST_CLAUDE_COMMAND",
50104
- "CODEXHOST_STOCK_CODEX_PATH"
50521
+ "CODEXHOST_STOCK_CODEX_PATH",
50522
+ "CODEXHOST_LAUNCHER_PID",
50523
+ "CODEXHOST_LAUNCHER_EXECUTABLE",
50524
+ "CODEXHOST_CONTROL_PORT",
50525
+ "CODEXHOST_CONTROL_NONCE",
50526
+ "CODEXHOST_NPM_NODE_PATH",
50527
+ "CODEXHOST_NPM_CLI_PATH",
50528
+ "CODEXHOST_NPM_LAUNCHER_PATH",
50529
+ "CODEXHOST_NPM_PACKAGE_ROOT"
50105
50530
  ]);
50106
50531
  return Object.fromEntries(Object.entries(source).filter(([key]) => !internal.has(key)));
50107
50532
  }
@@ -50145,7 +50570,7 @@ function isHostApprovalRequestId(value) {
50145
50570
  function isHostQuestionRequestId(value) {
50146
50571
  return typeof value === "number" && Number.isSafeInteger(value) && value >= HOST_QUESTION_REQUEST_ID_MIN && value <= HOST_QUESTION_REQUEST_ID_MAX;
50147
50572
  }
50148
- function classifyCreateRequestRoute(request, defaultAgent2) {
50573
+ function classifyCreateRequestRoute(request, defaultAgent) {
50149
50574
  const route = decodeCreateRoute(request);
50150
50575
  if (!route) return null;
50151
50576
  if (route.harnessId !== "codex") {
@@ -50159,8 +50584,8 @@ function classifyCreateRequestRoute(request, defaultAgent2) {
50159
50584
  return {
50160
50585
  requestMethod: "thread/start",
50161
50586
  modelCarrier: "official-model",
50162
- selectedHarness: defaultAgent2,
50163
- selectionSource: defaultAgent2 === "pi" ? "default-agent" : "official-model"
50587
+ selectedHarness: defaultAgent,
50588
+ selectionSource: defaultAgent === "pi" ? "default-agent" : "official-model"
50164
50589
  };
50165
50590
  }
50166
50591
  function requestObject(request) {
@@ -50322,6 +50747,10 @@ var AppServerHost = class {
50322
50747
  continue;
50323
50748
  }
50324
50749
  const request = requestResult.data;
50750
+ if (request.method === "codexhost/update/check" || request.method === "codexhost/update/start" || request.method === "codexhost/update/status") {
50751
+ await this.#handleUpdateRequest(request);
50752
+ continue;
50753
+ }
50325
50754
  if (request.method === "codexhost/harness/inspect") {
50326
50755
  await this.#inspectHarness(request);
50327
50756
  continue;
@@ -50334,6 +50763,10 @@ var AppServerHost = class {
50334
50763
  await this.#inspectThread(request);
50335
50764
  continue;
50336
50765
  }
50766
+ if (request.method === "codexhost/thread/usage/inspect") {
50767
+ await this.#inspectThreadUsage(request);
50768
+ continue;
50769
+ }
50337
50770
  if (request.method === "codexhost/thread/ownership/list") {
50338
50771
  await this.#listThreadOwnership(request);
50339
50772
  continue;
@@ -50662,9 +51095,9 @@ var AppServerHost = class {
50662
51095
  );
50663
51096
  return;
50664
51097
  }
50665
- let record2;
51098
+ let record3;
50666
51099
  try {
50667
- record2 = await this.#repository.setArchived(location.record.hostThreadId, archived);
51100
+ record3 = await this.#repository.setArchived(location.record.hostThreadId, archived);
50668
51101
  } catch {
50669
51102
  await this.#writer.json(
50670
51103
  rpcError(request, -32081, "External Thread archive state could not be persisted")
@@ -50672,13 +51105,13 @@ var AppServerHost = class {
50672
51105
  return;
50673
51106
  }
50674
51107
  const projected = externalThreadValue({
50675
- record: record2,
51108
+ record: record3,
50676
51109
  turns: [],
50677
51110
  sessionId,
50678
51111
  ...location.thread ? { running: location.thread.running } : { loaded: false }
50679
51112
  });
50680
51113
  if (location.thread) {
50681
- location.thread.record = record2;
51114
+ location.thread.record = record3;
50682
51115
  location.thread.thread = {
50683
51116
  ...location.thread.thread,
50684
51117
  ...projected,
@@ -50690,9 +51123,40 @@ var AppServerHost = class {
50690
51123
  );
50691
51124
  await this.#writer.json({
50692
51125
  method: archived ? "thread/archived" : "thread/unarchived",
50693
- params: { threadId: record2.hostThreadId }
51126
+ params: { threadId: record3.hostThreadId }
50694
51127
  });
50695
51128
  }
51129
+ async #handleUpdateRequest(request) {
51130
+ const params = updateEmptyParamsSchema.safeParse(
51131
+ request.params === void 0 ? {} : request.params
51132
+ );
51133
+ if (!params.success) {
51134
+ await this.#writer.json(rpcError(request, -32602, "Update params must be empty"));
51135
+ return;
51136
+ }
51137
+ const coordinator = this.#options.updateCoordinator;
51138
+ if (!coordinator) {
51139
+ await this.#writer.json(rpcError(request, -32090, "Application updates are unavailable"));
51140
+ return;
51141
+ }
51142
+ try {
51143
+ if (request.method === "codexhost/update/check") {
51144
+ const result2 = updateCheckResultSchema.parse(await coordinator.check());
51145
+ await this.#writer.json(rpcEnvelope(request, { result: jsonValueSchema.parse(result2) }));
51146
+ return;
51147
+ }
51148
+ if (request.method === "codexhost/update/status") {
51149
+ const result2 = updateStatusResultSchema.parse(await coordinator.status());
51150
+ await this.#writer.json(rpcEnvelope(request, { result: jsonValueSchema.parse(result2) }));
51151
+ return;
51152
+ }
51153
+ const result = updateStartResultSchema.parse(await coordinator.start());
51154
+ await this.#writer.json(rpcEnvelope(request, { result: jsonValueSchema.parse(result) }));
51155
+ coordinator.requestShutdown();
51156
+ } catch (error52) {
51157
+ await this.#writer.json(rpcError(request, -32091, errorMessage3(error52).slice(0, 500)));
51158
+ }
51159
+ }
50696
51160
  async #inspectHarness(request) {
50697
51161
  const params = harnessInspectParamsSchema.safeParse(request.params);
50698
51162
  if (!params.success) {
@@ -50760,11 +51224,35 @@ var AppServerHost = class {
50760
51224
  effectivePermissionModeId: resolution.thread.stateObserver.state.effectivePermissionModeId
50761
51225
  } : {},
50762
51226
  history: resolution.thread.session.capabilities.history,
51227
+ ...resolution.thread.latestUsage ? { usage: resolution.thread.latestUsage } : {},
50763
51228
  locked: true
50764
51229
  }
50765
51230
  );
50766
51231
  await this.#writer.json(rpcEnvelope(request, { result: jsonValueSchema.parse(inspection) }));
50767
51232
  }
51233
+ async #inspectThreadUsage(request) {
51234
+ const params = threadUsageInspectionParamsSchema.safeParse(request.params);
51235
+ if (!params.success) {
51236
+ await this.#writer.json(rpcError(request, -32602, "Invalid Thread Usage inspection params"));
51237
+ return;
51238
+ }
51239
+ const resolution = await this.#resolveExternalThread(params.data.threadId);
51240
+ if (resolution.kind === "error") {
51241
+ await this.#writer.json(rpcError(request, resolution.error.code, resolution.error.message));
51242
+ return;
51243
+ }
51244
+ if (resolution.kind === "official") {
51245
+ await this.#writer.json(
51246
+ rpcError(request, -32079, "Thread Usage is unavailable for official Codex Threads")
51247
+ );
51248
+ return;
51249
+ }
51250
+ const result = threadUsageInspectionSchema.parse({
51251
+ threadId: params.data.threadId,
51252
+ usage: resolution.thread.latestUsage
51253
+ });
51254
+ await this.#writer.json(rpcEnvelope(request, { result: jsonValueSchema.parse(result) }));
51255
+ }
50768
51256
  async #listThreadOwnership(request) {
50769
51257
  const params = threadOwnershipListParamsSchema.safeParse(request.params);
50770
51258
  if (!params.success) {
@@ -50774,8 +51262,8 @@ var AppServerHost = class {
50774
51262
  try {
50775
51263
  const threads = await Promise.all(
50776
51264
  params.data.threadIds.map(async (threadId2) => {
50777
- const record2 = await this.#repository.find(threadId2);
50778
- return record2 ? { threadId: threadId2, owner: "external", harnessId: record2.harnessId } : { threadId: threadId2, owner: "codex" };
51265
+ const record3 = await this.#repository.find(threadId2);
51266
+ return record3 ? { threadId: threadId2, owner: "external", harnessId: record3.harnessId } : { threadId: threadId2, owner: "codex" };
50779
51267
  })
50780
51268
  );
50781
51269
  const result = threadOwnershipListResultSchema.parse({ threads });
@@ -51042,9 +51530,9 @@ var AppServerHost = class {
51042
51530
  ephemeral: params.ephemeral === true,
51043
51531
  historyMode: params.historyMode === "paginated" ? "paginated" : "legacy"
51044
51532
  });
51045
- let record2;
51533
+ let record3;
51046
51534
  try {
51047
- record2 = await this.#repository.createProvisional(recordInput);
51535
+ record3 = await this.#repository.createProvisional(recordInput);
51048
51536
  } catch {
51049
51537
  this.#routeObservationTracker.rejectCreate(request.id);
51050
51538
  await this.#writer.json(rpcError(request, -32081, "External Thread could not be persisted"));
@@ -51059,7 +51547,7 @@ var AppServerHost = class {
51059
51547
  });
51060
51548
  if (!sessionResult.ok) {
51061
51549
  this.#routeObservationTracker.rejectCreate(request.id);
51062
- await this.#repository.removeProvisional(record2.hostThreadId).catch(() => void 0);
51550
+ await this.#repository.removeProvisional(record3.hostThreadId).catch(() => void 0);
51063
51551
  const mapped = mapExternalThreadHarnessError(sessionResult.error, "create");
51064
51552
  await this.#writer.json(rpcError(request, mapped.code, mapped.message));
51065
51553
  return;
@@ -51067,20 +51555,20 @@ var AppServerHost = class {
51067
51555
  const session = sessionResult.value;
51068
51556
  try {
51069
51557
  if (session.initialState.nativeRef) {
51070
- record2 = await this.#repository.commitNative(
51071
- record2.hostThreadId,
51558
+ record3 = await this.#repository.commitNative(
51559
+ record3.hostThreadId,
51072
51560
  session.initialState.nativeRef
51073
51561
  );
51074
51562
  }
51075
51563
  const thread = externalThreadValue({
51076
- record: record2,
51564
+ record: record3,
51077
51565
  turns: [],
51078
- sessionId: record2.hostThreadId
51566
+ sessionId: record3.hostThreadId
51079
51567
  });
51080
51568
  const externalThread = this.#registerExternalThread({
51081
- record: record2,
51569
+ record: record3,
51082
51570
  session,
51083
- sessionId: record2.hostThreadId,
51571
+ sessionId: record3.hostThreadId,
51084
51572
  thread,
51085
51573
  turns: [],
51086
51574
  ...requestedModel ? { requestedModel } : {},
@@ -51113,10 +51601,10 @@ var AppServerHost = class {
51113
51601
  params: { thread }
51114
51602
  });
51115
51603
  } catch {
51116
- this.#externalRuntime.remove(record2.hostThreadId);
51117
- this.#routeObservationTracker.forgetThread(record2.hostThreadId);
51604
+ this.#externalRuntime.remove(record3.hostThreadId);
51605
+ this.#routeObservationTracker.forgetThread(record3.hostThreadId);
51118
51606
  await session.close().catch(() => void 0);
51119
- await this.#repository.removeProvisional(record2.hostThreadId).catch(() => void 0);
51607
+ await this.#repository.removeProvisional(record3.hostThreadId).catch(() => void 0);
51120
51608
  await this.#writer.json(rpcError(request, -32081, "External Thread could not be persisted"));
51121
51609
  }
51122
51610
  }
@@ -51231,9 +51719,9 @@ var AppServerHost = class {
51231
51719
  );
51232
51720
  return;
51233
51721
  }
51234
- let record2;
51722
+ let record3;
51235
51723
  try {
51236
- record2 = await this.#repository.setTitle(location.record.hostThreadId, name);
51724
+ record3 = await this.#repository.setTitle(location.record.hostThreadId, name);
51237
51725
  } catch {
51238
51726
  await this.#writer.json(
51239
51727
  rpcError(request, -32081, "External Thread title could not be persisted")
@@ -51241,7 +51729,7 @@ var AppServerHost = class {
51241
51729
  return;
51242
51730
  }
51243
51731
  if (location.thread) {
51244
- location.thread.record = record2;
51732
+ location.thread.record = record3;
51245
51733
  location.thread.thread.name = name;
51246
51734
  location.thread.thread.updatedAt = unixSeconds();
51247
51735
  }
@@ -51404,128 +51892,20 @@ var AppServerHost = class {
51404
51892
  return;
51405
51893
  }
51406
51894
  const params = requestObject(request);
51407
- let requestedSelection;
51408
- try {
51409
- requestedSelection = decodeExternalTransportSelection(thread.harnessId, params.model);
51410
- } catch (error52) {
51411
- await this.#writer.json(rpcError(request, -32602, errorMessage3(error52)));
51412
- return;
51413
- }
51414
- if (typeof params.model === "string" && requestedSelection === null) {
51415
- await this.#writer.json(
51416
- rpcError(request, -32602, "Turn Model carrier does not belong to the Thread Harness")
51417
- );
51418
- return;
51419
- }
51420
- const requestedModel = requestedSelection?.model;
51421
- if (requestedModel) {
51422
- if (!thread.session.capabilities.configuration.selectModel) {
51423
- await this.#writer.json(
51424
- rpcError(request, -32078, "External Harness does not support Model selection")
51425
- );
51426
- return;
51427
- }
51428
- const current = thread.stateObserver.state.effectiveModel;
51429
- const pendingCreateSelection = current === void 0 && thread.requestedModel?.id === requestedModel.id;
51430
- if (current?.id !== requestedModel.id && !pendingCreateSelection) {
51431
- const beforeRevision = thread.stateObserver.revision;
51432
- const selection = await thread.session.execute({
51433
- type: "model.select",
51434
- model: requestedModel
51435
- });
51436
- if (!selection.ok) {
51437
- await this.#writer.json(rpcError(request, -32078, selection.error.message));
51438
- return;
51439
- }
51440
- try {
51441
- const state = await thread.stateObserver.waitForChange(beforeRevision);
51442
- if (state.effectiveModel?.id !== requestedModel.id) {
51443
- throw new Error("Harness Session activated a different Model");
51444
- }
51445
- } catch (error52) {
51446
- await this.#writer.json(rpcError(request, -32078, errorMessage3(error52)));
51447
- return;
51448
- }
51449
- }
51450
- thread.requestedModel = requestedModel;
51451
- }
51452
- const requestedThinkingOptionId = requestedSelection?.thinkingOptionId;
51453
- if (requestedThinkingOptionId) {
51454
- if (!thread.session.capabilities.configuration.selectThinkingOption) {
51455
- await this.#writer.json(
51456
- rpcError(request, -32078, "External Harness does not support Thinking selection")
51457
- );
51895
+ if (typeof params.model === "string") {
51896
+ let route;
51897
+ try {
51898
+ route = decodeCreateRoute({ id: request.id, method: "thread/start", params });
51899
+ } catch (error52) {
51900
+ await this.#writer.json(rpcError(request, -32602, errorMessage3(error52)));
51458
51901
  return;
51459
51902
  }
51460
- const current = thread.stateObserver.state.effectiveThinkingOptionId;
51461
- const pendingCreateSelection = current === void 0 && thread.requestedThinkingOptionId === requestedThinkingOptionId;
51462
- if (current !== requestedThinkingOptionId && !pendingCreateSelection) {
51463
- const beforeRevision = thread.stateObserver.revision;
51464
- const selection = await thread.session.execute({
51465
- type: "thinking.select",
51466
- thinkingOptionId: requestedThinkingOptionId
51467
- });
51468
- if (!selection.ok) {
51469
- await this.#writer.json(rpcError(request, -32078, selection.error.message));
51470
- return;
51471
- }
51472
- try {
51473
- const state = await thread.stateObserver.waitForChange(beforeRevision);
51474
- if (!state.effectiveThinkingOptionId) {
51475
- throw new Error("Harness Session did not report effective Thinking");
51476
- }
51477
- thread.requestedThinkingOptionId = state.effectiveThinkingOptionId;
51478
- } catch (error52) {
51479
- await this.#writer.json(rpcError(request, -32078, errorMessage3(error52)));
51480
- return;
51481
- }
51482
- }
51483
- }
51484
- const requestedPermissionModeId = requestedSelection?.permissionModeId;
51485
- if (requestedPermissionModeId) {
51486
- if (!thread.session.capabilities.configuration.selectPermissionMode) {
51903
+ if (route?.harnessId !== "codex" && route?.harnessId !== thread.harnessId) {
51487
51904
  await this.#writer.json(
51488
- rpcError(request, -32078, "External Harness does not support Permission Mode selection")
51905
+ rpcError(request, -32602, "Turn Model carrier does not belong to the Thread Harness")
51489
51906
  );
51490
51907
  return;
51491
51908
  }
51492
- const current = thread.stateObserver.state.effectivePermissionModeId;
51493
- const pendingCreateSelection = current === void 0 && thread.requestedPermissionModeId === requestedPermissionModeId;
51494
- if (current !== requestedPermissionModeId && !pendingCreateSelection) {
51495
- const beforeRevision = thread.stateObserver.revision;
51496
- const selection = await thread.session.execute({
51497
- type: "permissionMode.select",
51498
- permissionModeId: requestedPermissionModeId
51499
- });
51500
- if (!selection.ok) {
51501
- await this.#writer.json(rpcError(request, -32078, selection.error.message));
51502
- return;
51503
- }
51504
- try {
51505
- const state = await thread.stateObserver.waitForChange(beforeRevision);
51506
- if (state.effectivePermissionModeId !== requestedPermissionModeId) {
51507
- throw new Error("Harness Session activated a different Permission Mode");
51508
- }
51509
- } catch (error52) {
51510
- await this.#writer.json(rpcError(request, -32078, errorMessage3(error52)));
51511
- return;
51512
- }
51513
- }
51514
- thread.requestedPermissionModeId = requestedPermissionModeId;
51515
- }
51516
- if (requestedModel || requestedThinkingOptionId || requestedPermissionModeId) {
51517
- const transportModelId = params.model;
51518
- thread.transportModelId = transportModelId;
51519
- if (requestedPermissionModeId) {
51520
- try {
51521
- thread.record = await this.#repository.setTransportModelId(
51522
- thread.record.hostThreadId,
51523
- transportModelId
51524
- );
51525
- } catch (error52) {
51526
- this.#diagnose(error52);
51527
- }
51528
- }
51529
51909
  }
51530
51910
  let text;
51531
51911
  try {
@@ -51955,22 +52335,944 @@ var AppServerHost = class {
51955
52335
  }
51956
52336
  };
51957
52337
 
52338
+ // packages/host-runtime/src/update-coordinator.ts
52339
+ import { createConnection } from "node:net";
52340
+
52341
+ // packages/update-manager/dist/distribution.js
52342
+ import { lstat, readFile as readFile2 } from "node:fs/promises";
52343
+ import path8 from "node:path";
52344
+
52345
+ // packages/update-manager/dist/status.js
52346
+ var STATUS_SCHEMA_VERSION = 1;
52347
+ var SEMVER_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/u;
52348
+ function requireSemanticVersion(value) {
52349
+ if (!SEMVER_PATTERN.test(value))
52350
+ throw new Error("update version must be valid semantic version");
52351
+ return value;
52352
+ }
52353
+ function preparedStatus(version2, installation, now) {
52354
+ return {
52355
+ schemaVersion: STATUS_SCHEMA_VERSION,
52356
+ version: version2,
52357
+ installation,
52358
+ phase: "prepared",
52359
+ updatedAt: Math.floor(now / 1e3)
52360
+ };
52361
+ }
52362
+ function parseUpdateStatus(value) {
52363
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
52364
+ throw new Error("background update status must be an object");
52365
+ }
52366
+ const status = value;
52367
+ const allowed2 = ["error", "installation", "phase", "schemaVersion", "updatedAt", "version"];
52368
+ if (Object.keys(status).some((key) => !allowed2.includes(key))) {
52369
+ throw new Error("background update status contains unknown fields");
52370
+ }
52371
+ if (status.schemaVersion !== STATUS_SCHEMA_VERSION || typeof status.version !== "string" || !SEMVER_PATTERN.test(status.version) || !["npm", "windows-installer", "macos-dmg"].includes(String(status.installation)) || !["prepared", "waiting-for-exit", "installing", "restarting", "succeeded", "failed"].includes(String(status.phase)) || !Number.isSafeInteger(status.updatedAt) || status.error !== void 0 && typeof status.error !== "string") {
52372
+ throw new Error("background update status is invalid");
52373
+ }
52374
+ return status;
52375
+ }
52376
+
52377
+ // packages/update-manager/dist/distribution.js
52378
+ var UPDATE_RUNTIME_ENV = Object.freeze({
52379
+ launcherPid: "CODEXHOST_LAUNCHER_PID",
52380
+ launcherExecutable: "CODEXHOST_LAUNCHER_EXECUTABLE",
52381
+ controllerPort: "CODEXHOST_CONTROL_PORT",
52382
+ controllerNonce: "CODEXHOST_CONTROL_NONCE",
52383
+ npmNodePath: "CODEXHOST_NPM_NODE_PATH",
52384
+ npmCliPath: "CODEXHOST_NPM_CLI_PATH",
52385
+ npmLauncherPath: "CODEXHOST_NPM_LAUNCHER_PATH",
52386
+ npmPackageRoot: "CODEXHOST_NPM_PACKAGE_ROOT"
52387
+ });
52388
+ function object2(value, label) {
52389
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
52390
+ throw new Error(`${label} must be an object`);
52391
+ }
52392
+ return value;
52393
+ }
52394
+ function parseDistributionMetadata(value) {
52395
+ const metadata = object2(value, "distribution metadata");
52396
+ const allowed2 = ["distribution", "schemaVersion", "target", "version"];
52397
+ if (Object.keys(metadata).some((key) => !allowed2.includes(key))) {
52398
+ throw new Error("distribution metadata contains unknown fields");
52399
+ }
52400
+ if (metadata.schemaVersion !== 1 || metadata.distribution !== "npm" && metadata.distribution !== "installer" || !["macos-arm64", "macos-x64", "windows-x64", "windows-arm64"].includes(String(metadata.target)) || typeof metadata.version !== "string") {
52401
+ throw new Error("distribution metadata is invalid");
52402
+ }
52403
+ return {
52404
+ schemaVersion: 1,
52405
+ version: requireSemanticVersion(metadata.version),
52406
+ distribution: metadata.distribution,
52407
+ target: metadata.target
52408
+ };
52409
+ }
52410
+ function absoluteEnvironmentPath(environment, name) {
52411
+ const value = environment[name];
52412
+ if (!value || !path8.isAbsolute(value))
52413
+ throw new Error(`${name} must be an absolute path`);
52414
+ return path8.normalize(value);
52415
+ }
52416
+ function positiveEnvironmentInteger(environment, name) {
52417
+ const value = Number(environment[name]);
52418
+ if (!Number.isSafeInteger(value) || value <= 0)
52419
+ throw new Error(`${name} must be positive`);
52420
+ return value;
52421
+ }
52422
+ function expectedTarget(platform, architecture) {
52423
+ if (platform === "darwin" && architecture === "arm64")
52424
+ return "macos-arm64";
52425
+ if (platform === "darwin" && architecture === "x64")
52426
+ return "macos-x64";
52427
+ if (platform === "win32" && architecture === "arm64")
52428
+ return "windows-arm64";
52429
+ if (platform === "win32" && architecture === "x64")
52430
+ return "windows-x64";
52431
+ throw new Error(`unsupported update host ${platform}/${architecture}`);
52432
+ }
52433
+ function defaultUpdateStateDirectory(platform = process.platform, environment = process.env) {
52434
+ if (platform === "win32") {
52435
+ const root = environment.LOCALAPPDATA;
52436
+ if (!root || !path8.isAbsolute(root))
52437
+ throw new Error("LOCALAPPDATA is unavailable");
52438
+ return path8.join(root, "codexhost", "updates");
52439
+ }
52440
+ const home = environment.HOME;
52441
+ if (!home || !path8.isAbsolute(home))
52442
+ throw new Error("HOME is unavailable");
52443
+ return platform === "darwin" ? path8.join(home, "Library", "Application Support", "codexhost", "updates") : path8.join(home, ".codexhost", "updates");
52444
+ }
52445
+ async function resolveInstalledUpdateContext(options) {
52446
+ const environment = options.environment ?? process.env;
52447
+ const platform = options.platform ?? process.platform;
52448
+ const architecture = options.architecture ?? process.arch;
52449
+ if (!path8.isAbsolute(options.hostRuntimePath)) {
52450
+ throw new Error("Host Runtime path must be absolute");
52451
+ }
52452
+ const hostRuntimePath = path8.normalize(options.hostRuntimePath);
52453
+ const runtimeMetadata = await lstat(hostRuntimePath);
52454
+ if (!runtimeMetadata.isFile() || runtimeMetadata.isSymbolicLink()) {
52455
+ throw new Error("Host Runtime must be a regular file");
52456
+ }
52457
+ const appDirectory = path8.dirname(hostRuntimePath);
52458
+ const metadata = parseDistributionMetadata(JSON.parse(await readFile2(path8.join(appDirectory, "codexhost-distribution.json"), "utf8")));
52459
+ const target = expectedTarget(platform, architecture);
52460
+ if (metadata.target !== target) {
52461
+ throw new Error(`installed target ${metadata.target} does not match ${target}`);
52462
+ }
52463
+ const launcherPid = positiveEnvironmentInteger(environment, UPDATE_RUNTIME_ENV.launcherPid);
52464
+ const launcherExecutable = absoluteEnvironmentPath(environment, UPDATE_RUNTIME_ENV.launcherExecutable);
52465
+ const stateDirectory = path8.normalize(options.stateDirectory ?? defaultUpdateStateDirectory(platform, environment));
52466
+ const resourcesRoot = path8.dirname(appDirectory);
52467
+ const installationRoot = platform === "darwin" ? path8.dirname(path8.dirname(resourcesRoot)) : resourcesRoot;
52468
+ const updaterExecutable = path8.join(resourcesRoot, "libexec", platform === "win32" ? "codexhost-updater.exe" : "codexhost-updater");
52469
+ const common = {
52470
+ version: metadata.version,
52471
+ launcherPid,
52472
+ launcherExecutable,
52473
+ updaterExecutable,
52474
+ stateDirectory
52475
+ };
52476
+ const controller = {
52477
+ port: positiveEnvironmentInteger(environment, UPDATE_RUNTIME_ENV.controllerPort),
52478
+ nonce: environment[UPDATE_RUNTIME_ENV.controllerNonce] ?? ""
52479
+ };
52480
+ if (!/^[0-9a-f]{32}$/u.test(controller.nonce)) {
52481
+ throw new Error(`${UPDATE_RUNTIME_ENV.controllerNonce} must be a lowercase nonce`);
52482
+ }
52483
+ if (metadata.distribution === "npm") {
52484
+ const npmOptions = {
52485
+ ...common,
52486
+ nodePath: absoluteEnvironmentPath(environment, UPDATE_RUNTIME_ENV.npmNodePath),
52487
+ npmCliPath: absoluteEnvironmentPath(environment, UPDATE_RUNTIME_ENV.npmCliPath),
52488
+ npmLauncherPath: absoluteEnvironmentPath(environment, UPDATE_RUNTIME_ENV.npmLauncherPath),
52489
+ packageRoot: absoluteEnvironmentPath(environment, UPDATE_RUNTIME_ENV.npmPackageRoot)
52490
+ };
52491
+ if (path8.normalize(npmOptions.packageRoot) !== resourcesRoot) {
52492
+ throw new Error("npm platform package root does not own the Host Runtime");
52493
+ }
52494
+ return { metadata, common, controller, installation: { kind: "npm", options: npmOptions } };
52495
+ }
52496
+ if (platform === "win32") {
52497
+ return {
52498
+ metadata,
52499
+ common,
52500
+ controller,
52501
+ installation: {
52502
+ kind: "windows-installer",
52503
+ options: { ...common, installRoot: installationRoot }
52504
+ }
52505
+ };
52506
+ }
52507
+ if (platform === "darwin") {
52508
+ return {
52509
+ metadata,
52510
+ common,
52511
+ controller,
52512
+ installation: {
52513
+ kind: "macos-dmg",
52514
+ options: { ...common, appPath: installationRoot }
52515
+ }
52516
+ };
52517
+ }
52518
+ throw new Error("installer updates require Windows or macOS");
52519
+ }
52520
+
52521
+ // packages/update-manager/dist/github-release.js
52522
+ var CODEXHOST_LATEST_RELEASE_URL = "https://api.github.com/repos/BytePioneer-AI/codex-host/releases/latest";
52523
+ var SHA256_DIGEST_PATTERN = /^sha256:([0-9a-f]{64})$/u;
52524
+ var RELEASE_NOTES_URL_PATTERN = /^https:\/\/github\.com\/BytePioneer-AI\/codex-host\/releases\/tag\/(v[0-9A-Za-z.+-]+)$/u;
52525
+ var DOWNLOAD_URL_PREFIX = "https://github.com/BytePioneer-AI/codex-host/releases/download/";
52526
+ function record2(value, label) {
52527
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
52528
+ throw new Error(`${label} must be an object`);
52529
+ }
52530
+ return value;
52531
+ }
52532
+ function releaseAsset(value) {
52533
+ const asset = record2(value, "GitHub Release asset");
52534
+ if (typeof asset.name !== "string" || asset.name.length === 0 || asset.name.length > 160 || !Number.isSafeInteger(asset.size) || asset.size <= 0 || asset.size > 2 * 1024 * 1024 * 1024 || asset.digest != null && typeof asset.digest !== "string" || typeof asset.browser_download_url !== "string" || !asset.browser_download_url.startsWith(DOWNLOAD_URL_PREFIX)) {
52535
+ throw new Error("GitHub Release asset is invalid");
52536
+ }
52537
+ const url2 = new URL(asset.browser_download_url);
52538
+ if (url2.username || url2.password || url2.search || url2.hash) {
52539
+ throw new Error("GitHub Release asset URL is invalid");
52540
+ }
52541
+ return {
52542
+ name: asset.name,
52543
+ size: asset.size,
52544
+ digest: typeof asset.digest === "string" ? asset.digest : null,
52545
+ downloadUrl: asset.browser_download_url
52546
+ };
52547
+ }
52548
+ function parseLatestGitHubRelease(value) {
52549
+ const release = record2(value, "GitHub latest Release");
52550
+ if (release.draft !== false || release.prerelease !== false || typeof release.tag_name !== "string" || !release.tag_name.startsWith("v") || typeof release.html_url !== "string" || release.body != null && typeof release.body !== "string" || !Array.isArray(release.assets)) {
52551
+ throw new Error("GitHub latest Release is invalid");
52552
+ }
52553
+ const version2 = requireSemanticVersion(release.tag_name.slice(1));
52554
+ const notesMatch = RELEASE_NOTES_URL_PATTERN.exec(release.html_url);
52555
+ if (!notesMatch || notesMatch[1] !== release.tag_name) {
52556
+ throw new Error("GitHub Release notes URL does not match its tag");
52557
+ }
52558
+ const releaseNotes = typeof release.body === "string" && release.body.trim().length > 0 ? release.body.slice(0, 2e4) : null;
52559
+ return Object.freeze({
52560
+ version: version2,
52561
+ releaseNotes,
52562
+ releaseNotesUrl: release.html_url,
52563
+ assets: Object.freeze(release.assets.map(releaseAsset))
52564
+ });
52565
+ }
52566
+ async function fetchLatestGitHubRelease(options = {}) {
52567
+ const fetchImpl = options.fetch ?? fetch;
52568
+ const response = await fetchImpl(CODEXHOST_LATEST_RELEASE_URL, {
52569
+ headers: {
52570
+ accept: "application/vnd.github+json",
52571
+ "user-agent": "codexhost-updater",
52572
+ "x-github-api-version": "2022-11-28"
52573
+ },
52574
+ redirect: "error",
52575
+ ...options.signal ? { signal: options.signal } : {}
52576
+ });
52577
+ if (!response.ok)
52578
+ throw new Error(`GitHub latest Release request failed with HTTP ${response.status}`);
52579
+ return parseLatestGitHubRelease(await response.json());
52580
+ }
52581
+ function numericIdentifiers(value) {
52582
+ const core = value.split(/[+-]/u, 1)[0] ?? "";
52583
+ const parts = core.split(".").map(Number);
52584
+ return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
52585
+ }
52586
+ function compareSemanticVersions(leftValue, rightValue) {
52587
+ const left = requireSemanticVersion(leftValue);
52588
+ const right = requireSemanticVersion(rightValue);
52589
+ const leftNumbers = numericIdentifiers(left);
52590
+ const rightNumbers = numericIdentifiers(right);
52591
+ for (let index = 0; index < 3; index += 1) {
52592
+ const difference = (leftNumbers[index] ?? 0) - (rightNumbers[index] ?? 0);
52593
+ if (difference !== 0)
52594
+ return difference < 0 ? -1 : 1;
52595
+ }
52596
+ const leftPrerelease = left.split("-", 2)[1]?.split("+")[0];
52597
+ const rightPrerelease = right.split("-", 2)[1]?.split("+")[0];
52598
+ if (leftPrerelease === rightPrerelease)
52599
+ return 0;
52600
+ if (leftPrerelease === void 0)
52601
+ return 1;
52602
+ if (rightPrerelease === void 0)
52603
+ return -1;
52604
+ const leftParts = leftPrerelease.split(".");
52605
+ const rightParts = rightPrerelease.split(".");
52606
+ for (let index = 0; index < Math.max(leftParts.length, rightParts.length); index += 1) {
52607
+ const leftPart = leftParts[index];
52608
+ const rightPart = rightParts[index];
52609
+ if (leftPart === rightPart)
52610
+ continue;
52611
+ if (leftPart === void 0)
52612
+ return -1;
52613
+ if (rightPart === void 0)
52614
+ return 1;
52615
+ const leftNumeric = /^\d+$/u.test(leftPart);
52616
+ const rightNumeric = /^\d+$/u.test(rightPart);
52617
+ if (leftNumeric && rightNumeric)
52618
+ return Number(leftPart) < Number(rightPart) ? -1 : 1;
52619
+ if (leftNumeric !== rightNumeric)
52620
+ return leftNumeric ? -1 : 1;
52621
+ return leftPart < rightPart ? -1 : 1;
52622
+ }
52623
+ return 0;
52624
+ }
52625
+ function expectedInstallerAssetName(version2, target) {
52626
+ requireSemanticVersion(version2);
52627
+ const extension = target.startsWith("macos-") ? "dmg" : "exe";
52628
+ return `codexhost-${version2}-${target}.${extension}`;
52629
+ }
52630
+ function selectInstallerReleaseArtifact(release, target) {
52631
+ const name = expectedInstallerAssetName(release.version, target);
52632
+ const matches = release.assets.filter((asset2) => asset2.name === name);
52633
+ if (matches.length !== 1)
52634
+ throw new Error(`GitHub Release must contain exactly one ${name}`);
52635
+ const asset = matches[0];
52636
+ if (!asset)
52637
+ throw new Error(`GitHub Release must contain exactly one ${name}`);
52638
+ const digest = asset.digest === null ? null : SHA256_DIGEST_PATTERN.exec(asset.digest);
52639
+ const sha256 = digest?.[1];
52640
+ if (!sha256)
52641
+ throw new Error(`GitHub Release asset ${name} has no valid SHA-256 digest`);
52642
+ const expectedPrefix = `${DOWNLOAD_URL_PREFIX}v${release.version}/`;
52643
+ if (!asset.downloadUrl.startsWith(expectedPrefix) || !asset.downloadUrl.endsWith(`/${name}`)) {
52644
+ throw new Error(`GitHub Release asset ${name} has an unexpected download URL`);
52645
+ }
52646
+ return Object.freeze({
52647
+ name,
52648
+ source: Object.freeze({ url: asset.downloadUrl, sha256, size: asset.size })
52649
+ });
52650
+ }
52651
+
52652
+ // packages/update-manager/dist/operation-state.js
52653
+ import { lstat as lstat2, mkdir as mkdir2, open as open3, readFile as readFile3, readdir as readdir2, rm as rm3, writeFile } from "node:fs/promises";
52654
+ import path9 from "node:path";
52655
+ var LOCK_FILE = "active-update-v1.lock";
52656
+ var STATUS_FILE = "status-v1.json";
52657
+ var TERMINAL_PHASES = /* @__PURE__ */ new Set(["succeeded", "failed"]);
52658
+ async function regularFile(filePath) {
52659
+ try {
52660
+ const metadata = await lstat2(filePath);
52661
+ return metadata.isFile() && !metadata.isSymbolicLink();
52662
+ } catch (error52) {
52663
+ if (error52.code === "ENOENT")
52664
+ return false;
52665
+ throw error52;
52666
+ }
52667
+ }
52668
+ async function discoverLatestUpdateStatus(stateDirectory) {
52669
+ if (!path9.isAbsolute(stateDirectory))
52670
+ throw new Error("update state directory must be absolute");
52671
+ let entries;
52672
+ try {
52673
+ entries = await readdir2(stateDirectory, { withFileTypes: true });
52674
+ } catch (error52) {
52675
+ if (error52.code === "ENOENT")
52676
+ return null;
52677
+ throw error52;
52678
+ }
52679
+ const candidates2 = [];
52680
+ for (const entry of entries) {
52681
+ if (!entry.isDirectory() || entry.isSymbolicLink() || !entry.name.startsWith("update-"))
52682
+ continue;
52683
+ const statusPath = path9.join(stateDirectory, entry.name, STATUS_FILE);
52684
+ if (!await regularFile(statusPath))
52685
+ continue;
52686
+ try {
52687
+ candidates2.push({
52688
+ statusPath,
52689
+ status: parseUpdateStatus(JSON.parse(await readFile3(statusPath, "utf8")))
52690
+ });
52691
+ } catch {
52692
+ }
52693
+ }
52694
+ candidates2.sort((left, right) => right.status.updatedAt - left.status.updatedAt);
52695
+ return candidates2[0] ?? null;
52696
+ }
52697
+ async function cleanupTerminalUpdateState(stateDirectory, options = {}) {
52698
+ const now = Math.floor((options.now ?? Date.now()) / 1e3);
52699
+ const retentionSeconds = options.retentionSeconds ?? 7 * 24 * 60 * 60;
52700
+ let entries;
52701
+ try {
52702
+ entries = await readdir2(stateDirectory, { withFileTypes: true });
52703
+ } catch (error52) {
52704
+ if (error52.code === "ENOENT")
52705
+ return;
52706
+ throw error52;
52707
+ }
52708
+ for (const entry of entries) {
52709
+ if (!entry.isDirectory() || entry.isSymbolicLink() || !entry.name.startsWith("update-"))
52710
+ continue;
52711
+ const directory = path9.join(stateDirectory, entry.name);
52712
+ const statusPath = path9.join(directory, STATUS_FILE);
52713
+ try {
52714
+ const status = parseUpdateStatus(JSON.parse(await readFile3(statusPath, "utf8")));
52715
+ if (TERMINAL_PHASES.has(status.phase) && now - status.updatedAt > retentionSeconds) {
52716
+ await rm3(directory, { recursive: true, force: true });
52717
+ }
52718
+ } catch {
52719
+ }
52720
+ }
52721
+ }
52722
+ async function acquireUpdateOperationLock(stateDirectory) {
52723
+ if (!path9.isAbsolute(stateDirectory))
52724
+ throw new Error("update state directory must be absolute");
52725
+ await mkdir2(stateDirectory, { recursive: true, mode: 448 });
52726
+ const lockPath = path9.join(stateDirectory, LOCK_FILE);
52727
+ let handle;
52728
+ try {
52729
+ handle = await open3(lockPath, "wx", 384);
52730
+ } catch (error52) {
52731
+ if (error52.code === "EEXIST")
52732
+ return null;
52733
+ throw error52;
52734
+ }
52735
+ await handle.writeFile(`${JSON.stringify({ ownerPid: process.pid, statusPath: null })}
52736
+ `, "utf8");
52737
+ await handle.sync();
52738
+ await handle.close();
52739
+ let released = false;
52740
+ return {
52741
+ path: lockPath,
52742
+ async setStatusPath(statusPath) {
52743
+ if (released)
52744
+ throw new Error("update operation lock is released");
52745
+ if (!path9.isAbsolute(statusPath))
52746
+ throw new Error("update status path must be absolute");
52747
+ await writeFile(lockPath, `${JSON.stringify({ ownerPid: process.pid, statusPath: path9.normalize(statusPath) })}
52748
+ `, { encoding: "utf8", mode: 384 });
52749
+ },
52750
+ async release() {
52751
+ if (released)
52752
+ return;
52753
+ released = true;
52754
+ await rm3(lockPath, { force: true });
52755
+ }
52756
+ };
52757
+ }
52758
+ async function recoverUpdateOperationLock(stateDirectory) {
52759
+ const lockPath = path9.join(stateDirectory, LOCK_FILE);
52760
+ if (!await regularFile(lockPath))
52761
+ return;
52762
+ let statusPath;
52763
+ try {
52764
+ const value = JSON.parse(await readFile3(lockPath, "utf8"));
52765
+ statusPath = value.statusPath;
52766
+ } catch {
52767
+ return;
52768
+ }
52769
+ if (typeof statusPath !== "string" || !path9.isAbsolute(statusPath))
52770
+ return;
52771
+ try {
52772
+ const status = parseUpdateStatus(JSON.parse(await readFile3(statusPath, "utf8")));
52773
+ if (TERMINAL_PHASES.has(status.phase))
52774
+ await rm3(lockPath, { force: true });
52775
+ } catch {
52776
+ }
52777
+ }
52778
+
52779
+ // packages/update-manager/dist/update-manager.js
52780
+ import { spawn as spawn4 } from "node:child_process";
52781
+ import { randomUUID as randomUUID8 } from "node:crypto";
52782
+ import { chmod, copyFile as copyFile2, lstat as lstat4, mkdir as mkdir3, readFile as readFile4, rename as rename2, rm as rm4, writeFile as writeFile2 } from "node:fs/promises";
52783
+ import path10 from "node:path";
52784
+
52785
+ // packages/update-manager/dist/artifact.js
52786
+ import { createHash as createHash2 } from "node:crypto";
52787
+ import { createReadStream } from "node:fs";
52788
+ import { lstat as lstat3, open as open4 } from "node:fs/promises";
52789
+ var MAX_ARTIFACT_BYTES = 2 * 1024 * 1024 * 1024;
52790
+ var SHA256_PATTERN = /^[0-9a-f]{64}$/u;
52791
+ function validateArtifact(source) {
52792
+ const url2 = new URL(source.url);
52793
+ if (url2.protocol !== "https:" || url2.username || url2.password) {
52794
+ throw new Error("update artifact URL must use HTTPS without credentials");
52795
+ }
52796
+ if (!SHA256_PATTERN.test(source.sha256)) {
52797
+ throw new Error("update artifact SHA-256 must be lowercase hexadecimal");
52798
+ }
52799
+ if (source.size !== void 0 && (!Number.isSafeInteger(source.size) || source.size <= 0 || source.size > MAX_ARTIFACT_BYTES)) {
52800
+ throw new Error(`update artifact size must be between 1 and ${MAX_ARTIFACT_BYTES} bytes`);
52801
+ }
52802
+ return source;
52803
+ }
52804
+ async function writeChunk(file2, chunk) {
52805
+ let offset = 0;
52806
+ while (offset < chunk.byteLength) {
52807
+ const result = await file2.write(chunk, offset, chunk.byteLength - offset, null);
52808
+ if (result.bytesWritten === 0)
52809
+ throw new Error("update artifact write made no progress");
52810
+ offset += result.bytesWritten;
52811
+ }
52812
+ }
52813
+ async function downloadArtifact(source, destination) {
52814
+ const response = await fetch(source.url, {
52815
+ redirect: "follow",
52816
+ headers: { "accept-encoding": "identity" }
52817
+ });
52818
+ if (!response.ok || response.body === null) {
52819
+ throw new Error(`update artifact download failed with HTTP ${response.status}`);
52820
+ }
52821
+ const finalUrl = new URL(response.url);
52822
+ if (finalUrl.protocol !== "https:" || finalUrl.username || finalUrl.password) {
52823
+ throw new Error("update artifact redirected to a non-HTTPS URL");
52824
+ }
52825
+ const file2 = await open4(destination, "wx", 384);
52826
+ let bytes = 0;
52827
+ try {
52828
+ const reader = response.body.getReader();
52829
+ while (true) {
52830
+ const item = await reader.read();
52831
+ if (item.done)
52832
+ break;
52833
+ bytes += item.value.byteLength;
52834
+ if (bytes > MAX_ARTIFACT_BYTES) {
52835
+ await reader.cancel();
52836
+ throw new Error(`update artifact exceeds ${MAX_ARTIFACT_BYTES} bytes`);
52837
+ }
52838
+ await writeChunk(file2, item.value);
52839
+ }
52840
+ await file2.sync();
52841
+ } finally {
52842
+ await file2.close();
52843
+ }
52844
+ return { bytes, finalUrl: finalUrl.toString() };
52845
+ }
52846
+ async function sha256File(filePath) {
52847
+ const hash2 = createHash2("sha256");
52848
+ await new Promise((resolve2, reject) => {
52849
+ const stream = createReadStream(filePath);
52850
+ stream.on("data", (chunk) => hash2.update(chunk));
52851
+ stream.once("error", reject);
52852
+ stream.once("end", resolve2);
52853
+ });
52854
+ return hash2.digest("hex");
52855
+ }
52856
+ async function verifyDownloadedArtifact(source, filePath, result) {
52857
+ const metadata = await lstat3(filePath);
52858
+ if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size === 0) {
52859
+ throw new Error("downloaded update artifact is not a non-empty regular file");
52860
+ }
52861
+ if (metadata.size !== result.bytes) {
52862
+ throw new Error("downloaded update artifact size changed after download");
52863
+ }
52864
+ if (source.size !== void 0 && metadata.size !== source.size) {
52865
+ throw new Error(`update artifact size mismatch: expected ${source.size}, got ${metadata.size}`);
52866
+ }
52867
+ const actual = await sha256File(filePath);
52868
+ if (actual !== source.sha256) {
52869
+ throw new Error(`update artifact SHA-256 mismatch: expected ${source.sha256}, got ${actual}`);
52870
+ }
52871
+ }
52872
+
52873
+ // packages/update-manager/dist/update-manager.js
52874
+ var REQUEST_SCHEMA_VERSION = 1;
52875
+ function errorMessage4(error52) {
52876
+ return error52 instanceof Error ? error52.message : String(error52);
52877
+ }
52878
+ function requireAbsolutePath(value, label) {
52879
+ if (!path10.isAbsolute(value))
52880
+ throw new Error(`${label} must be an absolute path`);
52881
+ return path10.normalize(value);
52882
+ }
52883
+ async function requireRegularFile(value, label) {
52884
+ const filePath = requireAbsolutePath(value, label);
52885
+ const metadata = await lstat4(filePath).catch((error52) => {
52886
+ throw new Error(`${label} is unavailable: ${errorMessage4(error52)}`);
52887
+ });
52888
+ if (!metadata.isFile() || metadata.isSymbolicLink()) {
52889
+ throw new Error(`${label} must be a regular file`);
52890
+ }
52891
+ return filePath;
52892
+ }
52893
+ function requireLauncherPid(value) {
52894
+ if (!Number.isSafeInteger(value) || value <= 0) {
52895
+ throw new Error("Launcher PID must be a positive integer");
52896
+ }
52897
+ return value;
52898
+ }
52899
+ async function writePrivateJson(filePath, value) {
52900
+ await writeFile2(filePath, `${JSON.stringify(value)}
52901
+ `, {
52902
+ encoding: "utf8",
52903
+ mode: 384,
52904
+ flag: "wx"
52905
+ });
52906
+ }
52907
+ function defaultSpawnUpdater(executable, requestPath) {
52908
+ const child = spawn4(executable, ["apply", "--request", requestPath], {
52909
+ detached: true,
52910
+ stdio: "ignore",
52911
+ windowsHide: true
52912
+ });
52913
+ child.unref();
52914
+ return child;
52915
+ }
52916
+ function createBackgroundUpdateManager(dependencies = {}) {
52917
+ const platform = dependencies.platform ?? process.platform;
52918
+ const randomId = dependencies.randomId ?? randomUUID8;
52919
+ const download = dependencies.download ?? downloadArtifact;
52920
+ const spawnUpdater = dependencies.spawnUpdater ?? defaultSpawnUpdater;
52921
+ const now = dependencies.now ?? Date.now;
52922
+ const preparedRequests = /* @__PURE__ */ new Set();
52923
+ async function prepareCommon(options, installation) {
52924
+ const version2 = requireSemanticVersion(options.version);
52925
+ const launcherPid = requireLauncherPid(options.launcherPid);
52926
+ const launcherExecutable = await requireRegularFile(options.launcherExecutable, "Launcher executable");
52927
+ const updaterExecutable = await requireRegularFile(options.updaterExecutable, "Updater executable");
52928
+ const stateDirectory = requireAbsolutePath(options.stateDirectory, "update state directory");
52929
+ await mkdir3(stateDirectory, { recursive: true, mode: 448 });
52930
+ const workDirectory = path10.join(stateDirectory, `update-${version2}-${randomId()}`);
52931
+ await mkdir3(workDirectory, { recursive: false, mode: 448 });
52932
+ const executableSuffix = platform === "win32" ? ".exe" : "";
52933
+ const helperPath = path10.join(workDirectory, `codexhost-updater${executableSuffix}`);
52934
+ await copyFile2(updaterExecutable, helperPath);
52935
+ if (platform !== "win32")
52936
+ await chmod(helperPath, 448);
52937
+ const requestPath = path10.join(workDirectory, "request-v1.json");
52938
+ const statusPath = path10.join(workDirectory, "status-v1.json");
52939
+ await writePrivateJson(statusPath, preparedStatus(version2, installation, now()));
52940
+ return {
52941
+ version: version2,
52942
+ launcherPid,
52943
+ launcherExecutable,
52944
+ workDirectory,
52945
+ helperPath,
52946
+ requestPath,
52947
+ statusPath
52948
+ };
52949
+ }
52950
+ async function prepareArtifact(sourceValue, workDirectory, fileName) {
52951
+ const source = validateArtifact(sourceValue);
52952
+ const temporaryPath = path10.join(workDirectory, `.${fileName}.download`);
52953
+ const artifactPath = path10.join(workDirectory, fileName);
52954
+ try {
52955
+ const result = await download(source, temporaryPath);
52956
+ const finalUrl = new URL(result.finalUrl);
52957
+ if (finalUrl.protocol !== "https:" || finalUrl.username || finalUrl.password) {
52958
+ throw new Error("update artifact downloader returned a non-HTTPS URL");
52959
+ }
52960
+ await verifyDownloadedArtifact(source, temporaryPath, result);
52961
+ await rename2(temporaryPath, artifactPath);
52962
+ return { source, artifactPath };
52963
+ } catch (error52) {
52964
+ await rm4(temporaryPath, { force: true });
52965
+ throw error52;
52966
+ }
52967
+ }
52968
+ async function finalize2(common, installation, artifactPath) {
52969
+ const request = {
52970
+ schema_version: REQUEST_SCHEMA_VERSION,
52971
+ version: common.version,
52972
+ wait_pid: common.launcherPid,
52973
+ wait_executable: common.launcherExecutable,
52974
+ status_path: common.statusPath,
52975
+ installation
52976
+ };
52977
+ await writePrivateJson(common.requestPath, request);
52978
+ preparedRequests.add(common.requestPath);
52979
+ return Object.freeze({
52980
+ version: common.version,
52981
+ installation: installation.kind,
52982
+ requestPath: common.requestPath,
52983
+ statusPath: common.statusPath,
52984
+ helperPath: common.helperPath,
52985
+ ...artifactPath === void 0 ? {} : { artifactPath }
52986
+ });
52987
+ }
52988
+ return Object.freeze({
52989
+ async prepareNpm(options) {
52990
+ const common = await prepareCommon(options, "npm");
52991
+ return finalize2(common, {
52992
+ kind: "npm",
52993
+ node_path: await requireRegularFile(options.nodePath, "npm Node.js executable"),
52994
+ npm_cli_path: await requireRegularFile(options.npmCliPath, "npm CLI"),
52995
+ npm_launcher_path: await requireRegularFile(options.npmLauncherPath, "npm codexhost launcher"),
52996
+ package_root: requireAbsolutePath(options.packageRoot, "npm platform package root")
52997
+ });
52998
+ },
52999
+ async prepareWindowsInstaller(options) {
53000
+ if (platform !== "win32")
53001
+ throw new Error("Windows installer updates require Windows");
53002
+ const common = await prepareCommon(options, "windows-installer");
53003
+ const artifact = await prepareArtifact(options.artifact, common.workDirectory, "update.exe");
53004
+ return finalize2(common, {
53005
+ kind: "windows-installer",
53006
+ installer_path: artifact.artifactPath,
53007
+ artifact_sha256: artifact.source.sha256,
53008
+ install_root: requireAbsolutePath(options.installRoot, "Windows install root")
53009
+ }, artifact.artifactPath);
53010
+ },
53011
+ async prepareMacOsDmg(options) {
53012
+ if (platform !== "darwin")
53013
+ throw new Error("macOS DMG updates require macOS");
53014
+ const common = await prepareCommon(options, "macos-dmg");
53015
+ const appPath = requireAbsolutePath(options.appPath, "macOS application path");
53016
+ if (path10.extname(appPath) !== ".app") {
53017
+ throw new Error("macOS application path must end in .app");
53018
+ }
53019
+ const artifact = await prepareArtifact(options.artifact, common.workDirectory, "update.dmg");
53020
+ return finalize2(common, {
53021
+ kind: "macos-dmg",
53022
+ dmg_path: artifact.artifactPath,
53023
+ artifact_sha256: artifact.source.sha256,
53024
+ app_path: appPath
53025
+ }, artifact.artifactPath);
53026
+ },
53027
+ start(prepared) {
53028
+ if (!preparedRequests.delete(prepared.requestPath)) {
53029
+ throw new Error("background update was not prepared by this manager or was already started");
53030
+ }
53031
+ const child = spawnUpdater(prepared.helperPath, prepared.requestPath);
53032
+ if (child.pid === void 0)
53033
+ throw new Error("background Updater did not report a process ID");
53034
+ return Object.freeze({ ...prepared, updaterPid: child.pid });
53035
+ },
53036
+ async readStatus(statusPathValue) {
53037
+ const statusPath = requireAbsolutePath(statusPathValue, "update status path");
53038
+ try {
53039
+ return parseUpdateStatus(JSON.parse(await readFile4(statusPath, "utf8")));
53040
+ } catch (error52) {
53041
+ if (error52.code === "ENOENT")
53042
+ return null;
53043
+ throw error52;
53044
+ }
53045
+ }
53046
+ });
53047
+ }
53048
+
53049
+ // packages/host-runtime/src/update-coordinator.ts
53050
+ var ERROR_MAX_LENGTH = 500;
53051
+ var CONTROLLER_TIMEOUT_MS = 5e3;
53052
+ async function startCompatibilityUpdate(coordinator) {
53053
+ try {
53054
+ const check2 = await coordinator.check();
53055
+ if (check2.updateAvailable && check2.installationAvailable) {
53056
+ void coordinator.start().catch(() => void 0);
53057
+ return "update-started";
53058
+ }
53059
+ if (check2.error === null && check2.latestVersion !== null && check2.currentVersion === check2.latestVersion) {
53060
+ return "current";
53061
+ }
53062
+ } catch {
53063
+ }
53064
+ return "unavailable";
53065
+ }
53066
+ function boundedError(error52) {
53067
+ const message3 = error52 instanceof Error ? error52.message : String(error52);
53068
+ return message3.slice(0, ERROR_MAX_LENGTH) || "Update operation failed";
53069
+ }
53070
+ function publicStatus(status) {
53071
+ return {
53072
+ version: status.version,
53073
+ installation: status.installation,
53074
+ phase: status.phase,
53075
+ updatedAt: status.updatedAt,
53076
+ error: status.error?.slice(0, ERROR_MAX_LENGTH) ?? null
53077
+ };
53078
+ }
53079
+ function requestControllerShutdown(controller) {
53080
+ return new Promise((resolve2, reject) => {
53081
+ const socket = createConnection({ host: "127.0.0.1", port: controller.port });
53082
+ let response = "";
53083
+ const timeout = setTimeout(
53084
+ () => socket.destroy(new Error("Controller shutdown timed out")),
53085
+ CONTROLLER_TIMEOUT_MS
53086
+ );
53087
+ const settle = (operation) => {
53088
+ clearTimeout(timeout);
53089
+ operation();
53090
+ };
53091
+ socket.setEncoding("utf8");
53092
+ socket.once("error", (error52) => settle(() => reject(error52)));
53093
+ socket.on("data", (chunk) => {
53094
+ response += chunk;
53095
+ });
53096
+ socket.once(
53097
+ "end",
53098
+ () => settle(() => {
53099
+ if (response === "ready\n") resolve2();
53100
+ else reject(new Error("Desktop Controller rejected the shutdown request"));
53101
+ })
53102
+ );
53103
+ socket.once("connect", () => socket.write(`SHUTDOWN ${controller.nonce}
53104
+ `));
53105
+ });
53106
+ }
53107
+ function createHostUpdateCoordinator(options) {
53108
+ const manager = options.manager ?? createBackgroundUpdateManager(options.platform ? { platform: options.platform } : {});
53109
+ const contextPromise = resolveInstalledUpdateContext({
53110
+ hostRuntimePath: options.hostRuntimePath,
53111
+ ...options.environment ? { environment: options.environment } : {},
53112
+ ...options.platform ? { platform: options.platform } : {},
53113
+ ...options.architecture ? { architecture: options.architecture } : {}
53114
+ });
53115
+ const fetchLatest = options.fetchLatest ?? ((signal) => fetchLatestGitHubRelease({ signal: signal ?? AbortSignal.timeout(15e3) }));
53116
+ const shutdown = options.shutdown ?? requestControllerShutdown;
53117
+ let candidate = null;
53118
+ let shutdownPending = null;
53119
+ async function latestStatus(context) {
53120
+ const discovered = await discoverLatestUpdateStatus(context.common.stateDirectory);
53121
+ return discovered ? publicStatus(discovered.status) : null;
53122
+ }
53123
+ async function installable(context, release) {
53124
+ if (context.metadata.distribution === "npm") return true;
53125
+ try {
53126
+ selectInstallerReleaseArtifact(release, context.metadata.target);
53127
+ return true;
53128
+ } catch {
53129
+ return false;
53130
+ }
53131
+ }
53132
+ return Object.freeze({
53133
+ async check(signal) {
53134
+ let context;
53135
+ try {
53136
+ context = await contextPromise;
53137
+ await recoverUpdateOperationLock(context.common.stateDirectory);
53138
+ await cleanupTerminalUpdateState(context.common.stateDirectory);
53139
+ } catch (error52) {
53140
+ return {
53141
+ currentVersion: "0.0.0",
53142
+ latestVersion: null,
53143
+ updateAvailable: false,
53144
+ installationAvailable: false,
53145
+ releaseNotes: null,
53146
+ releaseNotesUrl: null,
53147
+ status: null,
53148
+ error: boundedError(error52)
53149
+ };
53150
+ }
53151
+ const status = await latestStatus(context);
53152
+ try {
53153
+ const release = await fetchLatest(signal);
53154
+ candidate = release;
53155
+ const updateAvailable = compareSemanticVersions(context.metadata.version, release.version) < 0;
53156
+ let installationAvailable = false;
53157
+ let error52 = null;
53158
+ if (updateAvailable) {
53159
+ installationAvailable = await installable(context, release);
53160
+ if (!installationAvailable) {
53161
+ error52 = "The latest GitHub Release has no verified asset for this installation";
53162
+ }
53163
+ }
53164
+ return {
53165
+ currentVersion: context.metadata.version,
53166
+ latestVersion: release.version,
53167
+ updateAvailable,
53168
+ installationAvailable,
53169
+ releaseNotes: release.releaseNotes,
53170
+ releaseNotesUrl: release.releaseNotesUrl,
53171
+ status,
53172
+ error: error52
53173
+ };
53174
+ } catch (error52) {
53175
+ return {
53176
+ currentVersion: context.metadata.version,
53177
+ latestVersion: null,
53178
+ updateAvailable: false,
53179
+ installationAvailable: false,
53180
+ releaseNotes: null,
53181
+ releaseNotesUrl: null,
53182
+ status,
53183
+ error: boundedError(error52)
53184
+ };
53185
+ }
53186
+ },
53187
+ async start() {
53188
+ const context = await contextPromise;
53189
+ await recoverUpdateOperationLock(context.common.stateDirectory);
53190
+ const lock = await acquireUpdateOperationLock(context.common.stateDirectory);
53191
+ if (!lock) {
53192
+ const existing = await latestStatus(context);
53193
+ if (existing) return { status: existing };
53194
+ throw new Error("Another update operation is already active");
53195
+ }
53196
+ try {
53197
+ const release = await fetchLatest();
53198
+ if (compareSemanticVersions(context.metadata.version, release.version) >= 0 || candidate && candidate.version !== release.version) {
53199
+ throw new Error("The selected update is no longer the current GitHub Release");
53200
+ }
53201
+ let prepared;
53202
+ if (context.installation.kind === "npm") {
53203
+ prepared = await manager.prepareNpm({
53204
+ ...context.installation.options,
53205
+ version: release.version
53206
+ });
53207
+ } else {
53208
+ const artifact = selectInstallerReleaseArtifact(release, context.metadata.target).source;
53209
+ prepared = context.installation.kind === "windows-installer" ? await manager.prepareWindowsInstaller({
53210
+ ...context.installation.options,
53211
+ version: release.version,
53212
+ artifact
53213
+ }) : await manager.prepareMacOsDmg({
53214
+ ...context.installation.options,
53215
+ version: release.version,
53216
+ artifact
53217
+ });
53218
+ }
53219
+ await lock.setStatusPath(prepared.statusPath);
53220
+ manager.start(prepared);
53221
+ const status = await manager.readStatus(prepared.statusPath);
53222
+ if (!status) throw new Error("Background Updater did not create status");
53223
+ shutdownPending = context.controller;
53224
+ return { status: publicStatus(status) };
53225
+ } catch (error52) {
53226
+ await lock.release();
53227
+ throw error52;
53228
+ }
53229
+ },
53230
+ async status() {
53231
+ try {
53232
+ const context = await contextPromise;
53233
+ await recoverUpdateOperationLock(context.common.stateDirectory);
53234
+ return { status: await latestStatus(context) };
53235
+ } catch {
53236
+ return { status: null };
53237
+ }
53238
+ },
53239
+ requestShutdown() {
53240
+ const controller = shutdownPending;
53241
+ shutdownPending = null;
53242
+ if (!controller) return;
53243
+ setTimeout(() => void shutdown(controller).catch(() => void 0), 50).unref();
53244
+ }
53245
+ });
53246
+ }
53247
+
51958
53248
  // packages/host-runtime/src/release-main.ts
51959
53249
  var STOCK_CODEX_PATH_ENV = "CODEXHOST_STOCK_CODEX_PATH";
51960
53250
  var DEFAULT_AGENT_ENV = "CODEXHOST_DEFAULT_AGENT";
51961
- var stockCodexPath = process.env[STOCK_CODEX_PATH_ENV];
51962
- if (!stockCodexPath) throw new Error(`${STOCK_CODEX_PATH_ENV} is required`);
51963
- var defaultAgent = process.env[DEFAULT_AGENT_ENV];
51964
- if (defaultAgent !== "codex" && defaultAgent !== "pi") {
51965
- throw new Error(`${DEFAULT_AGENT_ENV} must be 'codex' or 'pi'`);
51966
- }
51967
- var externalAdapters = createExternalHarnessAdapters(process.env);
51968
- var host = new AppServerHost({
51969
- stockCodexPath,
51970
- arguments: process.argv.slice(2),
51971
- defaultAgent,
51972
- environment: process.env,
51973
- externalAdapters
51974
- });
51975
- void prefetchClaudeCodeModelCatalog(externalAdapters);
51976
- process.exitCode = await host.run();
53251
+ var COMPATIBILITY_UPDATE_ARGUMENT = "--codexhost-compatibility-update";
53252
+ var arguments_ = process.argv.slice(2);
53253
+ var updateCoordinator = createHostUpdateCoordinator({
53254
+ hostRuntimePath: fileURLToPath(import.meta.url),
53255
+ environment: process.env
53256
+ });
53257
+ if (arguments_.length === 1 && arguments_[0] === COMPATIBILITY_UPDATE_ARGUMENT) {
53258
+ process.stdout.write(`${await startCompatibilityUpdate(updateCoordinator)}
53259
+ `);
53260
+ } else {
53261
+ const stockCodexPath = process.env[STOCK_CODEX_PATH_ENV];
53262
+ if (!stockCodexPath) throw new Error(`${STOCK_CODEX_PATH_ENV} is required`);
53263
+ const defaultAgent = process.env[DEFAULT_AGENT_ENV];
53264
+ if (defaultAgent !== "codex" && defaultAgent !== "pi") {
53265
+ throw new Error(`${DEFAULT_AGENT_ENV} must be 'codex' or 'pi'`);
53266
+ }
53267
+ const externalAdapters = createExternalHarnessAdapters(process.env);
53268
+ const host = new AppServerHost({
53269
+ stockCodexPath,
53270
+ arguments: arguments_,
53271
+ defaultAgent,
53272
+ environment: process.env,
53273
+ externalAdapters,
53274
+ updateCoordinator
53275
+ });
53276
+ void prefetchClaudeCodeModelCatalog(externalAdapters);
53277
+ process.exitCode = await host.run();
53278
+ }