remote-codex 0.1.7 → 0.1.9

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.
@@ -5,15 +5,15 @@ var __export = (target, all) => {
5
5
  };
6
6
 
7
7
  // src/index.ts
8
- import fs15 from "fs";
8
+ import fs16 from "fs";
9
9
 
10
10
  // src/app.ts
11
11
  import Fastify from "fastify";
12
12
  import multipart from "@fastify/multipart";
13
13
  import websocket from "@fastify/websocket";
14
14
  import { spawn as spawn3 } from "child_process";
15
- import fs14 from "fs";
16
- import path14 from "path";
15
+ import fs15 from "fs";
16
+ import path15 from "path";
17
17
  import { ZodError } from "zod";
18
18
 
19
19
  // ../../packages/config/src/index.ts
@@ -32,7 +32,10 @@ var envSchema = z.object({
32
32
  DATABASE_URL: z.string().optional(),
33
33
  CODEX_HOME: z.string().optional(),
34
34
  CODEX_COMMAND: z.string().min(1).optional(),
35
- CODEX_APP_SERVER_START_TIMEOUT_MS: z.coerce.number().int().positive().optional()
35
+ CODEX_APP_SERVER_START_TIMEOUT_MS: z.coerce.number().int().positive().optional(),
36
+ CLAUDE_HOME: z.string().optional(),
37
+ CLAUDE_COMMAND: z.string().min(1).optional(),
38
+ REMOTE_CODEX_ENABLED_AGENT_PROVIDERS: z.string().optional()
36
39
  });
