remote-codex 0.11.10 → 0.11.12

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.
@@ -8,9 +8,9 @@ var __export = (target, all) => {
8
8
  import Fastify from "fastify";
9
9
  import multipart from "@fastify/multipart";
10
10
  import websocket from "@fastify/websocket";
11
- import { spawn as spawn5 } from "child_process";
11
+ import { spawn as spawn6 } from "child_process";
12
12
  import fs26 from "fs";
13
- import path24 from "path";
13
+ import path26 from "path";
14
14
  import { ZodError } from "zod";
15
15
 
16
16
  // ../../packages/config/src/index.ts
@@ -176,7 +176,7 @@ function loadRuntimeConfig(env = process.env) {
176
176
  nodeEnv === "production"
177
177
  );
178
178
  const enabledProviders = new Set(
179
- (parsed.REMOTE_CODEX_ENABLED_AGENT_PROVIDERS ?? agentBackendIds.join(",")).split(",").map((provider) => provider.trim().toLowerCase()).filter(Boolean)
179
+ (parsed.REMOTE_CODEX_ENABLED_AGENT_PROVIDERS ?? agentBackendIds.join(",")).split(",").map((provider2) => provider2.trim().toLowerCase()).filter(Boolean)
180
180
  );
181
181
  const defaultAgentHomeRoot = runtimeRole === "worker" ? "/home/agent" : os.homedir();
182
182
  const codexHome = parsed.CODEX_HOME?.trim() ? path.resolve(parsed.CODEX_HOME) : path.join(defaultAgentHomeRoot, agentBackendMetadata.codex.defaultHomeDir);
@@ -465,15 +465,15 @@ var AgentRuntimeRegistry = class {
465
465
  }
466
466
  defaultProvider;
467
467
  runtimes = /* @__PURE__ */ new Map();
468
- get(provider) {
469
- const runtime = this.runtimes.get(provider);
468
+ get(provider2) {
469
+ const runtime = this.runtimes.get(provider2);
470
470
  if (!runtime) {
471
- throw new Error(`Agent runtime provider is not configured: ${provider}`);
471
+ throw new Error(`Agent runtime provider is not configured: ${provider2}`);
472
472
  }
473
473
  return runtime;
474
474
  }
475
- getOptional(provider) {
476
- return this.runtimes.get(provider) ?? null;
475
+ getOptional(provider2) {
476
+ return this.runtimes.get(provider2) ?? null;
477
477
  }
478
478
  all() {
479
479
  return [...this.runtimes.values()];
@@ -501,9 +501,9 @@ var transientAgentHistoryItemSymbol = /* @__PURE__ */ Symbol(
501
501
  "remoteCodex.transientAgentHistoryItem"
502
502
  );
503
503
  var AgentRuntimeError = class extends Error {
504
- constructor(message, provider, code = "request_failed", details, cause) {
504
+ constructor(message, provider2, code = "request_failed", details, cause) {
505
505
  super(message);
506
- this.provider = provider;
506
+ this.provider = provider2;
507
507
  this.code = code;
508
508
  this.details = details;
509
509
  this.cause = cause;
@@ -2316,7 +2316,7 @@ Subquery.prototype.getSQL = function() {
2316
2316
  function mapResultRow(columns, row, joinsNotNullableMap) {
2317
2317
  const nullifyMap = {};
2318
2318
  const result = columns.reduce(
2319
- (result2, { path: path25, field }, columnIndex) => {
2319
+ (result2, { path: path27, field }, columnIndex) => {
2320
2320
  let decoder;
2321
2321
  if (is(field, Column)) {
2322
2322
  decoder = field;
@@ -2326,8 +2326,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
2326
2326
  decoder = field.sql.decoder;
2327
2327
  }
2328
2328
  let node = result2;
2329
- for (const [pathChunkIndex, pathChunk] of path25.entries()) {
2330
- if (pathChunkIndex < path25.length - 1) {
2329
+ for (const [pathChunkIndex, pathChunk] of path27.entries()) {
2330
+ if (pathChunkIndex < path27.length - 1) {
2331
2331
  if (!(pathChunk in node)) {
2332
2332
  node[pathChunk] = {};
2333
2333
  }
@@ -2335,8 +2335,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
2335
2335
  } else {
2336
2336
  const rawValue = row[columnIndex];
2337
2337
  const value = node[pathChunk] = rawValue === null ? null : decoder.mapFromDriverValue(rawValue);
2338
- if (joinsNotNullableMap && is(field, Column) && path25.length === 2) {
2339
- const objectName = path25[0];
2338
+ if (joinsNotNullableMap && is(field, Column) && path27.length === 2) {
2339
+ const objectName = path27[0];
2340
2340
  if (!(objectName in nullifyMap)) {
2341
2341
  nullifyMap[objectName] = value === null ? getTableName(field.table) : false;
2342
2342
  } else if (typeof nullifyMap[objectName] === "string" && nullifyMap[objectName] !== getTableName(field.table)) {
@@ -7008,10 +7008,10 @@ function listThreadRecordsByWorkspaceId(db, workspaceId) {
7008
7008
  function getThreadRecordById(db, id) {
7009
7009
  return db.select().from(threads).where(eq(threads.id, id)).get();
7010
7010
  }
7011
- function getThreadRecordByProviderSessionId(db, provider, providerSessionId) {
7011
+ function getThreadRecordByProviderSessionId(db, provider2, providerSessionId) {
7012
7012
  return db.select().from(threads).where(
7013
7013
  and(
7014
- eq(threads.provider, provider),
7014
+ eq(threads.provider, provider2),
7015
7015
  eq(threads.providerSessionId, providerSessionId)
7016
7016
  )
7017
7017
  ).get();
@@ -7956,6 +7956,15 @@ function mapTurn(record) {
7956
7956
  items: Array.isArray(record.items) ? record.items : []
7957
7957
  };
7958
7958
  }
7959
+ function textOnlyUserInput(prompt) {
7960
+ return [
7961
+ {
7962
+ type: "text",
7963
+ text: prompt,
7964
+ text_elements: []
7965
+ }
7966
+ ];
7967
+ }
7959
7968
  function mapModel(record) {
7960
7969
  return {
7961
7970
  id: record.id,
@@ -8279,13 +8288,7 @@ var CodexAppServerManager = class extends EventEmitter3 {
8279
8288
  await this.ensureReady();
8280
8289
  const response = await this.client.request("turn/start", {
8281
8290
  threadId: input.threadId,
8282
- input: [
8283
- {
8284
- type: "text",
8285
- text: input.prompt,
8286
- text_elements: []
8287
- }
8288
- ],
8291
+ input: input.input ?? textOnlyUserInput(input.prompt),
8289
8292
  model: input.model ?? null,
8290
8293
  serviceTier: input.serviceTier === void 0 ? void 0 : input.serviceTier,
8291
8294
  effort: input.effort ?? null,
@@ -8306,13 +8309,7 @@ var CodexAppServerManager = class extends EventEmitter3 {
8306
8309
  const response = await this.client.request("turn/steer", {
8307
8310
  threadId: input.threadId,
8308
8311
  expectedTurnId: input.turnId,
8309
- input: [
8310
- {
8311
- type: "text",
8312
- text: input.prompt,
8313
- text_elements: []
8314
- }
8315
- ]
8312
+ input: input.input ?? textOnlyUserInput(input.prompt)
8316
8313
  });
8317
8314
  return response.turn ? mapTurn(response.turn) : null;
8318
8315
  }
@@ -8614,12 +8611,58 @@ ${entryPreview}` : null
8614
8611
  var DEFERRED_COMMAND_DETAIL_TITLE = "Command Output";
8615
8612
  var DEFERRED_TOOL_DETAIL_TITLE = "Tool Call Details";
8616
8613
  var DEFERRED_AGENT_TOOL_DETAIL_TITLE = "Agent Details";
8614
+ function parseUuidV7Timestamp(id) {
8615
+ const normalized = id.replace(/-/g, "");
8616
+ if (!/^[0-9a-f]{32}$/i.test(normalized) || normalized[12]?.toLowerCase() !== "7") {
8617
+ return null;
8618
+ }
8619
+ const millis = Number.parseInt(normalized.slice(0, 12), 16);
8620
+ if (!Number.isFinite(millis)) {
8621
+ return null;
8622
+ }
8623
+ return new Date(millis).toISOString();
8624
+ }
8617
8625
  function isRecord4(value) {
8618
8626
  return typeof value === "object" && value !== null && !Array.isArray(value);
8619
8627
  }
8620
8628
  function stringOrNull(value) {
8621
8629
  return typeof value === "string" && value.trim() ? value.trim() : null;
8622
8630
  }
8631
+ function isoTimestampOrNull(value) {
8632
+ if (typeof value === "number" && Number.isFinite(value)) {
8633
+ const epochMs = value < 1e10 ? value * 1e3 : value;
8634
+ return new Date(epochMs).toISOString();
8635
+ }
8636
+ const text2 = stringOrNull(value);
8637
+ if (!text2) {
8638
+ return null;
8639
+ }
8640
+ const parsed = Date.parse(text2);
8641
+ return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;
8642
+ }
8643
+ function codexItemCreatedAt(item) {
8644
+ const candidates = [
8645
+ item.createdAt,
8646
+ item.created_at,
8647
+ item.startedAt,
8648
+ item.started_at,
8649
+ item.completedAt,
8650
+ item.completed_at
8651
+ ];
8652
+ for (const candidate of candidates) {
8653
+ const timestamp = isoTimestampOrNull(candidate);
8654
+ if (timestamp) {
8655
+ return timestamp;
8656
+ }
8657
+ }
8658
+ return parseUuidV7Timestamp(item.id);
8659
+ }
8660
+ function withCodexItemTimestamp(item, historyItem) {
8661
+ return {
8662
+ ...historyItem,
8663
+ createdAt: historyItem.createdAt ?? codexItemCreatedAt(item)
8664
+ };
8665
+ }
8623
8666
  function numberOrNull(value) {
8624
8667
  if (typeof value === "number" && Number.isFinite(value)) {
8625
8668
  return value;
@@ -9108,7 +9151,7 @@ function extractFileChangeEntries(item) {
9108
9151
  isRecord4(entry.summary) ? entry.summary : null,
9109
9152
  isRecord4(entry.diff) ? entry.diff : null
9110
9153
  ].filter((candidate) => Boolean(candidate));
9111
- const path25 = uniqueStrings([
9154
+ const path27 = uniqueStrings([
9112
9155
  stringOrNull(valueFromRecords(nestedRecords, ["path", "filePath", "targetPath"])),
9113
9156
  stringOrNull(
9114
9157
  valueFromRecords(nestedRecords, [
@@ -9155,7 +9198,7 @@ function extractFileChangeEntries(item) {
9155
9198
  const diffStats = explicitAdditions === 0 && explicitDeletions === 0 && diffText ? countUnifiedDiffStats(diffText) : null;
9156
9199
  const additions = explicitAdditions || diffStats?.additions || 0;
9157
9200
  const deletions = explicitDeletions || diffStats?.deletions || 0;
9158
- const normalizedPath = path25 ?? (diffText ? projectRelativePathLabel(extractPathFromDiffText(diffText)) : null);
9201
+ const normalizedPath = path27 ?? (diffText ? projectRelativePathLabel(extractPathFromDiffText(diffText)) : null);
9159
9202
  if (!normalizedPath && additions === 0 && deletions === 0) {
9160
9203
  return null;
9161
9204
  }
@@ -9320,7 +9363,7 @@ function itemToHistoryItem(item, deferredDetails) {
9320
9363
  }
9321
9364
  }
9322
9365
  function liveCodexItemToHistoryItem(item, phase) {
9323
- const historyItem = itemToHistoryItem(item);
9366
+ const historyItem = withCodexItemTimestamp(item, itemToHistoryItem(item));
9324
9367
  if (historyItem.kind !== "commandExecution" && historyItem.kind !== "toolCall" && historyItem.kind !== "agentToolCall" && historyItem.kind !== "skillToolCall" && historyItem.kind !== "fileChange" && historyItem.kind !== "webSearch") {
9325
9368
  return null;
9326
9369
  }
@@ -9335,7 +9378,9 @@ function codexTurnToAgentTurn(turn) {
9335
9378
  rawTurnId: turn.id,
9336
9379
  status: turn.status,
9337
9380
  error: turn.error,
9338
- items: turn.items.map((item) => itemToHistoryItem(item)),
9381
+ items: turn.items.map(
9382
+ (item) => withCodexItemTimestamp(item, itemToHistoryItem(item))
9383
+ ),
9339
9384
  rawTurn: turn
9340
9385
  };
9341
9386
  }
@@ -9940,8 +9985,8 @@ var CodexManagementService = class {
9940
9985
  isRuntimeRequestError(error) {
9941
9986
  return isCodexRuntimeRequestError(error);
9942
9987
  }
9943
- canManageHookFiles(provider) {
9944
- return !provider || provider === "codex";
9988
+ canManageHookFiles(provider2) {
9989
+ return !provider2 || provider2 === "codex";
9945
9990
  }
9946
9991
  isUnsupportedHooksListError(error) {
9947
9992
  return isUnsupportedHooksListError(error);
@@ -10110,6 +10155,7 @@ var CodexManagementService = class {
10110
10155
 
10111
10156
  // ../../packages/codex/src/runtimeAdapter.ts
10112
10157
  import { EventEmitter as EventEmitter4 } from "events";
10158
+ import path9 from "path";
10113
10159
 
10114
10160
  // ../../packages/codex/src/requestMapper.ts
10115
10161
  function isRecord6(value) {
@@ -10447,6 +10493,50 @@ function mapCodexProviderRequest(providerRequest, approvalMode) {
10447
10493
  }
10448
10494
 
10449
10495
  // ../../packages/codex/src/runtimeAdapter.ts
10496
+ var promptPhotoTokenPattern = /\[PHOTO\s+([^\]]+)\]/g;
10497
+ function resolvePromptAssetPath(assetPath, cwd) {
10498
+ if (!assetPath) {
10499
+ return null;
10500
+ }
10501
+ if (path9.isAbsolute(assetPath)) {
10502
+ return path9.normalize(assetPath);
10503
+ }
10504
+ if (!cwd) {
10505
+ return null;
10506
+ }
10507
+ return path9.resolve(cwd, assetPath);
10508
+ }
10509
+ function codexUserInputFromPrompt(prompt, cwd) {
10510
+ const matches = [...prompt.matchAll(promptPhotoTokenPattern)];
10511
+ if (matches.length === 0) {
10512
+ return void 0;
10513
+ }
10514
+ const input = [];
10515
+ let cursor = 0;
10516
+ let includedImage = false;
10517
+ for (const match of matches) {
10518
+ const token = match[0];
10519
+ const assetPath = match[1]?.trim() ?? "";
10520
+ const start = match.index ?? 0;
10521
+ const precedingText = prompt.slice(cursor, start);
10522
+ if (precedingText) {
10523
+ input.push({ type: "text", text: precedingText, text_elements: [] });
10524
+ }
10525
+ const resolvedPath = resolvePromptAssetPath(assetPath, cwd);
10526
+ if (resolvedPath) {
10527
+ input.push({ type: "localImage", path: resolvedPath });
10528
+ includedImage = true;
10529
+ } else {
10530
+ input.push({ type: "text", text: token, text_elements: [] });
10531
+ }
10532
+ cursor = start + token.length;
10533
+ }
10534
+ const trailingText = prompt.slice(cursor);
10535
+ if (trailingText) {
10536
+ input.push({ type: "text", text: trailingText, text_elements: [] });
10537
+ }
10538
+ return includedImage ? input : void 0;
10539
+ }
10450
10540
  var codexCapabilities = {
10451
10541
  sessions: {
10452
10542
  list: true,
@@ -10961,6 +11051,13 @@ var CodexRuntimeAdapter = class extends EventEmitter4 {
10961
11051
  threadId: input.providerSessionId,
10962
11052
  prompt: input.prompt
10963
11053
  };
11054
+ const structuredInput = codexUserInputFromPrompt(
11055
+ input.prompt,
11056
+ input.workspacePath
11057
+ );
11058
+ if (structuredInput) {
11059
+ turnInput.input = structuredInput;
11060
+ }
10964
11061
  if (input.developerInstructions !== void 0) {
10965
11062
  turnInput.developerInstructions = input.developerInstructions;
10966
11063
  }
@@ -10987,11 +11084,17 @@ var CodexRuntimeAdapter = class extends EventEmitter4 {
10987
11084
  return mapTurn2(await codexRuntimeCall(() => this.manager.startTurn(turnInput)));
10988
11085
  }
10989
11086
  async sendInput(input) {
10990
- const turn = await codexRuntimeCall(() => this.manager.steerTurn({
11087
+ const structuredInput = codexUserInputFromPrompt(
11088
+ input.prompt,
11089
+ input.workspacePath
11090
+ );
11091
+ const steerInput = {
10991
11092
  threadId: input.providerSessionId,
10992
11093
  turnId: input.providerTurnId,
10993
- prompt: input.prompt
10994
- }));
11094
+ prompt: input.prompt,
11095
+ ...structuredInput ? { input: structuredInput } : {}
11096
+ };
11097
+ const turn = await codexRuntimeCall(() => this.manager.steerTurn(steerInput));
10995
11098
  return turn ? mapTurn2(turn) : null;
10996
11099
  }
10997
11100
  async interruptTurn(input) {
@@ -11640,13 +11743,13 @@ import { EventEmitter as EventEmitter5 } from "events";
11640
11743
  import { execFile } from "child_process";
11641
11744
  import fs8 from "fs/promises";
11642
11745
  import { createRequire } from "module";
11643
- import path9 from "path";
11746
+ import path10 from "path";
11644
11747
  import { pathToFileURL } from "url";
11645
11748
  import { promisify } from "util";
11646
11749
  var execFileAsync = promisify(execFile);
11647
- var promptPhotoTokenPattern = /\[PHOTO\s+([^\]]+)\]/g;
11750
+ var promptPhotoTokenPattern2 = /\[PHOTO\s+([^\]]+)\]/g;
11648
11751
  function mimeTypeForImagePath(filePath) {
11649
- const extension = path9.extname(filePath).toLowerCase();
11752
+ const extension = path10.extname(filePath).toLowerCase();
11650
11753
  switch (extension) {
11651
11754
  case ".jpg":
11652
11755
  case ".jpeg":
@@ -11676,13 +11779,13 @@ function extensionForImageMediaType(mediaType) {
11676
11779
  return null;
11677
11780
  }
11678
11781
  }
11679
- function resolvePromptAssetPath(assetPath, cwd) {
11782
+ function resolvePromptAssetPath2(assetPath, cwd) {
11680
11783
  if (!cwd) {
11681
11784
  return null;
11682
11785
  }
11683
- const resolvedPath = path9.isAbsolute(assetPath) ? path9.normalize(assetPath) : path9.resolve(cwd, assetPath);
11684
- const relativePath = path9.relative(cwd, resolvedPath);
11685
- if (relativePath === "" || !relativePath.startsWith("..") && !path9.isAbsolute(relativePath)) {
11786
+ const resolvedPath = path10.isAbsolute(assetPath) ? path10.normalize(assetPath) : path10.resolve(cwd, assetPath);
11787
+ const relativePath = path10.relative(cwd, resolvedPath);
11788
+ if (relativePath === "" || !relativePath.startsWith("..") && !path10.isAbsolute(relativePath)) {
11686
11789
  return resolvedPath;
11687
11790
  }
11688
11791
  return null;
@@ -11701,7 +11804,7 @@ async function* singleUserMessage(content) {
11701
11804
  };
11702
11805
  }
11703
11806
  async function promptWithImageBlocks(prompt, cwd) {
11704
- const matches = [...prompt.matchAll(promptPhotoTokenPattern)];
11807
+ const matches = [...prompt.matchAll(promptPhotoTokenPattern2)];
11705
11808
  if (matches.length === 0) {
11706
11809
  return prompt;
11707
11810
  }
@@ -11716,7 +11819,7 @@ async function promptWithImageBlocks(prompt, cwd) {
11716
11819
  if (precedingText) {
11717
11820
  blocks.push({ type: "text", text: precedingText });
11718
11821
  }
11719
- const resolvedPath = resolvePromptAssetPath(assetPath, cwd);
11822
+ const resolvedPath = resolvePromptAssetPath2(assetPath, cwd);
11720
11823
  const mediaType = resolvedPath ? mimeTypeForImagePath(resolvedPath) : null;
11721
11824
  if (!resolvedPath || !mediaType) {
11722
11825
  blocks.push({ type: "text", text: token });
@@ -13099,13 +13202,13 @@ var ClaudeRuntimeAdapter = class extends EventEmitter5 {
13099
13202
  return null;
13100
13203
  }
13101
13204
  const relativePath = `./.temp/threads/${input.localThreadId}/claude-history-${safeAssetFilePart(input.messageId)}-${input.blockIndex}.${extension}`;
13102
- const targetPath = path9.resolve(input.workspacePath, relativePath);
13103
- const relativeToWorkspace = path9.relative(input.workspacePath, targetPath);
13104
- if (relativeToWorkspace === "" || relativeToWorkspace.startsWith("..") || path9.isAbsolute(relativeToWorkspace)) {
13205
+ const targetPath = path10.resolve(input.workspacePath, relativePath);
13206
+ const relativeToWorkspace = path10.relative(input.workspacePath, targetPath);
13207
+ if (relativeToWorkspace === "" || relativeToWorkspace.startsWith("..") || path10.isAbsolute(relativeToWorkspace)) {
13105
13208
  return null;
13106
13209
  }
13107
13210
  try {
13108
- await fs8.mkdir(path9.dirname(targetPath), { recursive: true });
13211
+ await fs8.mkdir(path10.dirname(targetPath), { recursive: true });
13109
13212
  await fs8.writeFile(targetPath, Buffer.from(data, "base64"));
13110
13213
  return `[PHOTO ${relativePath}]`;
13111
13214
  } catch {
@@ -13264,7 +13367,7 @@ async function importOptionalPackage(specifier) {
13264
13367
  throw localError;
13265
13368
  }
13266
13369
  try {
13267
- const requireFromGlobal = createRequire(path9.join(globalRoot, "remote-codex-global.cjs"));
13370
+ const requireFromGlobal = createRequire(path10.join(globalRoot, "remote-codex-global.cjs"));
13268
13371
  const resolved = requireFromGlobal.resolve(specifier);
13269
13372
  return await dynamicImport(pathToFileURL(resolved).href);
13270
13373
  } catch {
@@ -13284,7 +13387,7 @@ async function npmGlobalRoot() {
13284
13387
  }
13285
13388
 
13286
13389
  // ../../packages/opencode/src/historyItems.ts
13287
- import path10 from "path";
13390
+ import path11 from "path";
13288
13391
  function isRecord9(value) {
13289
13392
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
13290
13393
  }
@@ -13394,25 +13497,25 @@ function displayPath(pathValue, options) {
13394
13497
  if (!pathValue) {
13395
13498
  return null;
13396
13499
  }
13397
- if (!path10.isAbsolute(pathValue) || !options.workspacePath) {
13500
+ if (!path11.isAbsolute(pathValue) || !options.workspacePath) {
13398
13501
  return pathValue;
13399
13502
  }
13400
- const root = path10.resolve(options.workspacePath);
13401
- const absolutePath = path10.resolve(pathValue);
13402
- const relativePath = path10.relative(root, absolutePath);
13403
- if (!relativePath || relativePath.startsWith("..") || path10.isAbsolute(relativePath)) {
13503
+ const root = path11.resolve(options.workspacePath);
13504
+ const absolutePath = path11.resolve(pathValue);
13505
+ const relativePath = path11.relative(root, absolutePath);
13506
+ if (!relativePath || relativePath.startsWith("..") || path11.isAbsolute(relativePath)) {
13404
13507
  return pathValue;
13405
13508
  }
13406
13509
  return relativePath;
13407
13510
  }
13408
- function toolIsLowInformationPatch(normalized, state, input, patchText, path25, metadataStats) {
13511
+ function toolIsLowInformationPatch(normalized, state, input, patchText, path27, metadataStats) {
13409
13512
  if (normalized !== "applypatch" && normalized !== "patch") {
13410
13513
  return false;
13411
13514
  }
13412
13515
  if (toolStateStatus(state) !== "running") {
13413
13516
  return false;
13414
13517
  }
13415
- if (path25 || patchText || metadataStats || stringValue2(state.output)) {
13518
+ if (path27 || patchText || metadataStats || stringValue2(state.output)) {
13416
13519
  return false;
13417
13520
  }
13418
13521
  return !isRecord9(input) || Object.keys(input).length === 0;
@@ -13455,7 +13558,7 @@ function fileChangeStatsFromMetadata(metadata) {
13455
13558
  if (files.length === 0) {
13456
13559
  return null;
13457
13560
  }
13458
- const paths = files.map((file) => stringValue2(file.filePath) ?? stringValue2(file.path) ?? stringValue2(file.relativePath)).filter((path25) => Boolean(path25));
13561
+ const paths = files.map((file) => stringValue2(file.filePath) ?? stringValue2(file.path) ?? stringValue2(file.relativePath)).filter((path27) => Boolean(path27));
13459
13562
  const addedLines = files.reduce((total, file) => total + (numberValue(file.additions) ?? numberValue(file.addedLines) ?? numberValue(file.added) ?? 0), 0);
13460
13563
  const removedLines = files.reduce((total, file) => total + (numberValue(file.deletions) ?? numberValue(file.removedLines) ?? numberValue(file.removed) ?? 0), 0);
13461
13564
  return {
@@ -13603,28 +13706,28 @@ function mapAssistantTool(messageId2, tool, options) {
13603
13706
  ].includes(normalized)) {
13604
13707
  const metadataStats = fileChangeStatsFromMetadata(state.metadata);
13605
13708
  const patchText = isRecord9(input) ? stringValue2(input.patchText) ?? stringValue2(input.patch) ?? stringValue2(input.diff) : null;
13606
- const path25 = metadataStats?.path ?? filePathFromInput(input) ?? extractPathFromPatchText(patchText);
13607
- if (toolIsLowInformationPatch(normalized, state, input, patchText, path25, metadataStats)) {
13709
+ const path27 = metadataStats?.path ?? filePathFromInput(input) ?? extractPathFromPatchText(patchText);
13710
+ if (toolIsLowInformationPatch(normalized, state, input, patchText, path27, metadataStats)) {
13608
13711
  return null;
13609
13712
  }
13610
13713
  const output = stringValue2(state.output);
13611
13714
  const diffStats = countUnifiedDiffStats2(patchText);
13612
- const displayFilePath = displayPath(path25, options);
13715
+ const displayFilePath = displayPath(path27, options);
13613
13716
  return {
13614
13717
  id,
13615
13718
  kind: "fileChange",
13616
13719
  text: metadataStats ? metadataStats.changedFiles > 1 ? `${metadataStats.changedFiles} changed files` : displayFilePath ?? metadataStats.previewText : displayFilePath ?? output ?? summary ?? name,
13617
13720
  previewText: metadataStats ? metadataStats.changedFiles > 1 ? `${metadataStats.changedFiles} changed files` : displayFilePath ?? metadataStats.previewText : displayFilePath ? `${name}: ${displayFilePath}` : output ?? summary ?? name,
13618
13721
  detailText,
13619
- changedFiles: metadataStats?.changedFiles ?? (path25 ? 1 : null),
13722
+ changedFiles: metadataStats?.changedFiles ?? (path27 ? 1 : null),
13620
13723
  addedLines: metadataStats?.addedLines ?? diffStats?.addedLines ?? null,
13621
13724
  removedLines: metadataStats?.removedLines ?? diffStats?.removedLines ?? null,
13622
13725
  status: toolStateStatus(state)
13623
13726
  };
13624
13727
  }
13625
13728
  if (["read", "grep", "glob", "list", "ls", "bashoutput"].includes(normalized)) {
13626
- const path25 = filePathFromInput(input);
13627
- const text2 = displayPath(path25, options) ?? summary ?? name;
13729
+ const path27 = filePathFromInput(input);
13730
+ const text2 = displayPath(path27, options) ?? summary ?? name;
13628
13731
  return {
13629
13732
  id,
13630
13733
  kind: "fileRead",
@@ -13970,7 +14073,7 @@ import { execFile as execFile2 } from "child_process";
13970
14073
  import fs9 from "fs/promises";
13971
14074
  import net from "net";
13972
14075
  import { createRequire as createRequire2 } from "module";
13973
- import path11 from "path";
14076
+ import path12 from "path";
13974
14077
  import { pathToFileURL as pathToFileURL2 } from "url";
13975
14078
  import { promisify as promisify2 } from "util";
13976
14079
  var execFileAsync2 = promisify2(execFile2);
@@ -14102,21 +14205,21 @@ function configuredProviderModelRecords(config) {
14102
14205
  if (!providerConfig) {
14103
14206
  return configured;
14104
14207
  }
14105
- Object.entries(providerConfig).forEach(([providerID, provider]) => {
14106
- if (!isRecord10(provider) || !isRecord10(provider.models)) {
14208
+ Object.entries(providerConfig).forEach(([providerID, provider2]) => {
14209
+ if (!isRecord10(provider2) || !isRecord10(provider2.models)) {
14107
14210
  return;
14108
14211
  }
14109
- Object.entries(provider.models).forEach(([modelID, model]) => {
14212
+ Object.entries(provider2.models).forEach(([modelID, model]) => {
14110
14213
  configured.set(providerModelKey(providerID, modelID), isRecord10(model) ? model : {});
14111
14214
  });
14112
14215
  });
14113
14216
  return configured;
14114
14217
  }
14115
- function mapProviderModel(provider, record, index, configuredRecord) {
14116
- if (!isRecord10(provider) || !isRecord10(record)) {
14218
+ function mapProviderModel(provider2, record, index, configuredRecord) {
14219
+ if (!isRecord10(provider2) || !isRecord10(record)) {
14117
14220
  return null;
14118
14221
  }
14119
- const providerID = stringValue3(record.providerID) ?? stringValue3(provider.id);
14222
+ const providerID = stringValue3(record.providerID) ?? stringValue3(provider2.id);
14120
14223
  const id = stringValue3(record.id);
14121
14224
  if (!providerID || !id) {
14122
14225
  return null;
@@ -14124,8 +14227,8 @@ function mapProviderModel(provider, record, index, configuredRecord) {
14124
14227
  const name = stringValue3(configuredRecord?.name) ?? stringValue3(record.name) ?? id;
14125
14228
  const variants = configuredRecord ? isRecord10(configuredRecord.variants) ? Object.keys(configuredRecord.variants) : [] : isRecord10(record.variants) ? Object.keys(record.variants) : [];
14126
14229
  const model = providerModelKey(providerID, id);
14127
- const providerName = stringValue3(provider.name) ?? providerID;
14128
- const disabled = configuredRecord?.status === "disabled" || record.status === "disabled" || provider.disabled === true;
14230
+ const providerName = stringValue3(provider2.name) ?? providerID;
14231
+ const disabled = configuredRecord?.status === "disabled" || record.status === "disabled" || provider2.disabled === true;
14129
14232
  const reasoningEfforts = variants.map((variant) => ({
14130
14233
  reasoningEffort: variant,
14131
14234
  description: variant === "none" ? "No reasoning" : variant === "xhigh" ? "Maximum reasoning" : `${variant[0]?.toUpperCase() ?? ""}${variant.slice(1)} reasoning`
@@ -14145,15 +14248,15 @@ function providerModels(result, configuredModels) {
14145
14248
  const data = isRecord10(result) && Array.isArray(result.providers) ? result : isRecord10(result) && isRecord10(result.data) && Array.isArray(result.data.providers) ? result.data : null;
14146
14249
  const providers = Array.isArray(data?.providers) ? data.providers : [];
14147
14250
  const models = [];
14148
- providers.forEach((provider) => {
14149
- if (!isRecord10(provider) || !isRecord10(provider.models)) {
14251
+ providers.forEach((provider2) => {
14252
+ if (!isRecord10(provider2) || !isRecord10(provider2.models)) {
14150
14253
  return;
14151
14254
  }
14152
- Object.values(provider.models).forEach((model) => {
14255
+ Object.values(provider2.models).forEach((model) => {
14153
14256
  if (!isRecord10(model)) {
14154
14257
  return;
14155
14258
  }
14156
- const providerID = stringValue3(model.providerID) ?? stringValue3(provider.id);
14259
+ const providerID = stringValue3(model.providerID) ?? stringValue3(provider2.id);
14157
14260
  const modelID = stringValue3(model.id);
14158
14261
  if (!providerID || !modelID) {
14159
14262
  return;
@@ -14162,7 +14265,7 @@ function providerModels(result, configuredModels) {
14162
14265
  if (configuredModels && configuredModels.size > 0 && !configuredRecord) {
14163
14266
  return;
14164
14267
  }
14165
- const mapped = mapProviderModel(provider, model, models.length, configuredRecord);
14268
+ const mapped = mapProviderModel(provider2, model, models.length, configuredRecord);
14166
14269
  if (mapped) {
14167
14270
  models.push(mapped);
14168
14271
  }
@@ -15045,7 +15148,7 @@ async function importOptionalPackage2(specifier) {
15045
15148
  throw localError;
15046
15149
  }
15047
15150
  try {
15048
- const requireFromGlobal = createRequire2(path11.join(globalRoot, "remote-codex-global.cjs"));
15151
+ const requireFromGlobal = createRequire2(path12.join(globalRoot, "remote-codex-global.cjs"));
15049
15152
  const resolved = resolveOptionalPackage(requireFromGlobal, globalRoot, specifier);
15050
15153
  return await dynamicImport(pathToFileURL2(resolved).href);
15051
15154
  } catch {
@@ -15058,7 +15161,7 @@ function resolveOptionalPackage(requireFromGlobal, globalRoot, specifier) {
15058
15161
  return requireFromGlobal.resolve(specifier);
15059
15162
  } catch (error) {
15060
15163
  if (error.code === "ERR_PACKAGE_PATH_NOT_EXPORTED" && specifier === "@opencode-ai/sdk/v2") {
15061
- return path11.join(globalRoot, "@opencode-ai", "sdk", "dist", "v2", "index.js");
15164
+ return path12.join(globalRoot, "@opencode-ai", "sdk", "dist", "v2", "index.js");
15062
15165
  }
15063
15166
  throw error;
15064
15167
  }
@@ -15074,6 +15177,498 @@ async function npmGlobalRoot2() {
15074
15177
  }
15075
15178
  }
15076
15179
 
15180
+ // src/e2e-fake-runtime.ts
15181
+ import { EventEmitter as EventEmitter7 } from "events";
15182
+ var provider = "claude";
15183
+ var firstDelta = "IOS_STREAM_DELTA_READY";
15184
+ var secondDelta = " IOS_STREAM_COMPLETED";
15185
+ var approvalPromptMarker = "IOS_PENDING_APPROVAL";
15186
+ var questionPromptMarker = "IOS_PENDING_QUESTION";
15187
+ var planPromptMarker = "IOS_PENDING_PLAN";
15188
+ var E2EFakeRuntime = class extends EventEmitter7 {
15189
+ provider = provider;
15190
+ displayName = "E2E Fake Runtime";
15191
+ description = "Deterministic runtime for live iOS and web end-to-end tests.";
15192
+ capabilities = {
15193
+ sessions: { list: true, read: true, resume: true, importLocal: false },
15194
+ turns: { start: true, streamInput: false, steer: false, interrupt: true, compact: false },
15195
+ branching: { fork: false, hardRollback: false, resumeAt: false, rewindFiles: false },
15196
+ controls: {
15197
+ planMode: false,
15198
+ permissionRequests: false,
15199
+ sandboxMode: true,
15200
+ performanceMode: false,
15201
+ goals: false
15202
+ },
15203
+ management: {
15204
+ models: true,
15205
+ mcpStatus: true,
15206
+ skills: false,
15207
+ hooks: false,
15208
+ hookTrust: false,
15209
+ hostConfigFiles: false,
15210
+ providerSettings: false
15211
+ },
15212
+ usage: { contextWindow: true, tokenUsage: true, costUsd: true }
15213
+ };
15214
+ managementSchema = {
15215
+ hostConfigFiles: [],
15216
+ toolboxItems: [],
15217
+ hookCommandTemplates: [],
15218
+ providerConfigFormat: "none",
15219
+ mcpConfigFormat: "none",
15220
+ configArchives: false,
15221
+ buildRestart: false
15222
+ };
15223
+ installation = {
15224
+ packageName: "remote-codex-e2e-fake-runtime",
15225
+ installed: true,
15226
+ installedVersion: "test",
15227
+ latestVersion: null,
15228
+ installCommand: "",
15229
+ updateCommand: "",
15230
+ busy: false,
15231
+ lastError: null
15232
+ };
15233
+ sessions = /* @__PURE__ */ new Map();
15234
+ providerRequests = /* @__PURE__ */ new Map();
15235
+ activeTurnId = null;
15236
+ startedAt = null;
15237
+ getStatus() {
15238
+ return {
15239
+ state: "ready",
15240
+ transport: "none",
15241
+ lastStartedAt: this.startedAt,
15242
+ lastError: null,
15243
+ restartCount: 0
15244
+ };
15245
+ }
15246
+ async start() {
15247
+ this.startedAt = (/* @__PURE__ */ new Date()).toISOString();
15248
+ }
15249
+ async stop() {
15250
+ this.activeTurnId = null;
15251
+ }
15252
+ async listModels() {
15253
+ return [
15254
+ {
15255
+ id: "ios-e2e-stream",
15256
+ model: "ios-e2e-stream",
15257
+ displayName: "iOS E2E Stream",
15258
+ description: "Deterministic streaming test model.",
15259
+ isDefault: true,
15260
+ hidden: false,
15261
+ supportedReasoningEfforts: [
15262
+ { reasoningEffort: "low", description: "Low" },
15263
+ { reasoningEffort: "medium", description: "Medium" },
15264
+ { reasoningEffort: "high", description: "High" }
15265
+ ],
15266
+ defaultReasoningEffort: "medium"
15267
+ }
15268
+ ];
15269
+ }
15270
+ async listSessions() {
15271
+ return [...this.sessions.values()].map((session) => ({
15272
+ provider,
15273
+ providerSessionId: session.providerSessionId,
15274
+ cwd: session.cwd,
15275
+ title: session.title,
15276
+ preview: session.preview,
15277
+ createdAt: session.createdAt,
15278
+ updatedAt: session.updatedAt,
15279
+ status: session.status,
15280
+ rawSession: session
15281
+ }));
15282
+ }
15283
+ async listLoadedSessions() {
15284
+ return [...this.sessions.keys()];
15285
+ }
15286
+ async readSession(providerSessionId) {
15287
+ const session = this.sessions.get(providerSessionId);
15288
+ if (!session) {
15289
+ throw new Error(`E2E fake session missing: ${providerSessionId}`);
15290
+ }
15291
+ return session;
15292
+ }
15293
+ async startSession(input) {
15294
+ const providerSessionId = `e2e-session-${this.sessions.size + 1}`;
15295
+ const now = (/* @__PURE__ */ new Date()).toISOString();
15296
+ const session = {
15297
+ provider,
15298
+ providerSessionId,
15299
+ cwd: input.cwd,
15300
+ title: null,
15301
+ preview: null,
15302
+ createdAt: now,
15303
+ updatedAt: now,
15304
+ status: "idle",
15305
+ turns: [],
15306
+ totalTurnCount: 0,
15307
+ rawSession: null
15308
+ };
15309
+ this.sessions.set(providerSessionId, session);
15310
+ return {
15311
+ provider,
15312
+ providerSessionId,
15313
+ model: input.model,
15314
+ reasoningEffort: input.reasoningEffort ?? null,
15315
+ sandboxMode: input.sandboxMode ?? null,
15316
+ session,
15317
+ rawSession: session
15318
+ };
15319
+ }
15320
+ async resumeSession(input) {
15321
+ const session = await this.readSession(input.providerSessionId);
15322
+ return {
15323
+ provider,
15324
+ providerSessionId: input.providerSessionId,
15325
+ model: input.model ?? null,
15326
+ reasoningEffort: null,
15327
+ sandboxMode: input.sandboxMode ?? null,
15328
+ session,
15329
+ rawSession: session
15330
+ };
15331
+ }
15332
+ async startTurn(input) {
15333
+ const session = await this.readSession(input.providerSessionId);
15334
+ const providerTurnId = `e2e-turn-${session.turns.length + 1}`;
15335
+ const now = (/* @__PURE__ */ new Date()).toISOString();
15336
+ const userItem = {
15337
+ id: `${providerTurnId}:user`,
15338
+ createdAt: now,
15339
+ kind: "userMessage",
15340
+ text: input.prompt
15341
+ };
15342
+ const assistantItem = {
15343
+ id: `${providerTurnId}:assistant`,
15344
+ createdAt: now,
15345
+ kind: "agentMessage",
15346
+ text: "",
15347
+ status: "running"
15348
+ };
15349
+ const turn = {
15350
+ providerTurnId,
15351
+ startedAt: now,
15352
+ status: "inProgress",
15353
+ error: null,
15354
+ items: input.hidden ? [] : [userItem],
15355
+ rawTurn: null
15356
+ };
15357
+ session.turns.push(turn);
15358
+ session.totalTurnCount = session.turns.length;
15359
+ session.status = "running";
15360
+ session.updatedAt = now;
15361
+ session.preview = input.prompt;
15362
+ this.activeTurnId = providerTurnId;
15363
+ this.emitRuntimeEvent({
15364
+ type: "turn.started",
15365
+ provider,
15366
+ providerSessionId: input.providerSessionId,
15367
+ turn
15368
+ });
15369
+ if (input.prompt.includes(approvalPromptMarker)) {
15370
+ setTimeout(() => {
15371
+ if (turn.status !== "inProgress") {
15372
+ return;
15373
+ }
15374
+ const requestId = `${providerTurnId}:approval`;
15375
+ this.providerRequests.set(requestId, {
15376
+ providerSessionId: input.providerSessionId,
15377
+ providerTurnId,
15378
+ assistantText: "IOS_PENDING_APPROVAL_RESOLVED"
15379
+ });
15380
+ this.emit("provider-request", {
15381
+ provider,
15382
+ id: requestId,
15383
+ method: "item/commandExecution/requestApproval",
15384
+ params: {
15385
+ providerSessionId: input.providerSessionId,
15386
+ providerTurnId,
15387
+ itemId: `${providerTurnId}:approval-item`,
15388
+ command: "printf ios-e2e-approval",
15389
+ cwd: session.cwd,
15390
+ reason: "iOS E2E approval request."
15391
+ }
15392
+ });
15393
+ }, 250);
15394
+ return turn;
15395
+ }
15396
+ if (input.prompt.includes(questionPromptMarker)) {
15397
+ setTimeout(() => {
15398
+ if (turn.status !== "inProgress") {
15399
+ return;
15400
+ }
15401
+ const requestId = `${providerTurnId}:question`;
15402
+ this.providerRequests.set(requestId, {
15403
+ providerSessionId: input.providerSessionId,
15404
+ providerTurnId,
15405
+ assistantText: "IOS_PENDING_QUESTION_RESOLVED"
15406
+ });
15407
+ this.emit("provider-request", {
15408
+ provider,
15409
+ id: requestId,
15410
+ method: "tool/AskUserQuestion",
15411
+ params: {
15412
+ providerSessionId: input.providerSessionId,
15413
+ providerTurnId,
15414
+ toolUseId: `${providerTurnId}:question-item`
15415
+ }
15416
+ });
15417
+ }, 250);
15418
+ return turn;
15419
+ }
15420
+ if (input.prompt.includes(planPromptMarker)) {
15421
+ const planItem = {
15422
+ id: `${providerTurnId}:plan`,
15423
+ createdAt: now,
15424
+ kind: "plan",
15425
+ text: "1. Verify the iOS pending request path.\n2. Keep plan mode active until the user decides.",
15426
+ status: "completed"
15427
+ };
15428
+ setTimeout(() => {
15429
+ if (turn.status !== "inProgress") {
15430
+ return;
15431
+ }
15432
+ turn.items.push(planItem);
15433
+ this.emitRuntimeEvent({
15434
+ type: "item.started",
15435
+ provider,
15436
+ providerSessionId: input.providerSessionId,
15437
+ providerTurnId,
15438
+ item: { ...planItem }
15439
+ });
15440
+ this.completeTurn(input.providerSessionId, providerTurnId);
15441
+ }, 250);
15442
+ return turn;
15443
+ }
15444
+ setTimeout(() => {
15445
+ if (turn.status !== "inProgress") {
15446
+ return;
15447
+ }
15448
+ assistantItem.text = firstDelta;
15449
+ turn.items.push(assistantItem);
15450
+ this.emitRuntimeEvent({
15451
+ type: "item.started",
15452
+ provider,
15453
+ providerSessionId: input.providerSessionId,
15454
+ providerTurnId,
15455
+ item: { ...assistantItem }
15456
+ });
15457
+ this.emitRuntimeEvent({
15458
+ type: "output.delta",
15459
+ provider,
15460
+ providerSessionId: input.providerSessionId,
15461
+ providerTurnId,
15462
+ itemId: assistantItem.id,
15463
+ delta: firstDelta
15464
+ });
15465
+ }, 250);
15466
+ setTimeout(() => {
15467
+ if (turn.status !== "inProgress") {
15468
+ return;
15469
+ }
15470
+ assistantItem.text = `${firstDelta}${secondDelta}`;
15471
+ assistantItem.status = "completed";
15472
+ turn.status = "completed";
15473
+ session.status = "idle";
15474
+ session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
15475
+ this.activeTurnId = null;
15476
+ this.emitRuntimeEvent({
15477
+ type: "output.delta",
15478
+ provider,
15479
+ providerSessionId: input.providerSessionId,
15480
+ providerTurnId,
15481
+ itemId: assistantItem.id,
15482
+ delta: secondDelta
15483
+ });
15484
+ this.emitRuntimeEvent({
15485
+ type: "item.completed",
15486
+ provider,
15487
+ providerSessionId: input.providerSessionId,
15488
+ providerTurnId,
15489
+ item: { ...assistantItem }
15490
+ });
15491
+ this.emitRuntimeEvent({
15492
+ type: "turn.completed",
15493
+ provider,
15494
+ providerSessionId: input.providerSessionId,
15495
+ turn
15496
+ });
15497
+ }, 2e4);
15498
+ return turn;
15499
+ }
15500
+ async interruptTurn(input) {
15501
+ const session = await this.readSession(input.providerSessionId);
15502
+ const turn = session.turns.find((entry) => entry.providerTurnId === input.providerTurnId);
15503
+ if (!turn) {
15504
+ return null;
15505
+ }
15506
+ turn.status = "interrupted";
15507
+ session.status = "interrupted";
15508
+ this.activeTurnId = null;
15509
+ return turn;
15510
+ }
15511
+ async listMcpServers() {
15512
+ return [];
15513
+ }
15514
+ mapProviderRequest(request) {
15515
+ if (request.provider !== provider || !isRecord11(request.params)) {
15516
+ return null;
15517
+ }
15518
+ const providerSessionId = stringValue4(request.params.providerSessionId);
15519
+ const providerTurnId = stringValue4(request.params.providerTurnId);
15520
+ if (!providerSessionId) {
15521
+ return null;
15522
+ }
15523
+ if (request.method === "item/commandExecution/requestApproval") {
15524
+ const requestId = String(request.id);
15525
+ const description = [
15526
+ stringValue4(request.params.reason),
15527
+ stringValue4(request.params.command) ? `Command: ${stringValue4(request.params.command)}` : null,
15528
+ stringValue4(request.params.cwd) ? `CWD: ${stringValue4(request.params.cwd)}` : null
15529
+ ].filter(Boolean).join("\n");
15530
+ return {
15531
+ providerRequestId: request.id,
15532
+ providerSessionId,
15533
+ autoApprovedResult: null,
15534
+ pendingRequest: {
15535
+ providerRequestId: request.id,
15536
+ responseKind: "commandExecutionApproval",
15537
+ request: {
15538
+ id: requestId,
15539
+ kind: "requestUserInput",
15540
+ title: "Command approval required",
15541
+ description: description || "iOS E2E approval request.",
15542
+ turnId: providerTurnId,
15543
+ itemId: stringValue4(request.params.itemId),
15544
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
15545
+ questions: [
15546
+ {
15547
+ id: "approval",
15548
+ header: "Command approval required",
15549
+ question: description || "iOS E2E approval request.",
15550
+ isOther: false,
15551
+ isSecret: false,
15552
+ options: [
15553
+ { label: "Allow", description: "Permit this action and continue the current turn." },
15554
+ { label: "Deny", description: "Decline this action." }
15555
+ ]
15556
+ }
15557
+ ]
15558
+ }
15559
+ }
15560
+ };
15561
+ }
15562
+ if (request.method === "tool/AskUserQuestion") {
15563
+ const requestId = String(request.id);
15564
+ return {
15565
+ providerRequestId: request.id,
15566
+ providerSessionId,
15567
+ autoApprovedResult: null,
15568
+ pendingRequest: {
15569
+ providerRequestId: request.id,
15570
+ responseKind: "askUserQuestion",
15571
+ request: {
15572
+ id: requestId,
15573
+ kind: "requestUserInput",
15574
+ title: "Mode",
15575
+ description: "Which iOS E2E path should continue?",
15576
+ turnId: providerTurnId,
15577
+ itemId: stringValue4(request.params.toolUseId),
15578
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
15579
+ questions: [
15580
+ {
15581
+ id: "question-1",
15582
+ header: "Mode",
15583
+ question: "Which iOS E2E path should continue?",
15584
+ isOther: true,
15585
+ isSecret: false,
15586
+ options: [
15587
+ { label: "Short", description: "Keep the response concise." },
15588
+ { label: "Detailed", description: "Include more context." }
15589
+ ]
15590
+ }
15591
+ ]
15592
+ },
15593
+ responsePayload: {
15594
+ continueAsPrompt: false
15595
+ }
15596
+ }
15597
+ };
15598
+ }
15599
+ return null;
15600
+ }
15601
+ buildProviderRequestResponse(pending, input) {
15602
+ if (pending.responseKind === "commandExecutionApproval") {
15603
+ const answer = input.answers.approval?.answers[0]?.trim().toLowerCase();
15604
+ return { decision: answer === "allow" ? "accept" : "deny" };
15605
+ }
15606
+ return {
15607
+ kind: pending.responseKind,
15608
+ answers: input.answers
15609
+ };
15610
+ }
15611
+ respondToProviderRequest(id, result) {
15612
+ void result;
15613
+ const request = this.providerRequests.get(id);
15614
+ if (!request) {
15615
+ return;
15616
+ }
15617
+ this.providerRequests.delete(id);
15618
+ const session = this.sessions.get(request.providerSessionId);
15619
+ const turn = session?.turns.find((entry) => entry.providerTurnId === request.providerTurnId);
15620
+ if (!session || !turn || turn.status !== "inProgress") {
15621
+ return;
15622
+ }
15623
+ const assistantItem = {
15624
+ id: `${request.providerTurnId}:assistant`,
15625
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
15626
+ kind: "agentMessage",
15627
+ text: request.assistantText,
15628
+ status: "completed"
15629
+ };
15630
+ turn.items.push(assistantItem);
15631
+ this.emitRuntimeEvent({
15632
+ type: "item.started",
15633
+ provider,
15634
+ providerSessionId: request.providerSessionId,
15635
+ providerTurnId: request.providerTurnId,
15636
+ item: { ...assistantItem }
15637
+ });
15638
+ this.completeTurn(request.providerSessionId, request.providerTurnId);
15639
+ }
15640
+ completeTurn(providerSessionId, providerTurnId) {
15641
+ const session = this.sessions.get(providerSessionId);
15642
+ const turn = session?.turns.find((entry) => entry.providerTurnId === providerTurnId);
15643
+ if (!session || !turn || turn.status !== "inProgress") {
15644
+ return;
15645
+ }
15646
+ turn.status = "completed";
15647
+ session.status = "idle";
15648
+ session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
15649
+ this.activeTurnId = null;
15650
+ this.emitRuntimeEvent({
15651
+ type: "turn.completed",
15652
+ provider,
15653
+ providerSessionId,
15654
+ turn
15655
+ });
15656
+ }
15657
+ emitRuntimeEvent(event) {
15658
+ this.emit("event", event);
15659
+ }
15660
+ };
15661
+ function isE2EFakeRuntimeEnabled(env = process.env) {
15662
+ const value = env.REMOTE_CODEX_E2E_FAKE_RUNTIME;
15663
+ return value ? ["1", "true", "yes", "on"].includes(value.toLowerCase()) : false;
15664
+ }
15665
+ function isRecord11(value) {
15666
+ return typeof value === "object" && value !== null && !Array.isArray(value);
15667
+ }
15668
+ function stringValue4(value) {
15669
+ return typeof value === "string" ? value : null;
15670
+ }
15671
+
15077
15672
  // src/agent-runtime-bootstrap.ts
15078
15673
  function createAgentRuntimeBootstrap(config) {
15079
15674
  const runtimes = [];
@@ -15104,6 +15699,10 @@ function createAgentRuntimeBootstrap(config) {
15104
15699
  runtimes.push(createOpenCodeRuntime(config));
15105
15700
  providerHostHomes.opencode = opencodeConfig.home;
15106
15701
  }
15702
+ if (isE2EFakeRuntimeEnabled()) {
15703
+ runtimes.push(new E2EFakeRuntime());
15704
+ providerHostHomes.claude = claudeConfig.home;
15705
+ }
15107
15706
  return {
15108
15707
  agentRuntimes: new AgentRuntimeRegistry(runtimes),
15109
15708
  localCodexSessionStore: new LocalCodexSessionStore(codexConfig.home),
@@ -15137,8 +15736,8 @@ function createOpenCodeRuntime(config) {
15137
15736
  }
15138
15737
 
15139
15738
  // src/event-bus.ts
15140
- import { EventEmitter as EventEmitter7 } from "events";
15141
- var SupervisorEventBus = class extends EventEmitter7 {
15739
+ import { EventEmitter as EventEmitter8 } from "events";
15740
+ var SupervisorEventBus = class extends EventEmitter8 {
15142
15741
  emitThreadEvent(event) {
15143
15742
  this.emit("thread-event", event);
15144
15743
  }
@@ -15475,8 +16074,8 @@ var ThreadGoalCoordinator = class {
15475
16074
  normalizeThreadGoalStatusForThread(goal, record) {
15476
16075
  return normalizeThreadGoalStatusForThread(goal, record);
15477
16076
  }
15478
- async ensureGoalsFeatureEnabled(provider) {
15479
- await this.goalFeatureManagement.ensureGoalsFeatureEnabled(provider);
16077
+ async ensureGoalsFeatureEnabled(provider2) {
16078
+ await this.goalFeatureManagement.ensureGoalsFeatureEnabled(provider2);
15480
16079
  }
15481
16080
  };
15482
16081
  function toIsoFromEpoch2(value) {
@@ -15590,7 +16189,7 @@ var DEFERRED_FILE_CHANGE_DETAIL_TITLE = "File Change Details";
15590
16189
  var DEFERRED_FILE_READ_DETAIL_TITLE = "File Read Details";
15591
16190
  var DEFERRED_WEB_SEARCH_DETAIL_TITLE = "Web Search Details";
15592
16191
  var DEFERRED_HOOK_DETAIL_TITLE = "Hook Details";
15593
- function parseUuidV7Timestamp(id) {
16192
+ function parseUuidV7Timestamp2(id) {
15594
16193
  const normalized = id.replace(/-/g, "");
15595
16194
  if (!/^[0-9a-f]{32}$/i.test(normalized) || normalized[12]?.toLowerCase() !== "7") {
15596
16195
  return null;
@@ -15955,7 +16554,7 @@ function mergePersistedHistoryItemsIntoTurns(turns, persistedItemsByTurnId, defe
15955
16554
  function agentTurnToThreadTurnDto(turn, deferredDetails) {
15956
16555
  const baseTurn = {
15957
16556
  id: turn.providerTurnId,
15958
- startedAt: turn.startedAt ?? parseUuidV7Timestamp(turn.providerTurnId),
16557
+ startedAt: turn.startedAt ?? parseUuidV7Timestamp2(turn.providerTurnId),
15959
16558
  status: turn.status,
15960
16559
  error: turn.error?.message ?? null,
15961
16560
  items: visibleRuntimeTurnItems(turn.items).map(
@@ -16200,6 +16799,7 @@ var ThreadLiveStateStore = class {
16200
16799
  sequence: input.sequence
16201
16800
  } : {
16202
16801
  id: input.itemId,
16802
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
16203
16803
  kind: "agentMessage",
16204
16804
  text: input.delta,
16205
16805
  sequence: input.sequence
@@ -16310,7 +16910,7 @@ function agentMessageMatchScore(finalText, liveText) {
16310
16910
 
16311
16911
  // src/thread-usage-accounting.ts
16312
16912
  var CONTEXT_BASELINE_TOKENS = 12e3;
16313
- function isRecord11(value) {
16913
+ function isRecord12(value) {
16314
16914
  return typeof value === "object" && value !== null && !Array.isArray(value);
16315
16915
  }
16316
16916
  function numberOrNull2(value) {
@@ -16358,11 +16958,11 @@ function computeContextRemainingPercent(tokensInContextWindow, contextWindow) {
16358
16958
  return clampPercentage(Math.round(remaining / effectiveWindow * 100));
16359
16959
  }
16360
16960
  function buildThreadContextUsageFromPayload(payload, model = null, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
16361
- const tokenUsage = isRecord11(payload) ? payload : null;
16961
+ const tokenUsage = isRecord12(payload) ? payload : null;
16362
16962
  const modelContextWindow = numberOrNull2(
16363
16963
  tokenUsage?.modelContextWindow ?? tokenUsage?.model_context_window
16364
16964
  ) ?? contextWindowForModel(model);
16365
- const lastUsage = isRecord11(tokenUsage?.last) ? tokenUsage.last : null;
16965
+ const lastUsage = isRecord12(tokenUsage?.last) ? tokenUsage.last : null;
16366
16966
  const tokensInContextWindow = numberOrNull2(
16367
16967
  lastUsage?.totalTokens ?? lastUsage?.total_tokens
16368
16968
  );
@@ -16394,7 +16994,7 @@ function shouldResetThreadContextUsageForTurnStart(current) {
16394
16994
  return current?.availability !== "available";
16395
16995
  }
16396
16996
  function buildTurnTokenBreakdown(payload) {
16397
- const usage = isRecord11(payload) ? payload : null;
16997
+ const usage = isRecord12(payload) ? payload : null;
16398
16998
  const totalTokens2 = numberOrNull2(usage?.totalTokens ?? usage?.total_tokens);
16399
16999
  const inputTokens = numberOrNull2(usage?.inputTokens ?? usage?.input_tokens);
16400
17000
  const cachedInputTokens = numberOrNull2(
@@ -16440,12 +17040,12 @@ function subtractTurnTokenBreakdowns(current, previous) {
16440
17040
  };
16441
17041
  }
16442
17042
  function parseThreadTurnTokenUsage(payload) {
16443
- const tokenUsage = isRecord11(payload) ? payload : null;
17043
+ const tokenUsage = isRecord12(payload) ? payload : null;
16444
17044
  const total = buildTurnTokenBreakdown(
16445
- isRecord11(tokenUsage?.total) ? tokenUsage.total : null
17045
+ isRecord12(tokenUsage?.total) ? tokenUsage.total : null
16446
17046
  );
16447
17047
  const last = buildTurnTokenBreakdown(
16448
- isRecord11(tokenUsage?.last) ? tokenUsage.last : null
17048
+ isRecord12(tokenUsage?.last) ? tokenUsage.last : null
16449
17049
  );
16450
17050
  const modelContextWindow = numberOrNull2(
16451
17051
  tokenUsage?.modelContextWindow ?? tokenUsage?.model_context_window
@@ -16469,7 +17069,7 @@ function parseStoredThreadTurnTokenUsageState(value) {
16469
17069
  try {
16470
17070
  const parsed = JSON.parse(value);
16471
17071
  const baselineTotal = buildTurnTokenBreakdown(
16472
- isRecord11(parsed?.baselineTotal) ? parsed.baselineTotal : null
17072
+ isRecord12(parsed?.baselineTotal) ? parsed.baselineTotal : null
16473
17073
  );
16474
17074
  return {
16475
17075
  baselineTotal,
@@ -16509,12 +17109,12 @@ function stringifyStoredThreadTurnTokenUsageState(state) {
16509
17109
  });
16510
17110
  }
16511
17111
  function buildThreadTurnTokenUsage(payload, baselineTotal, previous = null) {
16512
- const tokenUsage = isRecord11(payload) ? payload : null;
17112
+ const tokenUsage = isRecord12(payload) ? payload : null;
16513
17113
  const cumulativeTotal = buildTurnTokenBreakdown(
16514
- isRecord11(tokenUsage?.total) ? tokenUsage.total : null
17114
+ isRecord12(tokenUsage?.total) ? tokenUsage.total : null
16515
17115
  );
16516
17116
  const last = buildTurnTokenBreakdown(
16517
- isRecord11(tokenUsage?.last) ? tokenUsage.last : null
17117
+ isRecord12(tokenUsage?.last) ? tokenUsage.last : null
16518
17118
  );
16519
17119
  const modelContextWindow = numberOrNull2(
16520
17120
  tokenUsage?.modelContextWindow ?? tokenUsage?.model_context_window
@@ -16586,7 +17186,7 @@ var ThreadUsageAccounting = class {
16586
17186
  input.localThreadId
16587
17187
  );
16588
17188
  const currentCumulativeTotal = buildTurnTokenBreakdown(
16589
- isRecord11(input.tokenUsage?.total) ? input.tokenUsage.total : null
17189
+ isRecord12(input.tokenUsage?.total) ? input.tokenUsage.total : null
16590
17190
  );
16591
17191
  if (currentCumulativeTotal) {
16592
17192
  this.threadCumulativeTokenUsage.set(input.localThreadId, currentCumulativeTotal);
@@ -16976,10 +17576,10 @@ var ThreadRuntimeEventProjector = class {
16976
17576
  }
16977
17577
  }
16978
17578
  }
16979
- findRecordByProviderSessionId(provider, providerSessionId) {
17579
+ findRecordByProviderSessionId(provider2, providerSessionId) {
16980
17580
  return getThreadRecordByProviderSessionId(
16981
17581
  this.input.db,
16982
- provider ?? "codex",
17582
+ provider2 ?? "codex",
16983
17583
  providerSessionId
16984
17584
  );
16985
17585
  }
@@ -17069,8 +17669,8 @@ var ProviderRequestCoordinator = class {
17069
17669
  dismissedTurnId
17070
17670
  };
17071
17671
  }
17072
- respondToProviderRequest(provider, pending, input) {
17073
- const runtime = this.callbacks.runtimeForProvider(provider);
17672
+ respondToProviderRequest(provider2, pending, input) {
17673
+ const runtime = this.callbacks.runtimeForProvider(provider2);
17074
17674
  if (!runtime.buildProviderRequestResponse) {
17075
17675
  throw new HttpError(409, {
17076
17676
  code: "conflict",
@@ -17099,7 +17699,7 @@ var ProviderRequestCoordinator = class {
17099
17699
  const defaultMappedRequest = runtime.mapProviderRequest?.(request, {
17100
17700
  approvalMode: "guarded"
17101
17701
  });
17102
- const providerSessionIdFromParams = isRecord12(request.params) ? request.params.providerSessionId ?? request.params.threadId ?? request.params.conversationId ?? request.params.sessionId : null;
17702
+ const providerSessionIdFromParams = isRecord13(request.params) ? request.params.providerSessionId ?? request.params.threadId ?? request.params.conversationId ?? request.params.sessionId : null;
17103
17703
  const providerSessionId = defaultMappedRequest?.providerSessionId ?? (typeof providerSessionIdFromParams === "string" ? providerSessionIdFromParams : null);
17104
17704
  const record = providerSessionId ? this.callbacks.findRecordByProviderSessionId(request.provider, providerSessionId) : null;
17105
17705
  if (!record) {
@@ -17312,7 +17912,7 @@ function buildRequestAnswerLines(request, input) {
17312
17912
  return `- ${question.question}: ${answers.join(", ")}`;
17313
17913
  }).filter((line) => Boolean(line));
17314
17914
  }
17315
- function isRecord12(value) {
17915
+ function isRecord13(value) {
17316
17916
  return typeof value === "object" && value !== null && !Array.isArray(value);
17317
17917
  }
17318
17918
 
@@ -17702,20 +18302,20 @@ var ThreadProviderRuntimeCoordinator = class {
17702
18302
  this.agentRuntimes = agentRuntimes;
17703
18303
  }
17704
18304
  agentRuntimes;
17705
- normalizeProvider(provider) {
17706
- if (!provider) {
18305
+ normalizeProvider(provider2) {
18306
+ if (!provider2) {
17707
18307
  return defaultAgentBackendId;
17708
18308
  }
17709
- if (isAgentBackendId(provider)) {
17710
- return provider;
18309
+ if (isAgentBackendId(provider2)) {
18310
+ return provider2;
17711
18311
  }
17712
18312
  throw new HttpError(400, {
17713
18313
  code: "bad_request",
17714
- message: `Unsupported agent runtime provider: ${provider}`
18314
+ message: `Unsupported agent runtime provider: ${provider2}`
17715
18315
  });
17716
18316
  }
17717
- runtimeForProvider(provider) {
17718
- const normalizedProvider = this.normalizeProvider(provider);
18317
+ runtimeForProvider(provider2) {
18318
+ const normalizedProvider = this.normalizeProvider(provider2);
17719
18319
  const runtime = this.optionalRuntimeForProvider(normalizedProvider);
17720
18320
  if (!runtime) {
17721
18321
  throw new HttpError(501, {
@@ -17725,8 +18325,8 @@ var ThreadProviderRuntimeCoordinator = class {
17725
18325
  }
17726
18326
  return runtime;
17727
18327
  }
17728
- optionalRuntimeForProvider(provider) {
17729
- return this.agentRuntimes.getOptional(this.normalizeProvider(provider)) ?? null;
18328
+ optionalRuntimeForProvider(provider2) {
18329
+ return this.agentRuntimes.getOptional(this.normalizeProvider(provider2)) ?? null;
17730
18330
  }
17731
18331
  allRuntimes() {
17732
18332
  return this.agentRuntimes.all();
@@ -17734,22 +18334,22 @@ var ThreadProviderRuntimeCoordinator = class {
17734
18334
  providerForRecord(record) {
17735
18335
  return this.normalizeProvider(record.provider);
17736
18336
  }
17737
- isCodexProvider(provider) {
17738
- return this.providerForRecord({ provider }) === "codex";
18337
+ isCodexProvider(provider2) {
18338
+ return this.providerForRecord({ provider: provider2 }) === "codex";
17739
18339
  }
17740
- runtimeSupportsFastMode(provider) {
17741
- return this.optionalRuntimeForProvider(provider)?.capabilities.controls.performanceMode ?? false;
18340
+ runtimeSupportsFastMode(provider2) {
18341
+ return this.optionalRuntimeForProvider(provider2)?.capabilities.controls.performanceMode ?? false;
17742
18342
  }
17743
- fastModeForProvider(provider, fastMode) {
17744
- return this.runtimeSupportsFastMode(provider) ? normalizeFastMode(fastMode) : false;
18343
+ fastModeForProvider(provider2, fastMode) {
18344
+ return this.runtimeSupportsFastMode(provider2) ? normalizeFastMode(fastMode) : false;
17745
18345
  }
17746
18346
  performanceModeForRecord(record) {
17747
18347
  return performanceModeForFastMode(
17748
18348
  this.fastModeForProvider(record.provider, record.fastMode)
17749
18349
  );
17750
18350
  }
17751
- async listLoadedProviderSessionIds(provider = "codex") {
17752
- const runtime = this.optionalRuntimeForProvider(provider);
18351
+ async listLoadedProviderSessionIds(provider2 = "codex") {
18352
+ const runtime = this.optionalRuntimeForProvider(provider2);
17753
18353
  if (!runtime) {
17754
18354
  return /* @__PURE__ */ new Set();
17755
18355
  }
@@ -17757,11 +18357,11 @@ var ThreadProviderRuntimeCoordinator = class {
17757
18357
  await runtime.listLoadedSessions().catch(() => [])
17758
18358
  );
17759
18359
  }
17760
- async listProviderModels(provider = "codex") {
17761
- return this.runtimeForProvider(provider).listModels().catch(() => []);
18360
+ async listProviderModels(provider2 = "codex") {
18361
+ return this.runtimeForProvider(provider2).listModels().catch(() => []);
17762
18362
  }
17763
- async listProviderModelOptions(provider = "codex") {
17764
- const models = await this.runtimeForProvider(provider).listModels();
18363
+ async listProviderModelOptions(provider2 = "codex") {
18364
+ const models = await this.runtimeForProvider(provider2).listModels();
17765
18365
  return models.map((model) => this.modelOptionFromAgentModel(model));
17766
18366
  }
17767
18367
  normalizeReasoningForModel(modelRecords, model, requested) {
@@ -17946,15 +18546,15 @@ var ThreadManagementCoordinator = class {
17946
18546
  });
17947
18547
  }
17948
18548
  }
17949
- assertHookFileManagement(provider) {
17950
- if (!this.hookFileManagement.canManageHookFiles(provider)) {
18549
+ assertHookFileManagement(provider2) {
18550
+ if (!this.hookFileManagement.canManageHookFiles(provider2)) {
17951
18551
  throw new HttpError(409, {
17952
18552
  code: "conflict",
17953
18553
  message: "This backend does not support hooks file editing."
17954
18554
  });
17955
18555
  }
17956
18556
  }
17957
- async toThreadHooksDto(provider, workspacePath, entry, fallbackWarnings = []) {
18557
+ async toThreadHooksDto(provider2, workspacePath, entry, fallbackWarnings = []) {
17958
18558
  const { globalHooksPath, projectHooksPath } = this.hookFileManagement.hooksPaths(workspacePath);
17959
18559
  const officialHooks = (entry?.hooks ?? []).map((hook) => ({
17960
18560
  key: hook.key,
@@ -17973,7 +18573,7 @@ var ThreadManagementCoordinator = class {
17973
18573
  currentHash: hook.currentHash,
17974
18574
  trustStatus: hook.trustStatus
17975
18575
  }));
17976
- const [globalHooks, projectHooks] = this.hookFileManagement.canManageHookFiles(provider) ? await Promise.all([
18576
+ const [globalHooks, projectHooks] = this.hookFileManagement.canManageHookFiles(provider2) ? await Promise.all([
17977
18577
  this.hookFileManagement.readLocalHookDtos({
17978
18578
  hooksPath: globalHooksPath,
17979
18579
  source: "user",
@@ -18125,7 +18725,8 @@ var ThreadPromptTurnCoordinator = class {
18125
18725
  await runtime.sendInput({
18126
18726
  providerSessionId: record.providerSessionId,
18127
18727
  providerTurnId: steerTurnId,
18128
- prompt: input.prompt
18728
+ prompt: input.prompt,
18729
+ workspacePath: input.workspacePath
18129
18730
  });
18130
18731
  updateThreadRecord(this.db, localThreadId, {
18131
18732
  providerTurnId: steerTurnId,
@@ -18256,9 +18857,9 @@ var ThreadSessionCoordinator = class {
18256
18857
  performanceModeSettings;
18257
18858
  localSessionLookup;
18258
18859
  async startThreadSession(input) {
18259
- const provider = this.providerRuntime.normalizeProvider(input.threadInput.provider);
18860
+ const provider2 = this.providerRuntime.normalizeProvider(input.threadInput.provider);
18260
18861
  const normalizedTitle = input.threadInput.title?.trim() || input.defaultTitle;
18261
- const runtime = this.providerRuntime.runtimeForProvider(provider);
18862
+ const runtime = this.providerRuntime.runtimeForProvider(provider2);
18262
18863
  const modelRecords = await runtime.listModels().catch(() => []);
18263
18864
  const reasoningEffort = this.providerRuntime.normalizeReasoningForModel(
18264
18865
  modelRecords,
@@ -18266,8 +18867,8 @@ var ThreadSessionCoordinator = class {
18266
18867
  input.threadInput.reasoningEffort ?? null
18267
18868
  );
18268
18869
  const sandboxMode = defaultSandboxModeForApprovalMode(input.threadInput.approvalMode);
18269
- const fastMode = this.providerRuntime.runtimeSupportsFastMode(provider) ? this.performanceModeSettings.readFastMode() : false;
18270
- if (this.providerRuntime.runtimeSupportsFastMode(provider)) {
18870
+ const fastMode = this.providerRuntime.runtimeSupportsFastMode(provider2) ? this.performanceModeSettings.readFastMode() : false;
18871
+ if (this.providerRuntime.runtimeSupportsFastMode(provider2)) {
18271
18872
  ensureFastModeSupported(input.threadInput.model, fastMode, modelRecords);
18272
18873
  }
18273
18874
  const response = await runtime.startSession({
@@ -18279,7 +18880,7 @@ var ThreadSessionCoordinator = class {
18279
18880
  performanceMode: performanceModeForFastMode(fastMode)
18280
18881
  });
18281
18882
  return {
18282
- provider,
18883
+ provider: provider2,
18283
18884
  normalizedTitle,
18284
18885
  response,
18285
18886
  reasoningEffort,
@@ -18308,30 +18909,30 @@ var ThreadSessionCoordinator = class {
18308
18909
  return this.localSessionLookup.findSession(providerSessionId);
18309
18910
  }
18310
18911
  async resolveLocalImportSession(input) {
18311
- const provider = normalizeAgentBackendId(input.provider) ?? "codex";
18312
- if (provider !== "codex") {
18313
- return this.resolveRuntimeImportSession(provider, input.sessionId);
18912
+ const provider2 = normalizeAgentBackendId(input.provider) ?? "codex";
18913
+ if (provider2 !== "codex") {
18914
+ return this.resolveRuntimeImportSession(provider2, input.sessionId);
18314
18915
  }
18315
18916
  return this.localSessionLookup.findImportSession(input.sessionId, {
18316
18917
  fastMode: this.performanceModeSettings.readFastMode(),
18317
- provider
18918
+ provider: provider2
18318
18919
  });
18319
18920
  }
18320
- async resolveRuntimeImportSession(provider, sessionId) {
18921
+ async resolveRuntimeImportSession(provider2, sessionId) {
18321
18922
  try {
18322
- const session = await this.providerRuntime.runtimeForProvider(provider).readSession(sessionId);
18923
+ const session = await this.providerRuntime.runtimeForProvider(provider2).readSession(sessionId);
18323
18924
  if (!session.cwd) {
18324
18925
  return null;
18325
18926
  }
18326
18927
  return {
18327
- provider,
18928
+ provider: provider2,
18328
18929
  source: "supervisor",
18329
18930
  sessionId,
18330
18931
  cwd: session.cwd,
18331
18932
  title: session.title?.trim() || session.preview?.trim() || "Untitled imported session",
18332
18933
  model: null,
18333
18934
  summaryText: session.preview,
18334
- fastMode: this.providerRuntime.runtimeSupportsFastMode(provider) ? this.performanceModeSettings.readFastMode() : false
18935
+ fastMode: this.providerRuntime.runtimeSupportsFastMode(provider2) ? this.performanceModeSettings.readFastMode() : false
18335
18936
  };
18336
18937
  } catch {
18337
18938
  return null;
@@ -18703,9 +19304,9 @@ var ThreadHistoryPersistenceCoordinator = class {
18703
19304
 
18704
19305
  // src/thread-deletion-coordinator.ts
18705
19306
  import fs10 from "fs/promises";
18706
- import path12 from "path";
19307
+ import path13 from "path";
18707
19308
  function threadTempDirectoryPath(workspacePath, localThreadId) {
18708
- return path12.join(workspacePath, ".temp", "threads", localThreadId);
19309
+ return path13.join(workspacePath, ".temp", "threads", localThreadId);
18709
19310
  }
18710
19311
  var ThreadDeletionCoordinator = class {
18711
19312
  constructor(db, requestCoordinator, usageAccounting, liveState, auxiliaryState, callbacks) {
@@ -18756,7 +19357,11 @@ var ThreadDeletionCoordinator = class {
18756
19357
  };
18757
19358
 
18758
19359
  // src/exports/thread-pdf-export.ts
19360
+ import { spawn as spawn2 } from "child_process";
18759
19361
  import fs11 from "fs";
19362
+ import os3 from "os";
19363
+ import path14 from "path";
19364
+ import { pathToFileURL as pathToFileURL3 } from "url";
18760
19365
  import puppeteer from "puppeteer-core";
18761
19366
  import { marked } from "marked";
18762
19367
  var MAX_TEXT_CHARS = 12e3;
@@ -18770,6 +19375,8 @@ var EMBEDDED_CJK_FONT_CANDIDATES = [
18770
19375
  { path: "/mnt/c/Windows/Fonts/msyh.ttc", format: "truetype", weight: 400 }
18771
19376
  ];
18772
19377
  var PUPPETEER_CHANNEL = "chrome";
19378
+ var PDF_EXPORT_TIMEOUT_MS = 45e3;
19379
+ var MAX_CHROME_STDERR_CHARS = 4e3;
18773
19380
  var embeddedCjkFontCss = null;
18774
19381
  function escapeHtml(value) {
18775
19382
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
@@ -19796,35 +20403,15 @@ endobj
19796
20403
  `
19797
20404
  );
19798
20405
  }
19799
- const browser = await launchPdfBrowser();
19800
- try {
19801
- const page = await browser.newPage();
19802
- await page.setContent(renderThreadExportHtml(snapshot), {
19803
- waitUntil: "load"
19804
- });
19805
- return Buffer.from(
19806
- await page.pdf({
19807
- format: "Letter",
19808
- printBackground: true,
19809
- preferCSSPageSize: true
19810
- })
19811
- );
19812
- } finally {
19813
- await browser.close();
19814
- }
20406
+ return renderPdfWithChromeCli(renderThreadExportHtml(snapshot));
19815
20407
  }
19816
- async function launchPdfBrowser() {
19817
- const launchOptions = {
19818
- headless: true,
19819
- args: ["--no-sandbox", "--disable-setuid-sandbox", "--font-render-hinting=none"]
19820
- };
19821
- if (process.env.PUPPETEER_EXECUTABLE_PATH) {
19822
- launchOptions.executablePath = process.env.PUPPETEER_EXECUTABLE_PATH;
19823
- } else {
19824
- launchOptions.channel = PUPPETEER_CHANNEL;
19825
- }
20408
+ function resolvePdfBrowserExecutablePath() {
19826
20409
  try {
19827
- return await puppeteer.launch(launchOptions);
20410
+ const executablePath = process.env.PUPPETEER_EXECUTABLE_PATH ? process.env.PUPPETEER_EXECUTABLE_PATH : puppeteer.executablePath(PUPPETEER_CHANNEL);
20411
+ if (!fs11.existsSync(executablePath)) {
20412
+ throw new Error(`Browser executable was not found at ${executablePath}`);
20413
+ }
20414
+ return executablePath;
19828
20415
  } catch (error) {
19829
20416
  const detail = error instanceof Error ? error.message : String(error);
19830
20417
  throw new Error(
@@ -19832,6 +20419,89 @@ async function launchPdfBrowser() {
19832
20419
  );
19833
20420
  }
19834
20421
  }
20422
+ async function renderPdfWithChromeCli(html) {
20423
+ const executablePath = resolvePdfBrowserExecutablePath();
20424
+ const tempDir = await fs11.promises.mkdtemp(path14.join(os3.tmpdir(), "remote-codex-pdf-"));
20425
+ const htmlPath = path14.join(tempDir, "thread-export.html");
20426
+ const pdfPath = path14.join(tempDir, "thread-export.pdf");
20427
+ try {
20428
+ await fs11.promises.writeFile(htmlPath, html, "utf8");
20429
+ await printHtmlToPdf({
20430
+ executablePath,
20431
+ htmlPath,
20432
+ pdfPath
20433
+ });
20434
+ const pdf = await fs11.promises.readFile(pdfPath);
20435
+ if (pdf.length === 0 || !pdf.subarray(0, 4).equals(Buffer.from("%PDF"))) {
20436
+ throw new Error("Chrome did not produce a valid PDF file.");
20437
+ }
20438
+ return pdf;
20439
+ } finally {
20440
+ await fs11.promises.rm(tempDir, { force: true, recursive: true });
20441
+ }
20442
+ }
20443
+ async function printHtmlToPdf(input) {
20444
+ const args = [
20445
+ "--headless",
20446
+ "--disable-gpu",
20447
+ "--disable-dev-shm-usage",
20448
+ "--no-sandbox",
20449
+ "--disable-setuid-sandbox",
20450
+ "--font-render-hinting=none",
20451
+ "--no-pdf-header-footer",
20452
+ "--print-to-pdf-no-header",
20453
+ `--print-to-pdf=${input.pdfPath}`,
20454
+ pathToFileURL3(input.htmlPath).href
20455
+ ];
20456
+ await new Promise((resolve, reject) => {
20457
+ const child = spawn2(input.executablePath, args, {
20458
+ stdio: ["ignore", "ignore", "pipe"]
20459
+ });
20460
+ let settled = false;
20461
+ let stderr = "";
20462
+ const complete = (error) => {
20463
+ if (settled) {
20464
+ return;
20465
+ }
20466
+ settled = true;
20467
+ clearTimeout(timeout);
20468
+ child.kill("SIGTERM");
20469
+ if (error) {
20470
+ reject(error);
20471
+ } else {
20472
+ resolve();
20473
+ }
20474
+ };
20475
+ const timeout = setTimeout(() => {
20476
+ child.kill("SIGKILL");
20477
+ complete(new Error(`Chrome PDF export timed out after ${PDF_EXPORT_TIMEOUT_MS}ms.`));
20478
+ }, PDF_EXPORT_TIMEOUT_MS);
20479
+ child.stderr.on("data", (chunk) => {
20480
+ stderr = truncateChromeStderr(`${stderr}${chunk.toString("utf8")}`);
20481
+ if (stderr.includes("bytes written to file")) {
20482
+ complete();
20483
+ }
20484
+ });
20485
+ child.on("error", (error) => {
20486
+ complete(error);
20487
+ });
20488
+ child.on("close", (code, signal) => {
20489
+ if (code === 0) {
20490
+ complete();
20491
+ return;
20492
+ }
20493
+ const reason = signal ? `signal ${signal}` : `exit code ${code ?? "unknown"}`;
20494
+ const detail = stderr ? ` ${stderr}` : "";
20495
+ complete(new Error(`Chrome PDF export failed with ${reason}.${detail}`));
20496
+ });
20497
+ });
20498
+ }
20499
+ function truncateChromeStderr(value) {
20500
+ if (value.length <= MAX_CHROME_STDERR_CHARS) {
20501
+ return value;
20502
+ }
20503
+ return value.slice(value.length - MAX_CHROME_STDERR_CHARS);
20504
+ }
19835
20505
 
19836
20506
  // src/thread-export-coordinator.ts
19837
20507
  function userPromptPreviewFromTurn(turn) {
@@ -20102,7 +20772,7 @@ var ThreadForkCoordinator = class {
20102
20772
  // src/thread-attachment-coordinator.ts
20103
20773
  import { randomUUID as randomUUID3 } from "crypto";
20104
20774
  import fs12 from "fs/promises";
20105
- import path13 from "path";
20775
+ import path15 from "path";
20106
20776
  async function pathExists(absPath) {
20107
20777
  try {
20108
20778
  await fs12.access(absPath);
@@ -20112,8 +20782,8 @@ async function pathExists(absPath) {
20112
20782
  }
20113
20783
  }
20114
20784
  function sanitizeAttachmentFileName(originalName) {
20115
- const basename = path13.basename(originalName).trim() || "attachment";
20116
- const extension = path13.extname(basename).replace(/[^a-zA-Z0-9.]/g, "");
20785
+ const basename = path15.basename(originalName).trim() || "attachment";
20786
+ const extension = path15.extname(basename).replace(/[^a-zA-Z0-9.]/g, "");
20117
20787
  const rawStem = extension ? basename.slice(0, -extension.length) : basename;
20118
20788
  const sanitizedStem = rawStem.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 64);
20119
20789
  const stem = sanitizedStem || "attachment";
@@ -20121,7 +20791,7 @@ function sanitizeAttachmentFileName(originalName) {
20121
20791
  return `${stem}-${randomUUID3().slice(0, 8)}${normalizedExtension}`;
20122
20792
  }
20123
20793
  function threadTempDirectoryPath2(workspacePath, localThreadId) {
20124
- return path13.join(workspacePath, ".temp", "threads", localThreadId);
20794
+ return path15.join(workspacePath, ".temp", "threads", localThreadId);
20125
20795
  }
20126
20796
  var ThreadAttachmentCoordinator = class {
20127
20797
  constructor(db) {
@@ -20162,7 +20832,7 @@ var ThreadAttachmentCoordinator = class {
20162
20832
  const savedFileName = sanitizeAttachmentFileName(
20163
20833
  attachment.manifest.originalName
20164
20834
  );
20165
- await fs12.writeFile(path13.join(tempDirectory, savedFileName), attachment.buffer);
20835
+ await fs12.writeFile(path15.join(tempDirectory, savedFileName), attachment.buffer);
20166
20836
  const relativePath = `./.temp/threads/${localThreadId}/${savedFileName}`;
20167
20837
  const replacementToken = attachment.manifest.kind === "photo" ? `[PHOTO ${relativePath}]` : `[FILE ${relativePath}]`;
20168
20838
  rewrittenPrompt = rewrittenPrompt.split(attachment.manifest.placeholder).join(replacementToken);
@@ -20176,7 +20846,7 @@ var ThreadAttachmentCoordinator = class {
20176
20846
 
20177
20847
  // src/thread-import-coordinator.ts
20178
20848
  import fs13 from "fs/promises";
20179
- import path14 from "path";
20849
+ import path16 from "path";
20180
20850
  async function pathExists2(absPath) {
20181
20851
  try {
20182
20852
  await fs13.access(absPath);
@@ -20186,19 +20856,19 @@ async function pathExists2(absPath) {
20186
20856
  }
20187
20857
  }
20188
20858
  async function resolveComparablePath2(absPath) {
20189
- const resolved = path14.resolve(absPath);
20859
+ const resolved = path16.resolve(absPath);
20190
20860
  if (await pathExists2(resolved)) {
20191
20861
  return fs13.realpath(resolved);
20192
20862
  }
20193
- const parentPath = path14.dirname(resolved);
20863
+ const parentPath = path16.dirname(resolved);
20194
20864
  if (parentPath === resolved) {
20195
20865
  return resolved;
20196
20866
  }
20197
20867
  const resolvedParent = await resolveComparablePath2(parentPath);
20198
- return path14.join(resolvedParent, path14.basename(resolved));
20868
+ return path16.join(resolvedParent, path16.basename(resolved));
20199
20869
  }
20200
20870
  async function resolveImportedWorkspacePath(workspaceRoot, candidatePath) {
20201
- if (!path14.isAbsolute(candidatePath)) {
20871
+ if (!path16.isAbsolute(candidatePath)) {
20202
20872
  throw new HttpError(400, {
20203
20873
  code: "bad_request",
20204
20874
  message: "Imported session path must be absolute."
@@ -20206,7 +20876,7 @@ async function resolveImportedWorkspacePath(workspaceRoot, candidatePath) {
20206
20876
  }
20207
20877
  const resolvedRoot = await resolveComparablePath2(workspaceRoot);
20208
20878
  const resolvedCandidate = await resolveComparablePath2(candidatePath);
20209
- const normalizedRoot = resolvedRoot.endsWith(path14.sep) ? resolvedRoot : `${resolvedRoot}${path14.sep}`;
20879
+ const normalizedRoot = resolvedRoot.endsWith(path16.sep) ? resolvedRoot : `${resolvedRoot}${path16.sep}`;
20210
20880
  if (resolvedCandidate !== resolvedRoot && !resolvedCandidate.startsWith(normalizedRoot)) {
20211
20881
  throw new HttpError(403, {
20212
20882
  code: "forbidden",
@@ -20232,17 +20902,17 @@ var ThreadImportCoordinator = class {
20232
20902
  message: "Session id is required."
20233
20903
  });
20234
20904
  }
20235
- const provider = normalizeAgentBackendId(input.provider ?? "codex") ?? "codex";
20905
+ const provider2 = normalizeAgentBackendId(input.provider ?? "codex") ?? "codex";
20236
20906
  const existingThread = getThreadRecordByProviderSessionId(
20237
20907
  this.db,
20238
- provider,
20908
+ provider2,
20239
20909
  normalizedSessionId
20240
20910
  );
20241
20911
  if (existingThread) {
20242
20912
  return existingThread.id;
20243
20913
  }
20244
20914
  const importSession = await this.sessionCoordinator.resolveLocalImportSession({
20245
- provider,
20915
+ provider: provider2,
20246
20916
  sessionId: normalizedSessionId
20247
20917
  });
20248
20918
  if (!importSession) {
@@ -20259,7 +20929,7 @@ var ThreadImportCoordinator = class {
20259
20929
  if (!workspace) {
20260
20930
  workspace = createWorkspaceRecord(this.db, {
20261
20931
  absPath: importedPath,
20262
- label: path14.basename(importedPath) || "workspace"
20932
+ label: path16.basename(importedPath) || "workspace"
20263
20933
  });
20264
20934
  }
20265
20935
  const created = createThreadRecord(this.db, {
@@ -20316,8 +20986,8 @@ var ProviderFeatureCoordinator = class {
20316
20986
  mapGoalError(error) {
20317
20987
  this.codexGoalFeatures.mapGoalError(error);
20318
20988
  }
20319
- async ensureGoalsFeatureEnabled(provider) {
20320
- if (!this.providerRuntime.isCodexProvider(provider)) {
20989
+ async ensureGoalsFeatureEnabled(provider2) {
20990
+ if (!this.providerRuntime.isCodexProvider(provider2)) {
20321
20991
  return;
20322
20992
  }
20323
20993
  await this.codexGoalFeatures.ensureGoalsFeatureEnabled(
@@ -20382,7 +21052,7 @@ var ThreadService = class {
20382
21052
  this.historyPersistence = new ThreadHistoryPersistenceCoordinator(db, this.liveState);
20383
21053
  this.attachmentCoordinator = new ThreadAttachmentCoordinator(db);
20384
21054
  this.managementCoordinator = new ThreadManagementCoordinator(providerManagement, {
20385
- runtimeForProvider: (provider) => this.runtimeForProvider(provider)
21055
+ runtimeForProvider: (provider2) => this.runtimeForProvider(provider2)
20386
21056
  });
20387
21057
  this.sessionCoordinator = new ThreadSessionCoordinator(
20388
21058
  this.providerRuntime,
@@ -20408,7 +21078,7 @@ var ThreadService = class {
20408
21078
  this.liveState,
20409
21079
  this.providerRuntime,
20410
21080
  {
20411
- runtimeForProvider: (provider) => this.runtimeForProvider(provider),
21081
+ runtimeForProvider: (provider2) => this.runtimeForProvider(provider2),
20412
21082
  resetThreadContextUsage: (localThreadId, emitEvent) => this.resetThreadContextUsage(localThreadId, emitEvent),
20413
21083
  getThreadContextUsage: (localThreadId) => this.getThreadContextUsage(localThreadId),
20414
21084
  invalidateThreadDetailCache: (localThreadId) => this.invalidateThreadDetailCache(localThreadId),
@@ -20466,7 +21136,7 @@ var ThreadService = class {
20466
21136
  emitThreadEvent: (type, threadId, payload) => this.emitThreadEvent(type, threadId, payload),
20467
21137
  ensureThreadLoaded: (record) => this.ensureThreadLoadedForProviderOperation(record),
20468
21138
  requireProviderSessionId: (record) => this.requireProviderSessionId(record),
20469
- runtimeForProvider: (provider) => this.runtimeForProvider(provider)
21139
+ runtimeForProvider: (provider2) => this.runtimeForProvider(provider2)
20470
21140
  });
20471
21141
  this.auxiliaryState = new ThreadAuxiliaryStateStore(db, {
20472
21142
  cachedTurns: (localThreadId) => this.detailAssembler.cachedTurns(localThreadId),
@@ -20478,9 +21148,9 @@ var ThreadService = class {
20478
21148
  });
20479
21149
  this.requestCoordinator = new ProviderRequestCoordinator({
20480
21150
  emitThreadEvent: (type, threadId, payload) => this.emitThreadEvent(type, threadId, payload),
20481
- findRecordByProviderSessionId: (provider, providerSessionId) => this.findRecordByProviderSessionId(provider, providerSessionId),
21151
+ findRecordByProviderSessionId: (provider2, providerSessionId) => this.findRecordByProviderSessionId(provider2, providerSessionId),
20482
21152
  normalizeCollaborationMode,
20483
- runtimeForProvider: (provider) => this.runtimeForProvider(provider)
21153
+ runtimeForProvider: (provider2) => this.runtimeForProvider(provider2)
20484
21154
  });
20485
21155
  this.deletionCoordinator = new ThreadDeletionCoordinator(
20486
21156
  db,
@@ -20506,7 +21176,7 @@ var ThreadService = class {
20506
21176
  this.sessionCoordinator,
20507
21177
  {
20508
21178
  buildThreadPatch: (remoteSession, model, reasoningEffort) => buildThreadPatch(remoteSession, model, reasoningEffort),
20509
- fastModeForProvider: (provider, fastMode) => this.fastModeForProvider(provider, fastMode),
21179
+ fastModeForProvider: (provider2, fastMode) => this.fastModeForProvider(provider2, fastMode),
20510
21180
  getThreadDetail: (localThreadId) => this.getThreadDetail(localThreadId),
20511
21181
  invalidateThreadDetailCache: (localThreadId) => this.invalidateThreadDetailCache(localThreadId),
20512
21182
  normalizeCollaborationMode,
@@ -20540,7 +21210,7 @@ var ThreadService = class {
20540
21210
  deletePersistedHistoryItemsForTurn: (localThreadId, turnId) => this.historyPersistence.deletePersistedHistoryItemsForTurn(localThreadId, turnId),
20541
21211
  dismissPlanDecisionTurn: (localThreadId) => this.requestCoordinator.dismissPlanDecisionTurn(localThreadId),
20542
21212
  emitThreadEvent: (type, threadId, payload) => this.emitThreadEvent(type, threadId, payload),
20543
- fastModeForProvider: (provider, fastMode) => this.fastModeForProvider(provider, fastMode),
21213
+ fastModeForProvider: (provider2, fastMode) => this.fastModeForProvider(provider2, fastMode),
20544
21214
  hasPendingAskUserQuestion: (localThreadId) => this.hasPendingAskUserQuestion(localThreadId),
20545
21215
  invalidateThreadDetailCache: (localThreadId) => this.invalidateThreadDetailCache(localThreadId),
20546
21216
  listThreadGoalHistory: (localThreadId) => this.goalCoordinator.listThreadGoalHistory(localThreadId),
@@ -20595,14 +21265,14 @@ var ThreadService = class {
20595
21265
  forkCoordinator;
20596
21266
  attachmentCoordinator;
20597
21267
  importCoordinator;
20598
- normalizeProvider(provider) {
20599
- return this.providerRuntime.normalizeProvider(provider);
21268
+ normalizeProvider(provider2) {
21269
+ return this.providerRuntime.normalizeProvider(provider2);
20600
21270
  }
20601
- runtimeForProvider(provider) {
20602
- return this.providerRuntime.runtimeForProvider(provider);
21271
+ runtimeForProvider(provider2) {
21272
+ return this.providerRuntime.runtimeForProvider(provider2);
20603
21273
  }
20604
- optionalRuntimeForProvider(provider) {
20605
- return this.providerRuntime.optionalRuntimeForProvider(provider);
21274
+ optionalRuntimeForProvider(provider2) {
21275
+ return this.providerRuntime.optionalRuntimeForProvider(provider2);
20606
21276
  }
20607
21277
  providerForRecord(record) {
20608
21278
  return this.providerRuntime.providerForRecord(record);
@@ -20634,18 +21304,18 @@ var ThreadService = class {
20634
21304
  );
20635
21305
  }
20636
21306
  }
20637
- findRecordByProviderSessionId(provider, providerSessionId) {
21307
+ findRecordByProviderSessionId(provider2, providerSessionId) {
20638
21308
  return getThreadRecordByProviderSessionId(
20639
21309
  this.db,
20640
- this.providerForRecord({ provider }),
21310
+ this.providerForRecord({ provider: provider2 }),
20641
21311
  providerSessionId
20642
21312
  );
20643
21313
  }
20644
- runtimeSupportsFastMode(provider) {
20645
- return this.providerRuntime.runtimeSupportsFastMode(provider);
21314
+ runtimeSupportsFastMode(provider2) {
21315
+ return this.providerRuntime.runtimeSupportsFastMode(provider2);
20646
21316
  }
20647
- fastModeForProvider(provider, fastMode) {
20648
- return this.providerRuntime.fastModeForProvider(provider, fastMode);
21317
+ fastModeForProvider(provider2, fastMode) {
21318
+ return this.providerRuntime.fastModeForProvider(provider2, fastMode);
20649
21319
  }
20650
21320
  performanceModeForRecord(record) {
20651
21321
  return this.providerRuntime.performanceModeForRecord(record);
@@ -20653,11 +21323,11 @@ var ThreadService = class {
20653
21323
  async handleProviderRequest(request) {
20654
21324
  await this.handleProviderRuntimeRequest(request);
20655
21325
  }
20656
- async listLoadedProviderSessionIds(provider = "codex") {
20657
- return this.providerRuntime.listLoadedProviderSessionIds(provider);
21326
+ async listLoadedProviderSessionIds(provider2 = "codex") {
21327
+ return this.providerRuntime.listLoadedProviderSessionIds(provider2);
20658
21328
  }
20659
- async listProviderModels(provider = "codex") {
20660
- return this.providerRuntime.listProviderModels(provider);
21329
+ async listProviderModels(provider2 = "codex") {
21330
+ return this.providerRuntime.listProviderModels(provider2);
20661
21331
  }
20662
21332
  invalidateThreadDetailCache(localThreadId) {
20663
21333
  this.detailAssembler.invalidate(localThreadId);
@@ -20938,19 +21608,18 @@ var ThreadService = class {
20938
21608
  return this.getThreadDetail(localThreadId);
20939
21609
  }
20940
21610
  async sendPrompt(localThreadId, input, options = {}) {
20941
- const record = getThreadRecordById(this.db, localThreadId);
21611
+ let record = getThreadRecordById(this.db, localThreadId);
20942
21612
  if (!record) {
20943
21613
  throw new HttpError(404, {
20944
21614
  code: "not_found",
20945
21615
  message: "Thread was not found."
20946
21616
  });
20947
21617
  }
20948
- const providerSessionId = this.requireProviderSessionId(record);
20949
21618
  await this.importCoordinator.assertImportedThreadReadyForPrompt({
20950
21619
  source: record.source,
20951
21620
  provider: record.provider,
20952
- providerSessionId,
20953
- listLoadedProviderSessionIds: (provider) => this.listLoadedProviderSessionIds(provider)
21621
+ providerSessionId: this.requireProviderSessionId(record),
21622
+ listLoadedProviderSessionIds: (provider2) => this.listLoadedProviderSessionIds(provider2)
20954
21623
  });
20955
21624
  if (record.isConnected === false) {
20956
21625
  throw new HttpError(409, {
@@ -20958,6 +21627,23 @@ var ThreadService = class {
20958
21627
  message: "Connect this thread before sending a new prompt."
20959
21628
  });
20960
21629
  }
21630
+ if (this.providerForRecord(record) === "codex") {
21631
+ const providerSessionId2 = this.requireProviderSessionId(record);
21632
+ const loadedIds = await this.listLoadedProviderSessionIds(record.provider);
21633
+ if (!loadedIds.has(providerSessionId2)) {
21634
+ await this.ensureThreadLoadedForProviderOperation(record);
21635
+ record = getThreadRecordById(this.db, localThreadId);
21636
+ const resumedProviderSessionId = this.requireProviderSessionId(record);
21637
+ const refreshedLoadedIds = await this.listLoadedProviderSessionIds(record.provider);
21638
+ if (!refreshedLoadedIds.has(resumedProviderSessionId)) {
21639
+ throw new HttpError(409, {
21640
+ code: "conflict",
21641
+ message: "Connect this thread before sending a new prompt."
21642
+ });
21643
+ }
21644
+ }
21645
+ }
21646
+ const providerSessionId = this.requireProviderSessionId(record);
20961
21647
  const prompt = input.prompt.trim();
20962
21648
  const displayPrompt = options.displayPrompt?.trim() || prompt;
20963
21649
  if (!prompt) {
@@ -21381,7 +22067,7 @@ var ThreadService = class {
21381
22067
  provider: record.provider,
21382
22068
  providerSessionId: this.requireProviderSessionId(record),
21383
22069
  model: record.model,
21384
- listLoadedProviderSessionIds: (provider) => this.listLoadedProviderSessionIds(provider),
22070
+ listLoadedProviderSessionIds: (provider2) => this.listLoadedProviderSessionIds(provider2),
21385
22071
  resumeThread: (input2) => this.resumeThread(localThreadId, input2)
21386
22072
  });
21387
22073
  await this.updateThreadSettings(localThreadId, {
@@ -21407,7 +22093,7 @@ var ThreadService = class {
21407
22093
  }
21408
22094
  toThreadDto(record, loadedIds) {
21409
22095
  return toThreadDto(record, loadedIds, {
21410
- fastModeForProvider: (provider, fastMode) => this.fastModeForProvider(provider, fastMode),
22096
+ fastModeForProvider: (provider2, fastMode) => this.fastModeForProvider(provider2, fastMode),
21411
22097
  getThreadContextUsage: (localThreadId) => this.getThreadContextUsage(localThreadId)
21412
22098
  });
21413
22099
  }
@@ -21501,8 +22187,8 @@ var ThreadService = class {
21501
22187
 
21502
22188
  // src/routes/agent-runtimes.ts
21503
22189
  import fs15 from "fs/promises";
21504
- import { spawn as spawn2 } from "child_process";
21505
- import path15 from "path";
22190
+ import { spawn as spawn3 } from "child_process";
22191
+ import path17 from "path";
21506
22192
  import { z as z3 } from "zod";
21507
22193
 
21508
22194
  // src/provider-schemas.ts
@@ -21521,15 +22207,15 @@ var npmManagedPackageNames = {
21521
22207
  claude: ["@anthropic-ai/claude-code", "@anthropic-ai/claude-agent-sdk"],
21522
22208
  opencode: ["opencode-ai", "@opencode-ai/sdk"]
21523
22209
  };
21524
- function providerNotConfigured(provider) {
21525
- const error = new Error(`Agent runtime provider is not configured: ${provider}`);
22210
+ function providerNotConfigured(provider2) {
22211
+ const error = new Error(`Agent runtime provider is not configured: ${provider2}`);
21526
22212
  error.statusCode = 404;
21527
22213
  return error;
21528
22214
  }
21529
- function runtimeDto(app, provider) {
21530
- const runtime = app.services.agentRuntimes.getOptional(provider);
22215
+ function runtimeDto(app, provider2) {
22216
+ const runtime = app.services.agentRuntimes.getOptional(provider2);
21531
22217
  if (!runtime) {
21532
- throw providerNotConfigured(provider);
22218
+ throw providerNotConfigured(provider2);
21533
22219
  }
21534
22220
  const installation = {
21535
22221
  ...runtime.installation
@@ -21539,7 +22225,7 @@ function runtimeDto(app, provider) {
21539
22225
  displayName: runtime.displayName,
21540
22226
  description: runtime.description,
21541
22227
  enabled: isAgentRuntimeEnabled({ ...runtime, installation }),
21542
- isDefault: provider === defaultAgentBackendId,
22228
+ isDefault: provider2 === defaultAgentBackendId,
21543
22229
  status: runtime.getStatus(),
21544
22230
  capabilities: runtime.capabilities,
21545
22231
  managementSchema: runtime.managementSchema,
@@ -21559,18 +22245,18 @@ async function registerAgentRuntimeRoutes(app) {
21559
22245
  });
21560
22246
  });
21561
22247
  app.get("/api/agent-runtimes/:provider/status", async (request) => {
21562
- const { provider } = providerParamSchema.parse(request.params);
21563
- return runtimeDto(app, provider);
22248
+ const { provider: provider2 } = providerParamSchema.parse(request.params);
22249
+ return runtimeDto(app, provider2);
21564
22250
  });
21565
22251
  app.post("/api/agent-runtimes/:provider/restart", async (request) => {
21566
- const { provider } = providerParamSchema.parse(request.params);
21567
- const runtime = app.services.agentRuntimes.getOptional(provider);
22252
+ const { provider: provider2 } = providerParamSchema.parse(request.params);
22253
+ const runtime = app.services.agentRuntimes.getOptional(provider2);
21568
22254
  if (!runtime) {
21569
- throw providerNotConfigured(provider);
22255
+ throw providerNotConfigured(provider2);
21570
22256
  }
21571
22257
  await runtime.stop();
21572
22258
  await runtime.start();
21573
- return runtimeDto(app, provider);
22259
+ return runtimeDto(app, provider2);
21574
22260
  });
21575
22261
  app.post("/api/agent-runtimes/:provider/install", async (request) => {
21576
22262
  if (!app.services.config.agentRuntimeManagementEnabled) {
@@ -21579,11 +22265,11 @@ async function registerAgentRuntimeRoutes(app) {
21579
22265
  message: "Agent runtime install and update are disabled for this worker."
21580
22266
  });
21581
22267
  }
21582
- const { provider } = providerParamSchema.parse(request.params);
22268
+ const { provider: provider2 } = providerParamSchema.parse(request.params);
21583
22269
  const { action } = installActionSchema.parse(request.body ?? {});
21584
- const runtime = app.services.agentRuntimes.getOptional(provider);
22270
+ const runtime = app.services.agentRuntimes.getOptional(provider2);
21585
22271
  if (!runtime) {
21586
- throw providerNotConfigured(provider);
22272
+ throw providerNotConfigured(provider2);
21587
22273
  }
21588
22274
  const command = action === "install" ? runtime.installation.installCommand : runtime.installation.updateCommand;
21589
22275
  if (!command) {
@@ -21616,16 +22302,16 @@ async function registerAgentRuntimeRoutes(app) {
21616
22302
  if (updateWarning) {
21617
22303
  runtime.installation.lastError = updateWarning;
21618
22304
  }
21619
- return runtimeDto(app, provider);
22305
+ return runtimeDto(app, provider2);
21620
22306
  } finally {
21621
22307
  runtime.installation.busy = false;
21622
22308
  }
21623
22309
  });
21624
22310
  app.get("/api/agent-runtimes/:provider/models", async (request) => {
21625
- const { provider } = providerParamSchema.parse(request.params);
21626
- const runtime = app.services.agentRuntimes.getOptional(provider);
22311
+ const { provider: provider2 } = providerParamSchema.parse(request.params);
22312
+ const runtime = app.services.agentRuntimes.getOptional(provider2);
21627
22313
  if (!runtime) {
21628
- throw providerNotConfigured(provider);
22314
+ throw providerNotConfigured(provider2);
21629
22315
  }
21630
22316
  return (await runtime.listModels()).map((model) => ({
21631
22317
  id: model.id,
@@ -21649,10 +22335,10 @@ async function registerAgentRuntimeRoutes(app) {
21649
22335
  message: "Build restart is disabled for this worker."
21650
22336
  });
21651
22337
  }
21652
- const { provider } = providerParamSchema.parse(request.params);
21653
- const runtime = app.services.agentRuntimes.getOptional(provider);
22338
+ const { provider: provider2 } = providerParamSchema.parse(request.params);
22339
+ const runtime = app.services.agentRuntimes.getOptional(provider2);
21654
22340
  if (!runtime) {
21655
- throw providerNotConfigured(provider);
22341
+ throw providerNotConfigured(provider2);
21656
22342
  }
21657
22343
  if (!runtime.managementSchema.buildRestart) {
21658
22344
  const error = new Error("This backend does not support build and restart.");
@@ -21723,7 +22409,7 @@ async function refreshBackendInstallation(app, runtime) {
21723
22409
  async function installedPackageVersion(packageName) {
21724
22410
  const globalRoot = await npmGlobalRoot3();
21725
22411
  if (globalRoot) {
21726
- const global = await packageVersionFromPath(path15.join(globalRoot, packageName, "package.json"));
22412
+ const global = await packageVersionFromPath(path17.join(globalRoot, packageName, "package.json"));
21727
22413
  if (global) {
21728
22414
  return global;
21729
22415
  }
@@ -21760,8 +22446,8 @@ async function updatePathWarning(app, runtime, before, command) {
21760
22446
  `Command: ${command}`
21761
22447
  ].filter(Boolean).join(" ");
21762
22448
  }
21763
- function runtimeCommand(app, provider) {
21764
- switch (provider) {
22449
+ function runtimeCommand(app, provider2) {
22450
+ switch (provider2) {
21765
22451
  case "codex":
21766
22452
  return app.services.config.agentProviders.codex.command;
21767
22453
  case "claude":
@@ -21774,7 +22460,7 @@ function versionStringContains(installedVersion, latestVersion) {
21774
22460
  return Boolean(installedVersion && latestVersion && installedVersion.includes(latestVersion));
21775
22461
  }
21776
22462
  async function packageVersionFromNode(packageName) {
21777
- return packageVersionFromPath(path15.resolve("node_modules", packageName, "package.json"));
22463
+ return packageVersionFromPath(path17.resolve("node_modules", packageName, "package.json"));
21778
22464
  }
21779
22465
  async function packageVersionFromPath(packageJsonPath) {
21780
22466
  try {
@@ -21801,14 +22487,14 @@ async function npmGlobalBin() {
21801
22487
  return firstLine(result.stdout);
21802
22488
  }
21803
22489
  const prefix = await npmGlobalPrefix();
21804
- return prefix ? path15.join(prefix, "bin") : null;
22490
+ return prefix ? path17.join(prefix, "bin") : null;
21805
22491
  }
21806
22492
  async function npmGlobalPrefix() {
21807
22493
  const result = await runShellCommand("npm prefix -g", 3e3);
21808
22494
  return result.code === 0 ? firstLine(result.stdout) : null;
21809
22495
  }
21810
22496
  async function commandPathFor(command) {
21811
- if (path15.isAbsolute(command)) {
22497
+ if (path17.isAbsolute(command)) {
21812
22498
  return command;
21813
22499
  }
21814
22500
  const result = await runShellCommand(`command -v ${shellQuote(command)}`, 3e3);
@@ -21833,7 +22519,7 @@ function commandFailureMessage(command, result) {
21833
22519
  }
21834
22520
  function runShellCommand(command, timeoutMs = 0) {
21835
22521
  return new Promise((resolve) => {
21836
- const child = spawn2(command, {
22522
+ const child = spawn3(command, {
21837
22523
  shell: true,
21838
22524
  stdio: ["ignore", "pipe", "pipe"]
21839
22525
  });
@@ -22415,8 +23101,8 @@ async function registerSystemRoutes(app) {
22415
23101
  message: "Provider config archives are disabled for this worker."
22416
23102
  });
22417
23103
  }
22418
- const { provider } = providerParamSchema2.parse(request.params);
22419
- return app.services.providerHostConfigService.listArchives(provider);
23104
+ const { provider: provider2 } = providerParamSchema2.parse(request.params);
23105
+ return app.services.providerHostConfigService.listArchives(provider2);
22420
23106
  });
22421
23107
  app.post("/api/config/providers/:provider/archives", async (request) => {
22422
23108
  if (!app.services.config.managementRoutesEnabled) {
@@ -22425,13 +23111,13 @@ async function registerSystemRoutes(app) {
22425
23111
  message: "Provider config archives are disabled for this worker."
22426
23112
  });
22427
23113
  }
22428
- const { provider } = providerParamSchema2.parse(request.params);
23114
+ const { provider: provider2 } = providerParamSchema2.parse(request.params);
22429
23115
  const body = {};
22430
23116
  const parsedBody = createProviderHostConfigArchiveSchema.parse(request.body ?? {});
22431
23117
  if (parsedBody.label !== void 0) {
22432
23118
  body.label = parsedBody.label;
22433
23119
  }
22434
- return app.services.providerHostConfigService.createArchive(provider, body);
23120
+ return app.services.providerHostConfigService.createArchive(provider2, body);
22435
23121
  });
22436
23122
  app.patch("/api/config/providers/:provider/archives/:id", async (request) => {
22437
23123
  if (!app.services.config.managementRoutesEnabled) {
@@ -22480,7 +23166,7 @@ async function registerSystemRoutes(app) {
22480
23166
 
22481
23167
  // src/routes/threads.ts
22482
23168
  import fs17 from "fs/promises";
22483
- import path16 from "path";
23169
+ import path18 from "path";
22484
23170
  import { z as z5 } from "zod";
22485
23171
 
22486
23172
  // src/worker-identity.ts
@@ -22952,7 +23638,7 @@ async function registerThreadRoutes(app) {
22952
23638
  message: "Workspace was not found for this thread."
22953
23639
  });
22954
23640
  }
22955
- const candidatePath = path16.isAbsolute(query.path) ? query.path : path16.resolve(workspace.absPath, query.path);
23641
+ const candidatePath = path18.isAbsolute(query.path) ? query.path : path18.resolve(workspace.absPath, query.path);
22956
23642
  const requestedPath = await fs17.realpath(candidatePath).catch(() => null);
22957
23643
  if (!requestedPath) {
22958
23644
  throw new HttpError(404, {
@@ -22960,8 +23646,8 @@ async function registerThreadRoutes(app) {
22960
23646
  message: "Image file was not found."
22961
23647
  });
22962
23648
  }
22963
- const resolvedWorkspaceRoot = await fs17.realpath(app.services.config.workspaceRoot).catch(() => path16.resolve(app.services.config.workspaceRoot));
22964
- const workspacePrefix = resolvedWorkspaceRoot.endsWith(path16.sep) ? resolvedWorkspaceRoot : `${resolvedWorkspaceRoot}${path16.sep}`;
23649
+ const resolvedWorkspaceRoot = await fs17.realpath(app.services.config.workspaceRoot).catch(() => path18.resolve(app.services.config.workspaceRoot));
23650
+ const workspacePrefix = resolvedWorkspaceRoot.endsWith(path18.sep) ? resolvedWorkspaceRoot : `${resolvedWorkspaceRoot}${path18.sep}`;
22965
23651
  if (requestedPath !== resolvedWorkspaceRoot && !requestedPath.startsWith(workspacePrefix)) {
22966
23652
  throw new HttpError(403, {
22967
23653
  code: "forbidden",
@@ -23157,8 +23843,10 @@ async function registerThreadRoutes(app) {
23157
23843
 
23158
23844
  // src/routes/workspaces.ts
23159
23845
  import fs18 from "fs/promises";
23160
- import path17 from "path";
23161
- import { spawn as spawn3 } from "child_process";
23846
+ import { createReadStream } from "fs";
23847
+ import os4 from "os";
23848
+ import path19 from "path";
23849
+ import { spawn as spawn4 } from "child_process";
23162
23850
  import { Readable } from "stream";
23163
23851
  import { z as z6 } from "zod";
23164
23852
  var createWorkspaceSchema = z6.union([
@@ -23214,6 +23902,8 @@ var workspacePreviewQuerySchema = z6.object({
23214
23902
  });
23215
23903
  var PREVIEW_DEFAULT_LIMIT_BYTES = 5e4;
23216
23904
  var WORKSPACE_UPLOAD_MAX_BYTES = 50 * 1024 * 1024;
23905
+ var WORKSPACE_FOLDER_DOWNLOAD_MAX_BYTES = 100 * 1024 * 1024;
23906
+ var WORKSPACE_FOLDER_DOWNLOAD_MAX_FILES = 300;
23217
23907
  var WORKSPACE_TREE_IGNORED_NAMES = /* @__PURE__ */ new Set([
23218
23908
  ".git",
23219
23909
  "node_modules",
@@ -23243,7 +23933,7 @@ function toWorkspaceFileDto(file) {
23243
23933
  };
23244
23934
  }
23245
23935
  function languageForPath(filePath) {
23246
- const extension = path17.extname(filePath).slice(1).toLowerCase();
23936
+ const extension = path19.extname(filePath).slice(1).toLowerCase();
23247
23937
  switch (extension) {
23248
23938
  case "js":
23249
23939
  case "jsx":
@@ -23289,18 +23979,18 @@ function languageForPath(filePath) {
23289
23979
  }
23290
23980
  }
23291
23981
  function relativeWorkspacePath(rootPath, absPath) {
23292
- const relative = path17.relative(rootPath, absPath);
23293
- return relative === "" ? "" : relative.split(path17.sep).join("/");
23982
+ const relative = path19.relative(rootPath, absPath);
23983
+ return relative === "" ? "" : relative.split(path19.sep).join("/");
23294
23984
  }
23295
23985
  async function resolveWorkspaceItemPath(rootPath, relativePath = "") {
23296
- const candidate = path17.resolve(rootPath, relativePath || ".");
23986
+ const candidate = path19.resolve(rootPath, relativePath || ".");
23297
23987
  const comparable = await assertPathWithinRoot(rootPath, candidate);
23298
23988
  return comparable;
23299
23989
  }
23300
23990
  async function buildWorkspaceTreeNode(rootPath, absPath, depth = 0) {
23301
23991
  const stats = await fs18.stat(absPath);
23302
23992
  const relativePath = relativeWorkspacePath(rootPath, absPath);
23303
- const name = relativePath ? path17.basename(absPath) : path17.basename(rootPath);
23993
+ const name = relativePath ? path19.basename(absPath) : path19.basename(rootPath);
23304
23994
  if (!stats.isDirectory()) {
23305
23995
  return {
23306
23996
  name,
@@ -23335,7 +24025,7 @@ async function buildWorkspaceTreeNode(rootPath, absPath, depth = 0) {
23335
24025
  }).slice(0, 400);
23336
24026
  node.children = (await Promise.all(
23337
24027
  visible.map(async (entry) => {
23338
- const childPath = path17.join(absPath, entry.name);
24028
+ const childPath = path19.join(absPath, entry.name);
23339
24029
  try {
23340
24030
  if (!entry.isDirectory() && !entry.isFile()) {
23341
24031
  return null;
@@ -23359,19 +24049,19 @@ function requireWorkspaceRecord(app, workspaceId) {
23359
24049
  return record;
23360
24050
  }
23361
24051
  function artifactRoot(record) {
23362
- return path17.join(record.absPath, ".remote-codex", "artifacts");
24052
+ return path19.join(record.absPath, ".remote-codex", "artifacts");
23363
24053
  }
23364
24054
  function artifactFilePath(record, artifactId) {
23365
- return path17.join(artifactRoot(record), artifactId, "artifact.bin");
24055
+ return path19.join(artifactRoot(record), artifactId, "artifact.bin");
23366
24056
  }
23367
24057
  function artifactMetadataPath(record, artifactId) {
23368
- return path17.join(artifactRoot(record), artifactId, "metadata.json");
24058
+ return path19.join(artifactRoot(record), artifactId, "metadata.json");
23369
24059
  }
23370
24060
  function safeArtifactFileName(value) {
23371
- return path17.basename(value).replace(/[^a-zA-Z0-9_. -]/g, "_") || "artifact.bin";
24061
+ return path19.basename(value).replace(/[^a-zA-Z0-9_. -]/g, "_") || "artifact.bin";
23372
24062
  }
23373
24063
  function artifactIdFromName(name) {
23374
- const base = path17.basename(name).replace(/[^a-zA-Z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 96);
24064
+ const base = path19.basename(name).replace(/[^a-zA-Z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 96);
23375
24065
  return `${base || "artifact"}-${Date.now().toString(36)}`;
23376
24066
  }
23377
24067
  async function readArtifactMetadata(record, artifactId) {
@@ -23414,7 +24104,7 @@ async function listWorkspaceArtifacts(record) {
23414
24104
  return artifacts.sort((left, right) => right.createdAt.localeCompare(left.createdAt));
23415
24105
  }
23416
24106
  function contentTypeForPath(filePath) {
23417
- switch (path17.extname(filePath).slice(1).toLowerCase()) {
24107
+ switch (path19.extname(filePath).slice(1).toLowerCase()) {
23418
24108
  case "png":
23419
24109
  return "image/png";
23420
24110
  case "jpg":
@@ -23443,8 +24133,135 @@ function contentTypeForPath(filePath) {
23443
24133
  return "application/octet-stream";
23444
24134
  }
23445
24135
  }
24136
+ async function collectFolderZipEntries(rootPath, folderPath) {
24137
+ const folderName = path19.basename(folderPath) || "workspace-folder";
24138
+ const entries = [];
24139
+ let totalBytes = 0;
24140
+ const pending = [folderPath];
24141
+ while (pending.length > 0) {
24142
+ const current = pending.pop();
24143
+ const children = await fs18.readdir(current, { withFileTypes: true });
24144
+ for (const child of children) {
24145
+ const childPath = await resolveWorkspaceItemPath(rootPath, path19.relative(rootPath, path19.join(current, child.name)));
24146
+ if (child.isDirectory()) {
24147
+ pending.push(childPath);
24148
+ continue;
24149
+ }
24150
+ if (!child.isFile()) {
24151
+ continue;
24152
+ }
24153
+ const stats = await fs18.stat(childPath);
24154
+ totalBytes += stats.size;
24155
+ entries.push({
24156
+ absPath: childPath,
24157
+ archivePath: `${folderName}/${relativeWorkspacePath(folderPath, childPath)}`,
24158
+ size: stats.size,
24159
+ updatedAt: stats.mtime
24160
+ });
24161
+ if (entries.length >= WORKSPACE_FOLDER_DOWNLOAD_MAX_FILES) {
24162
+ throw new HttpError(400, {
24163
+ code: "bad_request",
24164
+ message: "Folder downloads must contain fewer than 300 files."
24165
+ });
24166
+ }
24167
+ if (totalBytes >= WORKSPACE_FOLDER_DOWNLOAD_MAX_BYTES) {
24168
+ throw new HttpError(400, {
24169
+ code: "bad_request",
24170
+ message: "Folder downloads must be smaller than 100 MB."
24171
+ });
24172
+ }
24173
+ }
24174
+ }
24175
+ return entries.sort((left, right) => left.archivePath.localeCompare(right.archivePath));
24176
+ }
24177
+ var crc32Table = new Uint32Array(256);
24178
+ for (let index = 0; index < 256; index += 1) {
24179
+ let value = index;
24180
+ for (let bit = 0; bit < 8; bit += 1) {
24181
+ value = value & 1 ? 3988292384 ^ value >>> 1 : value >>> 1;
24182
+ }
24183
+ crc32Table[index] = value >>> 0;
24184
+ }
24185
+ function crc32(buffer) {
24186
+ let value = 4294967295;
24187
+ for (const byte of buffer) {
24188
+ value = crc32Table[(value ^ byte) & 255] ^ value >>> 8;
24189
+ }
24190
+ return (value ^ 4294967295) >>> 0;
24191
+ }
24192
+ function zipDosDateTime(updatedAt) {
24193
+ const year = Math.max(1980, updatedAt.getFullYear());
24194
+ const dosTime = updatedAt.getHours() << 11 | updatedAt.getMinutes() << 5 | Math.floor(updatedAt.getSeconds() / 2);
24195
+ const dosDate = year - 1980 << 9 | updatedAt.getMonth() + 1 << 5 | updatedAt.getDate();
24196
+ return { dosDate, dosTime };
24197
+ }
24198
+ async function createFolderZipFile(rootPath, folderPath) {
24199
+ const entries = await collectFolderZipEntries(rootPath, folderPath);
24200
+ const localParts = [];
24201
+ const centralParts = [];
24202
+ let offset = 0;
24203
+ for (const entry of entries) {
24204
+ const data = await fs18.readFile(entry.absPath);
24205
+ const name = Buffer.from(entry.archivePath.split(path19.sep).join("/"), "utf8");
24206
+ const checksum = crc32(data);
24207
+ const { dosDate, dosTime } = zipDosDateTime(entry.updatedAt);
24208
+ const localHeader = Buffer.alloc(30);
24209
+ localHeader.writeUInt32LE(67324752, 0);
24210
+ localHeader.writeUInt16LE(20, 4);
24211
+ localHeader.writeUInt16LE(0, 6);
24212
+ localHeader.writeUInt16LE(0, 8);
24213
+ localHeader.writeUInt16LE(dosTime, 10);
24214
+ localHeader.writeUInt16LE(dosDate, 12);
24215
+ localHeader.writeUInt32LE(checksum, 14);
24216
+ localHeader.writeUInt32LE(data.length, 18);
24217
+ localHeader.writeUInt32LE(data.length, 22);
24218
+ localHeader.writeUInt16LE(name.length, 26);
24219
+ localHeader.writeUInt16LE(0, 28);
24220
+ localParts.push(localHeader, name, data);
24221
+ const centralHeader = Buffer.alloc(46);
24222
+ centralHeader.writeUInt32LE(33639248, 0);
24223
+ centralHeader.writeUInt16LE(20, 4);
24224
+ centralHeader.writeUInt16LE(20, 6);
24225
+ centralHeader.writeUInt16LE(0, 8);
24226
+ centralHeader.writeUInt16LE(0, 10);
24227
+ centralHeader.writeUInt16LE(dosTime, 12);
24228
+ centralHeader.writeUInt16LE(dosDate, 14);
24229
+ centralHeader.writeUInt32LE(checksum, 16);
24230
+ centralHeader.writeUInt32LE(data.length, 20);
24231
+ centralHeader.writeUInt32LE(data.length, 24);
24232
+ centralHeader.writeUInt16LE(name.length, 28);
24233
+ centralHeader.writeUInt16LE(0, 30);
24234
+ centralHeader.writeUInt16LE(0, 32);
24235
+ centralHeader.writeUInt16LE(0, 34);
24236
+ centralHeader.writeUInt16LE(0, 36);
24237
+ centralHeader.writeUInt32LE(0, 38);
24238
+ centralHeader.writeUInt32LE(offset, 42);
24239
+ centralParts.push(centralHeader, name);
24240
+ offset += localHeader.length + name.length + data.length;
24241
+ }
24242
+ const centralSize = centralParts.reduce((sum, part) => sum + part.length, 0);
24243
+ const endRecord = Buffer.alloc(22);
24244
+ endRecord.writeUInt32LE(101010256, 0);
24245
+ endRecord.writeUInt16LE(0, 4);
24246
+ endRecord.writeUInt16LE(0, 6);
24247
+ endRecord.writeUInt16LE(entries.length, 8);
24248
+ endRecord.writeUInt16LE(entries.length, 10);
24249
+ endRecord.writeUInt32LE(centralSize, 12);
24250
+ endRecord.writeUInt32LE(offset, 16);
24251
+ endRecord.writeUInt16LE(0, 20);
24252
+ const tempDir = await fs18.mkdtemp(path19.join(os4.tmpdir(), "remote-codex-folder-download-"));
24253
+ const zipPath = path19.join(tempDir, `${path19.basename(folderPath) || "workspace-folder"}.zip`);
24254
+ await fs18.writeFile(zipPath, Buffer.concat([...localParts, ...centralParts, endRecord]));
24255
+ return { zipPath, tempDir };
24256
+ }
24257
+ function cleanupTemporaryZip(zipPath, tempDir) {
24258
+ return async () => {
24259
+ await fs18.rm(zipPath, { force: true }).catch(() => void 0);
24260
+ await fs18.rm(tempDir, { recursive: true, force: true }).catch(() => void 0);
24261
+ };
24262
+ }
23446
24263
  function sanitizeUploadFilename(filename) {
23447
- const baseName = path17.basename(filename?.trim() || "upload");
24264
+ const baseName = path19.basename(filename?.trim() || "upload");
23448
24265
  if (!baseName || baseName === "." || baseName === "..") {
23449
24266
  return "upload";
23450
24267
  }
@@ -23456,7 +24273,7 @@ function inferGitRepoName(gitUrl) {
23456
24273
  const normalized = withoutQuery.replace(/[\\/]+$/, "");
23457
24274
  const rawName = normalized.split(/[/:]/).filter(Boolean).at(-1) ?? "";
23458
24275
  const repoName = rawName.endsWith(".git") ? rawName.slice(0, -4) : rawName;
23459
- if (!repoName || repoName === "." || repoName === ".." || repoName.includes(path17.sep)) {
24276
+ if (!repoName || repoName === "." || repoName === ".." || repoName.includes(path19.sep)) {
23460
24277
  throw new HttpError(400, {
23461
24278
  code: "bad_request",
23462
24279
  message: "Unable to infer a target directory from the Git URL."
@@ -23477,7 +24294,7 @@ async function pathExists4(absPath) {
23477
24294
  }
23478
24295
  function cloneRepository(gitUrl, targetPath) {
23479
24296
  return new Promise((resolve, reject) => {
23480
- const child = spawn3("git", ["clone", gitUrl, targetPath], {
24297
+ const child = spawn4("git", ["clone", gitUrl, targetPath], {
23481
24298
  stdio: ["ignore", "ignore", "pipe"]
23482
24299
  });
23483
24300
  let stderr = "";
@@ -23521,7 +24338,7 @@ async function registerWorkspaceRoutes(app) {
23521
24338
  });
23522
24339
  app.get("/api/workspaces/tree", async (request) => {
23523
24340
  const query = treeQuerySchema.parse(request.query);
23524
- const requestedPath = query.path ? path17.resolve(query.path) : app.services.config.workspaceRoot;
24341
+ const requestedPath = query.path ? path19.resolve(query.path) : app.services.config.workspaceRoot;
23525
24342
  const tree = await readWorkspaceTree({
23526
24343
  rootPath: app.services.config.workspaceRoot,
23527
24344
  targetPath: requestedPath,
@@ -23587,7 +24404,7 @@ async function registerWorkspaceRoutes(app) {
23587
24404
  const nextOffset = offset + read.bytesRead;
23588
24405
  return {
23589
24406
  path: relativeWorkspacePath(rootPath, filePath),
23590
- name: path17.basename(filePath),
24407
+ name: path19.basename(filePath),
23591
24408
  content: buffer.subarray(0, read.bytesRead).toString("utf8"),
23592
24409
  language: languageForPath(filePath),
23593
24410
  size: stats.size,
@@ -23621,13 +24438,25 @@ async function registerWorkspaceRoutes(app) {
23621
24438
  const rootPath = await fs18.realpath(record.absPath);
23622
24439
  const itemPath = await resolveWorkspaceItemPath(rootPath, query.path);
23623
24440
  const stats = await fs18.stat(itemPath);
24441
+ if (stats.isDirectory()) {
24442
+ const { zipPath, tempDir } = await createFolderZipFile(rootPath, itemPath);
24443
+ const filename2 = `${path19.basename(itemPath) || "workspace-folder"}.zip`;
24444
+ const cleanup = cleanupTemporaryZip(zipPath, tempDir);
24445
+ reply.raw.once("finish", () => void cleanup());
24446
+ reply.raw.once("close", () => void cleanup());
24447
+ reply.header("content-type", "application/zip").header(
24448
+ "content-disposition",
24449
+ `attachment; filename="${filename2}"; filename*=UTF-8''${encodeURIComponent(filename2)}`
24450
+ );
24451
+ return reply.send(createReadStream(zipPath));
24452
+ }
23624
24453
  if (!stats.isFile()) {
23625
24454
  throw new HttpError(400, {
23626
24455
  code: "bad_request",
23627
- message: "Only file downloads are supported from this endpoint."
24456
+ message: "Only file and folder downloads are supported from this endpoint."
23628
24457
  });
23629
24458
  }
23630
- const filename = path17.basename(itemPath);
24459
+ const filename = path19.basename(itemPath);
23631
24460
  reply.header("content-type", contentTypeForPath(itemPath)).header(
23632
24461
  "content-disposition",
23633
24462
  `attachment; filename="${filename}"; filename*=UTF-8''${encodeURIComponent(filename)}`
@@ -23693,7 +24522,7 @@ async function registerWorkspaceRoutes(app) {
23693
24522
  kind: "file",
23694
24523
  file: {
23695
24524
  path: file.path,
23696
- name: path17.basename(file.path),
24525
+ name: path19.basename(file.path),
23697
24526
  size: file.size
23698
24527
  }
23699
24528
  };
@@ -23735,7 +24564,7 @@ async function registerWorkspaceRoutes(app) {
23735
24564
  message: "Artifact content must not be empty."
23736
24565
  });
23737
24566
  }
23738
- const dir = path17.dirname(artifactFilePath(record, artifactId));
24567
+ const dir = path19.dirname(artifactFilePath(record, artifactId));
23739
24568
  await fs18.mkdir(dir, { recursive: true, mode: 448 });
23740
24569
  const filePath = artifactFilePath(record, artifactId);
23741
24570
  await fs18.writeFile(filePath, content, { flag: "wx" }).catch((error) => {
@@ -23798,7 +24627,7 @@ async function registerWorkspaceRoutes(app) {
23798
24627
  const params = z6.object({ id: z6.string().uuid(), artifactId: workspaceArtifactIdSchema }).parse(request.params);
23799
24628
  const record = requireWorkspaceRecord(app, params.id);
23800
24629
  const artifact = await readArtifactMetadata(record, params.artifactId);
23801
- await fs18.rm(path17.dirname(artifactFilePath(record, params.artifactId)), {
24630
+ await fs18.rm(path19.dirname(artifactFilePath(record, params.artifactId)), {
23802
24631
  recursive: true,
23803
24632
  force: true
23804
24633
  });
@@ -23813,7 +24642,7 @@ async function registerWorkspaceRoutes(app) {
23813
24642
  let validated;
23814
24643
  if ("gitUrl" in body) {
23815
24644
  const repoName = inferGitRepoName(body.gitUrl);
23816
- const targetPath = path17.join(settings.devHome, repoName);
24645
+ const targetPath = path19.join(settings.devHome, repoName);
23817
24646
  if (await pathExists4(targetPath)) {
23818
24647
  throw new HttpError(409, {
23819
24648
  code: "conflict",
@@ -24077,7 +24906,7 @@ async function registerAuthRoutes(app) {
24077
24906
 
24078
24907
  // src/provider-host-config-service.ts
24079
24908
  import fs19 from "fs/promises";
24080
- import path18 from "path";
24909
+ import path20 from "path";
24081
24910
  import { randomUUID as randomUUID4 } from "crypto";
24082
24911
  function providerError(message, statusCode = 404) {
24083
24912
  const error = new Error(message);
@@ -24085,16 +24914,16 @@ function providerError(message, statusCode = 404) {
24085
24914
  return error;
24086
24915
  }
24087
24916
  function resolveProviderHostFilePath(providerHome, name) {
24088
- return path18.join(providerHome, name);
24917
+ return path20.join(providerHome, name);
24089
24918
  }
24090
24919
  function resolveArchiveRoot(providerHome) {
24091
- return path18.join(providerHome, "supervisor-config-archives");
24920
+ return path20.join(providerHome, "supervisor-config-archives");
24092
24921
  }
24093
24922
  function resolveArchiveIndexPath(providerHome) {
24094
- return path18.join(resolveArchiveRoot(providerHome), "index.json");
24923
+ return path20.join(resolveArchiveRoot(providerHome), "index.json");
24095
24924
  }
24096
24925
  function resolveArchivePath(providerHome, archiveId) {
24097
- return path18.join(resolveArchiveRoot(providerHome), archiveId);
24926
+ return path20.join(resolveArchiveRoot(providerHome), archiveId);
24098
24927
  }
24099
24928
  function defaultArchiveLabel(createdAt) {
24100
24929
  return `Backup ${createdAt.replace("T", " ").replace(/\.\d{3}Z$/, " UTC")}`;
@@ -24147,44 +24976,44 @@ var ProviderHostConfigService = class {
24147
24976
  }
24148
24977
  agentRuntimes;
24149
24978
  providerHomes;
24150
- runtime(provider) {
24151
- const runtime = this.agentRuntimes.getOptional(provider);
24979
+ runtime(provider2) {
24980
+ const runtime = this.agentRuntimes.getOptional(provider2);
24152
24981
  if (!runtime) {
24153
- throw providerError(`Agent runtime provider is not configured: ${provider}`);
24982
+ throw providerError(`Agent runtime provider is not configured: ${provider2}`);
24154
24983
  }
24155
24984
  return runtime;
24156
24985
  }
24157
- providerHome(provider) {
24158
- const home = this.providerHomes[provider];
24986
+ providerHome(provider2) {
24987
+ const home = this.providerHomes[provider2];
24159
24988
  if (!home) {
24160
24989
  throw providerError("This backend does not expose host config files.");
24161
24990
  }
24162
24991
  return home;
24163
24992
  }
24164
- hostFileNames(provider) {
24165
- const runtime = this.runtime(provider);
24993
+ hostFileNames(provider2) {
24994
+ const runtime = this.runtime(provider2);
24166
24995
  if (!runtime.capabilities.management.hostConfigFiles) {
24167
24996
  throw providerError("This backend does not expose host config files.");
24168
24997
  }
24169
24998
  return runtime.managementSchema.hostConfigFiles.map((file) => file.name);
24170
24999
  }
24171
- assertHostFile(provider, name) {
24172
- const fileNames = this.hostFileNames(provider);
25000
+ assertHostFile(provider2, name) {
25001
+ const fileNames = this.hostFileNames(provider2);
24173
25002
  if (!fileNames.includes(name)) {
24174
25003
  throw providerError("Host config file is not exposed by this backend.", 400);
24175
25004
  }
24176
25005
  return name;
24177
25006
  }
24178
- archiveFileNames(provider) {
24179
- const runtime = this.runtime(provider);
25007
+ archiveFileNames(provider2) {
25008
+ const runtime = this.runtime(provider2);
24180
25009
  if (!runtime.managementSchema.configArchives) {
24181
25010
  throw providerError("This backend does not support config archives.");
24182
25011
  }
24183
- return this.hostFileNames(provider);
25012
+ return this.hostFileNames(provider2);
24184
25013
  }
24185
- async readFile(provider, name) {
24186
- const providerHome = this.providerHome(provider);
24187
- const fileName = this.assertHostFile(provider, name);
25014
+ async readFile(provider2, name) {
25015
+ const providerHome = this.providerHome(provider2);
25016
+ const fileName = this.assertHostFile(provider2, name);
24188
25017
  const filePath = resolveProviderHostFilePath(providerHome, fileName);
24189
25018
  try {
24190
25019
  const content = await fs19.readFile(filePath, "utf8");
@@ -24206,25 +25035,25 @@ var ProviderHostConfigService = class {
24206
25035
  };
24207
25036
  }
24208
25037
  }
24209
- async updateFile(provider, name, input) {
24210
- const providerHome = this.providerHome(provider);
24211
- const fileName = this.assertHostFile(provider, name);
25038
+ async updateFile(provider2, name, input) {
25039
+ const providerHome = this.providerHome(provider2);
25040
+ const fileName = this.assertHostFile(provider2, name);
24212
25041
  const filePath = resolveProviderHostFilePath(providerHome, fileName);
24213
- await fs19.mkdir(path18.dirname(filePath), { recursive: true });
25042
+ await fs19.mkdir(path20.dirname(filePath), { recursive: true });
24214
25043
  await fs19.writeFile(filePath, input.content, "utf8");
24215
- return this.readFile(provider, fileName);
25044
+ return this.readFile(provider2, fileName);
24216
25045
  }
24217
- async listArchives(provider) {
24218
- const runtime = this.runtime(provider);
25046
+ async listArchives(provider2) {
25047
+ const runtime = this.runtime(provider2);
24219
25048
  if (!runtime.managementSchema.configArchives) {
24220
25049
  return [];
24221
25050
  }
24222
- const index = await readArchiveIndex(this.providerHome(provider));
25051
+ const index = await readArchiveIndex(this.providerHome(provider2));
24223
25052
  return index.archives;
24224
25053
  }
24225
- async createArchive(provider, input) {
24226
- const providerHome = this.providerHome(provider);
24227
- const fileNames = this.archiveFileNames(provider);
25054
+ async createArchive(provider2, input) {
25055
+ const providerHome = this.providerHome(provider2);
25056
+ const fileNames = this.archiveFileNames(provider2);
24228
25057
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
24229
25058
  const id = `${createdAt.replace(/[-:.TZ]/g, "").slice(0, 14)}-${randomUUID4().slice(0, 8)}`;
24230
25059
  const archivePath = resolveArchivePath(providerHome, id);
@@ -24239,13 +25068,13 @@ var ProviderHostConfigService = class {
24239
25068
  );
24240
25069
  await fs19.mkdir(archivePath, { recursive: true });
24241
25070
  for (const name of fileNames) {
24242
- const hostFile = await this.readFile(provider, name);
25071
+ const hostFile = await this.readFile(provider2, name);
24243
25072
  files[name] = {
24244
25073
  name,
24245
25074
  exists: hostFile.exists
24246
25075
  };
24247
25076
  if (hostFile.exists) {
24248
- await fs19.writeFile(path18.join(archivePath, name), hostFile.content, "utf8");
25077
+ await fs19.writeFile(path20.join(archivePath, name), hostFile.content, "utf8");
24249
25078
  }
24250
25079
  }
24251
25080
  const archive = {
@@ -24261,9 +25090,9 @@ var ProviderHostConfigService = class {
24261
25090
  });
24262
25091
  return archive;
24263
25092
  }
24264
- async renameArchive(provider, id, input) {
24265
- const providerHome = this.providerHome(provider);
24266
- this.archiveFileNames(provider);
25093
+ async renameArchive(provider2, id, input) {
25094
+ const providerHome = this.providerHome(provider2);
25095
+ this.archiveFileNames(provider2);
24267
25096
  const { index, archive } = await findArchiveOrThrow(providerHome, id);
24268
25097
  const updated = {
24269
25098
  ...archive,
@@ -24275,17 +25104,17 @@ var ProviderHostConfigService = class {
24275
25104
  });
24276
25105
  return updated;
24277
25106
  }
24278
- async applyArchive(provider, id) {
24279
- const runtime = this.runtime(provider);
24280
- const providerHome = this.providerHome(provider);
24281
- const fileNames = this.archiveFileNames(provider);
25107
+ async applyArchive(provider2, id) {
25108
+ const runtime = this.runtime(provider2);
25109
+ const providerHome = this.providerHome(provider2);
25110
+ const fileNames = this.archiveFileNames(provider2);
24282
25111
  const { archive } = await findArchiveOrThrow(providerHome, id);
24283
25112
  const archivePath = resolveArchivePath(providerHome, archive.id);
24284
25113
  await fs19.mkdir(providerHome, { recursive: true });
24285
25114
  for (const name of fileNames) {
24286
25115
  const hostPath = resolveProviderHostFilePath(providerHome, name);
24287
25116
  if (archive.files[name]?.exists) {
24288
- const content = await fs19.readFile(path18.join(archivePath, name), "utf8");
25117
+ const content = await fs19.readFile(path20.join(archivePath, name), "utf8");
24289
25118
  await fs19.writeFile(hostPath, content, "utf8");
24290
25119
  } else {
24291
25120
  await fs19.rm(hostPath, { force: true });
@@ -24305,8 +25134,8 @@ import fs21 from "fs/promises";
24305
25134
 
24306
25135
  // src/shell/shell-prompt.ts
24307
25136
  import fs20 from "fs/promises";
24308
- import os3 from "os";
24309
- import path19 from "path";
25137
+ import os5 from "os";
25138
+ import path21 from "path";
24310
25139
  function basenameFromPath2(filePath) {
24311
25140
  if (!filePath) {
24312
25141
  return "";
@@ -24315,7 +25144,7 @@ function basenameFromPath2(filePath) {
24315
25144
  if (!normalized) {
24316
25145
  return "";
24317
25146
  }
24318
- return path19.basename(normalized) || normalized;
25147
+ return path21.basename(normalized) || normalized;
24319
25148
  }
24320
25149
  function isInteractiveShellCommand(command) {
24321
25150
  const normalized = (command ?? "").trim().toLowerCase();
@@ -24476,8 +25305,8 @@ function buildShellPromptInitScriptContents(command) {
24476
25305
  async function ensureShellPromptInitScript(command) {
24477
25306
  const normalized = command.trim().toLowerCase();
24478
25307
  const extension = normalized === "zsh" ? "zsh" : "sh";
24479
- const filePath = path19.join(
24480
- os3.tmpdir(),
25308
+ const filePath = path21.join(
25309
+ os5.tmpdir(),
24481
25310
  `remote-codex-shell-prompt.${extension}`
24482
25311
  );
24483
25312
  await fs20.writeFile(filePath, buildShellPromptInitScriptContents(command), "utf8");
@@ -25159,7 +25988,7 @@ var builtinPlugins = [
25159
25988
 
25160
25989
  // src/plugins/plugin-service.ts
25161
25990
  import fs22 from "fs/promises";
25162
- import path20 from "path";
25991
+ import path22 from "path";
25163
25992
  var MANAGED_CODEX_MCP_BEGIN = "# BEGIN remote-codex managed plugin MCP servers";
25164
25993
  var MANAGED_CODEX_MCP_END = "# END remote-codex managed plugin MCP servers";
25165
25994
  var REMOTE_CODEX_MOLECULE_MCP_TOOL_NAME = "remote_codex_render_molecule";
@@ -25171,7 +26000,7 @@ function normalizeManagedCommand(server, repoRoot) {
25171
26000
  if (server.name === "remote_codex_plugins") {
25172
26001
  return {
25173
26002
  command: process.execPath,
25174
- args: [path20.join(repoRoot, "bin", "remote-codex-plugin-mcp.mjs")]
26003
+ args: [path22.join(repoRoot, "bin", "remote-codex-plugin-mcp.mjs")]
25175
26004
  };
25176
26005
  }
25177
26006
  return {
@@ -25358,7 +26187,7 @@ var PluginService = class {
25358
26187
  if (!input.codexHome) {
25359
26188
  return;
25360
26189
  }
25361
- const configPath = path20.join(input.codexHome, "config.toml");
26190
+ const configPath = path22.join(input.codexHome, "config.toml");
25362
26191
  let current = "";
25363
26192
  try {
25364
26193
  current = await fs22.readFile(configPath, "utf8");
@@ -25376,7 +26205,7 @@ var PluginService = class {
25376
26205
  if (next === current) {
25377
26206
  return;
25378
26207
  }
25379
- await fs22.mkdir(path20.dirname(configPath), { recursive: true });
26208
+ await fs22.mkdir(path22.dirname(configPath), { recursive: true });
25380
26209
  await fs22.writeFile(configPath, next, "utf8");
25381
26210
  }
25382
26211
  async importPlugin(input) {
@@ -25617,7 +26446,7 @@ var PluginSettingsStore = class {
25617
26446
 
25618
26447
  // src/worker-bootstrap.ts
25619
26448
  import fs23 from "fs/promises";
25620
- import path21 from "path";
26449
+ import path23 from "path";
25621
26450
  function trimTrailingSlash(value) {
25622
26451
  return value.replace(/\/+$/, "");
25623
26452
  }
@@ -25628,7 +26457,7 @@ function tomlString(value) {
25628
26457
  return JSON.stringify(value);
25629
26458
  }
25630
26459
  async function writePrivateFile(filePath, content) {
25631
- await fs23.mkdir(path21.dirname(filePath), { recursive: true, mode: 448 });
26460
+ await fs23.mkdir(path23.dirname(filePath), { recursive: true, mode: 448 });
25632
26461
  await fs23.writeFile(filePath, content, { encoding: "utf8", mode: 384 });
25633
26462
  await fs23.chmod(filePath, 384);
25634
26463
  }
@@ -25644,7 +26473,7 @@ async function configureWorkerProviderGateway(config) {
25644
26473
  process.env.ANTHROPIC_BASE_URL = anthropicBaseUrl;
25645
26474
  if (config.agentProviders.codex.enabled) {
25646
26475
  await writePrivateFile(
25647
- path21.join(config.agentProviders.codex.home, "config.toml"),
26476
+ path23.join(config.agentProviders.codex.home, "config.toml"),
25648
26477
  [
25649
26478
  'model_provider = "sub2api"',
25650
26479
  'forced_login_method = "api"',
@@ -25660,7 +26489,7 @@ async function configureWorkerProviderGateway(config) {
25660
26489
  ].join("\n")
25661
26490
  );
25662
26491
  await writePrivateFile(
25663
- path21.join(config.agentProviders.codex.home, "auth.json"),
26492
+ path23.join(config.agentProviders.codex.home, "auth.json"),
25664
26493
  `${JSON.stringify(
25665
26494
  {
25666
26495
  OPENAI_API_KEY: config.llmGatewayToken
@@ -25673,7 +26502,7 @@ async function configureWorkerProviderGateway(config) {
25673
26502
  }
25674
26503
  if (config.agentProviders.claude.enabled) {
25675
26504
  await writePrivateFile(
25676
- path21.join(config.agentProviders.claude.home, "settings.json"),
26505
+ path23.join(config.agentProviders.claude.home, "settings.json"),
25677
26506
  `${JSON.stringify(
25678
26507
  {
25679
26508
  env: {
@@ -25689,7 +26518,7 @@ async function configureWorkerProviderGateway(config) {
25689
26518
  }
25690
26519
  if (config.agentProviders.opencode.enabled) {
25691
26520
  await writePrivateFile(
25692
- path21.join(config.agentProviders.opencode.home, "opencode.json"),
26521
+ path23.join(config.agentProviders.opencode.home, "opencode.json"),
25693
26522
  `${JSON.stringify(
25694
26523
  {
25695
26524
  provider: {
@@ -25736,8 +26565,8 @@ var BackendPluginHost = class {
25736
26565
  };
25737
26566
 
25738
26567
  // src/shell/pty-shell-backend.ts
25739
- import path22 from "path";
25740
- import { spawn as spawn4 } from "@homebridge/node-pty-prebuilt-multiarch";
26568
+ import path24 from "path";
26569
+ import { spawn as spawn5 } from "@homebridge/node-pty-prebuilt-multiarch";
25741
26570
 
25742
26571
  // src/shell/default-shell.ts
25743
26572
  import fs24 from "fs";
@@ -25756,7 +26585,7 @@ function resolveDefaultShell(env = process.env) {
25756
26585
  var MAX_SCROLLBACK_BYTES = 512 * 1024;
25757
26586
  var ANSI_ESCAPE_PATTERN = new RegExp(String.raw`\u001B\[[0-?]*[ -/]*[@-~]`, "g");
25758
26587
  function shellArgs(shell) {
25759
- const shellName = path22.basename(shell).toLowerCase();
26588
+ const shellName = path24.basename(shell).toLowerCase();
25760
26589
  if (process.platform === "win32") {
25761
26590
  return [];
25762
26591
  }
@@ -25778,7 +26607,7 @@ function lastVisibleLine(snapshot) {
25778
26607
  }
25779
26608
  function inferRuntime(session) {
25780
26609
  const promptLine = lastVisibleLine(session.scrollback);
25781
- const shell = path22.basename(session.shell);
26610
+ const shell = path24.basename(session.shell);
25782
26611
  const isCommandRunning = session.exitCode !== null ? false : !/[$#>]\s*$/.test(promptLine.trimEnd());
25783
26612
  return {
25784
26613
  panePid: session.pty.pid,
@@ -25806,7 +26635,7 @@ var PtyShellBackend = class {
25806
26635
  if (this.sessions.has(input.sessionId)) {
25807
26636
  return;
25808
26637
  }
25809
- const pty = spawn4(this.shell, shellArgs(this.shell), {
26638
+ const pty = spawn5(this.shell, shellArgs(this.shell), {
25810
26639
  name: "xterm-256color",
25811
26640
  cwd: input.cwd,
25812
26641
  cols: input.cols ?? 120,
@@ -25925,7 +26754,7 @@ var PtyShellBackend = class {
25925
26754
 
25926
26755
  // src/shell/tmux-manager.ts
25927
26756
  import fs25 from "fs";
25928
- import path23 from "path";
26757
+ import path25 from "path";
25929
26758
  import { spawn as spawnChild } from "child_process";
25930
26759
  async function defaultExecCommand(command, args) {
25931
26760
  return await new Promise((resolve, reject) => {
@@ -25952,16 +26781,16 @@ async function defaultExecCommand(command, args) {
25952
26781
  });
25953
26782
  }
25954
26783
  function resolveExecutablePath(command) {
25955
- if (command.includes(path23.sep)) {
26784
+ if (command.includes(path25.sep)) {
25956
26785
  return command;
25957
26786
  }
25958
26787
  const searchPath = process.env.PATH ?? "";
25959
- for (const entry of searchPath.split(path23.delimiter)) {
26788
+ for (const entry of searchPath.split(path25.delimiter)) {
25960
26789
  const trimmed = entry.trim();
25961
26790
  if (!trimmed) {
25962
26791
  continue;
25963
26792
  }
25964
- const candidate = path23.join(trimmed, command);
26793
+ const candidate = path25.join(trimmed, command);
25965
26794
  if (fs25.existsSync(candidate)) {
25966
26795
  return candidate;
25967
26796
  }
@@ -26720,22 +27549,22 @@ function payloadItems(payload, fields) {
26720
27549
  const record = recordFrom(payload);
26721
27550
  return arrayField(record, fields) ?? (record ? [record] : []);
26722
27551
  }
26723
- function artifactPreviewKind(type, format, path25) {
27552
+ function artifactPreviewKind(type, format, path27) {
26724
27553
  const candidates = [
26725
27554
  type,
26726
27555
  format,
26727
- path25?.split(".").pop() ?? null
27556
+ path27?.split(".").pop() ?? null
26728
27557
  ].map((value) => value?.trim().toLowerCase()).filter(Boolean);
26729
27558
  return candidates.some((value) => MOLECULE_ARTIFACT_TYPES.has(value)) ? "molecule" : "file";
26730
27559
  }
26731
27560
  function normalizeArtifactRef(value) {
26732
27561
  const record = recordFrom(value);
26733
- const path25 = stringField3(record, ["path", "filePath", "file_path", "filename", "fileName", "name"]);
27562
+ const path27 = stringField3(record, ["path", "filePath", "file_path", "filename", "fileName", "name"]);
26734
27563
  const type = stringField3(record, ["type", "artifactType", "artifact_type", "format", "extension"]);
26735
- const title = stringField3(record, ["title", "label", "name", "filename", "fileName"]) ?? path25 ?? "artifact";
27564
+ const title = stringField3(record, ["title", "label", "name", "filename", "fileName"]) ?? path27 ?? "artifact";
26736
27565
  return {
26737
27566
  title,
26738
- path: path25,
27567
+ path: path27,
26739
27568
  type,
26740
27569
  downloadUrl: stringField3(record, ["downloadUrl", "download_url", "url", "href"])
26741
27570
  };
@@ -26769,21 +27598,21 @@ function normalizeArtifact(module, runId, value) {
26769
27598
  if (!record) {
26770
27599
  return null;
26771
27600
  }
26772
- const path25 = stringField3(record, ["path", "filePath", "file_path", "filename", "fileName", "name"]);
27601
+ const path27 = stringField3(record, ["path", "filePath", "file_path", "filename", "fileName", "name"]);
26773
27602
  const type = stringField3(record, ["type", "artifactType", "artifact_type"]);
26774
27603
  const format = stringField3(record, ["format", "fileFormat", "file_format", "extension"]) ?? type;
26775
- const title = stringField3(record, ["title", "label", "name", "filename", "fileName"]) ?? path25 ?? `${module} artifact`;
27604
+ const title = stringField3(record, ["title", "label", "name", "filename", "fileName"]) ?? path27 ?? `${module} artifact`;
26776
27605
  return {
26777
27606
  module,
26778
27607
  runId,
26779
27608
  title,
26780
- path: path25,
27609
+ path: path27,
26781
27610
  type,
26782
27611
  format,
26783
27612
  mimeType: stringField3(record, ["mimeType", "mime_type", "contentType", "content_type"]),
26784
27613
  sizeBytes: numberField2(record, ["sizeBytes", "size_bytes", "bytes"]),
26785
27614
  downloadUrl: stringField3(record, ["downloadUrl", "download_url", "url", "href"]),
26786
- previewKind: artifactPreviewKind(type, format, path25)
27615
+ previewKind: artifactPreviewKind(type, format, path27)
26787
27616
  };
26788
27617
  }
26789
27618
  function normalizeRuns(module, result) {
@@ -26910,9 +27739,9 @@ var WorkerHarnessClient = class {
26910
27739
  }
26911
27740
  return { baseUrl, apiKey };
26912
27741
  }
26913
- async fetchText(path25) {
27742
+ async fetchText(path27) {
26914
27743
  const config = this.requireHarnessConfig();
26915
- const response = await this.fetchImpl(`${config.baseUrl}${path25}`, {
27744
+ const response = await this.fetchImpl(`${config.baseUrl}${path27}`, {
26916
27745
  headers: {
26917
27746
  "x-api-key": config.apiKey
26918
27747
  }
@@ -26923,11 +27752,11 @@ var WorkerHarnessClient = class {
26923
27752
  }
26924
27753
  return { text: text2 };
26925
27754
  }
26926
- async fetchPayload(path25, init = {}) {
27755
+ async fetchPayload(path27, init = {}) {
26927
27756
  const config = this.requireHarnessConfig();
26928
27757
  const headers = new Headers(init.headers);
26929
27758
  headers.set("x-api-key", config.apiKey);
26930
- const response = await this.fetchImpl(`${config.baseUrl}${path25}`, {
27759
+ const response = await this.fetchImpl(`${config.baseUrl}${path27}`, {
26931
27760
  ...init,
26932
27761
  headers
26933
27762
  });
@@ -26941,9 +27770,9 @@ var WorkerHarnessClient = class {
26941
27770
  return { text: text2 };
26942
27771
  }
26943
27772
  }
26944
- async fetchBinary(path25) {
27773
+ async fetchBinary(path27) {
26945
27774
  const config = this.requireHarnessConfig();
26946
- const response = await this.fetchImpl(`${config.baseUrl}${path25}`, {
27775
+ const response = await this.fetchImpl(`${config.baseUrl}${path27}`, {
26947
27776
  headers: {
26948
27777
  "x-api-key": config.apiKey
26949
27778
  }
@@ -27488,16 +28317,16 @@ var HttpError = class extends Error {
27488
28317
  };
27489
28318
  function findRepoRoot(start = process.cwd()) {
27490
28319
  if (process.env.REMOTE_CODEX_REPO_ROOT) {
27491
- return path24.resolve(process.env.REMOTE_CODEX_REPO_ROOT);
28320
+ return path26.resolve(process.env.REMOTE_CODEX_REPO_ROOT);
27492
28321
  }
27493
- let current = path24.resolve(start);
27494
- while (current !== path24.dirname(current)) {
27495
- if (fs26.existsSync(path24.join(current, "pnpm-workspace.yaml")) && fs26.existsSync(path24.join(current, "scripts", "service-restart.mjs"))) {
28322
+ let current = path26.resolve(start);
28323
+ while (current !== path26.dirname(current)) {
28324
+ if (fs26.existsSync(path26.join(current, "pnpm-workspace.yaml")) && fs26.existsSync(path26.join(current, "scripts", "service-restart.mjs"))) {
27496
28325
  return current;
27497
28326
  }
27498
- current = path24.dirname(current);
28327
+ current = path26.dirname(current);
27499
28328
  }
27500
- return path24.resolve(process.cwd());
28329
+ return path26.resolve(process.cwd());
27501
28330
  }
27502
28331
  function createServiceLifecycle() {
27503
28332
  return {
@@ -27509,14 +28338,14 @@ function createServiceLifecycle() {
27509
28338
  });
27510
28339
  }
27511
28340
  const repoRoot = findRepoRoot();
27512
- const restartScript = path24.join(repoRoot, "scripts", "service-restart.mjs");
27513
- if (!fs26.existsSync(restartScript) || !fs26.existsSync(path24.join(repoRoot, "pnpm-workspace.yaml"))) {
28341
+ const restartScript = path26.join(repoRoot, "scripts", "service-restart.mjs");
28342
+ if (!fs26.existsSync(restartScript) || !fs26.existsSync(path26.join(repoRoot, "pnpm-workspace.yaml"))) {
27514
28343
  throw new HttpError(503, {
27515
28344
  code: "service_unavailable",
27516
28345
  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."
27517
28346
  });
27518
28347
  }
27519
- const child = spawn5(process.execPath, [restartScript, "launch"], {
28348
+ const child = spawn6(process.execPath, [restartScript, "launch"], {
27520
28349
  cwd: repoRoot,
27521
28350
  detached: true,
27522
28351
  env: process.env,
@@ -27652,9 +28481,10 @@ function buildApp(options = {}) {
27652
28481
  });
27653
28482
  },
27654
28483
  wsHandler: (socket, request) => {
28484
+ const supervisorSocket = socket;
27655
28485
  const session = authService.verifyRequest(request);
27656
28486
  if (!session.authenticated) {
27657
- socket.close(1008, "Authentication is required.");
28487
+ supervisorSocket.close(1008, "Authentication is required.");
27658
28488
  return;
27659
28489
  }
27660
28490
  const supervisorSession = createSupervisorSocketSession({
@@ -27662,15 +28492,15 @@ function buildApp(options = {}) {
27662
28492
  eventBus,
27663
28493
  backendPluginHost,
27664
28494
  send(message) {
27665
- if (socket.readyState === 1) {
27666
- socket.send(JSON.stringify(message));
28495
+ if (supervisorSocket.readyState === 1) {
28496
+ supervisorSocket.send(JSON.stringify(message));
27667
28497
  }
27668
28498
  }
27669
28499
  });
27670
- socket.on("message", async (rawMessage) => {
28500
+ supervisorSocket.on("message", async (rawMessage) => {
27671
28501
  await supervisorSession.handleMessage(rawMessage.toString());
27672
28502
  });
27673
- socket.on("close", () => {
28503
+ supervisorSocket.on("close", () => {
27674
28504
  supervisorSession.close();
27675
28505
  });
27676
28506
  }
@@ -27829,13 +28659,39 @@ function createRelayRequestHandler(app) {
27829
28659
  },
27830
28660
  ...payload !== void 0 ? { payload } : {}
27831
28661
  });
28662
+ const responseBody = relayResponseBody(response);
27832
28663
  return {
27833
28664
  statusCode: response.statusCode,
27834
28665
  headers: relayResponseHeaders(response.headers),
27835
- body: response.body
28666
+ body: responseBody.body,
28667
+ ...responseBody.bodyEncoding ? { bodyEncoding: responseBody.bodyEncoding } : {}
27836
28668
  };
27837
28669
  };
27838
28670
  }
28671
+ function relayResponseBody(response) {
28672
+ const contentType = responseHeader(response.headers, "content-type");
28673
+ if (isTextRelayResponse(contentType)) {
28674
+ return { body: response.body };
28675
+ }
28676
+ return {
28677
+ body: response.rawPayload.toString("base64"),
28678
+ bodyEncoding: "base64"
28679
+ };
28680
+ }
28681
+ function responseHeader(headers, name) {
28682
+ const lowerName = name.toLowerCase();
28683
+ for (const [key, value] of Object.entries(headers)) {
28684
+ if (key.toLowerCase() !== lowerName || value === void 0) {
28685
+ continue;
28686
+ }
28687
+ return Array.isArray(value) ? value.join(", ") : String(value);
28688
+ }
28689
+ return "";
28690
+ }
28691
+ function isTextRelayResponse(contentType) {
28692
+ const lower = contentType.toLowerCase();
28693
+ return lower.startsWith("text/") || lower.includes("application/json") || lower.includes("+json") || lower.includes("application/javascript") || lower.includes("application/xml") || lower.includes("+xml") || lower.includes("image/svg+xml");
28694
+ }
27839
28695
  function createSupervisorSocketSession(input) {
27840
28696
  const closeHandlers = [];
27841
28697
  const socketState = /* @__PURE__ */ new Map();