37
40
  function resolveDatabaseUrl(nodeEnv, value) {
38
41
  if (value && value.trim()) {
@@ -48,6 +51,11 @@ function loadRuntimeConfig(env = process.env) {
48
51
  const nodeEnv = parsed.NODE_ENV ?? "development";
49
52
  const workspaceRoot = parsed.WORKSPACE_ROOT?.trim() ? path.resolve(parsed.WORKSPACE_ROOT) : os.homedir();
50
53
  const disableRequestLogging = parsed.DISABLE_REQUEST_LOGGING === void 0 ? nodeEnv === "production" : ["1", "true", "yes", "on"].includes(parsed.DISABLE_REQUEST_LOGGING.toLowerCase());
54
+ const enabledProviders = new Set(
55
+ (parsed.REMOTE_CODEX_ENABLED_AGENT_PROVIDERS ?? "codex").split(",").map((provider) => provider.trim().toLowerCase()).filter(Boolean)
56
+ );
57
+ const codexHome = parsed.CODEX_HOME?.trim() ? path.resolve(parsed.CODEX_HOME) : path.join(os.homedir(), ".codex");
58
+ const claudeHome = parsed.CLAUDE_HOME?.trim() ? path.resolve(parsed.CLAUDE_HOME) : path.join(os.homedir(), ".claude");
51
59
  return {
52
60
  nodeEnv,
53
61
  host: parsed.HOST ?? "127.0.0.1",
@@ -58,9 +66,21 @@ function loadRuntimeConfig(env = process.env) {
58
66
  appVersion: parsed.APP_VERSION ?? "0.1.0",
59
67
  workspaceRoot,
60
68
  databaseUrl: resolveDatabaseUrl(nodeEnv, parsed.DATABASE_URL),
61
- codexHome: parsed.CODEX_HOME?.trim() ? path.resolve(parsed.CODEX_HOME) : path.join(os.homedir(), ".codex"),
62
- codexCommand: parsed.CODEX_COMMAND ?? "codex",
63
- codexAppServerStartTimeoutMs: parsed.CODEX_APP_SERVER_START_TIMEOUT_MS ?? 1e4
69
+ agentProviders: {
70
+ codex: {
71
+ provider: "codex",
72
+ enabled: enabledProviders.has("codex"),
73
+ home: codexHome,
74
+ command: parsed.CODEX_COMMAND ?? "codex",
75
+ appServerStartTimeoutMs: parsed.CODEX_APP_SERVER_START_TIMEOUT_MS ?? 1e4
76
+ },
77
+ claude: {
78
+ provider: "claude",
79
+ enabled: enabledProviders.has("claude"),
80
+ home: claudeHome,
81
+ command: parsed.CLAUDE_COMMAND ?? "claude"
82
+ }
83
+ }
64
84
  };
65
85
  }
66
86
 
@@ -1281,7 +1301,7 @@ Subquery.prototype.getSQL = function() {
1281
1301
  function mapResultRow(columns, row, joinsNotNullableMap) {
1282
1302
  const nullifyMap = {};
1283
1303
  const result = columns.reduce(
1284
- (result2, { path: path15, field }, columnIndex) => {
1304
+ (result2, { path: path16, field }, columnIndex) => {
1285
1305
  let decoder;
1286
1306
  if (is(field, Column)) {
1287
1307
  decoder = field;
@@ -1291,8 +1311,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
1291
1311
  decoder = field.sql.decoder;
1292
1312
  }
1293
1313
  let node = result2;
1294
- for (const [pathChunkIndex, pathChunk] of path15.entries()) {
1295
- if (pathChunkIndex < path15.length - 1) {
1314
+ for (const [pathChunkIndex, pathChunk] of path16.entries()) {
1315
+ if (pathChunkIndex < path16.length - 1) {
1296
1316
  if (!(pathChunk in node)) {
1297
1317
  node[pathChunk] = {};
1298
1318
  }
@@ -1300,8 +1320,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
1300
1320
  } else {
1301
1321
  const rawValue = row[columnIndex];
1302
1322
  const value = node[pathChunk] = rawValue === null ? null : decoder.mapFromDriverValue(rawValue);
1303
- if (joinsNotNullableMap && is(field, Column) && path15.length === 2) {
1304
- const objectName = path15[0];
1323
+ if (joinsNotNullableMap && is(field, Column) && path16.length === 2) {
1324
+ const objectName = path16[0];
1305
1325
  if (!(objectName in nullifyMap)) {
1306
1326
  nullifyMap[objectName] = value === null ? getTableName(field.table) : false;
1307
1327
  } else if (typeof nullifyMap[objectName] === "string" && nullifyMap[objectName] !== getTableName(field.table)) {
@@ -5879,6 +5899,9 @@ function getActiveThreadGoalRecord(db, threadId) {
5879
5899
  ) ?? null;
5880
5900
  }
5881
5901
  function getThreadGoalRecordForUpsert(db, input) {
5902
+ if (input.createNew) {
5903
+ return null;
5904
+ }
5882
5905
  if (input.localGoalId) {
5883
5906
  const byId = db.select().from(threadGoals).where(eq(threadGoals.id, input.localGoalId)).get();
5884
5907
  if (byId?.threadId === input.threadId) {
@@ -5886,7 +5909,7 @@ function getThreadGoalRecordForUpsert(db, input) {
5886
5909
  }
5887
5910
  }
5888
5911
  const active = getActiveThreadGoalRecord(db, input.threadId);
5889
- if (active) {
5912
+ if (active && active.objective === input.objective) {
5890
5913
  return active;
5891
5914
  }
5892
5915
  const matchingObjective = db.select().from(threadGoals).where(
@@ -7527,7 +7550,7 @@ function extractFileChangeEntries(item) {
7527
7550
  isRecord2(entry.summary) ? entry.summary : null,
7528
7551
  isRecord2(entry.diff) ? entry.diff : null
7529
7552
  ].filter((candidate) => Boolean(candidate));
7530
- const path15 = uniqueStrings([
7553
+ const path16 = uniqueStrings([
7531
7554
  stringOrNull(valueFromRecords(nestedRecords, ["path", "filePath", "targetPath"])),
7532
7555
  stringOrNull(
7533
7556
  valueFromRecords(nestedRecords, [
@@ -7574,7 +7597,7 @@ function extractFileChangeEntries(item) {
7574
7597
  const diffStats = explicitAdditions === 0 && explicitDeletions === 0 && diffText ? countUnifiedDiffStats(diffText) : null;
7575
7598
  const additions = explicitAdditions || diffStats?.additions || 0;
7576
7599
  const deletions = explicitDeletions || diffStats?.deletions || 0;
7577
- const normalizedPath = path15 ?? (diffText ? extractPathFromDiffText(diffText) : null);
7600
+ const normalizedPath = path16 ?? (diffText ? extractPathFromDiffText(diffText) : null);
7578
7601
  if (!normalizedPath && additions === 0 && deletions === 0) {
7579
7602
  return null;
7580
7603
  }
@@ -8121,7 +8144,7 @@ var codexCapabilities = {
8121
8144
  planMode: true,
8122
8145
  permissionRequests: true,
8123
8146
  sandboxMode: true,
8124
- fastServiceTier: true,
8147
+ performanceMode: true,
8125
8148
  goals: true
8126
8149
  },
8127
8150
  management: {
@@ -8200,6 +8223,43 @@ function mapGoal(goal) {
8200
8223
  rawGoal: goal
8201
8224
  };
8202
8225
  }
8226
+ function serviceTierForPerformanceMode(performanceMode) {
8227
+ if (performanceMode === void 0) {
8228
+ return void 0;
8229
+ }
8230
+ return performanceMode === "fast" ? "fast" : null;
8231
+ }
8232
+ function buildSandboxPolicy(sandboxMode, workspacePath) {
8233
+ if (sandboxMode === void 0) {
8234
+ return void 0;
8235
+ }
8236
+ switch (sandboxMode) {
8237
+ case "danger-full-access":
8238
+ return {
8239
+ type: "dangerFullAccess"
8240
+ };
8241
+ case "read-only":
8242
+ return {
8243
+ type: "readOnly",
8244
+ access: {
8245
+ type: "fullAccess"
8246
+ },
8247
+ networkAccess: false
8248
+ };
8249
+ case "workspace-write":
8250
+ default:
8251
+ return {
8252
+ type: "workspaceWrite",
8253
+ writableRoots: workspacePath ? [workspacePath] : [],
8254
+ readOnlyAccess: {
8255
+ type: "fullAccess"
8256
+ },
8257
+ networkAccess: false,
8258
+ excludeTmpdirEnvVar: false,
8259
+ excludeSlashTmp: false
8260
+ };
8261
+ }
8262
+ }
8203
8263
  function mapSession(thread) {
8204
8264
  return {
8205
8265
  provider: "codex",
@@ -8465,6 +8525,8 @@ var CodexRuntimeAdapter = class extends EventEmitter3 {
8465
8525
  command: `node -e 'process.stdin.resume(); process.stdin.on("end", () => console.log(JSON.stringify({ systemMessage: "remote-codex hook ran" })))'`
8466
8526
  }
8467
8527
  ],
8528
+ providerConfigFormat: "toml",
8529
+ mcpConfigFormat: "codex-toml",
8468
8530
  configArchives: true,
8469
8531
  buildRestart: true
8470
8532
  };
@@ -8511,8 +8573,9 @@ var CodexRuntimeAdapter = class extends EventEmitter3 {
8511
8573
  if (input.sandboxMode !== void 0) {
8512
8574
  startInput.sandbox = input.sandboxMode;
8513
8575
  }
8514
- if (input.serviceTier !== void 0) {
8515
- startInput.serviceTier = input.serviceTier;
8576
+ const serviceTier = serviceTierForPerformanceMode(input.performanceMode);
8577
+ if (serviceTier !== void 0) {
8578
+ startInput.serviceTier = serviceTier;
8516
8579
  }
8517
8580
  const response = await codexRuntimeCall(() => this.manager.startThread(startInput));
8518
8581
  return {
@@ -8535,8 +8598,9 @@ var CodexRuntimeAdapter = class extends EventEmitter3 {
8535
8598
  if (input.sandboxMode !== void 0) {
8536
8599
  resumeInput.sandbox = input.sandboxMode;
8537
8600
  }
8538
- if (input.serviceTier !== void 0) {
8539
- resumeInput.serviceTier = input.serviceTier;
8601
+ const serviceTier = serviceTierForPerformanceMode(input.performanceMode);
8602
+ if (serviceTier !== void 0) {
8603
+ resumeInput.serviceTier = serviceTier;
8540
8604
  }
8541
8605
  const response = await codexRuntimeCall(() => this.manager.resumeThread(resumeInput));
8542
8606
  return {
@@ -8563,11 +8627,13 @@ var CodexRuntimeAdapter = class extends EventEmitter3 {
8563
8627
  if (input.collaborationMode !== void 0) {
8564
8628
  turnInput.collaborationMode = input.collaborationMode;
8565
8629
  }
8566
- if (input.sandboxPolicy !== void 0) {
8567
- turnInput.sandboxPolicy = input.sandboxPolicy;
8630
+ const sandboxPolicy = buildSandboxPolicy(input.sandboxMode, input.workspacePath);
8631
+ if (sandboxPolicy !== void 0) {
8632
+ turnInput.sandboxPolicy = sandboxPolicy;
8568
8633
  }
8569
- if (input.serviceTier !== void 0) {
8570
- turnInput.serviceTier = input.serviceTier;
8634
+ const serviceTier = serviceTierForPerformanceMode(input.performanceMode);
8635
+ if (serviceTier !== void 0) {
8636
+ turnInput.serviceTier = serviceTier;
8571
8637
  }
8572
8638
  return mapTurn2(await codexRuntimeCall(() => this.manager.startTurn(turnInput)));
8573
8639
  }
@@ -8640,174 +8706,713 @@ var CodexRuntimeAdapter = class extends EventEmitter3 {
8640
8706
  }
8641
8707
  };
8642
8708
 
8643
- // src/codex/local-session-store.ts
8644
- import fs4 from "fs/promises";
8645
- import path5 from "path";
8646
- import Database2 from "better-sqlite3";
8709
+ // src/codex/codex-management-service.ts
8710
+ import fs5 from "fs/promises";
8711
+ import path6 from "path";
8647
8712
 
8648
- // src/codex/thread-title.ts
8649
- var AUTO_THREAD_TITLE_MAX_CHARS = 15;
8650
- function normalizeWhitespace(value) {
8651
- return value.replace(/\s+/g, " ").trim();
8713
+ // src/codex/codexHostConfig.ts
8714
+ import fs4 from "fs";
8715
+ import fsp from "fs/promises";
8716
+ import path5 from "path";
8717
+ var SERVICE_TIER_LINE_GLOBAL_PATTERN = /^\s*service_tier\s*=\s*("fast"|"flex"|'fast'|'flex').*\n?/gm;
8718
+ function resolveCodexConfigPath(codexHome) {
8719
+ return path5.join(codexHome, "config.toml");
8652
8720
  }
8653
- function truncateAutoThreadTitle(value) {
8654
- const normalized = normalizeWhitespace(value);
8655
- if (!normalized) {
8656
- return "";
8657
- }
8658
- const characters = Array.from(normalized);
8659
- if (characters.length <= AUTO_THREAD_TITLE_MAX_CHARS) {
8660
- return normalized;
8661
- }
8662
- return `${characters.slice(0, AUTO_THREAD_TITLE_MAX_CHARS).join("")}...`;
8721
+ function parseCodexServiceTier(content) {
8722
+ const match = content.match(
8723
+ /^\s*service_tier\s*=\s*["'](?<tier>fast)["']\s*$/m
8724
+ );
8725
+ const tier = match?.groups?.tier;
8726
+ return tier === "fast" ? tier : null;
8663
8727
  }
8664
-
8665
- // src/codex/local-session-store.ts
8666
- function basenameFromPath(absPath) {
8667
- const normalized = absPath.replace(/[\\/]+$/, "");
8668
- return normalized.split(/[\\/]/).at(-1) ?? normalized;
8728
+ function isFastModeEnabledFromConfig(content) {
8729
+ return parseCodexServiceTier(content) === "fast";
8669
8730
  }
8670
- function summarizeTitleFromTurns(turns) {
8671
- const firstUserMessage = turns.flatMap((turn) => turn.items).find((item) => item.kind === "userMessage" && item.text.trim());
8672
- if (!firstUserMessage) {
8673
- return null;
8731
+ function readCodexFastModeSync(codexHome) {
8732
+ try {
8733
+ const content = fs4.readFileSync(resolveCodexConfigPath(codexHome), "utf8");
8734
+ return isFastModeEnabledFromConfig(content);
8735
+ } catch (error) {
8736
+ if (error.code === "ENOENT") {
8737
+ return false;
8738
+ }
8739
+ throw error;
8674
8740
  }
8675
- return truncateAutoThreadTitle(firstUserMessage.text);
8676
- }
8677
- function createHistoryItemId(turnId, prefix, index) {
8678
- return `${turnId}-${prefix}-${index}`;
8679
8741
  }
8680
- function finalizeTurn(turn, turns) {
8681
- if (!turn || turn.items.length === 0) {
8682
- return;
8742
+ function upsertCodexServiceTier(content, enabled) {
8743
+ const normalized = content.replace(/\r\n/g, "\n");
8744
+ const withoutServiceTier = normalized.replace(
8745
+ SERVICE_TIER_LINE_GLOBAL_PATTERN,
8746
+ ""
8747
+ );
8748
+ if (!enabled) {
8749
+ return withoutServiceTier.replace(/\n{3,}/g, "\n\n").trimEnd() + (withoutServiceTier.trim() ? "\n" : "");
8683
8750
  }
8684
- turns.push({
8685
- id: turn.id,
8686
- startedAt: turn.startedAt,
8687
- status: turn.status,
8688
- error: turn.error,
8689
- items: turn.items
8690
- });
8751
+ const nextLine = 'service_tier = "fast"';
8752
+ if (!withoutServiceTier.trim()) {
8753
+ return `${nextLine}
8754
+ `;
8755
+ }
8756
+ const firstSectionMatch = withoutServiceTier.match(/^\s*\[[^\]]+\]/m);
8757
+ if (!firstSectionMatch || firstSectionMatch.index === void 0) {
8758
+ return withoutServiceTier.endsWith("\n") ? `${withoutServiceTier}${nextLine}
8759
+ ` : `${withoutServiceTier}
8760
+ ${nextLine}
8761
+ `;
8762
+ }
8763
+ const beforeFirstSection = withoutServiceTier.slice(0, firstSectionMatch.index).trimEnd();
8764
+ const afterFirstSection = withoutServiceTier.slice(firstSectionMatch.index).replace(/^\n+/, "");
8765
+ return beforeFirstSection ? `${beforeFirstSection}
8766
+ ${nextLine}
8767
+ ${afterFirstSection}` : `${nextLine}
8768
+ ${afterFirstSection}`;
8691
8769
  }
8692
- function parseTranscript(contents) {
8693
- const turns = [];
8694
- let cwd = null;
8695
- let currentTurn = null;
8696
- let fallbackTurnCount = 0;
8697
- let agentItemCount = 0;
8698
- let userItemCount = 0;
8699
- const ensureCurrentTurn = (timestamp) => {
8700
- if (currentTurn) {
8701
- return currentTurn;
8702
- }
8703
- fallbackTurnCount += 1;
8704
- currentTurn = {
8705
- id: `local-turn-${fallbackTurnCount}`,
8706
- startedAt: timestamp ?? null,
8707
- status: "inProgress",
8708
- error: null,
8709
- items: []
8710
- };
8711
- agentItemCount = 0;
8712
- userItemCount = 0;
8713
- return currentTurn;
8714
- };
8715
- for (const line of contents.split("\n")) {
8716
- if (!line.trim()) {
8717
- continue;
8718
- }
8719
- let entry;
8720
- try {
8721
- entry = JSON.parse(line);
8722
- } catch {
8723
- continue;
8724
- }
8725
- if (entry.type === "session_meta") {
8726
- const payload2 = entry.payload ?? {};
8727
- if (typeof payload2.cwd === "string" && payload2.cwd.trim()) {
8728
- cwd = payload2.cwd;
8729
- }
8730
- continue;
8731
- }
8732
- if (entry.type !== "event_msg") {
8733
- continue;
8734
- }
8735
- const payload = entry.payload ?? {};
8736
- const payloadType = payload.type;
8737
- if (payloadType === "task_started") {
8738
- finalizeTurn(currentTurn, turns);
8739
- currentTurn = {
8740
- id: typeof payload.turn_id === "string" && payload.turn_id.trim() ? payload.turn_id : `local-turn-${fallbackTurnCount + 1}`,
8741
- startedAt: entry.timestamp ?? null,
8742
- status: "inProgress",
8743
- error: null,
8744
- items: []
8745
- };
8746
- agentItemCount = 0;
8747
- userItemCount = 0;
8748
- continue;
8749
- }
8750
- if (payloadType === "user_message" && typeof payload.message === "string") {
8751
- const turn = ensureCurrentTurn(entry.timestamp);
8752
- userItemCount += 1;
8753
- turn.items.push({
8754
- id: createHistoryItemId(turn.id, "user", userItemCount),
8755
- kind: "userMessage",
8756
- text: payload.message
8757
- });
8758
- continue;
8759
- }
8760
- if (payloadType === "agent_message" && typeof payload.message === "string") {
8761
- const turn = ensureCurrentTurn(entry.timestamp);
8762
- agentItemCount += 1;
8763
- turn.items.push({
8764
- id: createHistoryItemId(turn.id, "agent", agentItemCount),
8765
- kind: "agentMessage",
8766
- text: payload.message,
8767
- status: typeof payload.phase === "string" ? payload.phase : null
8768
- });
8769
- continue;
8770
- }
8771
- if (payloadType === "task_complete") {
8772
- const turn = ensureCurrentTurn(entry.timestamp);
8773
- turn.status = turn.error ? "failed" : "completed";
8774
- finalizeTurn(turn, turns);
8775
- currentTurn = null;
8776
- continue;
8777
- }
8778
- if (payloadType === "error") {
8779
- const turn = ensureCurrentTurn(entry.timestamp);
8780
- turn.status = "failed";
8781
- turn.error = typeof payload.message === "string" ? payload.message : "Local Codex session failed.";
8770
+ async function writeCodexFastMode(codexHome, enabled) {
8771
+ const configPath = resolveCodexConfigPath(codexHome);
8772
+ let current = "";
8773
+ try {
8774
+ current = await fsp.readFile(configPath, "utf8");
8775
+ } catch (error) {
8776
+ if (error.code !== "ENOENT") {
8777
+ throw error;
8782
8778
  }
8783
8779
  }
8784
- finalizeTurn(currentTurn, turns);
8785
- return {
8786
- cwd,
8787
- title: summarizeTitleFromTurns(turns),
8788
- turns
8789
- };
8780
+ const next = upsertCodexServiceTier(current, enabled);
8781
+ await fsp.mkdir(path5.dirname(configPath), { recursive: true });
8782
+ await fsp.writeFile(configPath, next, "utf8");
8783
+ return next;
8790
8784
  }
8791
- async function fileExists(filePath) {
8792
- try {
8793
- await fs4.access(filePath);
8794
- return true;
8795
- } catch {
8785
+ function isCodexFeatureEnabledFromConfig(content, featureName) {
8786
+ const escaped = featureName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8787
+ const featuresMatch = content.match(/^[^\S\r\n]*\[features\][^\S\r\n]*$/m);
8788
+ if (!featuresMatch || featuresMatch.index === void 0) {
8796
8789
  return false;
8797
8790
  }
8791
+ const sectionStart = featuresMatch.index + featuresMatch[0].length;
8792
+ const nextSectionMatch = content.slice(sectionStart).match(/^[^\S\r\n]*\[[^\]\r\n]+\][^\S\r\n]*$/m);
8793
+ const sectionEnd = nextSectionMatch && nextSectionMatch.index !== void 0 ? sectionStart + nextSectionMatch.index : content.length;
8794
+ const featuresBody = content.slice(sectionStart, sectionEnd);
8795
+ return new RegExp(`^\\s*${escaped}\\s*=\\s*true\\s*(?:#.*)?$`, "m").test(featuresBody);
8798
8796
  }
8799
- var LocalCodexSessionStore = class {
8800
- constructor(codexHome) {
8801
- this.codexHome = codexHome;
8802
- }
8803
- codexHome;
8804
- async findSession(sessionId) {
8797
+ async function readCodexFeatureFlag(codexHome, featureName) {
8798
+ try {
8799
+ const content = await fsp.readFile(resolveCodexConfigPath(codexHome), "utf8");
8800
+ return isCodexFeatureEnabledFromConfig(content, featureName);
8801
+ } catch (error) {
8802
+ if (error.code === "ENOENT") {
8803
+ return false;
8804
+ }
8805
+ throw error;
8806
+ }
8807
+ }
8808
+ function upsertCodexFeatureFlag(content, featureName, enabled) {
8809
+ const normalized = content.replace(/\r\n/g, "\n");
8810
+ const escaped = featureName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8811
+ const featuresMatch = normalized.match(/^[^\S\r\n]*\[features\][^\S\r\n]*$/m);
8812
+ const nextLine = `${featureName} = ${enabled ? "true" : "false"}`;
8813
+ if (!featuresMatch || featuresMatch.index === void 0) {
8814
+ const prefix = normalized.trimEnd();
8815
+ return prefix ? `${prefix}
8816
+
8817
+ [features]
8818
+ ${nextLine}
8819
+ ` : `[features]
8820
+ ${nextLine}
8821
+ `;
8822
+ }
8823
+ const sectionStart = featuresMatch.index + featuresMatch[0].length;
8824
+ const nextSectionMatch = normalized.slice(sectionStart).match(/^[^\S\r\n]*\[[^\]\r\n]+\][^\S\r\n]*$/m);
8825
+ const sectionEnd = nextSectionMatch && nextSectionMatch.index !== void 0 ? sectionStart + nextSectionMatch.index : normalized.length;
8826
+ const beforeSectionBody = normalized.slice(0, sectionStart);
8827
+ const sectionBody = normalized.slice(sectionStart, sectionEnd);
8828
+ const afterSection = normalized.slice(sectionEnd);
8829
+ const flagPattern = new RegExp(
8830
+ `(^[^\\S\\r\\n]*)${escaped}[^\\S\\r\\n]*=[^\\S\\r\\n]*(true|false)[^\\S\\r\\n]*(?:#.*)?$`,
8831
+ "m"
8832
+ );
8833
+ if (flagPattern.test(sectionBody)) {
8834
+ return beforeSectionBody + sectionBody.replace(flagPattern, `$1${nextLine}`) + afterSection;
8835
+ }
8836
+ const bodyWithFlag = `${sectionBody.replace(/\n*$/, "")}
8837
+ ${nextLine}
8838
+ `;
8839
+ const normalizedAfterSection = afterSection.startsWith("\n") ? afterSection : `
8840
+ ${afterSection}`;
8841
+ return beforeSectionBody + bodyWithFlag + normalizedAfterSection;
8842
+ }
8843
+ async function writeCodexFeatureFlag(codexHome, featureName, enabled) {
8844
+ const configPath = resolveCodexConfigPath(codexHome);
8845
+ let current = "";
8846
+ try {
8847
+ current = await fsp.readFile(configPath, "utf8");
8848
+ } catch (error) {
8849
+ if (error.code !== "ENOENT") {
8850
+ throw error;
8851
+ }
8852
+ }
8853
+ const next = upsertCodexFeatureFlag(current, featureName, enabled);
8854
+ await fsp.mkdir(path5.dirname(configPath), { recursive: true });
8855
+ await fsp.writeFile(configPath, next, "utf8");
8856
+ return next;
8857
+ }
8858
+
8859
+ // src/codex/runtime-errors.ts
8860
+ function unwrapCodexJsonRpcError(error) {
8861
+ if (error instanceof JsonRpcClientError) {
8862
+ return error;
8863
+ }
8864
+ if (error instanceof AgentRuntimeError && error.provider === "codex") {
8865
+ return error.cause instanceof JsonRpcClientError ? error.cause : null;
8866
+ }
8867
+ return null;
8868
+ }
8869
+ function isCodexRemoteError(error) {
8870
+ const codexError = unwrapCodexJsonRpcError(error);
8871
+ return codexError?.code === "remote_error" ? codexError : null;
8872
+ }
8873
+ function isCodexRuntimeRequestError(error) {
8874
+ return Boolean(unwrapCodexJsonRpcError(error));
8875
+ }
8876
+ function isRemoteThreadBootstrapError(error) {
8877
+ const codexError = isCodexRemoteError(error);
8878
+ if (!codexError) {
8879
+ return false;
8880
+ }
8881
+ return codexError.message.includes("includeTurns is unavailable before first user message") || codexError.message.includes("is not materialized yet") || codexError.message.includes("no rollout found for thread id") || codexError.message.includes("failed to load rollout") || codexError.message.includes("rollout at") && codexError.message.includes("is empty");
8882
+ }
8883
+ function isUnsupportedHooksListError(error) {
8884
+ const codexError = isCodexRemoteError(error);
8885
+ if (!codexError) {
8886
+ return false;
8887
+ }
8888
+ const remoteCode = codexError.details?.code;
8889
+ const message = codexError.message.toLowerCase();
8890
+ return remoteCode === -32601 || message.includes("endpoint not found") || message.includes("method not found") || message.includes("hooks/list") && message.includes("not found");
8891
+ }
8892
+ function parseTurnSteerRace(error) {
8893
+ const codexError = isCodexRemoteError(error);
8894
+ if (!codexError) {
8895
+ return null;
8896
+ }
8897
+ if (codexError.message === "no active turn to steer") {
8898
+ return { type: "missing" };
8899
+ }
8900
+ const mismatchPrefix = "expected active turn id `";
8901
+ const mismatchSeparator = "` but found `";
8902
+ if (!codexError.message.startsWith(mismatchPrefix)) {
8903
+ return null;
8904
+ }
8905
+ const actualTurnId = codexError.message.slice(mismatchPrefix.length).split(mismatchSeparator)[1]?.replace(/`$/, "");
8906
+ if (!actualTurnId) {
8907
+ return null;
8908
+ }
8909
+ return {
8910
+ type: "turnIdMismatch",
8911
+ actualTurnId
8912
+ };
8913
+ }
8914
+
8915
+ // src/codex/codex-management-service.ts
8916
+ var GOAL_FEATURE_DISABLED_MESSAGE = "Codex /goal is experimental. Enable it by adding `goals = true` under `[features]` in ~/.codex/config.toml, then restart the Codex app-server.";
8917
+ var HOOK_EVENT_JSON_KEYS = {
8918
+ preToolUse: "PreToolUse",
8919
+ permissionRequest: "PermissionRequest",
8920
+ postToolUse: "PostToolUse",
8921
+ preCompact: "PreCompact",
8922
+ postCompact: "PostCompact",
8923
+ sessionStart: "SessionStart",
8924
+ userPromptSubmit: "UserPromptSubmit",
8925
+ stop: "Stop"
8926
+ };
8927
+ var HOOK_EVENT_DTO_KEYS = Object.fromEntries(
8928
+ Object.entries(HOOK_EVENT_JSON_KEYS).map(([dtoKey, jsonKey]) => [jsonKey, dtoKey])
8929
+ );
8930
+ function isRecord4(value) {
8931
+ return typeof value === "object" && value !== null && !Array.isArray(value);
8932
+ }
8933
+ function normalizeHooksJson(value) {
8934
+ if (!isRecord4(value) || !isRecord4(value.hooks)) {
8935
+ return { hooks: {} };
8936
+ }
8937
+ const hooks = {};
8938
+ for (const [eventName, groups] of Object.entries(value.hooks)) {
8939
+ hooks[eventName] = Array.isArray(groups) ? groups : [];
8940
+ }
8941
+ return { ...value, hooks };
8942
+ }
8943
+ function readJsonFileOrDefault(filePath) {
8944
+ return fs5.readFile(filePath, "utf8").then((raw) => {
8945
+ if (!raw.trim()) {
8946
+ return { hooks: {} };
8947
+ }
8948
+ return normalizeHooksJson(JSON.parse(raw));
8949
+ }).catch((error) => {
8950
+ if (error.code === "ENOENT") {
8951
+ return { hooks: {} };
8952
+ }
8953
+ throw error;
8954
+ });
8955
+ }
8956
+ function validateHookInput(input) {
8957
+ if (!HOOK_EVENT_JSON_KEYS[input.eventName]) {
8958
+ throw new HttpError(400, {
8959
+ code: "bad_request",
8960
+ message: "Unsupported hook event."
8961
+ });
8962
+ }
8963
+ if (input.scope !== "global" && input.scope !== "project") {
8964
+ throw new HttpError(400, {
8965
+ code: "bad_request",
8966
+ message: "Hook scope must be global or project."
8967
+ });
8968
+ }
8969
+ if (!input.command.trim()) {
8970
+ throw new HttpError(400, {
8971
+ code: "bad_request",
8972
+ message: "Hook command cannot be empty."
8973
+ });
8974
+ }
8975
+ if (input.timeoutSec !== void 0 && input.timeoutSec !== null && (!Number.isInteger(input.timeoutSec) || input.timeoutSec <= 0 || input.timeoutSec > 86400)) {
8976
+ throw new HttpError(400, {
8977
+ code: "bad_request",
8978
+ message: "Hook timeout must be a positive number of seconds."
8979
+ });
8980
+ }
8981
+ }
8982
+ function hooksPathForInput(codexHome, workspacePath, input) {
8983
+ return input.scope === "global" ? path6.join(codexHome, "hooks.json") : path6.join(workspacePath, ".codex", "hooks.json");
8984
+ }
8985
+ function hookInputMatches(group, handler, input) {
8986
+ if (!isRecord4(group) || !isRecord4(handler)) {
8987
+ return false;
8988
+ }
8989
+ const matcher = typeof group.matcher === "string" ? group.matcher : null;
8990
+ const handlerCommand = typeof handler.command === "string" ? handler.command : "";
8991
+ const handlerTimeout = typeof handler.timeout === "number" && Number.isFinite(handler.timeout) ? handler.timeout : null;
8992
+ const handlerStatusMessage = typeof handler.statusMessage === "string" ? handler.statusMessage : null;
8993
+ return handler.type === "command" && (input.matcher?.trim() || null) === matcher && input.command.trim() === handlerCommand && (input.timeoutSec ?? null) === handlerTimeout && (input.statusMessage?.trim() || null) === handlerStatusMessage;
8994
+ }
8995
+ function hookMatchesInput(hook, input) {
8996
+ return hook.source === input.scope && hook.eventName === input.eventName && (hook.matcher ?? null) === (input.matcher ?? null) && hook.command === input.command && (input.timeoutSec == null || hook.timeoutSec === input.timeoutSec) && (hook.statusMessage ?? null) === (input.statusMessage ?? null);
8997
+ }
8998
+ async function findOfficialHookForInput(runtime, workspacePath, input) {
8999
+ if (!runtime.listHooks) {
9000
+ return null;
9001
+ }
9002
+ const [entry] = await runtime.listHooks({
9003
+ cwds: [workspacePath]
9004
+ });
9005
+ const officialHooks = (entry?.hooks ?? []).map((hook) => ({
9006
+ key: hook.key,
9007
+ eventName: hook.eventName,
9008
+ handlerType: hook.handlerType,
9009
+ matcher: hook.matcher,
9010
+ command: hook.command,
9011
+ timeoutSec: hook.timeoutSec,
9012
+ statusMessage: hook.statusMessage,
9013
+ sourcePath: hook.sourcePath,
9014
+ source: hook.source,
9015
+ pluginId: hook.pluginId,
9016
+ displayOrder: hook.displayOrder,
9017
+ enabled: hook.enabled,
9018
+ isManaged: hook.isManaged,
9019
+ currentHash: hook.currentHash,
9020
+ trustStatus: hook.trustStatus
9021
+ }));
9022
+ return officialHooks.find((hook) => hookMatchesInput(hook, input)) ?? null;
9023
+ }
9024
+ async function trustHookForInput(runtime, workspacePath, input) {
9025
+ const hook = await findOfficialHookForInput(runtime, workspacePath, input);
9026
+ if (!runtime.setHookTrust || !hook || !hook.key || !hook.currentHash || hook.isManaged) {
9027
+ return;
9028
+ }
9029
+ await runtime.setHookTrust({
9030
+ key: hook.key,
9031
+ trustedHash: hook.currentHash
9032
+ });
9033
+ }
9034
+ var CodexManagementService = class {
9035
+ constructor(codexHome) {
9036
+ this.codexHome = codexHome;
9037
+ }
9038
+ codexHome;
9039
+ readFastMode() {
9040
+ return readCodexFastModeSync(this.codexHome);
9041
+ }
9042
+ writeFastMode(enabled) {
9043
+ return writeCodexFastMode(this.codexHome, enabled);
9044
+ }
9045
+ mapGoalError(error) {
9046
+ const codexError = unwrapCodexJsonRpcError(error);
9047
+ if (codexError) {
9048
+ const remoteMessage = codexError.message || "";
9049
+ if (remoteMessage.toLowerCase().includes("goals feature is disabled")) {
9050
+ throw new HttpError(409, {
9051
+ code: "goal_feature_disabled",
9052
+ message: GOAL_FEATURE_DISABLED_MESSAGE
9053
+ });
9054
+ }
9055
+ throw new HttpError(502, {
9056
+ code: "provider_goal_error",
9057
+ message: remoteMessage || "Provider goal operation failed.",
9058
+ details: {
9059
+ provider: "codex"
9060
+ }
9061
+ });
9062
+ }
9063
+ throw error;
9064
+ }
9065
+ async ensureGoalsFeatureEnabled(runtime) {
9066
+ try {
9067
+ if (await readCodexFeatureFlag(this.codexHome, "goals")) {
9068
+ return;
9069
+ }
9070
+ await writeCodexFeatureFlag(this.codexHome, "goals", true);
9071
+ await runtime.stop();
9072
+ await runtime.start();
9073
+ } catch (error) {
9074
+ if (isCodexRuntimeRequestError(error)) {
9075
+ throw new HttpError(409, {
9076
+ code: "goal_feature_disabled",
9077
+ message: GOAL_FEATURE_DISABLED_MESSAGE
9078
+ });
9079
+ }
9080
+ throw error;
9081
+ }
9082
+ }
9083
+ isRuntimeRequestError(error) {
9084
+ return isCodexRuntimeRequestError(error);
9085
+ }
9086
+ async writeHookEntry(runtime, workspacePath, input) {
9087
+ validateHookInput(input);
9088
+ const hooksPath = hooksPathForInput(this.codexHome, workspacePath, input);
9089
+ const config = await readJsonFileOrDefault(hooksPath);
9090
+ const eventKey = HOOK_EVENT_JSON_KEYS[input.eventName];
9091
+ const matcher = input.matcher?.trim() || null;
9092
+ const handler = {
9093
+ type: "command",
9094
+ command: input.command.trim()
9095
+ };
9096
+ if (input.timeoutSec !== void 0 && input.timeoutSec !== null) {
9097
+ handler.timeout = input.timeoutSec;
9098
+ }
9099
+ if (input.statusMessage?.trim()) {
9100
+ handler.statusMessage = input.statusMessage.trim();
9101
+ }
9102
+ const group = {
9103
+ hooks: [handler]
9104
+ };
9105
+ if (matcher) {
9106
+ group.matcher = matcher;
9107
+ }
9108
+ const currentGroups = Array.isArray(config.hooks[eventKey]) ? config.hooks[eventKey] : [];
9109
+ config.hooks[eventKey] = [...currentGroups, group];
9110
+ await fs5.mkdir(path6.dirname(hooksPath), { recursive: true });
9111
+ await fs5.writeFile(hooksPath, `${JSON.stringify(config, null, 2)}
9112
+ `, "utf8");
9113
+ await trustHookForInput(runtime, workspacePath, input);
9114
+ }
9115
+ async updateHookEntry(runtime, workspacePath, input) {
9116
+ validateHookInput(input);
9117
+ validateHookInput(input.target);
9118
+ if (input.scope !== input.target.scope) {
9119
+ throw new HttpError(400, {
9120
+ code: "bad_request",
9121
+ message: "Hook scope cannot be changed while editing."
9122
+ });
9123
+ }
9124
+ const hooksPath = hooksPathForInput(this.codexHome, workspacePath, input);
9125
+ const config = await readJsonFileOrDefault(hooksPath);
9126
+ const targetEventKey = HOOK_EVENT_JSON_KEYS[input.target.eventName];
9127
+ const nextEventKey = HOOK_EVENT_JSON_KEYS[input.eventName];
9128
+ const currentGroups = Array.isArray(config.hooks[targetEventKey]) ? config.hooks[targetEventKey] : [];
9129
+ let replacementGroup = null;
9130
+ config.hooks[targetEventKey] = currentGroups.map((group) => {
9131
+ if (replacementGroup || !isRecord4(group) || !Array.isArray(group.hooks)) {
9132
+ return group;
9133
+ }
9134
+ const hookIndex = group.hooks.findIndex(
9135
+ (handler2) => hookInputMatches(group, handler2, input.target)
9136
+ );
9137
+ if (hookIndex < 0) {
9138
+ return group;
9139
+ }
9140
+ const handler = {
9141
+ type: "command",
9142
+ command: input.command.trim()
9143
+ };
9144
+ if (input.timeoutSec !== void 0 && input.timeoutSec !== null) {
9145
+ handler.timeout = input.timeoutSec;
9146
+ }
9147
+ if (input.statusMessage?.trim()) {
9148
+ handler.statusMessage = input.statusMessage.trim();
9149
+ }
9150
+ replacementGroup = {
9151
+ hooks: [handler]
9152
+ };
9153
+ const matcher = input.matcher?.trim() || null;
9154
+ if (matcher) {
9155
+ replacementGroup.matcher = matcher;
9156
+ }
9157
+ if (targetEventKey !== nextEventKey) {
9158
+ const remainingHooks = group.hooks.filter((_, index) => index !== hookIndex);
9159
+ return {
9160
+ ...group,
9161
+ hooks: remainingHooks
9162
+ };
9163
+ }
9164
+ return {
9165
+ ...replacementGroup,
9166
+ hooks: group.hooks.map(
9167
+ (existing, index) => index === hookIndex ? replacementGroup.hooks[0] : existing
9168
+ )
9169
+ };
9170
+ }).filter((group) => {
9171
+ if (!isRecord4(group) || !Array.isArray(group.hooks)) {
9172
+ return true;
9173
+ }
9174
+ return group.hooks.length > 0;
9175
+ });
9176
+ if (!replacementGroup) {
9177
+ throw new HttpError(404, {
9178
+ code: "not_found",
9179
+ message: "Hook was not found in hooks.json."
9180
+ });
9181
+ }
9182
+ if (targetEventKey !== nextEventKey) {
9183
+ if (config.hooks[targetEventKey]?.length === 0) {
9184
+ delete config.hooks[targetEventKey];
9185
+ }
9186
+ const nextGroups = Array.isArray(config.hooks[nextEventKey]) ? config.hooks[nextEventKey] : [];
9187
+ config.hooks[nextEventKey] = [...nextGroups, replacementGroup];
9188
+ }
9189
+ await fs5.mkdir(path6.dirname(hooksPath), { recursive: true });
9190
+ await fs5.writeFile(hooksPath, `${JSON.stringify(config, null, 2)}
9191
+ `, "utf8");
9192
+ await trustHookForInput(runtime, workspacePath, input);
9193
+ }
9194
+ async readLocalHookDtos(input) {
9195
+ const config = await readJsonFileOrDefault(input.hooksPath);
9196
+ const hooks = [];
9197
+ for (const [eventKey, groups] of Object.entries(config.hooks)) {
9198
+ const eventName = HOOK_EVENT_DTO_KEYS[eventKey];
9199
+ if (!eventName || !Array.isArray(groups)) {
9200
+ continue;
9201
+ }
9202
+ groups.forEach((group, groupIndex) => {
9203
+ if (!isRecord4(group) || !Array.isArray(group.hooks)) {
9204
+ return;
9205
+ }
9206
+ const matcher = typeof group.matcher === "string" ? group.matcher : null;
9207
+ group.hooks.forEach((handler, handlerIndex) => {
9208
+ if (!isRecord4(handler) || handler.type !== "command") {
9209
+ return;
9210
+ }
9211
+ const command = typeof handler.command === "string" ? handler.command : null;
9212
+ if (!command) {
9213
+ return;
9214
+ }
9215
+ const timeoutSec = typeof handler.timeout === "number" && Number.isFinite(handler.timeout) ? handler.timeout : 600;
9216
+ const statusMessage = typeof handler.statusMessage === "string" ? handler.statusMessage : null;
9217
+ const key = `${input.source}:${input.hooksPath}:${eventKey}:${groupIndex}:${handlerIndex}`;
9218
+ hooks.push({
9219
+ key,
9220
+ eventName,
9221
+ handlerType: "command",
9222
+ matcher,
9223
+ command,
9224
+ timeoutSec,
9225
+ statusMessage,
9226
+ sourcePath: input.hooksPath,
9227
+ source: input.source,
9228
+ pluginId: null,
9229
+ displayOrder: input.displayOffset + hooks.length,
9230
+ enabled: true,
9231
+ isManaged: false,
9232
+ currentHash: "",
9233
+ trustStatus: "untrusted"
9234
+ });
9235
+ });
9236
+ });
9237
+ }
9238
+ return hooks;
9239
+ }
9240
+ hooksPaths(workspacePath) {
9241
+ return {
9242
+ globalHooksPath: path6.join(this.codexHome, "hooks.json"),
9243
+ projectHooksPath: path6.join(workspacePath, ".codex", "hooks.json")
9244
+ };
9245
+ }
9246
+ };
9247
+
9248
+ // src/codex/local-session-store.ts
9249
+ import fs6 from "fs/promises";
9250
+ import path7 from "path";
9251
+ import Database2 from "better-sqlite3";
9252
+
9253
+ // src/codex/thread-title.ts
9254
+ var AUTO_THREAD_TITLE_MAX_CHARS = 15;
9255
+ function normalizeWhitespace(value) {
9256
+ return value.replace(/\s+/g, " ").trim();
9257
+ }
9258
+ function truncateAutoThreadTitle(value) {
9259
+ const normalized = normalizeWhitespace(value);
9260
+ if (!normalized) {
9261
+ return "";
9262
+ }
9263
+ const characters = Array.from(normalized);
9264
+ if (characters.length <= AUTO_THREAD_TITLE_MAX_CHARS) {
9265
+ return normalized;
9266
+ }
9267
+ return `${characters.slice(0, AUTO_THREAD_TITLE_MAX_CHARS).join("")}...`;
9268
+ }
9269
+
9270
+ // src/codex/local-session-store.ts
9271
+ function basenameFromPath(absPath) {
9272
+ const normalized = absPath.replace(/[\\/]+$/, "");
9273
+ return normalized.split(/[\\/]/).at(-1) ?? normalized;
9274
+ }
9275
+ function summarizeTitleFromTurns(turns) {
9276
+ const firstUserMessage = turns.flatMap((turn) => turn.items).find((item) => item.kind === "userMessage" && item.text.trim());
9277
+ if (!firstUserMessage) {
9278
+ return null;
9279
+ }
9280
+ return truncateAutoThreadTitle(firstUserMessage.text);
9281
+ }
9282
+ function createHistoryItemId(turnId, prefix, index) {
9283
+ return `${turnId}-${prefix}-${index}`;
9284
+ }
9285
+ function finalizeTurn(turn, turns) {
9286
+ if (!turn || turn.items.length === 0) {
9287
+ return;
9288
+ }
9289
+ turns.push({
9290
+ id: turn.id,
9291
+ startedAt: turn.startedAt,
9292
+ status: turn.status,
9293
+ error: turn.error,
9294
+ items: turn.items
9295
+ });
9296
+ }
9297
+ function parseTranscript(contents) {
9298
+ const turns = [];
9299
+ let cwd = null;
9300
+ let currentTurn = null;
9301
+ let fallbackTurnCount = 0;
9302
+ let agentItemCount = 0;
9303
+ let userItemCount = 0;
9304
+ const ensureCurrentTurn = (timestamp) => {
9305
+ if (currentTurn) {
9306
+ return currentTurn;
9307
+ }
9308
+ fallbackTurnCount += 1;
9309
+ currentTurn = {
9310
+ id: `local-turn-${fallbackTurnCount}`,
9311
+ startedAt: timestamp ?? null,
9312
+ status: "inProgress",
9313
+ error: null,
9314
+ items: []
9315
+ };
9316
+ agentItemCount = 0;
9317
+ userItemCount = 0;
9318
+ return currentTurn;
9319
+ };
9320
+ for (const line of contents.split("\n")) {
9321
+ if (!line.trim()) {
9322
+ continue;
9323
+ }
9324
+ let entry;
9325
+ try {
9326
+ entry = JSON.parse(line);
9327
+ } catch {
9328
+ continue;
9329
+ }
9330
+ if (entry.type === "session_meta") {
9331
+ const payload2 = entry.payload ?? {};
9332
+ if (typeof payload2.cwd === "string" && payload2.cwd.trim()) {
9333
+ cwd = payload2.cwd;
9334
+ }
9335
+ continue;
9336
+ }
9337
+ if (entry.type !== "event_msg") {
9338
+ continue;
9339
+ }
9340
+ const payload = entry.payload ?? {};
9341
+ const payloadType = payload.type;
9342
+ if (payloadType === "task_started") {
9343
+ finalizeTurn(currentTurn, turns);
9344
+ currentTurn = {
9345
+ id: typeof payload.turn_id === "string" && payload.turn_id.trim() ? payload.turn_id : `local-turn-${fallbackTurnCount + 1}`,
9346
+ startedAt: entry.timestamp ?? null,
9347
+ status: "inProgress",
9348
+ error: null,
9349
+ items: []
9350
+ };
9351
+ agentItemCount = 0;
9352
+ userItemCount = 0;
9353
+ continue;
9354
+ }
9355
+ if (payloadType === "user_message" && typeof payload.message === "string") {
9356
+ const turn = ensureCurrentTurn(entry.timestamp);
9357
+ userItemCount += 1;
9358
+ turn.items.push({
9359
+ id: createHistoryItemId(turn.id, "user", userItemCount),
9360
+ kind: "userMessage",
9361
+ text: payload.message
9362
+ });
9363
+ continue;
9364
+ }
9365
+ if (payloadType === "agent_message" && typeof payload.message === "string") {
9366
+ const turn = ensureCurrentTurn(entry.timestamp);
9367
+ agentItemCount += 1;
9368
+ turn.items.push({
9369
+ id: createHistoryItemId(turn.id, "agent", agentItemCount),
9370
+ kind: "agentMessage",
9371
+ text: payload.message,
9372
+ status: typeof payload.phase === "string" ? payload.phase : null
9373
+ });
9374
+ continue;
9375
+ }
9376
+ if (payloadType === "task_complete") {
9377
+ const turn = ensureCurrentTurn(entry.timestamp);
9378
+ turn.status = turn.error ? "failed" : "completed";
9379
+ finalizeTurn(turn, turns);
9380
+ currentTurn = null;
9381
+ continue;
9382
+ }
9383
+ if (payloadType === "error") {
9384
+ const turn = ensureCurrentTurn(entry.timestamp);
9385
+ turn.status = "failed";
9386
+ turn.error = typeof payload.message === "string" ? payload.message : "Local Codex session failed.";
9387
+ }
9388
+ }
9389
+ finalizeTurn(currentTurn, turns);
9390
+ return {
9391
+ cwd,
9392
+ title: summarizeTitleFromTurns(turns),
9393
+ turns
9394
+ };
9395
+ }
9396
+ async function fileExists(filePath) {
9397
+ try {
9398
+ await fs6.access(filePath);
9399
+ return true;
9400
+ } catch {
9401
+ return false;
9402
+ }
9403
+ }
9404
+ var LocalCodexSessionStore = class {
9405
+ constructor(codexHome) {
9406
+ this.codexHome = codexHome;
9407
+ }
9408
+ codexHome;
9409
+ async findSession(sessionId) {
8805
9410
  const stateRecord = await this.findSessionInStateDatabases(sessionId);
8806
9411
  const transcriptPath = await this.resolveTranscriptPath(
8807
9412
  stateRecord?.rolloutPath ?? null,
8808
9413
  sessionId
8809
9414
  );
8810
- const transcript = transcriptPath ? parseTranscript(await fs4.readFile(transcriptPath, "utf8")) : null;
9415
+ const transcript = transcriptPath ? parseTranscript(await fs6.readFile(transcriptPath, "utf8")) : null;
8811
9416
  const cwd = stateRecord?.cwd ?? transcript?.cwd ?? null;
8812
9417
  if (!cwd) {
8813
9418
  return null;
@@ -8824,14 +9429,14 @@ var LocalCodexSessionStore = class {
8824
9429
  async findSessionInStateDatabases(sessionId) {
8825
9430
  let entries;
8826
9431
  try {
8827
- entries = await fs4.readdir(this.codexHome);
9432
+ entries = await fs6.readdir(this.codexHome);
8828
9433
  } catch {
8829
9434
  return null;
8830
9435
  }
8831
9436
  const stateFiles = await Promise.all(
8832
9437
  entries.filter((entry) => /^state_\d+\.sqlite$/i.test(entry)).map(async (entry) => {
8833
- const absPath = path5.join(this.codexHome, entry);
8834
- const stats = await fs4.stat(absPath);
9438
+ const absPath = path7.join(this.codexHome, entry);
9439
+ const stats = await fs6.stat(absPath);
8835
9440
  return {
8836
9441
  absPath,
8837
9442
  mtimeMs: stats.mtimeMs
@@ -8870,22 +9475,22 @@ var LocalCodexSessionStore = class {
8870
9475
  }
8871
9476
  async resolveTranscriptPath(rolloutPath, sessionId) {
8872
9477
  if (rolloutPath?.trim()) {
8873
- const absolutePath = path5.isAbsolute(rolloutPath) ? rolloutPath : path5.resolve(this.codexHome, rolloutPath);
9478
+ const absolutePath = path7.isAbsolute(rolloutPath) ? rolloutPath : path7.resolve(this.codexHome, rolloutPath);
8874
9479
  if (await fileExists(absolutePath)) {
8875
9480
  return absolutePath;
8876
9481
  }
8877
9482
  }
8878
- return this.findTranscriptFile(path5.join(this.codexHome, "sessions"), sessionId);
9483
+ return this.findTranscriptFile(path7.join(this.codexHome, "sessions"), sessionId);
8879
9484
  }
8880
9485
  async findTranscriptFile(directory, sessionId) {
8881
9486
  let entries;
8882
9487
  try {
8883
- entries = await fs4.readdir(directory, { withFileTypes: true });
9488
+ entries = await fs6.readdir(directory, { withFileTypes: true });
8884
9489
  } catch {
8885
9490
  return null;
8886
9491
  }
8887
9492
  for (const entry of entries) {
8888
- const absPath = path5.join(directory, entry.name);
9493
+ const absPath = path7.join(directory, entry.name);
8889
9494
  if (entry.isDirectory()) {
8890
9495
  const nested = await this.findTranscriptFile(absPath, sessionId);
8891
9496
  if (nested) {
@@ -8903,23 +9508,33 @@ var LocalCodexSessionStore = class {
8903
9508
 
8904
9509
  // src/agent-runtime-bootstrap.ts
8905
9510
  function createAgentRuntimeBootstrap(config) {
8906
- const codexRuntime = new CodexRuntimeAdapter(
9511
+ const runtimes = [];
9512
+ const providerHostHomes = {};
9513
+ const codexConfig = config.agentProviders.codex;
9514
+ const codexRuntime = codexConfig.enabled ? new CodexRuntimeAdapter(
8907
9515
  new CodexAppServerManager({
8908
- command: config.codexCommand,
8909
- startupTimeoutMs: config.codexAppServerStartTimeoutMs,
9516
+ command: codexConfig.command,
9517
+ startupTimeoutMs: codexConfig.appServerStartTimeoutMs,
8910
9518
  clientInfo: {
8911
9519
  name: "remote-codex-supervisor",
8912
9520
  title: config.appName,
8913
9521
  version: config.appVersion
8914
9522
  }
8915
9523
  })
8916
- );
9524
+ ) : null;
9525
+ if (codexRuntime) {
9526
+ runtimes.push(codexRuntime);
9527
+ providerHostHomes.codex = codexConfig.home;
9528
+ }
9529
+ const claudeConfig = config.agentProviders.claude;
9530
+ if (claudeConfig.enabled) {
9531
+ providerHostHomes.claude = claudeConfig.home;
9532
+ }
8917
9533
  return {
8918
- agentRuntimes: new AgentRuntimeRegistry([codexRuntime]),
8919
- localCodexSessionStore: new LocalCodexSessionStore(config.codexHome),
8920
- providerHostHomes: {
8921
- codex: config.codexHome
8922
- }
9534
+ agentRuntimes: new AgentRuntimeRegistry(runtimes),
9535
+ localCodexSessionStore: new LocalCodexSessionStore(codexConfig.home),
9536
+ codexManagement: new CodexManagementService(codexConfig.home),
9537
+ providerHostHomes
8923
9538
  };
8924
9539
  }
8925
9540
 
@@ -8948,11 +9563,11 @@ var SupervisorEventBus = class extends EventEmitter4 {
8948
9563
 
8949
9564
  // src/thread-service.ts
8950
9565
  import { randomUUID as randomUUID2 } from "crypto";
8951
- import fs8 from "fs/promises";
8952
- import path8 from "path";
9566
+ import fs9 from "fs/promises";
9567
+ import path9 from "path";
8953
9568
 
8954
9569
  // src/exports/thread-pdf-export.ts
8955
- import fs5 from "fs";
9570
+ import fs7 from "fs";
8956
9571
  import puppeteer from "puppeteer-core";
8957
9572
  import { marked } from "marked";
8958
9573
  var MAX_TEXT_CHARS = 12e3;
@@ -8985,10 +9600,10 @@ function renderEmbeddedCjkFontCss() {
8985
9600
  embeddedCjkFontCss = "";
8986
9601
  for (const candidate of EMBEDDED_CJK_FONT_CANDIDATES) {
8987
9602
  try {
8988
- if (!fs5.existsSync(candidate.path)) {
9603
+ if (!fs7.existsSync(candidate.path)) {
8989
9604
  continue;
8990
9605
  }
8991
- const font = fs5.readFileSync(candidate.path);
9606
+ const font = fs7.readFileSync(candidate.path);
8992
9607
  embeddedCjkFontCss = `
8993
9608
  @font-face {
8994
9609
  font-family: "RemoteCodexCJK";
@@ -10030,208 +10645,6 @@ async function launchPdfBrowser() {
10030
10645
  }
10031
10646
  }
10032
10647
 
10033
- // src/codex/codexHostConfig.ts
10034
- import fs6 from "fs";
10035
- import fsp from "fs/promises";
10036
- import path6 from "path";
10037
- var SERVICE_TIER_LINE_GLOBAL_PATTERN = /^\s*service_tier\s*=\s*("fast"|"flex"|'fast'|'flex').*\n?/gm;
10038
- function resolveCodexConfigPath(codexHome) {
10039
- return path6.join(codexHome, "config.toml");
10040
- }
10041
- function parseCodexServiceTier(content) {
10042
- const match = content.match(
10043
- /^\s*service_tier\s*=\s*["'](?<tier>fast)["']\s*$/m
10044
- );
10045
- const tier = match?.groups?.tier;
10046
- return tier === "fast" ? tier : null;
10047
- }
10048
- function isFastModeEnabledFromConfig(content) {
10049
- return parseCodexServiceTier(content) === "fast";
10050
- }
10051
- function readCodexFastModeSync(codexHome) {
10052
- try {
10053
- const content = fs6.readFileSync(resolveCodexConfigPath(codexHome), "utf8");
10054
- return isFastModeEnabledFromConfig(content);
10055
- } catch (error) {
10056
- if (error.code === "ENOENT") {
10057
- return false;
10058
- }
10059
- throw error;
10060
- }
10061
- }
10062
- function upsertCodexServiceTier(content, enabled) {
10063
- const normalized = content.replace(/\r\n/g, "\n");
10064
- const withoutServiceTier = normalized.replace(
10065
- SERVICE_TIER_LINE_GLOBAL_PATTERN,
10066
- ""
10067
- );
10068
- if (!enabled) {
10069
- return withoutServiceTier.replace(/\n{3,}/g, "\n\n").trimEnd() + (withoutServiceTier.trim() ? "\n" : "");
10070
- }
10071
- const nextLine = 'service_tier = "fast"';
10072
- if (!withoutServiceTier.trim()) {
10073
- return `${nextLine}
10074
- `;
10075
- }
10076
- const firstSectionMatch = withoutServiceTier.match(/^\s*\[[^\]]+\]/m);
10077
- if (!firstSectionMatch || firstSectionMatch.index === void 0) {
10078
- return withoutServiceTier.endsWith("\n") ? `${withoutServiceTier}${nextLine}
10079
- ` : `${withoutServiceTier}
10080
- ${nextLine}
10081
- `;
10082
- }
10083
- const beforeFirstSection = withoutServiceTier.slice(0, firstSectionMatch.index).trimEnd();
10084
- const afterFirstSection = withoutServiceTier.slice(firstSectionMatch.index).replace(/^\n+/, "");
10085
- return beforeFirstSection ? `${beforeFirstSection}
10086
- ${nextLine}
10087
- ${afterFirstSection}` : `${nextLine}
10088
- ${afterFirstSection}`;
10089
- }
10090
- async function writeCodexFastMode(codexHome, enabled) {
10091
- const configPath = resolveCodexConfigPath(codexHome);
10092
- let current = "";
10093
- try {
10094
- current = await fsp.readFile(configPath, "utf8");
10095
- } catch (error) {
10096
- if (error.code !== "ENOENT") {
10097
- throw error;
10098
- }
10099
- }
10100
- const next = upsertCodexServiceTier(current, enabled);
10101
- await fsp.mkdir(path6.dirname(configPath), { recursive: true });
10102
- await fsp.writeFile(configPath, next, "utf8");
10103
- return next;
10104
- }
10105
- function isCodexFeatureEnabledFromConfig(content, featureName) {
10106
- const escaped = featureName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
10107
- const featuresMatch = content.match(/^[^\S\r\n]*\[features\][^\S\r\n]*$/m);
10108
- if (!featuresMatch || featuresMatch.index === void 0) {
10109
- return false;
10110
- }
10111
- const sectionStart = featuresMatch.index + featuresMatch[0].length;
10112
- const nextSectionMatch = content.slice(sectionStart).match(/^[^\S\r\n]*\[[^\]\r\n]+\][^\S\r\n]*$/m);
10113
- const sectionEnd = nextSectionMatch && nextSectionMatch.index !== void 0 ? sectionStart + nextSectionMatch.index : content.length;
10114
- const featuresBody = content.slice(sectionStart, sectionEnd);
10115
- return new RegExp(`^\\s*${escaped}\\s*=\\s*true\\s*(?:#.*)?$`, "m").test(featuresBody);
10116
- }
10117
- async function readCodexFeatureFlag(codexHome, featureName) {
10118
- try {
10119
- const content = await fsp.readFile(resolveCodexConfigPath(codexHome), "utf8");
10120
- return isCodexFeatureEnabledFromConfig(content, featureName);
10121
- } catch (error) {
10122
- if (error.code === "ENOENT") {
10123
- return false;
10124
- }
10125
- throw error;
10126
- }
10127
- }
10128
- function upsertCodexFeatureFlag(content, featureName, enabled) {
10129
- const normalized = content.replace(/\r\n/g, "\n");
10130
- const escaped = featureName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
10131
- const featuresMatch = normalized.match(/^[^\S\r\n]*\[features\][^\S\r\n]*$/m);
10132
- const nextLine = `${featureName} = ${enabled ? "true" : "false"}`;
10133
- if (!featuresMatch || featuresMatch.index === void 0) {
10134
- const prefix = normalized.trimEnd();
10135
- return prefix ? `${prefix}
10136
-
10137
- [features]
10138
- ${nextLine}
10139
- ` : `[features]
10140
- ${nextLine}
10141
- `;
10142
- }
10143
- const sectionStart = featuresMatch.index + featuresMatch[0].length;
10144
- const nextSectionMatch = normalized.slice(sectionStart).match(/^[^\S\r\n]*\[[^\]\r\n]+\][^\S\r\n]*$/m);
10145
- const sectionEnd = nextSectionMatch && nextSectionMatch.index !== void 0 ? sectionStart + nextSectionMatch.index : normalized.length;
10146
- const beforeSectionBody = normalized.slice(0, sectionStart);
10147
- const sectionBody = normalized.slice(sectionStart, sectionEnd);
10148
- const afterSection = normalized.slice(sectionEnd);
10149
- const flagPattern = new RegExp(
10150
- `(^[^\\S\\r\\n]*)${escaped}[^\\S\\r\\n]*=[^\\S\\r\\n]*(true|false)[^\\S\\r\\n]*(?:#.*)?$`,
10151
- "m"
10152
- );
10153
- if (flagPattern.test(sectionBody)) {
10154
- return beforeSectionBody + sectionBody.replace(flagPattern, `$1${nextLine}`) + afterSection;
10155
- }
10156
- const bodyWithFlag = `${sectionBody.replace(/\n*$/, "")}
10157
- ${nextLine}
10158
- `;
10159
- const normalizedAfterSection = afterSection.startsWith("\n") ? afterSection : `
10160
- ${afterSection}`;
10161
- return beforeSectionBody + bodyWithFlag + normalizedAfterSection;
10162
- }
10163
- async function writeCodexFeatureFlag(codexHome, featureName, enabled) {
10164
- const configPath = resolveCodexConfigPath(codexHome);
10165
- let current = "";
10166
- try {
10167
- current = await fsp.readFile(configPath, "utf8");
10168
- } catch (error) {
10169
- if (error.code !== "ENOENT") {
10170
- throw error;
10171
- }
10172
- }
10173
- const next = upsertCodexFeatureFlag(current, featureName, enabled);
10174
- await fsp.mkdir(path6.dirname(configPath), { recursive: true });
10175
- await fsp.writeFile(configPath, next, "utf8");
10176
- return next;
10177
- }
10178
-
10179
- // src/codex/runtime-errors.ts
10180
- function unwrapCodexJsonRpcError(error) {
10181
- if (error instanceof JsonRpcClientError) {
10182
- return error;
10183
- }
10184
- if (error instanceof AgentRuntimeError && error.provider === "codex") {
10185
- return error.cause instanceof JsonRpcClientError ? error.cause : null;
10186
- }
10187
- return null;
10188
- }
10189
- function isCodexRemoteError(error) {
10190
- const codexError = unwrapCodexJsonRpcError(error);
10191
- return codexError?.code === "remote_error" ? codexError : null;
10192
- }
10193
- function isCodexRuntimeRequestError(error) {
10194
- return Boolean(unwrapCodexJsonRpcError(error));
10195
- }
10196
- function isRemoteThreadBootstrapError(error) {
10197
- const codexError = isCodexRemoteError(error);
10198
- if (!codexError) {
10199
- return false;
10200
- }
10201
- return codexError.message.includes("includeTurns is unavailable before first user message") || codexError.message.includes("is not materialized yet") || codexError.message.includes("no rollout found for thread id") || codexError.message.includes("failed to load rollout") || codexError.message.includes("rollout at") && codexError.message.includes("is empty");
10202
- }
10203
- function isUnsupportedHooksListError(error) {
10204
- const codexError = isCodexRemoteError(error);
10205
- if (!codexError) {
10206
- return false;
10207
- }
10208
- const remoteCode = codexError.details?.code;
10209
- const message = codexError.message.toLowerCase();
10210
- return remoteCode === -32601 || message.includes("endpoint not found") || message.includes("method not found") || message.includes("hooks/list") && message.includes("not found");
10211
- }
10212
- function parseTurnSteerRace(error) {
10213
- const codexError = isCodexRemoteError(error);
10214
- if (!codexError) {
10215
- return null;
10216
- }
10217
- if (codexError.message === "no active turn to steer") {
10218
- return { type: "missing" };
10219
- }
10220
- const mismatchPrefix = "expected active turn id `";
10221
- const mismatchSeparator = "` but found `";
10222
- if (!codexError.message.startsWith(mismatchPrefix)) {
10223
- return null;
10224
- }
10225
- const actualTurnId = codexError.message.slice(mismatchPrefix.length).split(mismatchSeparator)[1]?.replace(/`$/, "");
10226
- if (!actualTurnId) {
10227
- return null;
10228
- }
10229
- return {
10230
- type: "turnIdMismatch",
10231
- actualTurnId
10232
- };
10233
- }
10234
-
10235
10648
  // src/thread-history-items.ts
10236
10649
  var DEFERRED_COMMAND_DETAIL_TITLE2 = "Command Output";
10237
10650
  var DEFERRED_TOOL_DETAIL_TITLE2 = "Tool Call Details";
@@ -10298,7 +10711,7 @@ function deferLargeHistoryItemDetails(turn, deferredDetails) {
10298
10711
  };
10299
10712
  }
10300
10713
  function shouldPersistLiveHistoryItem(item) {
10301
- return item.kind === "commandExecution" || item.kind === "fileChange" || item.kind === "hook" || item.kind === "toolCall" || item.kind === "webSearch";
10714
+ return item.kind === "agentMessage" || item.kind === "commandExecution" || item.kind === "fileChange" || item.kind === "hook" || item.kind === "toolCall" || item.kind === "webSearch";
10302
10715
  }
10303
10716
  function parseStoredHistoryItem(value) {
10304
10717
  try {
@@ -10333,14 +10746,59 @@ function sortTurnItemsByRecordedSequence(items) {
10333
10746
  leadingItems.push(items[index]);
10334
10747
  index += 1;
10335
10748
  }
10336
- return [...leadingItems, ...sortHistoryItemsBySequence(items.slice(index))];
10749
+ const trailingItems = items.slice(index);
10750
+ if (!trailingItems.some(hasHistoryItemSequence)) {
10751
+ return items;
10752
+ }
10753
+ const sequenceValues = trailingItems.map((item) => historyItemSequence(item)).filter(Number.isFinite);
10754
+ const maxSequence = sequenceValues.length > 0 ? Math.max(...sequenceValues) : 0;
10755
+ const orderedItems = [];
10756
+ let cursor = 0;
10757
+ while (cursor < trailingItems.length) {
10758
+ const item = trailingItems[cursor];
10759
+ if (hasHistoryItemSequence(item)) {
10760
+ orderedItems.push({ item, index: cursor, order: historyItemSequence(item) });
10761
+ cursor += 1;
10762
+ continue;
10763
+ }
10764
+ const blockStart = cursor;
10765
+ while (cursor < trailingItems.length && !hasHistoryItemSequence(trailingItems[cursor])) {
10766
+ cursor += 1;
10767
+ }
10768
+ const block = trailingItems.slice(blockStart, cursor);
10769
+ const previousSequenced = [...trailingItems.slice(0, blockStart)].reverse().find(hasHistoryItemSequence);
10770
+ const nextSequenced = trailingItems.slice(cursor).find(hasHistoryItemSequence);
10771
+ const previousSequence = previousSequenced ? historyItemSequence(previousSequenced) : null;
10772
+ const nextSequence = nextSequenced ? historyItemSequence(nextSequenced) : null;
10773
+ block.forEach((blockItem, blockIndex) => {
10774
+ let order;
10775
+ if (previousSequence === null && nextSequence !== null) {
10776
+ order = nextSequence - (block.length - blockIndex) / (block.length + 1);
10777
+ } else if (previousSequence !== null && nextSequence !== null && nextSequence > previousSequence) {
10778
+ const span = nextSequence - previousSequence;
10779
+ order = previousSequence + (blockIndex + 1) / (block.length + 1) * span;
10780
+ } else {
10781
+ order = maxSequence + 1 + blockIndex / (block.length + 1);
10782
+ }
10783
+ orderedItems.push({
10784
+ item: blockItem,
10785
+ index: blockStart + blockIndex,
10786
+ order
10787
+ });
10788
+ });
10789
+ }
10790
+ const sortedTrailingItems = orderedItems.sort((left, right) => {
10791
+ const orderDelta = left.order - right.order;
10792
+ return orderDelta === 0 ? left.index - right.index : orderDelta;
10793
+ }).map((entry) => entry.item);
10794
+ return [...leadingItems, ...sortedTrailingItems];
10337
10795
  }
10338
10796
  function mergeHistoryItemsBySequence(items, missingItems) {
10339
10797
  if (missingItems.length === 0) {
10340
- return items;
10798
+ return sortTurnItemsByRecordedSequence(items);
10341
10799
  }
10342
10800
  if (!missingItems.some(hasHistoryItemSequence)) {
10343
- return [...items, ...missingItems];
10801
+ return sortTurnItemsByRecordedSequence([...items, ...missingItems]);
10344
10802
  }
10345
10803
  const mergedItems = [...items];
10346
10804
  const orderedMissingItems = sortHistoryItemsBySequence(missingItems);
@@ -10365,16 +10823,20 @@ function mergeHistoryItemsBySequence(items, missingItems) {
10365
10823
  }
10366
10824
  mergedItems.push(missingItem);
10367
10825
  }
10368
- return mergedItems;
10826
+ return sortTurnItemsByRecordedSequence(mergedItems);
10827
+ }
10828
+ function copyPersistedSequence(item, persistedItem) {
10829
+ if (!hasHistoryItemSequence(persistedItem)) {
10830
+ return item;
10831
+ }
10832
+ const sequence = historyItemSequence(persistedItem);
10833
+ return item.sequence === sequence ? item : { ...item, sequence };
10369
10834
  }
10370
10835
  function mergePersistedHistoryItemsIntoTurns(turns, persistedItemsByTurnId, deferredDetails) {
10371
10836
  if (persistedItemsByTurnId.size === 0) {
10372
10837
  return turns;
10373
10838
  }
10374
10839
  return turns.map((turn) => {
10375
- if (turn.status === "inProgress") {
10376
- return turn;
10377
- }
10378
10840
  const persistedItems = persistedItemsByTurnId.get(turn.id);
10379
10841
  if (!persistedItems || persistedItems.length === 0) {
10380
10842
  return turn;
@@ -10383,23 +10845,32 @@ function mergePersistedHistoryItemsIntoTurns(turns, persistedItemsByTurnId, defe
10383
10845
  const persistedItemsById = new Map(persistedItems.map((item) => [item.id, item]));
10384
10846
  const nextItems = turn.items.map((item) => {
10385
10847
  const persistedItem = persistedItemsById.get(item.id);
10386
- if (!persistedItem || item.kind !== persistedItem.kind || persistedItem.kind !== "commandExecution" && persistedItem.kind !== "toolCall") {
10848
+ if (!persistedItem) {
10387
10849
  return item;
10388
10850
  }
10389
- const existingText = item.detailText?.trim() || item.text.trim();
10390
- const persistedText = persistedItem.detailText?.trim() || persistedItem.text.trim();
10391
- if (persistedText.length <= existingText.length) {
10851
+ persistedItemsById.delete(item.id);
10852
+ if (item.kind !== persistedItem.kind) {
10392
10853
  return item;
10393
10854
  }
10394
- changed = true;
10395
- persistedItemsById.delete(item.id);
10396
- return persistedItem.kind === "commandExecution" ? deferCommandHistoryItem2(
10397
- persistedItem,
10398
- deferredDetails
10399
- ) : deferToolCallHistoryItem2(
10400
- persistedItem,
10401
- deferredDetails
10402
- );
10855
+ const sequencedItem = copyPersistedSequence(item, persistedItem);
10856
+ if (sequencedItem !== item) {
10857
+ changed = true;
10858
+ }
10859
+ if (persistedItem.kind === "commandExecution" || persistedItem.kind === "toolCall") {
10860
+ const existingText = item.detailText?.trim() || item.text.trim();
10861
+ const persistedText = persistedItem.detailText?.trim() || persistedItem.text.trim();
10862
+ if (persistedText.length > existingText.length) {
10863
+ changed = true;
10864
+ return persistedItem.kind === "commandExecution" ? deferCommandHistoryItem2(
10865
+ persistedItem,
10866
+ deferredDetails
10867
+ ) : deferToolCallHistoryItem2(
10868
+ persistedItem,
10869
+ deferredDetails
10870
+ );
10871
+ }
10872
+ }
10873
+ return sequencedItem;
10403
10874
  });
10404
10875
  const existingItemIds = new Set(nextItems.map((item) => item.id));
10405
10876
  const missingItems = [...persistedItemsById.values()].filter((item) => !existingItemIds.has(item.id)).map(
@@ -10457,25 +10928,25 @@ function applyRecordedTurnItemOrders(turns, turnItemOrder) {
10457
10928
  }
10458
10929
 
10459
10930
  // src/codex/modelPricing.ts
10460
- import fs7 from "fs";
10461
- import path7 from "path";
10931
+ import fs8 from "fs";
10932
+ import path8 from "path";
10462
10933
  var TOKEN_PRICE_DENOMINATOR = 1e6;
10463
10934
  var cachedPricingConfig = null;
10464
10935
  function resolvePackageRoot2(start = process.cwd()) {
10465
10936
  if (process.env.REMOTE_CODEX_PACKAGE_ROOT) {
10466
- return path7.resolve(process.env.REMOTE_CODEX_PACKAGE_ROOT);
10937
+ return path8.resolve(process.env.REMOTE_CODEX_PACKAGE_ROOT);
10467
10938
  }
10468
- let current = path7.resolve(start);
10469
- while (current !== path7.dirname(current)) {
10470
- if (fs7.existsSync(path7.join(current, "pnpm-workspace.yaml"))) {
10939
+ let current = path8.resolve(start);
10940
+ while (current !== path8.dirname(current)) {
10941
+ if (fs8.existsSync(path8.join(current, "pnpm-workspace.yaml"))) {
10471
10942
  return current;
10472
10943
  }
10473
- current = path7.dirname(current);
10944
+ current = path8.dirname(current);
10474
10945
  }
10475
10946
  throw new Error("Unable to locate package root for Codex pricing config.");
10476
10947
  }
10477
10948
  function getPricingConfigPath() {
10478
- return path7.join(resolvePackageRoot2(), "config", "codex-model-pricing.json");
10949
+ return path8.join(resolvePackageRoot2(), "config", "codex-model-pricing.json");
10479
10950
  }
10480
10951
  function isPositiveNumber(value) {
10481
10952
  return typeof value === "number" && Number.isFinite(value) && value >= 0;
@@ -10552,7 +11023,7 @@ function getPricingConfig() {
10552
11023
  return cachedPricingConfig;
10553
11024
  }
10554
11025
  const configPath = getPricingConfigPath();
10555
- const content = fs7.readFileSync(configPath, "utf8");
11026
+ const content = fs8.readFileSync(configPath, "utf8");
10556
11027
  cachedPricingConfig = parsePricingConfig(JSON.parse(content));
10557
11028
  return cachedPricingConfig;
10558
11029
  }
@@ -10633,20 +11104,6 @@ var THREAD_DETAIL_CACHE_TTL_MS = 5e3;
10633
11104
  var CONTEXT_BASELINE_TOKENS = 12e3;
10634
11105
  var FAST_MODE_NOTE_ON = "Fast mode on";
10635
11106
  var FAST_MODE_NOTE_OFF = "Fast mode off";
10636
- var HOOK_EVENT_JSON_KEYS = {
10637
- preToolUse: "PreToolUse",
10638
- permissionRequest: "PermissionRequest",
10639
- postToolUse: "PostToolUse",
10640
- preCompact: "PreCompact",
10641
- postCompact: "PostCompact",
10642
- sessionStart: "SessionStart",
10643
- userPromptSubmit: "UserPromptSubmit",
10644
- stop: "Stop"
10645
- };
10646
- var HOOK_EVENT_DTO_KEYS = Object.fromEntries(
10647
- Object.entries(HOOK_EVENT_JSON_KEYS).map(([dtoKey, jsonKey]) => [jsonKey, dtoKey])
10648
- );
10649
- var GOAL_FEATURE_DISABLED_MESSAGE = "Codex /goal is experimental. Enable it by adding `goals = true` under `[features]` in ~/.codex/config.toml, then restart the Codex app-server.";
10650
11107
  function toIsoFromEpoch2(value) {
10651
11108
  if (typeof value !== "number" || !Number.isFinite(value)) {
10652
11109
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -10718,22 +11175,36 @@ function mergeGoalHistoryEntry(existing, incoming) {
10718
11175
  localGoalId: latest.localGoalId ?? fallback.localGoalId ?? null
10719
11176
  };
10720
11177
  }
10721
- function mapCodexGoalError(error) {
10722
- const codexError = unwrapCodexJsonRpcError(error);
10723
- if (codexError) {
10724
- const remoteMessage = codexError.message || "";
10725
- if (remoteMessage.toLowerCase().includes("goals feature is disabled")) {
10726
- throw new HttpError(409, {
10727
- code: "goal_feature_disabled",
10728
- message: GOAL_FEATURE_DISABLED_MESSAGE
10729
- });
10730
- }
10731
- throw new HttpError(502, {
10732
- code: "codex_goal_error",
10733
- message: remoteMessage || "Codex goal operation failed."
10734
- });
11178
+ function goalHistoryStatusRank(status) {
11179
+ return ["active", "paused", "budgetLimited"].includes(status) ? 0 : 1;
11180
+ }
11181
+ function resetGoalProgress(goal) {
11182
+ return {
11183
+ ...goal,
11184
+ tokensUsed: 0,
11185
+ timeUsedSeconds: 0
11186
+ };
11187
+ }
11188
+ function goalObjectiveChanged(existing, nextObjective) {
11189
+ return existing !== null && typeof nextObjective === "string" && nextObjective.trim().length > 0 && nextObjective !== existing.objective;
11190
+ }
11191
+ function isLocalGoalStatus(status) {
11192
+ return ["active", "paused", "budgetLimited"].includes(status);
11193
+ }
11194
+ function localGoalSnapshotForFallback(goalHistory) {
11195
+ const activeGoal = goalHistory.find((entry) => isLocalGoalStatus(entry.status)) ?? null;
11196
+ if (activeGoal) {
11197
+ return activeGoal;
11198
+ }
11199
+ return goalHistory.find((entry) => entry.status === "terminated") ?? null;
11200
+ }
11201
+ function localGoalSnapshotToPreserve(goalHistory, remoteGoal) {
11202
+ const activeGoal = goalHistory.find((entry) => isLocalGoalStatus(entry.status)) ?? null;
11203
+ const hasTerminatedHistory = goalHistory.some((entry) => entry.status === "terminated");
11204
+ if (activeGoal && hasTerminatedHistory && isLocalGoalStatus(remoteGoal.status) && activeGoal.objective === remoteGoal.objective && activeGoal.tokensUsed === 0 && activeGoal.timeUsedSeconds === 0 && (remoteGoal.tokensUsed > 0 || remoteGoal.timeUsedSeconds > 0)) {
11205
+ return activeGoal;
10735
11206
  }
10736
- throw error;
11207
+ return activeGoal ? null : goalHistory.find((entry) => entry.status === "terminated") ?? null;
10737
11208
  }
10738
11209
  function defaultSandboxModeForApprovalMode(approvalMode) {
10739
11210
  return approvalMode === "guarded" ? "workspace-write" : "danger-full-access";
@@ -10748,34 +11219,6 @@ function normalizeSandboxMode(value) {
10748
11219
  return null;
10749
11220
  }
10750
11221
  }
10751
- function buildTurnSandboxPolicy(sandboxMode, writableRoot) {
10752
- switch (sandboxMode) {
10753
- case "danger-full-access":
10754
- return {
10755
- type: "dangerFullAccess"
10756
- };
10757
- case "read-only":
10758
- return {
10759
- type: "readOnly",
10760
- access: {
10761
- type: "fullAccess"
10762
- },
10763
- networkAccess: false
10764
- };
10765
- case "workspace-write":
10766
- default:
10767
- return {
10768
- type: "workspaceWrite",
10769
- writableRoots: [writableRoot],
10770
- readOnlyAccess: {
10771
- type: "fullAccess"
10772
- },
10773
- networkAccess: false,
10774
- excludeTmpdirEnvVar: false,
10775
- excludeSlashTmp: false
10776
- };
10777
- }
10778
- }
10779
11222
  function normalizeReasoningEffort(value) {
10780
11223
  switch (value) {
10781
11224
  case "none":
@@ -10795,8 +11238,8 @@ function normalizeCollaborationMode(value) {
10795
11238
  function normalizeFastMode(value) {
10796
11239
  return value === true || value === 1;
10797
11240
  }
10798
- function serviceTierForFastMode(fastMode) {
10799
- return fastMode ? "fast" : null;
11241
+ function performanceModeForFastMode(fastMode) {
11242
+ return fastMode ? "fast" : "standard";
10800
11243
  }
10801
11244
  function normalizePricingTier(value) {
10802
11245
  return value === "fast" || value === "standard" ? value : null;
@@ -10841,26 +11284,26 @@ function buildAnsweredRequestNote(request, input) {
10841
11284
  }
10842
11285
  async function pathExists(absPath) {
10843
11286
  try {
10844
- await fs8.access(absPath);
11287
+ await fs9.access(absPath);
10845
11288
  return true;
10846
11289
  } catch {
10847
11290
  return false;
10848
11291
  }
10849
11292
  }
10850
11293
  async function resolveComparablePath2(absPath) {
10851
- const resolved = path8.resolve(absPath);
11294
+ const resolved = path9.resolve(absPath);
10852
11295
  if (await pathExists(resolved)) {
10853
- return fs8.realpath(resolved);
11296
+ return fs9.realpath(resolved);
10854
11297
  }
10855
- const parentPath = path8.dirname(resolved);
11298
+ const parentPath = path9.dirname(resolved);
10856
11299
  if (parentPath === resolved) {
10857
11300
  return resolved;
10858
11301
  }
10859
11302
  const resolvedParent = await resolveComparablePath2(parentPath);
10860
- return path8.join(resolvedParent, path8.basename(resolved));
11303
+ return path9.join(resolvedParent, path9.basename(resolved));
10861
11304
  }
10862
11305
  async function resolveImportedWorkspacePath(workspaceRoot, candidatePath) {
10863
- if (!path8.isAbsolute(candidatePath)) {
11306
+ if (!path9.isAbsolute(candidatePath)) {
10864
11307
  throw new HttpError(400, {
10865
11308
  code: "bad_request",
10866
11309
  message: "Imported session path must be absolute."
@@ -10868,7 +11311,7 @@ async function resolveImportedWorkspacePath(workspaceRoot, candidatePath) {
10868
11311
  }
10869
11312
  const resolvedRoot = await resolveComparablePath2(workspaceRoot);
10870
11313
  const resolvedCandidate = await resolveComparablePath2(candidatePath);
10871
- const normalizedRoot = resolvedRoot.endsWith(path8.sep) ? resolvedRoot : `${resolvedRoot}${path8.sep}`;
11314
+ const normalizedRoot = resolvedRoot.endsWith(path9.sep) ? resolvedRoot : `${resolvedRoot}${path9.sep}`;
10872
11315
  if (resolvedCandidate !== resolvedRoot && !resolvedCandidate.startsWith(normalizedRoot)) {
10873
11316
  throw new HttpError(403, {
10874
11317
  code: "forbidden",
@@ -10888,7 +11331,7 @@ function toWorkspaceDto(record) {
10888
11331
  lastOpenedAt: record.lastOpenedAt
10889
11332
  };
10890
11333
  }
10891
- function isRecord4(value) {
11334
+ function isRecord5(value) {
10892
11335
  return typeof value === "object" && value !== null && !Array.isArray(value);
10893
11336
  }
10894
11337
  function numberOrNull2(value) {
@@ -10927,11 +11370,11 @@ function computeContextRemainingPercent(tokensInContextWindow, contextWindow) {
10927
11370
  return clampPercentage(Math.round(remaining / effectiveWindow * 100));
10928
11371
  }
10929
11372
  function buildThreadContextUsageFromPayload(payload, model = null, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
10930
- const tokenUsage = isRecord4(payload) ? payload : null;
11373
+ const tokenUsage = isRecord5(payload) ? payload : null;
10931
11374
  const modelContextWindow = contextWindowForModel(model) ?? numberOrNull2(
10932
11375
  tokenUsage?.modelContextWindow ?? tokenUsage?.model_context_window
10933
11376
  );
10934
- const lastUsage = isRecord4(tokenUsage?.last) ? tokenUsage.last : null;
11377
+ const lastUsage = isRecord5(tokenUsage?.last) ? tokenUsage.last : null;
10935
11378
  const tokensInContextWindow = numberOrNull2(
10936
11379
  lastUsage?.totalTokens ?? lastUsage?.total_tokens
10937
11380
  );
@@ -10950,7 +11393,7 @@ function buildThreadContextUsageFromPayload(payload, model = null, timestamp = (
10950
11393
  };
10951
11394
  }
10952
11395
  function buildTurnTokenBreakdown(payload) {
10953
- const usage = isRecord4(payload) ? payload : null;
11396
+ const usage = isRecord5(payload) ? payload : null;
10954
11397
  const totalTokens2 = numberOrNull2(usage?.totalTokens ?? usage?.total_tokens);
10955
11398
  const inputTokens = numberOrNull2(usage?.inputTokens ?? usage?.input_tokens);
10956
11399
  const cachedInputTokens = numberOrNull2(
@@ -10996,12 +11439,12 @@ function subtractTurnTokenBreakdowns(current, previous) {
10996
11439
  };
10997
11440
  }
10998
11441
  function parseThreadTurnTokenUsage(payload) {
10999
- const tokenUsage = isRecord4(payload) ? payload : null;
11442
+ const tokenUsage = isRecord5(payload) ? payload : null;
11000
11443
  const total = buildTurnTokenBreakdown(
11001
- isRecord4(tokenUsage?.total) ? tokenUsage.total : null
11444
+ isRecord5(tokenUsage?.total) ? tokenUsage.total : null
11002
11445
  );
11003
11446
  const last = buildTurnTokenBreakdown(
11004
- isRecord4(tokenUsage?.last) ? tokenUsage.last : null
11447
+ isRecord5(tokenUsage?.last) ? tokenUsage.last : null
11005
11448
  );
11006
11449
  const modelContextWindow = numberOrNull2(
11007
11450
  tokenUsage?.modelContextWindow ?? tokenUsage?.model_context_window
@@ -11025,7 +11468,7 @@ function parseStoredThreadTurnTokenUsageState(value) {
11025
11468
  try {
11026
11469
  const parsed = JSON.parse(value);
11027
11470
  const baselineTotal = buildTurnTokenBreakdown(
11028
- isRecord4(parsed?.baselineTotal) ? parsed.baselineTotal : null
11471
+ isRecord5(parsed?.baselineTotal) ? parsed.baselineTotal : null
11029
11472
  );
11030
11473
  return {
11031
11474
  baselineTotal,
@@ -11062,12 +11505,12 @@ function stringifyStoredThreadTurnTokenUsageState(state) {
11062
11505
  });
11063
11506
  }
11064
11507
  function buildThreadTurnTokenUsage(payload, baselineTotal, previous = null) {
11065
- const tokenUsage = isRecord4(payload) ? payload : null;
11508
+ const tokenUsage = isRecord5(payload) ? payload : null;
11066
11509
  const cumulativeTotal = buildTurnTokenBreakdown(
11067
- isRecord4(tokenUsage?.total) ? tokenUsage.total : null
11510
+ isRecord5(tokenUsage?.total) ? tokenUsage.total : null
11068
11511
  );
11069
11512
  const last = buildTurnTokenBreakdown(
11070
- isRecord4(tokenUsage?.last) ? tokenUsage.last : null
11513
+ isRecord5(tokenUsage?.last) ? tokenUsage.last : null
11071
11514
  );
11072
11515
  const modelContextWindow = numberOrNull2(
11073
11516
  tokenUsage?.modelContextWindow ?? tokenUsage?.model_context_window
@@ -11085,8 +11528,8 @@ function parseThreadTurnTokenUsageJson(value) {
11085
11528
  return parseStoredThreadTurnTokenUsageState(value).usage;
11086
11529
  }
11087
11530
  function sanitizeAttachmentFileName(originalName) {
11088
- const basename = path8.basename(originalName).trim() || "attachment";
11089
- const extension = path8.extname(basename).replace(/[^a-zA-Z0-9.]/g, "");
11531
+ const basename = path9.basename(originalName).trim() || "attachment";
11532
+ const extension = path9.extname(basename).replace(/[^a-zA-Z0-9.]/g, "");
11090
11533
  const rawStem = extension ? basename.slice(0, -extension.length) : basename;
11091
11534
  const sanitizedStem = rawStem.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 64);
11092
11535
  const stem = sanitizedStem || "attachment";
@@ -11094,269 +11537,7 @@ function sanitizeAttachmentFileName(originalName) {
11094
11537
  return `${stem}-${randomUUID2().slice(0, 8)}${normalizedExtension}`;
11095
11538
  }
11096
11539
  function threadTempDirectoryPath(workspacePath, localThreadId) {
11097
- return path8.join(workspacePath, ".temp", "threads", localThreadId);
11098
- }
11099
- function normalizeHooksJson(value) {
11100
- if (!isRecord4(value) || !isRecord4(value.hooks)) {
11101
- return { hooks: {} };
11102
- }
11103
- const hooks = {};
11104
- for (const [eventName, groups] of Object.entries(value.hooks)) {
11105
- hooks[eventName] = Array.isArray(groups) ? groups : [];
11106
- }
11107
- return { ...value, hooks };
11108
- }
11109
- function readJsonFileOrDefault(filePath) {
11110
- return fs8.readFile(filePath, "utf8").then((raw) => {
11111
- if (!raw.trim()) {
11112
- return { hooks: {} };
11113
- }
11114
- return normalizeHooksJson(JSON.parse(raw));
11115
- }).catch((error) => {
11116
- if (error.code === "ENOENT") {
11117
- return { hooks: {} };
11118
- }
11119
- throw error;
11120
- });
11121
- }
11122
- function validateHookInput(input) {
11123
- if (!HOOK_EVENT_JSON_KEYS[input.eventName]) {
11124
- throw new HttpError(400, {
11125
- code: "bad_request",
11126
- message: "Unsupported hook event."
11127
- });
11128
- }
11129
- if (input.scope !== "global" && input.scope !== "project") {
11130
- throw new HttpError(400, {
11131
- code: "bad_request",
11132
- message: "Hook scope must be global or project."
11133
- });
11134
- }
11135
- if (!input.command.trim()) {
11136
- throw new HttpError(400, {
11137
- code: "bad_request",
11138
- message: "Hook command cannot be empty."
11139
- });
11140
- }
11141
- if (input.timeoutSec !== void 0 && input.timeoutSec !== null && (!Number.isInteger(input.timeoutSec) || input.timeoutSec <= 0 || input.timeoutSec > 86400)) {
11142
- throw new HttpError(400, {
11143
- code: "bad_request",
11144
- message: "Hook timeout must be a positive number of seconds."
11145
- });
11146
- }
11147
- }
11148
- async function writeHookJsonEntry({
11149
- codexHome,
11150
- workspacePath,
11151
- input
11152
- }) {
11153
- validateHookInput(input);
11154
- const hooksPath = input.scope === "global" ? path8.join(codexHome, "hooks.json") : path8.join(workspacePath, ".codex", "hooks.json");
11155
- const config = await readJsonFileOrDefault(hooksPath);
11156
- const eventKey = HOOK_EVENT_JSON_KEYS[input.eventName];
11157
- const matcher = input.matcher?.trim() || null;
11158
- const handler = {
11159
- type: "command",
11160
- command: input.command.trim()
11161
- };
11162
- if (input.timeoutSec !== void 0 && input.timeoutSec !== null) {
11163
- handler.timeout = input.timeoutSec;
11164
- }
11165
- if (input.statusMessage?.trim()) {
11166
- handler.statusMessage = input.statusMessage.trim();
11167
- }
11168
- const group = {
11169
- hooks: [handler]
11170
- };
11171
- if (matcher) {
11172
- group.matcher = matcher;
11173
- }
11174
- const currentGroups = Array.isArray(config.hooks[eventKey]) ? config.hooks[eventKey] : [];
11175
- config.hooks[eventKey] = [...currentGroups, group];
11176
- await fs8.mkdir(path8.dirname(hooksPath), { recursive: true });
11177
- await fs8.writeFile(hooksPath, `${JSON.stringify(config, null, 2)}
11178
- `, "utf8");
11179
- }
11180
- function hookInputMatches(group, handler, input) {
11181
- if (!isRecord4(group) || !isRecord4(handler)) {
11182
- return false;
11183
- }
11184
- const matcher = typeof group.matcher === "string" ? group.matcher : null;
11185
- const handlerCommand = typeof handler.command === "string" ? handler.command : "";
11186
- const handlerTimeout = typeof handler.timeout === "number" && Number.isFinite(handler.timeout) ? handler.timeout : null;
11187
- const handlerStatusMessage = typeof handler.statusMessage === "string" ? handler.statusMessage : null;
11188
- return handler.type === "command" && (input.matcher?.trim() || null) === matcher && input.command.trim() === handlerCommand && (input.timeoutSec ?? null) === handlerTimeout && (input.statusMessage?.trim() || null) === handlerStatusMessage;
11189
- }
11190
- async function updateHookJsonEntry({
11191
- codexHome,
11192
- workspacePath,
11193
- input
11194
- }) {
11195
- validateHookInput(input);
11196
- validateHookInput(input.target);
11197
- if (input.scope !== input.target.scope) {
11198
- throw new HttpError(400, {
11199
- code: "bad_request",
11200
- message: "Hook scope cannot be changed while editing."
11201
- });
11202
- }
11203
- const hooksPath = input.scope === "global" ? path8.join(codexHome, "hooks.json") : path8.join(workspacePath, ".codex", "hooks.json");
11204
- const config = await readJsonFileOrDefault(hooksPath);
11205
- const targetEventKey = HOOK_EVENT_JSON_KEYS[input.target.eventName];
11206
- const nextEventKey = HOOK_EVENT_JSON_KEYS[input.eventName];
11207
- const currentGroups = Array.isArray(config.hooks[targetEventKey]) ? config.hooks[targetEventKey] : [];
11208
- let replacementGroup = null;
11209
- config.hooks[targetEventKey] = currentGroups.map((group) => {
11210
- if (replacementGroup || !isRecord4(group) || !Array.isArray(group.hooks)) {
11211
- return group;
11212
- }
11213
- const hookIndex = group.hooks.findIndex(
11214
- (handler2) => hookInputMatches(group, handler2, input.target)
11215
- );
11216
- if (hookIndex < 0) {
11217
- return group;
11218
- }
11219
- const handler = {
11220
- type: "command",
11221
- command: input.command.trim()
11222
- };
11223
- if (input.timeoutSec !== void 0 && input.timeoutSec !== null) {
11224
- handler.timeout = input.timeoutSec;
11225
- }
11226
- if (input.statusMessage?.trim()) {
11227
- handler.statusMessage = input.statusMessage.trim();
11228
- }
11229
- replacementGroup = {
11230
- hooks: [handler]
11231
- };
11232
- const matcher = input.matcher?.trim() || null;
11233
- if (matcher) {
11234
- replacementGroup.matcher = matcher;
11235
- }
11236
- if (targetEventKey !== nextEventKey) {
11237
- const remainingHooks = group.hooks.filter((_, index) => index !== hookIndex);
11238
- return {
11239
- ...group,
11240
- hooks: remainingHooks
11241
- };
11242
- }
11243
- return {
11244
- ...replacementGroup,
11245
- hooks: group.hooks.map(
11246
- (existing, index) => index === hookIndex ? replacementGroup.hooks[0] : existing
11247
- )
11248
- };
11249
- }).filter((group) => {
11250
- if (!isRecord4(group) || !Array.isArray(group.hooks)) {
11251
- return true;
11252
- }
11253
- return group.hooks.length > 0;
11254
- });
11255
- if (!replacementGroup) {
11256
- throw new HttpError(404, {
11257
- code: "not_found",
11258
- message: "Hook was not found in hooks.json."
11259
- });
11260
- }
11261
- if (targetEventKey !== nextEventKey) {
11262
- if (config.hooks[targetEventKey]?.length === 0) {
11263
- delete config.hooks[targetEventKey];
11264
- }
11265
- const nextGroups = Array.isArray(config.hooks[nextEventKey]) ? config.hooks[nextEventKey] : [];
11266
- config.hooks[nextEventKey] = [...nextGroups, replacementGroup];
11267
- }
11268
- await fs8.mkdir(path8.dirname(hooksPath), { recursive: true });
11269
- await fs8.writeFile(hooksPath, `${JSON.stringify(config, null, 2)}
11270
- `, "utf8");
11271
- }
11272
- async function readLocalHookDtos({
11273
- hooksPath,
11274
- source,
11275
- displayOffset
11276
- }) {
11277
- const config = await readJsonFileOrDefault(hooksPath);
11278
- const hooks = [];
11279
- for (const [eventKey, groups] of Object.entries(config.hooks)) {
11280
- const eventName = HOOK_EVENT_DTO_KEYS[eventKey];
11281
- if (!eventName || !Array.isArray(groups)) {
11282
- continue;
11283
- }
11284
- groups.forEach((group, groupIndex) => {
11285
- if (!isRecord4(group) || !Array.isArray(group.hooks)) {
11286
- return;
11287
- }
11288
- const matcher = typeof group.matcher === "string" ? group.matcher : null;
11289
- group.hooks.forEach((handler, handlerIndex) => {
11290
- if (!isRecord4(handler) || handler.type !== "command") {
11291
- return;
11292
- }
11293
- const command = typeof handler.command === "string" ? handler.command : null;
11294
- if (!command) {
11295
- return;
11296
- }
11297
- const timeoutSec = typeof handler.timeout === "number" && Number.isFinite(handler.timeout) ? handler.timeout : 600;
11298
- const statusMessage = typeof handler.statusMessage === "string" ? handler.statusMessage : null;
11299
- const key = `${source}:${hooksPath}:${eventKey}:${groupIndex}:${handlerIndex}`;
11300
- hooks.push({
11301
- key,
11302
- eventName,
11303
- handlerType: "command",
11304
- matcher,
11305
- command,
11306
- timeoutSec,
11307
- statusMessage,
11308
- sourcePath: hooksPath,
11309
- source,
11310
- pluginId: null,
11311
- displayOrder: displayOffset + hooks.length,
11312
- enabled: true,
11313
- isManaged: false,
11314
- currentHash: "",
11315
- trustStatus: "untrusted"
11316
- });
11317
- });
11318
- });
11319
- }
11320
- return hooks;
11321
- }
11322
- function hookMatchesInput(hook, input) {
11323
- return hook.source === input.scope && hook.eventName === input.eventName && (hook.matcher ?? null) === (input.matcher ?? null) && hook.command === input.command && (input.timeoutSec == null || hook.timeoutSec === input.timeoutSec) && (hook.statusMessage ?? null) === (input.statusMessage ?? null);
11324
- }
11325
- async function findOfficialHookForInput(runtime, workspacePath, input) {
11326
- if (!runtime.listHooks) {
11327
- return null;
11328
- }
11329
- const [entry] = await runtime.listHooks({
11330
- cwds: [workspacePath]
11331
- });
11332
- const officialHooks = (entry?.hooks ?? []).map((hook) => ({
11333
- key: hook.key,
11334
- eventName: hook.eventName,
11335
- handlerType: hook.handlerType,
11336
- matcher: hook.matcher,
11337
- command: hook.command,
11338
- timeoutSec: hook.timeoutSec,
11339
- statusMessage: hook.statusMessage,
11340
- sourcePath: hook.sourcePath,
11341
- source: hook.source,
11342
- pluginId: hook.pluginId,
11343
- displayOrder: hook.displayOrder,
11344
- enabled: hook.enabled,
11345
- isManaged: hook.isManaged,
11346
- currentHash: hook.currentHash,
11347
- trustStatus: hook.trustStatus
11348
- }));
11349
- return officialHooks.find((hook) => hookMatchesInput(hook, input)) ?? null;
11350
- }
11351
- async function trustHookForInput(runtime, workspacePath, input) {
11352
- const hook = await findOfficialHookForInput(runtime, workspacePath, input);
11353
- if (!runtime.setHookTrust || !hook || !hook.key || !hook.currentHash || hook.isManaged) {
11354
- return;
11355
- }
11356
- await runtime.setHookTrust({
11357
- key: hook.key,
11358
- trustedHash: hook.currentHash
11359
- });
11540
+ return path9.join(workspacePath, ".temp", "threads", localThreadId);
11360
11541
  }
11361
11542
  function buildTurnDto(turn, metadata) {
11362
11543
  const tokenUsage = parseThreadTurnTokenUsageJson(metadata?.tokenUsageJson);
@@ -11416,13 +11597,13 @@ function safeTranscriptExportFileName(title, extension) {
11416
11597
  return `remote-codex-${stem || "thread"}-${timestamp}.${extension}`;
11417
11598
  }
11418
11599
  var ThreadService = class {
11419
- constructor(db, agentRuntimes, eventBus, localSessionStore, workspaceRoot, codexHome) {
11600
+ constructor(db, agentRuntimes, eventBus, localSessionStore, workspaceRoot, codexManagement) {
11420
11601
  this.db = db;
11421
11602
  this.agentRuntimes = agentRuntimes;
11422
11603
  this.eventBus = eventBus;
11423
11604
  this.localSessionStore = localSessionStore;
11424
11605
  this.workspaceRoot = workspaceRoot;
11425
- this.codexHome = codexHome;
11606
+ this.codexManagement = codexManagement;
11426
11607
  for (const runtime of this.agentRuntimes.all()) {
11427
11608
  runtime.on("event", (event) => {
11428
11609
  void this.handleRuntimeEvent(event);
@@ -11437,7 +11618,7 @@ var ThreadService = class {
11437
11618
  eventBus;
11438
11619
  localSessionStore;
11439
11620
  workspaceRoot;
11440
- codexHome;
11621
+ codexManagement;
11441
11622
  pendingRequests = /* @__PURE__ */ new Map();
11442
11623
  dismissedPlanDecisionTurns = /* @__PURE__ */ new Map();
11443
11624
  threadDetailCache = /* @__PURE__ */ new Map();
@@ -11497,13 +11678,13 @@ var ThreadService = class {
11497
11678
  return this.providerForRecord({ provider }) === "codex";
11498
11679
  }
11499
11680
  runtimeSupportsFastMode(provider) {
11500
- return this.runtimeForProvider(provider).capabilities.controls.fastServiceTier;
11681
+ return this.runtimeForProvider(provider).capabilities.controls.performanceMode;
11501
11682
  }
11502
11683
  fastModeForProvider(provider, fastMode) {
11503
11684
  return this.runtimeSupportsFastMode(provider) ? normalizeFastMode(fastMode) : false;
11504
11685
  }
11505
- serviceTierForRecord(record) {
11506
- return serviceTierForFastMode(this.fastModeForProvider(record.provider, record.fastMode));
11686
+ performanceModeForRecord(record) {
11687
+ return performanceModeForFastMode(this.fastModeForProvider(record.provider, record.fastMode));
11507
11688
  }
11508
11689
  assertCodexHooksFileManagement(provider) {
11509
11690
  if (!this.isCodexProvider(provider)) {
@@ -11740,7 +11921,7 @@ var ThreadService = class {
11740
11921
  const matchedModel = modelRecords.find((entry) => entry.model === input.model);
11741
11922
  const reasoningEffort = normalizeReasoningEffort(matchedModel?.defaultReasoningEffort) ?? "medium";
11742
11923
  const sandboxMode = defaultSandboxModeForApprovalMode(input.approvalMode);
11743
- const fastMode = this.runtimeSupportsFastMode(provider) ? readCodexFastModeSync(this.codexHome) : false;
11924
+ const fastMode = this.runtimeSupportsFastMode(provider) ? this.codexManagement.readFastMode() : false;
11744
11925
  if (this.runtimeSupportsFastMode(provider)) {
11745
11926
  ensureFastModeSupported(input.model, fastMode);
11746
11927
  }
@@ -11749,7 +11930,7 @@ var ThreadService = class {
11749
11930
  model: input.model,
11750
11931
  approvalMode: input.approvalMode,
11751
11932
  sandboxMode,
11752
- serviceTier: serviceTierForFastMode(fastMode)
11933
+ performanceMode: performanceModeForFastMode(fastMode)
11753
11934
  });
11754
11935
  const created = createThreadRecord(this.db, {
11755
11936
  workspaceId: workspace.id,
@@ -11804,7 +11985,7 @@ var ThreadService = class {
11804
11985
  if (!workspace) {
11805
11986
  workspace = createWorkspaceRecord(this.db, {
11806
11987
  absPath: importedPath,
11807
- label: path8.basename(importedPath) || "workspace"
11988
+ label: path9.basename(importedPath) || "workspace"
11808
11989
  });
11809
11990
  }
11810
11991
  const created = createThreadRecord(this.db, {
@@ -11820,7 +12001,7 @@ var ThreadService = class {
11820
12001
  approvalMode: "yolo",
11821
12002
  sandboxMode: defaultSandboxModeForApprovalMode("yolo"),
11822
12003
  summaryText: localSession.turns.flatMap((turn) => turn.items).find((item) => item.kind === "userMessage")?.text ?? null,
11823
- fastMode: readCodexFastModeSync(this.codexHome),
12004
+ fastMode: this.codexManagement.readFastMode(),
11824
12005
  source: "local_codex_import",
11825
12006
  isConnected: false
11826
12007
  });
@@ -11869,8 +12050,8 @@ var ThreadService = class {
11869
12050
  cachedDetail.turns,
11870
12051
  pagedTurns.turns
11871
12052
  );
11872
- const goal = await this.getThreadGoalForRecord(updated).catch(() => null);
11873
12053
  const goalHistory = this.listThreadGoalHistory(updated.id);
12054
+ const goal = await this.getThreadGoalForRecord(updated).catch(() => null) ?? localGoalSnapshotForFallback(goalHistory);
11874
12055
  return {
11875
12056
  thread: this.toThreadDto(updated, loadedIds),
11876
12057
  workspace: toWorkspaceDto(workspace),
@@ -12010,7 +12191,7 @@ var ThreadService = class {
12010
12191
  });
12011
12192
  }
12012
12193
  this.requireProviderSessionId(record);
12013
- return this.getThreadGoalForRecord(record, { allowEnableFeature: true });
12194
+ return await this.getThreadGoalForRecord(record, { allowEnableFeature: true }) ?? localGoalSnapshotForFallback(this.listThreadGoalHistory(localThreadId));
12014
12195
  }
12015
12196
  async updateThreadGoal(localThreadId, input) {
12016
12197
  const record = getThreadRecordById(this.db, localThreadId);
@@ -12037,14 +12218,32 @@ var ThreadService = class {
12037
12218
  try {
12038
12219
  await this.ensureGoalsFeatureEnabled(record.provider);
12039
12220
  await this.ensureThreadLoadedForCodexOperation(record);
12040
- const upstreamStatus = input.status === "terminated" ? void 0 : input.status;
12221
+ const activeGoal = this.listThreadGoalHistory(localThreadId).find(
12222
+ (goal2) => ["active", "paused", "budgetLimited"].includes(goal2.status)
12223
+ ) ?? null;
12224
+ const creatingNewGoal = goalObjectiveChanged(activeGoal, input.objective);
12225
+ if (creatingNewGoal) {
12226
+ markActiveThreadGoalRecordTerminated(this.db, localThreadId);
12227
+ }
12228
+ if (input.status === "terminated") {
12229
+ const terminatedGoal = markActiveThreadGoalRecordTerminated(this.db, localThreadId);
12230
+ const goalHistory = this.listThreadGoalHistory(localThreadId);
12231
+ const goal2 = terminatedGoal ? toThreadGoalDtoFromRecord(terminatedGoal) : goalHistory[0] ?? null;
12232
+ this.emitThreadEvent("thread.goal.updated", localThreadId, {
12233
+ goal: goal2,
12234
+ goalHistory
12235
+ });
12236
+ return goal2;
12237
+ }
12238
+ const upstreamStatus = input.status;
12041
12239
  const goal = await runtime.setGoal({
12042
12240
  providerSessionId,
12043
12241
  ...input.objective !== void 0 ? { objective: input.objective } : {},
12044
12242
  ...upstreamStatus !== void 0 ? { status: upstreamStatus } : {},
12045
12243
  ...input.tokenBudget !== void 0 ? { tokenBudget: input.tokenBudget } : {}
12046
12244
  });
12047
- const dto = normalizeThreadGoalStatusForThread(toThreadGoalDtoFromAgentGoal(goal), record);
12245
+ const upstreamDto = normalizeThreadGoalStatusForThread(toThreadGoalDtoFromAgentGoal(goal), record);
12246
+ const dto = creatingNewGoal ? resetGoalProgress(upstreamDto) : upstreamDto;
12048
12247
  const persistedGoal = toThreadGoalDtoFromRecord(
12049
12248
  this.persistThreadGoalSnapshot(localThreadId, dto)
12050
12249
  );
@@ -12054,7 +12253,7 @@ var ThreadService = class {
12054
12253
  });
12055
12254
  return persistedGoal;
12056
12255
  } catch (error) {
12057
- mapCodexGoalError(error);
12256
+ this.codexManagement.mapGoalError(error);
12058
12257
  }
12059
12258
  }
12060
12259
  async clearThreadGoal(localThreadId) {
@@ -12088,7 +12287,7 @@ var ThreadService = class {
12088
12287
  this.emitThreadEvent("thread.goal.cleared", localThreadId, { goalHistory });
12089
12288
  return { cleared, goalHistory };
12090
12289
  } catch (error) {
12091
- mapCodexGoalError(error);
12290
+ this.codexManagement.mapGoalError(error);
12092
12291
  }
12093
12292
  }
12094
12293
  async getThreadGoalForRecord(record, options = {}) {
@@ -12109,9 +12308,16 @@ var ThreadService = class {
12109
12308
  return null;
12110
12309
  }
12111
12310
  const dto = normalizeThreadGoalStatusForThread(toThreadGoalDtoFromAgentGoal(goal), record);
12311
+ const localGoal = localGoalSnapshotToPreserve(
12312
+ this.listThreadGoalHistory(record.id),
12313
+ dto
12314
+ );
12315
+ if (localGoal) {
12316
+ return localGoal;
12317
+ }
12112
12318
  return toThreadGoalDtoFromRecord(this.persistThreadGoalSnapshot(record.id, dto));
12113
12319
  } catch (error) {
12114
- if (isCodexRuntimeRequestError(error)) {
12320
+ if (this.codexManagement.isRuntimeRequestError(error)) {
12115
12321
  return null;
12116
12322
  }
12117
12323
  throw error;
@@ -12144,29 +12350,20 @@ var ThreadService = class {
12144
12350
  deduped.set(key, existing ? mergeGoalHistoryEntry(existing, goal) : goal);
12145
12351
  }
12146
12352
  return [...deduped.values()].sort(
12147
- (left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt)
12353
+ (left, right) => {
12354
+ const updatedDelta = Date.parse(right.updatedAt) - Date.parse(left.updatedAt);
12355
+ if (updatedDelta !== 0) {
12356
+ return updatedDelta;
12357
+ }
12358
+ return goalHistoryStatusRank(left.status) - goalHistoryStatusRank(right.status);
12359
+ }
12148
12360
  );
12149
12361
  }
12150
12362
  async ensureGoalsFeatureEnabled(provider) {
12151
12363
  if (!this.isCodexProvider(provider)) {
12152
12364
  return;
12153
12365
  }
12154
- try {
12155
- if (await readCodexFeatureFlag(this.codexHome, "goals")) {
12156
- return;
12157
- }
12158
- await writeCodexFeatureFlag(this.codexHome, "goals", true);
12159
- await this.codexRuntime().stop();
12160
- await this.codexRuntime().start();
12161
- } catch (error) {
12162
- if (isCodexRuntimeRequestError(error)) {
12163
- throw new HttpError(409, {
12164
- code: "goal_feature_disabled",
12165
- message: GOAL_FEATURE_DISABLED_MESSAGE
12166
- });
12167
- }
12168
- throw error;
12169
- }
12366
+ await this.codexManagement.ensureGoalsFeatureEnabled(this.codexRuntime());
12170
12367
  }
12171
12368
  async ensureThreadLoadedForCodexOperation(record) {
12172
12369
  if (!record.providerSessionId) {
@@ -12244,7 +12441,7 @@ var ThreadService = class {
12244
12441
  });
12245
12442
  }
12246
12443
  const tempDirectory = threadTempDirectoryPath(workspace.absPath, localThreadId);
12247
- await fs8.mkdir(tempDirectory, { recursive: true });
12444
+ await fs9.mkdir(tempDirectory, { recursive: true });
12248
12445
  let rewrittenPrompt = input.prompt;
12249
12446
  for (const attachment of attachments) {
12250
12447
  if (!rewrittenPrompt.includes(attachment.manifest.placeholder)) {
@@ -12254,7 +12451,7 @@ var ThreadService = class {
12254
12451
  });
12255
12452
  }
12256
12453
  const savedFileName = sanitizeAttachmentFileName(attachment.manifest.originalName);
12257
- await fs8.writeFile(path8.join(tempDirectory, savedFileName), attachment.buffer);
12454
+ await fs9.writeFile(path9.join(tempDirectory, savedFileName), attachment.buffer);
12258
12455
  const relativePath = `./.temp/threads/${localThreadId}/${savedFileName}`;
12259
12456
  const replacementToken = attachment.manifest.kind === "photo" ? `[PHOTO ${relativePath}]` : `[FILE ${relativePath}]`;
12260
12457
  rewrittenPrompt = rewrittenPrompt.split(attachment.manifest.placeholder).join(replacementToken);
@@ -12286,7 +12483,7 @@ var ThreadService = class {
12286
12483
  providerSessionId,
12287
12484
  model: input.model ?? record.model ?? null,
12288
12485
  sandboxMode,
12289
- serviceTier: serviceTierForFastMode(fastMode)
12486
+ performanceMode: performanceModeForFastMode(fastMode)
12290
12487
  });
12291
12488
  } catch (error) {
12292
12489
  if (!isRemoteThreadBootstrapError(error)) {
@@ -12391,7 +12588,7 @@ var ThreadService = class {
12391
12588
  const sandboxMode = (input.sandboxMode !== void 0 ? normalizeSandboxMode(input.sandboxMode) : normalizeSandboxMode(record.sandboxMode)) ?? defaultSandboxModeForApprovalMode(record.approvalMode ?? "yolo");
12392
12589
  const fastMode = this.fastModeForProvider(record.provider, record.fastMode);
12393
12590
  ensureFastModeSupported(effectiveModel, fastMode);
12394
- const serviceTier = serviceTierForFastMode(fastMode);
12591
+ const performanceMode = performanceModeForFastMode(fastMode);
12395
12592
  const connectedRecord = {
12396
12593
  ...record,
12397
12594
  providerSessionId
@@ -12408,7 +12605,7 @@ var ThreadService = class {
12408
12605
  normalizedReasoning,
12409
12606
  collaborationMode,
12410
12607
  sandboxMode,
12411
- serviceTier,
12608
+ performanceMode,
12412
12609
  workspacePath: workspace.absPath
12413
12610
  });
12414
12611
  }
@@ -12418,26 +12615,27 @@ var ThreadService = class {
12418
12615
  normalizedReasoning,
12419
12616
  collaborationMode,
12420
12617
  sandboxMode,
12421
- serviceTier,
12618
+ performanceMode,
12422
12619
  workspacePath: workspace.absPath
12423
12620
  });
12424
12621
  }
12425
12622
  async startPromptTurn(localThreadId, record, input) {
12426
12623
  const runtime = this.runtimeForProvider(record.provider);
12427
12624
  const modelRecords = await runtime.listModels().catch(() => []);
12428
- ensureFastModeSupported(input.effectiveModel, input.serviceTier === "fast");
12625
+ ensureFastModeSupported(input.effectiveModel, input.performanceMode === "fast");
12429
12626
  const pricingSnapshot = buildTurnPricingSnapshot(
12430
12627
  input.effectiveModel,
12431
- input.serviceTier === "fast"
12628
+ input.performanceMode === "fast"
12432
12629
  );
12433
12630
  const turn = await runtime.startTurn({
12434
12631
  providerSessionId: record.providerSessionId,
12435
12632
  prompt: input.prompt,
12436
12633
  model: input.effectiveModel,
12437
- serviceTier: input.serviceTier,
12634
+ performanceMode: input.performanceMode,
12438
12635
  reasoningEffort: input.normalizedReasoning,
12439
12636
  collaborationMode: input.collaborationMode,
12440
- sandboxPolicy: buildTurnSandboxPolicy(input.sandboxMode, input.workspacePath)
12637
+ sandboxMode: input.sandboxMode,
12638
+ workspacePath: input.workspacePath
12441
12639
  });
12442
12640
  upsertThreadTurnMetadata(this.db, {
12443
12641
  threadId: localThreadId,
@@ -12525,7 +12723,7 @@ var ThreadService = class {
12525
12723
  normalizedReasoning: input.normalizedReasoning,
12526
12724
  collaborationMode: input.collaborationMode,
12527
12725
  sandboxMode: input.sandboxMode,
12528
- serviceTier: input.serviceTier,
12726
+ performanceMode: input.performanceMode,
12529
12727
  workspacePath: input.workspacePath
12530
12728
  });
12531
12729
  }
@@ -12559,7 +12757,7 @@ var ThreadService = class {
12559
12757
  this.clearPendingPlanDecisionRequests(localThreadId, true);
12560
12758
  }
12561
12759
  if (supportsFastMode2 && currentFastMode !== nextFastMode) {
12562
- await writeCodexFastMode(this.codexHome, nextFastMode);
12760
+ await this.codexManagement.writeFastMode(nextFastMode);
12563
12761
  this.appendActivityNote(localThreadId, {
12564
12762
  kind: "fastMode",
12565
12763
  text: nextFastMode ? FAST_MODE_NOTE_ON : FAST_MODE_NOTE_OFF
@@ -12912,12 +13110,7 @@ var ThreadService = class {
12912
13110
  });
12913
13111
  }
12914
13112
  this.assertCodexHooksFileManagement(record.provider);
12915
- await writeHookJsonEntry({
12916
- codexHome: this.codexHome,
12917
- workspacePath: workspace.absPath,
12918
- input
12919
- });
12920
- await trustHookForInput(runtime, workspace.absPath, input);
13113
+ await this.codexManagement.writeHookEntry(runtime, workspace.absPath, input);
12921
13114
  return this.listThreadHooks(localThreadId);
12922
13115
  }
12923
13116
  async updateThreadHook(localThreadId, input) {
@@ -12943,12 +13136,7 @@ var ThreadService = class {
12943
13136
  });
12944
13137
  }
12945
13138
  this.assertCodexHooksFileManagement(record.provider);
12946
- await updateHookJsonEntry({
12947
- codexHome: this.codexHome,
12948
- workspacePath: workspace.absPath,
12949
- input
12950
- });
12951
- await trustHookForInput(runtime, workspace.absPath, input);
13139
+ await this.codexManagement.updateHookEntry(runtime, workspace.absPath, input);
12952
13140
  return this.listThreadHooks(localThreadId);
12953
13141
  }
12954
13142
  async trustThreadHook(localThreadId, input) {
@@ -13051,7 +13239,7 @@ var ThreadService = class {
13051
13239
  const workspace = getWorkspaceRecordById(this.db, record.workspaceId);
13052
13240
  if (workspace) {
13053
13241
  const tempDirectory = threadTempDirectoryPath(workspace.absPath, localThreadId);
13054
- await fs8.rm(tempDirectory, { recursive: true, force: true }).catch(() => {
13242
+ await fs9.rm(tempDirectory, { recursive: true, force: true }).catch(() => {
13055
13243
  });
13056
13244
  }
13057
13245
  this.pendingRequests.delete(localThreadId);
@@ -13226,7 +13414,7 @@ var ThreadService = class {
13226
13414
  if (!record) {
13227
13415
  return;
13228
13416
  }
13229
- const tokenUsage = isRecord4(event.usage) ? event.usage : null;
13417
+ const tokenUsage = isRecord5(event.usage) ? event.usage : null;
13230
13418
  const usage = buildThreadContextUsageFromPayload(
13231
13419
  tokenUsage,
13232
13420
  record.model
@@ -13242,7 +13430,7 @@ var ThreadService = class {
13242
13430
  );
13243
13431
  const previousCumulativeTotal = this.threadCumulativeTokenUsage.get(record.id);
13244
13432
  const currentCumulativeTotal = buildTurnTokenBreakdown(
13245
- isRecord4(tokenUsage?.total) ? tokenUsage.total : null
13433
+ isRecord5(tokenUsage?.total) ? tokenUsage.total : null
13246
13434
  );
13247
13435
  if (currentCumulativeTotal) {
13248
13436
  this.threadCumulativeTokenUsage.set(record.id, currentCumulativeTotal);
@@ -13412,10 +13600,22 @@ var ThreadService = class {
13412
13600
  if (!record) {
13413
13601
  return;
13414
13602
  }
13415
- this.recordTurnItemOrder(record.id, event.providerTurnId, event.itemId);
13603
+ const sequence = this.recordTurnItemOrder(
13604
+ record.id,
13605
+ event.providerTurnId,
13606
+ event.itemId
13607
+ );
13608
+ this.appendLiveAgentMessageDelta(
13609
+ record.id,
13610
+ event.providerTurnId,
13611
+ event.itemId,
13612
+ event.delta,
13613
+ sequence
13614
+ );
13416
13615
  this.emitThreadEvent("thread.output.delta", record.id, {
13417
13616
  turnId: event.providerTurnId,
13418
13617
  itemId: event.itemId,
13618
+ sequence,
13419
13619
  delta: event.delta
13420
13620
  });
13421
13621
  return;
@@ -13489,7 +13689,7 @@ var ThreadService = class {
13489
13689
  const defaultMappedRequest = runtime.mapProviderRequest?.(request, {
13490
13690
  approvalMode: "guarded"
13491
13691
  });
13492
- const providerSessionIdFromParams = isRecord4(request.params) ? request.params.providerSessionId ?? request.params.threadId ?? request.params.conversationId ?? request.params.sessionId : null;
13692
+ const providerSessionIdFromParams = isRecord5(request.params) ? request.params.providerSessionId ?? request.params.threadId ?? request.params.conversationId ?? request.params.sessionId : null;
13493
13693
  const providerSessionId = defaultMappedRequest?.providerSessionId ?? (typeof providerSessionIdFromParams === "string" ? providerSessionIdFromParams : null);
13494
13694
  const record = providerSessionId ? this.findRecordByProviderSessionId(request.provider, providerSessionId) : null;
13495
13695
  if (!record) {
@@ -13658,9 +13858,32 @@ var ThreadService = class {
13658
13858
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
13659
13859
  });
13660
13860
  }
13861
+ appendLiveAgentMessageDelta(localThreadId, turnId, itemId, delta, sequence) {
13862
+ const current = this.threadLiveItems.get(localThreadId);
13863
+ const currentItems = current?.turnId === turnId ? current.items : [];
13864
+ const existing = currentItems.find((entry) => entry.id === itemId);
13865
+ const nextItem = existing?.kind === "agentMessage" ? {
13866
+ ...existing,
13867
+ text: `${existing.text}${delta}`,
13868
+ sequence
13869
+ } : {
13870
+ id: itemId,
13871
+ kind: "agentMessage",
13872
+ text: delta,
13873
+ sequence
13874
+ };
13875
+ this.persistLiveHistoryItem(localThreadId, turnId, nextItem);
13876
+ this.setLiveItems(localThreadId, {
13877
+ turnId,
13878
+ items: sortHistoryItemsBySequence([
13879
+ ...currentItems.filter((entry) => entry.id !== itemId),
13880
+ nextItem
13881
+ ]),
13882
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
13883
+ });
13884
+ }
13661
13885
  async toThreadHooksDto(provider, workspacePath, entry, fallbackWarnings = []) {
13662
- const globalHooksPath = path8.join(this.codexHome, "hooks.json");
13663
- const projectHooksPath = path8.join(workspacePath, ".codex", "hooks.json");
13886
+ const { globalHooksPath, projectHooksPath } = this.codexManagement.hooksPaths(workspacePath);
13664
13887
  const officialHooks = (entry?.hooks ?? []).map((hook) => ({
13665
13888
  key: hook.key,
13666
13889
  eventName: hook.eventName,
@@ -13679,12 +13902,12 @@ var ThreadService = class {
13679
13902
  trustStatus: hook.trustStatus
13680
13903
  }));
13681
13904
  const [globalHooks, projectHooks] = this.isCodexProvider(provider) ? await Promise.all([
13682
- readLocalHookDtos({
13905
+ this.codexManagement.readLocalHookDtos({
13683
13906
  hooksPath: globalHooksPath,
13684
13907
  source: "user",
13685
13908
  displayOffset: officialHooks.length
13686
13909
  }),
13687
- readLocalHookDtos({
13910
+ this.codexManagement.readLocalHookDtos({
13688
13911
  hooksPath: projectHooksPath,
13689
13912
  source: "project",
13690
13913
  displayOffset: officialHooks.length + 1e4
@@ -13719,11 +13942,17 @@ var ThreadService = class {
13719
13942
  return null;
13720
13943
  }
13721
13944
  const matchingTurn = turns.find((turn) => turn.id === current.turnId);
13722
- const materializedItemIds = new Set(
13723
- matchingTurn?.items.map((item) => item.id) ?? []
13945
+ const materializedItemsById = new Map(
13946
+ matchingTurn?.items.map((item) => [item.id, item]) ?? []
13724
13947
  );
13725
13948
  const nextItems = current.items.filter(
13726
- (item) => !materializedItemIds.has(item.id)
13949
+ (item) => {
13950
+ const materializedItem = materializedItemsById.get(item.id);
13951
+ if (!materializedItem) {
13952
+ return true;
13953
+ }
13954
+ return typeof item.sequence === "number" && Number.isFinite(item.sequence) && materializedItem.sequence !== item.sequence;
13955
+ }
13727
13956
  );
13728
13957
  if (nextItems.length === current.items.length) {
13729
13958
  return current;
@@ -14266,8 +14495,8 @@ async function registerSystemRoutes(app2) {
14266
14495
  }
14267
14496
 
14268
14497
  // src/routes/threads.ts
14269
- import fs9 from "fs/promises";
14270
- import path9 from "path";
14498
+ import fs10 from "fs/promises";
14499
+ import path10 from "path";
14271
14500
  import { z as z5 } from "zod";
14272
14501
  var createThreadSchema = z5.object({
14273
14502
  workspaceId: z5.string().uuid(),
@@ -14304,7 +14533,7 @@ var updateThreadSettingsSchema = z5.object({
14304
14533
  });
14305
14534
  var updateThreadGoalSchema = z5.object({
14306
14535
  objective: z5.string().min(1).nullable().optional(),
14307
- status: z5.enum(["active", "paused", "budgetLimited", "complete"]).nullable().optional(),
14536
+ status: z5.enum(["active", "paused", "budgetLimited", "complete", "terminated"]).nullable().optional(),
14308
14537
  tokenBudget: z5.number().int().positive().nullable().optional()
14309
14538
  }).refine((body) => Object.keys(body).length > 0, {
14310
14539
  message: "At least one goal field must be provided."
@@ -14626,23 +14855,23 @@ async function registerThreadRoutes(app2) {
14626
14855
  message: "Workspace was not found for this thread."
14627
14856
  });
14628
14857
  }
14629
- const candidatePath = path9.isAbsolute(query.path) ? query.path : path9.resolve(workspace.absPath, query.path);
14630
- const requestedPath = await fs9.realpath(candidatePath).catch(() => null);
14858
+ const candidatePath = path10.isAbsolute(query.path) ? query.path : path10.resolve(workspace.absPath, query.path);
14859
+ const requestedPath = await fs10.realpath(candidatePath).catch(() => null);
14631
14860
  if (!requestedPath) {
14632
14861
  throw new HttpError(404, {
14633
14862
  code: "not_found",
14634
14863
  message: "Image file was not found."
14635
14864
  });
14636
14865
  }
14637
- const resolvedWorkspaceRoot = await fs9.realpath(app2.services.config.workspaceRoot).catch(() => path9.resolve(app2.services.config.workspaceRoot));
14638
- const workspacePrefix = resolvedWorkspaceRoot.endsWith(path9.sep) ? resolvedWorkspaceRoot : `${resolvedWorkspaceRoot}${path9.sep}`;
14866
+ const resolvedWorkspaceRoot = await fs10.realpath(app2.services.config.workspaceRoot).catch(() => path10.resolve(app2.services.config.workspaceRoot));
14867
+ const workspacePrefix = resolvedWorkspaceRoot.endsWith(path10.sep) ? resolvedWorkspaceRoot : `${resolvedWorkspaceRoot}${path10.sep}`;
14639
14868
  if (requestedPath !== resolvedWorkspaceRoot && !requestedPath.startsWith(workspacePrefix)) {
14640
14869
  throw new HttpError(403, {
14641
14870
  code: "forbidden",
14642
14871
  message: "Image path must stay within the configured workspace root."
14643
14872
  });
14644
14873
  }
14645
- const stats = await fs9.stat(requestedPath).catch(() => null);
14874
+ const stats = await fs10.stat(requestedPath).catch(() => null);
14646
14875
  if (!stats?.isFile()) {
14647
14876
  throw new HttpError(404, {
14648
14877
  code: "not_found",
@@ -14653,7 +14882,7 @@ async function registerThreadRoutes(app2) {
14653
14882
  const contentType = lowerPath.endsWith(".png") ? "image/png" : lowerPath.endsWith(".jpg") || lowerPath.endsWith(".jpeg") ? "image/jpeg" : lowerPath.endsWith(".gif") ? "image/gif" : lowerPath.endsWith(".webp") ? "image/webp" : lowerPath.endsWith(".svg") ? "image/svg+xml" : lowerPath.endsWith(".heic") ? "image/heic" : lowerPath.endsWith(".heif") ? "image/heif" : "application/octet-stream";
14654
14883
  reply.header("Content-Type", contentType);
14655
14884
  reply.header("Cache-Control", "private, max-age=60");
14656
- return reply.send(await fs9.readFile(requestedPath));
14885
+ return reply.send(await fs10.readFile(requestedPath));
14657
14886
  });
14658
14887
  app2.patch("/api/threads/:id", async (request) => {
14659
14888
  const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
@@ -14827,8 +15056,8 @@ async function registerThreadRoutes(app2) {
14827
15056
  }
14828
15057
 
14829
15058
  // src/routes/workspaces.ts
14830
- import fs10 from "fs/promises";
14831
- import path10 from "path";
15059
+ import fs11 from "fs/promises";
15060
+ import path11 from "path";
14832
15061
  import { spawn as spawn2 } from "child_process";
14833
15062
  import { z as z6 } from "zod";
14834
15063
  var createWorkspaceSchema = z6.union([
@@ -14868,7 +15097,7 @@ function inferGitRepoName(gitUrl) {
14868
15097
  const normalized = withoutQuery.replace(/[\\/]+$/, "");
14869
15098
  const rawName = normalized.split(/[/:]/).filter(Boolean).at(-1) ?? "";
14870
15099
  const repoName = rawName.endsWith(".git") ? rawName.slice(0, -4) : rawName;
14871
- if (!repoName || repoName === "." || repoName === ".." || repoName.includes(path10.sep)) {
15100
+ if (!repoName || repoName === "." || repoName === ".." || repoName.includes(path11.sep)) {
14872
15101
  throw new HttpError(400, {
14873
15102
  code: "bad_request",
14874
15103
  message: "Unable to infer a target directory from the Git URL."
@@ -14878,7 +15107,7 @@ function inferGitRepoName(gitUrl) {
14878
15107
  }
14879
15108
  async function pathExists2(absPath) {
14880
15109
  try {
14881
- await fs10.stat(absPath);
15110
+ await fs11.stat(absPath);
14882
15111
  return true;
14883
15112
  } catch (error) {
14884
15113
  if (error.code === "ENOENT") {
@@ -14933,7 +15162,7 @@ async function registerWorkspaceRoutes(app2) {
14933
15162
  });
14934
15163
  app2.get("/api/workspaces/tree", async (request) => {
14935
15164
  const query = treeQuerySchema.parse(request.query);
14936
- const requestedPath = query.path ? path10.resolve(query.path) : app2.services.config.workspaceRoot;
15165
+ const requestedPath = query.path ? path11.resolve(query.path) : app2.services.config.workspaceRoot;
14937
15166
  const tree = await readWorkspaceTree({
14938
15167
  rootPath: app2.services.config.workspaceRoot,
14939
15168
  targetPath: requestedPath,
@@ -14965,7 +15194,7 @@ async function registerWorkspaceRoutes(app2) {
14965
15194
  let validated;
14966
15195
  if ("gitUrl" in body) {
14967
15196
  const repoName = inferGitRepoName(body.gitUrl);
14968
- const targetPath = path10.join(settings.devHome, repoName);
15197
+ const targetPath = path11.join(settings.devHome, repoName);
14969
15198
  if (await pathExists2(targetPath)) {
14970
15199
  throw new HttpError(409, {
14971
15200
  code: "conflict",
@@ -15074,8 +15303,8 @@ async function registerWorkspaceRoutes(app2) {
15074
15303
  }
15075
15304
 
15076
15305
  // src/provider-host-config-service.ts
15077
- import fs11 from "fs/promises";
15078
- import path11 from "path";
15306
+ import fs12 from "fs/promises";
15307
+ import path12 from "path";
15079
15308
  import { randomUUID as randomUUID3 } from "crypto";
15080
15309
  function providerError(message, statusCode = 404) {
15081
15310
  const error = new Error(message);
@@ -15083,23 +15312,23 @@ function providerError(message, statusCode = 404) {
15083
15312
  return error;
15084
15313
  }
15085
15314
  function resolveProviderHostFilePath(providerHome, name) {
15086
- return path11.join(providerHome, name);
15315
+ return path12.join(providerHome, name);
15087
15316
  }
15088
15317
  function resolveArchiveRoot(providerHome) {
15089
- return path11.join(providerHome, "supervisor-config-archives");
15318
+ return path12.join(providerHome, "supervisor-config-archives");
15090
15319
  }
15091
15320
  function resolveArchiveIndexPath(providerHome) {
15092
- return path11.join(resolveArchiveRoot(providerHome), "index.json");
15321
+ return path12.join(resolveArchiveRoot(providerHome), "index.json");
15093
15322
  }
15094
15323
  function resolveArchivePath(providerHome, archiveId) {
15095
- return path11.join(resolveArchiveRoot(providerHome), archiveId);
15324
+ return path12.join(resolveArchiveRoot(providerHome), archiveId);
15096
15325
  }
15097
15326
  function defaultArchiveLabel(createdAt) {
15098
15327
  return `Backup ${createdAt.replace("T", " ").replace(/\.\d{3}Z$/, " UTC")}`;
15099
15328
  }
15100
15329
  async function readArchiveIndex(providerHome) {
15101
15330
  try {
15102
- const raw = await fs11.readFile(resolveArchiveIndexPath(providerHome), "utf8");
15331
+ const raw = await fs12.readFile(resolveArchiveIndexPath(providerHome), "utf8");
15103
15332
  const parsed = JSON.parse(raw);
15104
15333
  return {
15105
15334
  archives: Array.isArray(parsed.archives) ? parsed.archives : []
@@ -15113,8 +15342,8 @@ async function readArchiveIndex(providerHome) {
15113
15342
  }
15114
15343
  async function writeArchiveIndex(providerHome, index) {
15115
15344
  const root = resolveArchiveRoot(providerHome);
15116
- await fs11.mkdir(root, { recursive: true });
15117
- await fs11.writeFile(
15345
+ await fs12.mkdir(root, { recursive: true });
15346
+ await fs12.writeFile(
15118
15347
  resolveArchiveIndexPath(providerHome),
15119
15348
  `${JSON.stringify(index, null, 2)}
15120
15349
  `,
@@ -15185,7 +15414,7 @@ var ProviderHostConfigService = class {
15185
15414
  const fileName = this.assertHostFile(provider, name);
15186
15415
  const filePath = resolveProviderHostFilePath(providerHome, fileName);
15187
15416
  try {
15188
- const content = await fs11.readFile(filePath, "utf8");
15417
+ const content = await fs12.readFile(filePath, "utf8");
15189
15418
  return {
15190
15419
  name: fileName,
15191
15420
  path: filePath,
@@ -15208,8 +15437,8 @@ var ProviderHostConfigService = class {
15208
15437
  const providerHome = this.providerHome(provider);
15209
15438
  const fileName = this.assertHostFile(provider, name);
15210
15439
  const filePath = resolveProviderHostFilePath(providerHome, fileName);
15211
- await fs11.mkdir(path11.dirname(filePath), { recursive: true });
15212
- await fs11.writeFile(filePath, input.content, "utf8");
15440
+ await fs12.mkdir(path12.dirname(filePath), { recursive: true });
15441
+ await fs12.writeFile(filePath, input.content, "utf8");
15213
15442
  return this.readFile(provider, fileName);
15214
15443
  }
15215
15444
  async listArchives(provider) {
@@ -15235,7 +15464,7 @@ var ProviderHostConfigService = class {
15235
15464
  }
15236
15465
  ])
15237
15466
  );
15238
- await fs11.mkdir(archivePath, { recursive: true });
15467
+ await fs12.mkdir(archivePath, { recursive: true });
15239
15468
  for (const name of fileNames) {
15240
15469
  const hostFile = await this.readFile(provider, name);
15241
15470
  files[name] = {
@@ -15243,7 +15472,7 @@ var ProviderHostConfigService = class {
15243
15472
  exists: hostFile.exists
15244
15473
  };
15245
15474
  if (hostFile.exists) {
15246
- await fs11.writeFile(path11.join(archivePath, name), hostFile.content, "utf8");
15475
+ await fs12.writeFile(path12.join(archivePath, name), hostFile.content, "utf8");
15247
15476
  }
15248
15477
  }
15249
15478
  const archive = {
@@ -15279,14 +15508,14 @@ var ProviderHostConfigService = class {
15279
15508
  const fileNames = this.archiveFileNames(provider);
15280
15509
  const { archive } = await findArchiveOrThrow(providerHome, id);
15281
15510
  const archivePath = resolveArchivePath(providerHome, archive.id);
15282
- await fs11.mkdir(providerHome, { recursive: true });
15511
+ await fs12.mkdir(providerHome, { recursive: true });
15283
15512
  for (const name of fileNames) {
15284
15513
  const hostPath = resolveProviderHostFilePath(providerHome, name);
15285
15514
  if (archive.files[name]?.exists) {
15286
- const content = await fs11.readFile(path11.join(archivePath, name), "utf8");
15287
- await fs11.writeFile(hostPath, content, "utf8");
15515
+ const content = await fs12.readFile(path12.join(archivePath, name), "utf8");
15516
+ await fs12.writeFile(hostPath, content, "utf8");
15288
15517
  } else {
15289
- await fs11.rm(hostPath, { force: true });
15518
+ await fs12.rm(hostPath, { force: true });
15290
15519
  }
15291
15520
  }
15292
15521
  await runtime.stop();
@@ -15299,12 +15528,12 @@ var ProviderHostConfigService = class {
15299
15528
  };
15300
15529
 
15301
15530
  // src/shell/shell-session-service.ts
15302
- import fs12 from "fs/promises";
15531
+ import fs13 from "fs/promises";
15303
15532
  import os3 from "os";
15304
- import path12 from "path";
15533
+ import path13 from "path";
15305
15534
  async function pathExists3(filePath) {
15306
15535
  try {
15307
- await fs12.access(filePath);
15536
+ await fs13.access(filePath);
15308
15537
  return true;
15309
15538
  } catch {
15310
15539
  return false;
@@ -15329,7 +15558,7 @@ function basenameFromPath2(filePath) {
15329
15558
  if (!normalized) {
15330
15559
  return "";
15331
15560
  }
15332
- return path12.basename(normalized) || normalized;
15561
+ return path13.basename(normalized) || normalized;
15333
15562
  }
15334
15563
  function isInteractiveShellCommand(command) {
15335
15564
  const normalized = (command ?? "").trim().toLowerCase();
@@ -15490,11 +15719,11 @@ function buildShellPromptInitScriptContents(command) {
15490
15719
  async function ensureShellPromptInitScript(command) {
15491
15720
  const normalized = command.trim().toLowerCase();
15492
15721
  const extension = normalized === "zsh" ? "zsh" : "sh";
15493
- const filePath = path12.join(
15722
+ const filePath = path13.join(
15494
15723
  os3.tmpdir(),
15495
15724
  `remote-codex-shell-prompt.${extension}`
15496
15725
  );
15497
- await fs12.writeFile(filePath, buildShellPromptInitScriptContents(command), "utf8");
15726
+ await fs13.writeFile(filePath, buildShellPromptInitScriptContents(command), "utf8");
15498
15727
  return filePath;
15499
15728
  }
15500
15729
  async function buildShellPromptInitCommand(command, options = {}) {
@@ -16080,8 +16309,8 @@ var ShellSessionService = class {
16080
16309
  };
16081
16310
 
16082
16311
  // src/shell/tmux-manager.ts
16083
- import fs13 from "fs";
16084
- import path13 from "path";
16312
+ import fs14 from "fs";
16313
+ import path14 from "path";
16085
16314
  import { spawn as spawnChild } from "child_process";
16086
16315
  async function defaultExecCommand(command, args) {
16087
16316
  return await new Promise((resolve, reject) => {
@@ -16108,17 +16337,17 @@ async function defaultExecCommand(command, args) {
16108
16337
  });
16109
16338
  }
16110
16339
  function resolveExecutablePath(command) {
16111
- if (command.includes(path13.sep)) {
16340
+ if (command.includes(path14.sep)) {
16112
16341
  return command;
16113
16342
  }
16114
16343
  const searchPath = process.env.PATH ?? "";
16115
- for (const entry of searchPath.split(path13.delimiter)) {
16344
+ for (const entry of searchPath.split(path14.delimiter)) {
16116
16345
  const trimmed = entry.trim();
16117
16346
  if (!trimmed) {
16118
16347
  continue;
16119
16348
  }
16120
- const candidate = path13.join(trimmed, command);
16121
- if (fs13.existsSync(candidate)) {
16349
+ const candidate = path14.join(trimmed, command);
16350
+ if (fs14.existsSync(candidate)) {
16122
16351
  return candidate;
16123
16352
  }
16124
16353
  }
@@ -16447,16 +16676,16 @@ var HttpError = class extends Error {
16447
16676
  };
16448
16677
  function findRepoRoot(start = process.cwd()) {
16449
16678
  if (process.env.REMOTE_CODEX_REPO_ROOT) {
16450
- return path14.resolve(process.env.REMOTE_CODEX_REPO_ROOT);
16679
+ return path15.resolve(process.env.REMOTE_CODEX_REPO_ROOT);
16451
16680
  }
16452
- let current = path14.resolve(start);
16453
- while (current !== path14.dirname(current)) {
16454
- if (fs14.existsSync(path14.join(current, "pnpm-workspace.yaml")) && fs14.existsSync(path14.join(current, "scripts", "service-restart.mjs"))) {
16681
+ let current = path15.resolve(start);
16682
+ while (current !== path15.dirname(current)) {
16683
+ if (fs15.existsSync(path15.join(current, "pnpm-workspace.yaml")) && fs15.existsSync(path15.join(current, "scripts", "service-restart.mjs"))) {
16455
16684
  return current;
16456
16685
  }
16457
- current = path14.dirname(current);
16686
+ current = path15.dirname(current);
16458
16687
  }
16459
- return path14.resolve(process.cwd());
16688
+ return path15.resolve(process.cwd());
16460
16689
  }
16461
16690
  function createServiceLifecycle() {
16462
16691
  return {
@@ -16468,8 +16697,8 @@ function createServiceLifecycle() {
16468
16697
  });
16469
16698
  }
16470
16699
  const repoRoot = findRepoRoot();
16471
- const restartScript = path14.join(repoRoot, "scripts", "service-restart.mjs");
16472
- if (!fs14.existsSync(restartScript) || !fs14.existsSync(path14.join(repoRoot, "pnpm-workspace.yaml"))) {
16700
+ const restartScript = path15.join(repoRoot, "scripts", "service-restart.mjs");
16701
+ if (!fs15.existsSync(restartScript) || !fs15.existsSync(path15.join(repoRoot, "pnpm-workspace.yaml"))) {
16473
16702
  throw new HttpError(503, {
16474
16703
  code: "service_unavailable",
16475
16704
  message: "Build and restart requires a Remote Codex source checkout. Set REMOTE_CODEX_REPO_ROOT to the checkout path, or update the npm package with npm install -g remote-codex@latest."
@@ -16500,7 +16729,7 @@ function buildApp(options = {}) {
16500
16729
  eventBus,
16501
16730
  runtimeBootstrap.localCodexSessionStore,
16502
16731
  config.workspaceRoot,
16503
- config.codexHome
16732
+ runtimeBootstrap.codexManagement
16504
16733
  );
16505
16734
  const shellService = options.shellService ?? new ShellSessionService(database.db, eventBus, new TmuxManager());
16506
16735
  const providerHostConfigService = new ProviderHostConfigService(
@@ -16810,7 +17039,7 @@ function makeShellErrorEnvelope(shellId, error) {
16810
17039
  }
16811
17040
 
16812
17041
  // src/index.ts
16813
- if (fs15.existsSync(".env")) {
17042
+ if (fs16.existsSync(".env")) {
16814
17043
  process.loadEnvFile?.(".env");
16815
17044
  }
16816
17045
  var app = buildApp();