remote-codex 0.11.18 → 0.11.19

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.
@@ -9,8 +9,8 @@ import Fastify from "fastify";
9
9
  import multipart from "@fastify/multipart";
10
10
  import websocket from "@fastify/websocket";
11
11
  import { spawn as spawn6 } from "child_process";
12
- import fs26 from "fs";
13
- import path26 from "path";
12
+ import fs28 from "fs";
13
+ import path28 from "path";
14
14
  import { ZodError } from "zod";
15
15
 
16
16
  // ../../packages/config/src/index.ts
@@ -2318,7 +2318,7 @@ Subquery.prototype.getSQL = function() {
2318
2318
  function mapResultRow(columns, row, joinsNotNullableMap) {
2319
2319
  const nullifyMap = {};
2320
2320
  const result = columns.reduce(
2321
- (result2, { path: path27, field }, columnIndex) => {
2321
+ (result2, { path: path29, field }, columnIndex) => {
2322
2322
  let decoder;
2323
2323
  if (is(field, Column)) {
2324
2324
  decoder = field;
@@ -2328,8 +2328,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
2328
2328
  decoder = field.sql.decoder;
2329
2329
  }
2330
2330
  let node = result2;
2331
- for (const [pathChunkIndex, pathChunk] of path27.entries()) {
2332
- if (pathChunkIndex < path27.length - 1) {
2331
+ for (const [pathChunkIndex, pathChunk] of path29.entries()) {
2332
+ if (pathChunkIndex < path29.length - 1) {
2333
2333
  if (!(pathChunk in node)) {
2334
2334
  node[pathChunk] = {};
2335
2335
  }
@@ -2337,8 +2337,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
2337
2337
  } else {
2338
2338
  const rawValue = row[columnIndex];
2339
2339
  const value = node[pathChunk] = rawValue === null ? null : decoder.mapFromDriverValue(rawValue);
2340
- if (joinsNotNullableMap && is(field, Column) && path27.length === 2) {
2341
- const objectName = path27[0];
2340
+ if (joinsNotNullableMap && is(field, Column) && path29.length === 2) {
2341
+ const objectName = path29[0];
2342
2342
  if (!(objectName in nullifyMap)) {
2343
2343
  nullifyMap[objectName] = value === null ? getTableName(field.table) : false;
2344
2344
  } else if (typeof nullifyMap[objectName] === "string" && nullifyMap[objectName] !== getTableName(field.table)) {
@@ -6496,6 +6496,7 @@ var threadTurnMetadata = sqliteTable(
6496
6496
  pricingModelKey: text("pricing_model_key"),
6497
6497
  pricingTierKey: text("pricing_tier_key"),
6498
6498
  tokenUsageJson: text("token_usage_json"),
6499
+ displayPrompt: text("display_prompt"),
6499
6500
  createdAt: text("created_at").notNull(),
6500
6501
  updatedAt: text("updated_at").notNull()
6501
6502
  },
@@ -7119,6 +7120,7 @@ function upsertThreadTurnMetadata(db, input) {
7119
7120
  pricingModelKey: input.pricingModelKey !== void 0 ? input.pricingModelKey : existing.pricingModelKey,
7120
7121
  pricingTierKey: input.pricingTierKey !== void 0 ? input.pricingTierKey : existing.pricingTierKey,
7121
7122
  tokenUsageJson: input.tokenUsageJson !== void 0 ? input.tokenUsageJson : existing.tokenUsageJson,
7123
+ displayPrompt: input.displayPrompt !== void 0 ? input.displayPrompt : existing.displayPrompt,
7122
7124
  updatedAt: now
7123
7125
  }).where(eq(threadTurnMetadata.id, existing.id)).run();
7124
7126
  return;
@@ -7133,6 +7135,7 @@ function upsertThreadTurnMetadata(db, input) {
7133
7135
  pricingModelKey: input.pricingModelKey ?? null,
7134
7136
  pricingTierKey: input.pricingTierKey ?? null,
7135
7137
  tokenUsageJson: input.tokenUsageJson ?? null,
7138
+ displayPrompt: input.displayPrompt ?? null,
7136
7139
  createdAt: now,
7137
7140
  updatedAt: now
7138
7141
  }).run();
@@ -9248,7 +9251,7 @@ function extractFileChangeEntries(item) {
9248
9251
  isRecord4(entry.summary) ? entry.summary : null,
9249
9252
  isRecord4(entry.diff) ? entry.diff : null
9250
9253
  ].filter((candidate) => Boolean(candidate));
9251
- const path27 = uniqueStrings([
9254
+ const path29 = uniqueStrings([
9252
9255
  stringOrNull(valueFromRecords(nestedRecords, ["path", "filePath", "targetPath"])),
9253
9256
  stringOrNull(
9254
9257
  valueFromRecords(nestedRecords, [
@@ -9295,7 +9298,7 @@ function extractFileChangeEntries(item) {
9295
9298
  const diffStats = explicitAdditions === 0 && explicitDeletions === 0 && diffText ? countUnifiedDiffStats(diffText) : null;
9296
9299
  const additions = explicitAdditions || diffStats?.additions || 0;
9297
9300
  const deletions = explicitDeletions || diffStats?.deletions || 0;
9298
- const normalizedPath = path27 ?? (diffText ? projectRelativePathLabel(extractPathFromDiffText(diffText)) : null);
9301
+ const normalizedPath = path29 ?? (diffText ? projectRelativePathLabel(extractPathFromDiffText(diffText)) : null);
9299
9302
  if (!normalizedPath && additions === 0 && deletions === 0) {
9300
9303
  return null;
9301
9304
  }
@@ -13605,14 +13608,14 @@ function displayPath(pathValue, options) {
13605
13608
  }
13606
13609
  return relativePath;
13607
13610
  }
13608
- function toolIsLowInformationPatch(normalized, state, input, patchText, path27, metadataStats) {
13611
+ function toolIsLowInformationPatch(normalized, state, input, patchText, path29, metadataStats) {
13609
13612
  if (normalized !== "applypatch" && normalized !== "patch") {
13610
13613
  return false;
13611
13614
  }
13612
13615
  if (toolStateStatus(state) !== "running") {
13613
13616
  return false;
13614
13617
  }
13615
- if (path27 || patchText || metadataStats || stringValue2(state.output)) {
13618
+ if (path29 || patchText || metadataStats || stringValue2(state.output)) {
13616
13619
  return false;
13617
13620
  }
13618
13621
  return !isRecord9(input) || Object.keys(input).length === 0;
@@ -13655,7 +13658,7 @@ function fileChangeStatsFromMetadata(metadata) {
13655
13658
  if (files.length === 0) {
13656
13659
  return null;
13657
13660
  }
13658
- const paths = files.map((file) => stringValue2(file.filePath) ?? stringValue2(file.path) ?? stringValue2(file.relativePath)).filter((path27) => Boolean(path27));
13661
+ const paths = files.map((file) => stringValue2(file.filePath) ?? stringValue2(file.path) ?? stringValue2(file.relativePath)).filter((path29) => Boolean(path29));
13659
13662
  const addedLines = files.reduce((total, file) => total + (numberValue(file.additions) ?? numberValue(file.addedLines) ?? numberValue(file.added) ?? 0), 0);
13660
13663
  const removedLines = files.reduce((total, file) => total + (numberValue(file.deletions) ?? numberValue(file.removedLines) ?? numberValue(file.removed) ?? 0), 0);
13661
13664
  return {
@@ -13803,28 +13806,28 @@ function mapAssistantTool(messageId2, tool, options) {
13803
13806
  ].includes(normalized)) {
13804
13807
  const metadataStats = fileChangeStatsFromMetadata(state.metadata);
13805
13808
  const patchText = isRecord9(input) ? stringValue2(input.patchText) ?? stringValue2(input.patch) ?? stringValue2(input.diff) : null;
13806
- const path27 = metadataStats?.path ?? filePathFromInput(input) ?? extractPathFromPatchText(patchText);
13807
- if (toolIsLowInformationPatch(normalized, state, input, patchText, path27, metadataStats)) {
13809
+ const path29 = metadataStats?.path ?? filePathFromInput(input) ?? extractPathFromPatchText(patchText);
13810
+ if (toolIsLowInformationPatch(normalized, state, input, patchText, path29, metadataStats)) {
13808
13811
  return null;
13809
13812
  }
13810
13813
  const output = stringValue2(state.output);
13811
13814
  const diffStats = countUnifiedDiffStats2(patchText);
13812
- const displayFilePath = displayPath(path27, options);
13815
+ const displayFilePath = displayPath(path29, options);
13813
13816
  return {
13814
13817
  id,
13815
13818
  kind: "fileChange",
13816
13819
  text: metadataStats ? metadataStats.changedFiles > 1 ? `${metadataStats.changedFiles} changed files` : displayFilePath ?? metadataStats.previewText : displayFilePath ?? output ?? summary ?? name,
13817
13820
  previewText: metadataStats ? metadataStats.changedFiles > 1 ? `${metadataStats.changedFiles} changed files` : displayFilePath ?? metadataStats.previewText : displayFilePath ? `${name}: ${displayFilePath}` : output ?? summary ?? name,
13818
13821
  detailText,
13819
- changedFiles: metadataStats?.changedFiles ?? (path27 ? 1 : null),
13822
+ changedFiles: metadataStats?.changedFiles ?? (path29 ? 1 : null),
13820
13823
  addedLines: metadataStats?.addedLines ?? diffStats?.addedLines ?? null,
13821
13824
  removedLines: metadataStats?.removedLines ?? diffStats?.removedLines ?? null,
13822
13825
  status: toolStateStatus(state)
13823
13826
  };
13824
13827
  }
13825
13828
  if (["read", "grep", "glob", "list", "ls", "bashoutput"].includes(normalized)) {
13826
- const path27 = filePathFromInput(input);
13827
- const text2 = displayPath(path27, options) ?? summary ?? name;
13829
+ const path29 = filePathFromInput(input);
13830
+ const text2 = displayPath(path29, options) ?? summary ?? name;
13828
13831
  return {
13829
13832
  id,
13830
13833
  kind: "fileRead",
@@ -18313,8 +18316,13 @@ function applyLiveAgentMessageOrderingHints(turns, localThreadId, liveState) {
18313
18316
  }
18314
18317
  function buildTurnDto(turn, metadata) {
18315
18318
  const tokenUsage = parseThreadTurnTokenUsageJson(metadata?.tokenUsageJson);
18319
+ const displayPrompt = metadata?.displayPrompt?.trim();
18320
+ const items = displayPrompt && turn.items.some((item) => /\[localImage\]/.test(item.text)) ? turn.items.map(
18321
+ (item) => item.kind === "userMessage" && /\[localImage\]/.test(item.text) ? { ...item, text: displayPrompt } : item
18322
+ ) : turn.items;
18316
18323
  return {
18317
18324
  ...turn,
18325
+ items,
18318
18326
  startedAt: turn.startedAt ?? metadata?.createdAt ?? null,
18319
18327
  model: metadata?.model ?? null,
18320
18328
  reasoningEffort: normalizeReasoningEffort(metadata?.reasoningEffort),
@@ -18824,7 +18832,8 @@ var ThreadPromptTurnCoordinator = class {
18824
18832
  input.effectiveModel
18825
18833
  ),
18826
18834
  pricingModelKey: pricingSnapshot?.pricingModelKey ?? null,
18827
- pricingTierKey: pricingSnapshot?.pricingTierKey ?? null
18835
+ pricingTierKey: pricingSnapshot?.pricingTierKey ?? null,
18836
+ displayPrompt
18828
18837
  });
18829
18838
  const patch = {
18830
18839
  providerTurnId: turn.providerTurnId,
@@ -19443,7 +19452,8 @@ var ThreadHistoryPersistenceCoordinator = class {
19443
19452
  reasoningEffortAvailable: runtimeMetadata.reasoningEffortAvailable ?? null,
19444
19453
  pricingModelKey: runtimeMetadata.pricingModelKey ?? null,
19445
19454
  pricingTierKey: runtimeMetadata.pricingTierKey ?? null,
19446
- tokenUsageJson: runtimeMetadata.tokenUsageJson ?? null
19455
+ tokenUsageJson: runtimeMetadata.tokenUsageJson ?? null,
19456
+ displayPrompt: runtimeMetadata.displayPrompt ?? null
19447
19457
  });
19448
19458
  deleteThreadTurnMetadataByThreadAndTurnId(this.db, localThreadId, runtimeTurnId);
19449
19459
  }
@@ -20650,6 +20660,24 @@ function truncateChromeStderr(value) {
20650
20660
  return value.slice(value.length - MAX_CHROME_STDERR_CHARS);
20651
20661
  }
20652
20662
 
20663
+ // src/thread-turn-metadata.ts
20664
+ function listThreadTurnMetadataMap(db, localThreadId) {
20665
+ return new Map(
20666
+ listThreadTurnMetadataByThreadId(db, localThreadId).map((entry) => [
20667
+ entry.turnId,
20668
+ {
20669
+ model: entry.model ?? null,
20670
+ reasoningEffort: entry.reasoningEffort ?? null,
20671
+ reasoningEffortAvailable: entry.reasoningEffortAvailable ?? null,
20672
+ pricingModelKey: entry.pricingModelKey ?? null,
20673
+ pricingTierKey: normalizePricingTier(entry.pricingTierKey),
20674
+ tokenUsageJson: entry.tokenUsageJson ?? null,
20675
+ createdAt: entry.createdAt ?? null
20676
+ }
20677
+ ])
20678
+ );
20679
+ }
20680
+
20653
20681
  // src/thread-export-coordinator.ts
20654
20682
  function userPromptPreviewFromTurn(turn) {
20655
20683
  const prompt = turn.items.find((item) => item.kind === "userMessage")?.text.trim();
@@ -20743,20 +20771,7 @@ var ThreadExportCoordinator = class {
20743
20771
  });
20744
20772
  }
20745
20773
  this.callbacks.requireProviderSessionId(record);
20746
- const turnMetadataById = new Map(
20747
- listThreadTurnMetadataByThreadId(this.db, localThreadId).map((entry) => [
20748
- entry.turnId,
20749
- {
20750
- model: entry.model ?? null,
20751
- reasoningEffort: entry.reasoningEffort ?? null,
20752
- reasoningEffortAvailable: entry.reasoningEffortAvailable ?? null,
20753
- pricingModelKey: entry.pricingModelKey ?? null,
20754
- pricingTierKey: normalizePricingTier(entry.pricingTierKey),
20755
- tokenUsageJson: entry.tokenUsageJson ?? null,
20756
- createdAt: entry.createdAt ?? null
20757
- }
20758
- ])
20759
- );
20774
+ const turnMetadataById = listThreadTurnMetadataMap(this.db, localThreadId);
20760
20775
  const cachedDetail = await this.detailAssembler.buildCacheEntry({
20761
20776
  localThreadId,
20762
20777
  record,
@@ -20823,20 +20838,7 @@ var ThreadForkCoordinator = class {
20823
20838
  });
20824
20839
  }
20825
20840
  this.callbacks.requireProviderSessionId(record);
20826
- const turnMetadataById = new Map(
20827
- listThreadTurnMetadataByThreadId(this.db, localThreadId).map((entry) => [
20828
- entry.turnId,
20829
- {
20830
- model: entry.model ?? null,
20831
- reasoningEffort: entry.reasoningEffort ?? null,
20832
- reasoningEffortAvailable: entry.reasoningEffortAvailable ?? null,
20833
- pricingModelKey: entry.pricingModelKey ?? null,
20834
- pricingTierKey: normalizePricingTier(entry.pricingTierKey),
20835
- tokenUsageJson: entry.tokenUsageJson ?? null,
20836
- createdAt: entry.createdAt ?? null
20837
- }
20838
- ])
20839
- );
20841
+ const turnMetadataById = listThreadTurnMetadataMap(this.db, localThreadId);
20840
20842
  const cachedDetail = await this.detailAssembler.buildCacheEntry({
20841
20843
  localThreadId,
20842
20844
  record,
@@ -21441,6 +21443,26 @@ var ThreadService = class {
21441
21443
  }
21442
21444
  return record.providerSessionId;
21443
21445
  }
21446
+ requireThreadRecord(localThreadId) {
21447
+ const record = getThreadRecordById(this.db, localThreadId);
21448
+ if (!record) {
21449
+ throw new HttpError(404, {
21450
+ code: "not_found",
21451
+ message: "Thread was not found."
21452
+ });
21453
+ }
21454
+ return record;
21455
+ }
21456
+ requireWorkspaceForThread(record) {
21457
+ const workspace = getWorkspaceRecordById(this.db, record.workspaceId);
21458
+ if (!workspace) {
21459
+ throw new HttpError(404, {
21460
+ code: "not_found",
21461
+ message: "Workspace was not found for this thread."
21462
+ });
21463
+ }
21464
+ return workspace;
21465
+ }
21444
21466
  materializeHiddenRuntimeTurns(localThreadId, turns) {
21445
21467
  for (const turn of this.liveState.hiddenRemoteTurns(localThreadId, turns)) {
21446
21468
  const displayTurnId = this.liveState.displayTurnIdForRuntimeTurn(localThreadId, turn.providerTurnId) ?? turn.providerTurnId;
@@ -21575,37 +21597,12 @@ var ThreadService = class {
21575
21597
  return this.getThreadDetail(localThreadId);
21576
21598
  }
21577
21599
  async getThreadDetail(localThreadId, options = {}) {
21578
- const record = getThreadRecordById(this.db, localThreadId);
21579
- if (!record) {
21580
- throw new HttpError(404, {
21581
- code: "not_found",
21582
- message: "Thread was not found."
21583
- });
21584
- }
21585
- const workspace = getWorkspaceRecordById(this.db, record.workspaceId);
21586
- if (!workspace) {
21587
- throw new HttpError(404, {
21588
- code: "not_found",
21589
- message: "Workspace was not found for this thread."
21590
- });
21591
- }
21600
+ const record = this.requireThreadRecord(localThreadId);
21601
+ const workspace = this.requireWorkspaceForThread(record);
21592
21602
  this.requireProviderSessionId(record);
21593
21603
  const loadedIds = await this.listLoadedProviderSessionIds(record.provider);
21594
21604
  const workspacePathStatus = await pathExists3(workspace.absPath) ? "present" : "missing";
21595
- const turnMetadataById = new Map(
21596
- listThreadTurnMetadataByThreadId(this.db, localThreadId).map((entry) => [
21597
- entry.turnId,
21598
- {
21599
- model: entry.model ?? null,
21600
- reasoningEffort: entry.reasoningEffort ?? null,
21601
- reasoningEffortAvailable: entry.reasoningEffortAvailable ?? null,
21602
- pricingModelKey: entry.pricingModelKey ?? null,
21603
- pricingTierKey: normalizePricingTier(entry.pricingTierKey),
21604
- tokenUsageJson: entry.tokenUsageJson ?? null,
21605
- createdAt: entry.createdAt ?? null
21606
- }
21607
- ])
21608
- );
21605
+ const turnMetadataById = listThreadTurnMetadataMap(this.db, localThreadId);
21609
21606
  const cachedDetail = await this.detailAssembler.buildCacheEntry({
21610
21607
  localThreadId,
21611
21608
  record,
@@ -21663,33 +21660,15 @@ var ThreadService = class {
21663
21660
  return this.exportCoordinator.exportThreadTranscript(localThreadId, input);
21664
21661
  }
21665
21662
  async getThreadGoal(localThreadId) {
21666
- const record = getThreadRecordById(this.db, localThreadId);
21667
- if (!record) {
21668
- throw new HttpError(404, {
21669
- code: "not_found",
21670
- message: "Thread was not found."
21671
- });
21672
- }
21663
+ const record = this.requireThreadRecord(localThreadId);
21673
21664
  return this.goalCoordinator.getThreadGoal(record);
21674
21665
  }
21675
21666
  async updateThreadGoal(localThreadId, input) {
21676
- const record = getThreadRecordById(this.db, localThreadId);
21677
- if (!record) {
21678
- throw new HttpError(404, {
21679
- code: "not_found",
21680
- message: "Thread was not found."
21681
- });
21682
- }
21667
+ const record = this.requireThreadRecord(localThreadId);
21683
21668
  return this.goalCoordinator.updateThreadGoal(record, input);
21684
21669
  }
21685
21670
  async clearThreadGoal(localThreadId) {
21686
- const record = getThreadRecordById(this.db, localThreadId);
21687
- if (!record) {
21688
- throw new HttpError(404, {
21689
- code: "not_found",
21690
- message: "Thread was not found."
21691
- });
21692
- }
21671
+ const record = this.requireThreadRecord(localThreadId);
21693
21672
  return this.goalCoordinator.clearThreadGoal(record);
21694
21673
  }
21695
21674
  async ensureThreadLoadedForProviderOperation(record) {
@@ -21711,28 +21690,9 @@ var ThreadService = class {
21711
21690
  await this.resumeThread(record.id, resumeInput);
21712
21691
  }
21713
21692
  async getThreadHistoryItemDetail(localThreadId, itemId) {
21714
- const record = getThreadRecordById(this.db, localThreadId);
21715
- if (!record) {
21716
- throw new HttpError(404, {
21717
- code: "not_found",
21718
- message: "Thread was not found."
21719
- });
21720
- }
21693
+ const record = this.requireThreadRecord(localThreadId);
21721
21694
  this.requireProviderSessionId(record);
21722
- const turnMetadataById = new Map(
21723
- listThreadTurnMetadataByThreadId(this.db, localThreadId).map((entry) => [
21724
- entry.turnId,
21725
- {
21726
- model: entry.model ?? null,
21727
- reasoningEffort: entry.reasoningEffort ?? null,
21728
- reasoningEffortAvailable: entry.reasoningEffortAvailable ?? null,
21729
- pricingModelKey: entry.pricingModelKey ?? null,
21730
- pricingTierKey: normalizePricingTier(entry.pricingTierKey),
21731
- tokenUsageJson: entry.tokenUsageJson ?? null,
21732
- createdAt: entry.createdAt ?? null
21733
- }
21734
- ])
21735
- );
21695
+ const turnMetadataById = listThreadTurnMetadataMap(this.db, localThreadId);
21736
21696
  const cachedDetail = await this.detailAssembler.buildCacheEntry({
21737
21697
  localThreadId,
21738
21698
  record,
@@ -21763,13 +21723,7 @@ var ThreadService = class {
21763
21723
  return this.getThreadDetail(localThreadId);
21764
21724
  }
21765
21725
  async sendPrompt(localThreadId, input, options = {}) {
21766
- let record = getThreadRecordById(this.db, localThreadId);
21767
- if (!record) {
21768
- throw new HttpError(404, {
21769
- code: "not_found",
21770
- message: "Thread was not found."
21771
- });
21772
- }
21726
+ let record = this.requireThreadRecord(localThreadId);
21773
21727
  await this.importCoordinator.assertImportedThreadReadyForPrompt({
21774
21728
  source: record.source,
21775
21729
  provider: record.provider,
@@ -21869,13 +21823,7 @@ var ThreadService = class {
21869
21823
  });
21870
21824
  }
21871
21825
  async updateThreadSettings(localThreadId, input) {
21872
- const record = getThreadRecordById(this.db, localThreadId);
21873
- if (!record) {
21874
- throw new HttpError(404, {
21875
- code: "not_found",
21876
- message: "Thread was not found."
21877
- });
21878
- }
21826
+ const record = this.requireThreadRecord(localThreadId);
21879
21827
  const nextSettings = await this.sessionCoordinator.resolveThreadSettings({
21880
21828
  provider: record.provider,
21881
21829
  currentModel: record.model,
@@ -21916,13 +21864,7 @@ var ThreadService = class {
21916
21864
  return this.toThreadDto(updated, loadedIds);
21917
21865
  }
21918
21866
  async updateThreadTitle(localThreadId, title) {
21919
- const record = getThreadRecordById(this.db, localThreadId);
21920
- if (!record) {
21921
- throw new HttpError(404, {
21922
- code: "not_found",
21923
- message: "Thread was not found."
21924
- });
21925
- }
21867
+ const record = this.requireThreadRecord(localThreadId);
21926
21868
  const normalizedTitle = title.trim();
21927
21869
  if (!normalizedTitle) {
21928
21870
  throw new HttpError(400, {
@@ -21941,13 +21883,7 @@ var ThreadService = class {
21941
21883
  return this.toThreadDto(updated, loadedIds);
21942
21884
  }
21943
21885
  async compactThread(localThreadId) {
21944
- const record = getThreadRecordById(this.db, localThreadId);
21945
- if (!record) {
21946
- throw new HttpError(404, {
21947
- code: "not_found",
21948
- message: "Thread was not found."
21949
- });
21950
- }
21886
+ const record = this.requireThreadRecord(localThreadId);
21951
21887
  const providerSessionId = this.requireProviderSessionId(record);
21952
21888
  if (record.isConnected === false) {
21953
21889
  throw new HttpError(409, {
@@ -21982,72 +21918,30 @@ var ThreadService = class {
21982
21918
  return this.forkCoordinator.forkThread(localThreadId, input);
21983
21919
  }
21984
21920
  async listThreadSkills(localThreadId) {
21985
- const record = getThreadRecordById(this.db, localThreadId);
21986
- if (!record) {
21987
- throw new HttpError(404, {
21988
- code: "not_found",
21989
- message: "Thread was not found."
21990
- });
21991
- }
21992
- const workspace = getWorkspaceRecordById(this.db, record.workspaceId);
21993
- if (!workspace) {
21994
- throw new HttpError(404, {
21995
- code: "not_found",
21996
- message: "Workspace was not found for this thread."
21997
- });
21998
- }
21921
+ const record = this.requireThreadRecord(localThreadId);
21922
+ const workspace = this.requireWorkspaceForThread(record);
21999
21923
  return this.managementCoordinator.listThreadSkills({
22000
21924
  provider: record.provider,
22001
21925
  workspacePath: workspace.absPath
22002
21926
  });
22003
21927
  }
22004
21928
  async listThreadMcpServers(localThreadId) {
22005
- const record = getThreadRecordById(this.db, localThreadId);
22006
- if (!record) {
22007
- throw new HttpError(404, {
22008
- code: "not_found",
22009
- message: "Thread was not found."
22010
- });
22011
- }
21929
+ const record = this.requireThreadRecord(localThreadId);
22012
21930
  return this.managementCoordinator.listThreadMcpServers({
22013
21931
  provider: record.provider
22014
21932
  });
22015
21933
  }
22016
21934
  async listThreadHooks(localThreadId) {
22017
- const record = getThreadRecordById(this.db, localThreadId);
22018
- if (!record) {
22019
- throw new HttpError(404, {
22020
- code: "not_found",
22021
- message: "Thread was not found."
22022
- });
22023
- }
22024
- const workspace = getWorkspaceRecordById(this.db, record.workspaceId);
22025
- if (!workspace) {
22026
- throw new HttpError(404, {
22027
- code: "not_found",
22028
- message: "Workspace was not found for this thread."
22029
- });
22030
- }
21935
+ const record = this.requireThreadRecord(localThreadId);
21936
+ const workspace = this.requireWorkspaceForThread(record);
22031
21937
  return this.managementCoordinator.listThreadHooks({
22032
21938
  provider: record.provider,
22033
21939
  workspacePath: workspace.absPath
22034
21940
  });
22035
21941
  }
22036
21942
  async createThreadHook(localThreadId, input) {
22037
- const record = getThreadRecordById(this.db, localThreadId);
22038
- if (!record) {
22039
- throw new HttpError(404, {
22040
- code: "not_found",
22041
- message: "Thread was not found."
22042
- });
22043
- }
22044
- const workspace = getWorkspaceRecordById(this.db, record.workspaceId);
22045
- if (!workspace) {
22046
- throw new HttpError(404, {
22047
- code: "not_found",
22048
- message: "Workspace was not found for this thread."
22049
- });
22050
- }
21943
+ const record = this.requireThreadRecord(localThreadId);
21944
+ const workspace = this.requireWorkspaceForThread(record);
22051
21945
  return this.managementCoordinator.createThreadHook({
22052
21946
  provider: record.provider,
22053
21947
  workspacePath: workspace.absPath,
@@ -22055,20 +21949,8 @@ var ThreadService = class {
22055
21949
  });
22056
21950
  }
22057
21951
  async updateThreadHook(localThreadId, input) {
22058
- const record = getThreadRecordById(this.db, localThreadId);
22059
- if (!record) {
22060
- throw new HttpError(404, {
22061
- code: "not_found",
22062
- message: "Thread was not found."
22063
- });
22064
- }
22065
- const workspace = getWorkspaceRecordById(this.db, record.workspaceId);
22066
- if (!workspace) {
22067
- throw new HttpError(404, {
22068
- code: "not_found",
22069
- message: "Workspace was not found for this thread."
22070
- });
22071
- }
21952
+ const record = this.requireThreadRecord(localThreadId);
21953
+ const workspace = this.requireWorkspaceForThread(record);
22072
21954
  return this.managementCoordinator.updateThreadHook({
22073
21955
  provider: record.provider,
22074
21956
  workspacePath: workspace.absPath,
@@ -22076,20 +21958,8 @@ var ThreadService = class {
22076
21958
  });
22077
21959
  }
22078
21960
  async trustThreadHook(localThreadId, input) {
22079
- const record = getThreadRecordById(this.db, localThreadId);
22080
- if (!record) {
22081
- throw new HttpError(404, {
22082
- code: "not_found",
22083
- message: "Thread was not found."
22084
- });
22085
- }
22086
- const workspace = getWorkspaceRecordById(this.db, record.workspaceId);
22087
- if (!workspace) {
22088
- throw new HttpError(404, {
22089
- code: "not_found",
22090
- message: "Workspace was not found for this thread."
22091
- });
22092
- }
21961
+ const record = this.requireThreadRecord(localThreadId);
21962
+ const workspace = this.requireWorkspaceForThread(record);
22093
21963
  return this.managementCoordinator.trustThreadHook({
22094
21964
  provider: record.provider,
22095
21965
  workspacePath: workspace.absPath,
@@ -22097,20 +21967,8 @@ var ThreadService = class {
22097
21967
  });
22098
21968
  }
22099
21969
  async untrustThreadHook(localThreadId, input) {
22100
- const record = getThreadRecordById(this.db, localThreadId);
22101
- if (!record) {
22102
- throw new HttpError(404, {
22103
- code: "not_found",
22104
- message: "Thread was not found."
22105
- });
22106
- }
22107
- const workspace = getWorkspaceRecordById(this.db, record.workspaceId);
22108
- if (!workspace) {
22109
- throw new HttpError(404, {
22110
- code: "not_found",
22111
- message: "Workspace was not found for this thread."
22112
- });
22113
- }
21970
+ const record = this.requireThreadRecord(localThreadId);
21971
+ const workspace = this.requireWorkspaceForThread(record);
22114
21972
  return this.managementCoordinator.untrustThreadHook({
22115
21973
  provider: record.provider,
22116
21974
  workspacePath: workspace.absPath,
@@ -22118,13 +21976,7 @@ var ThreadService = class {
22118
21976
  });
22119
21977
  }
22120
21978
  async interruptThread(localThreadId, requestedTurnId) {
22121
- const record = getThreadRecordById(this.db, localThreadId);
22122
- if (!record) {
22123
- throw new HttpError(404, {
22124
- code: "not_found",
22125
- message: "Thread was not found."
22126
- });
22127
- }
21979
+ const record = this.requireThreadRecord(localThreadId);
22128
21980
  const providerSessionId = this.requireProviderSessionId(record);
22129
21981
  const interruptInput = {
22130
21982
  provider: record.provider,
@@ -22152,13 +22004,7 @@ var ThreadService = class {
22152
22004
  return this.deletionCoordinator.deleteThread(localThreadId);
22153
22005
  }
22154
22006
  async respondToRequest(localThreadId, requestId, input) {
22155
- const record = getThreadRecordById(this.db, localThreadId);
22156
- if (!record) {
22157
- throw new HttpError(404, {
22158
- code: "not_found",
22159
- message: "Thread was not found."
22160
- });
22161
- }
22007
+ const record = this.requireThreadRecord(localThreadId);
22162
22008
  const requestResponse = this.requestCoordinator.respondToRequest(
22163
22009
  localThreadId,
22164
22010
  requestId,
@@ -22313,20 +22159,7 @@ var ThreadService = class {
22313
22159
  if (!record) {
22314
22160
  return;
22315
22161
  }
22316
- const turnMetadataById = new Map(
22317
- listThreadTurnMetadataByThreadId(this.db, localThreadId).map((entry) => [
22318
- entry.turnId,
22319
- {
22320
- model: entry.model ?? null,
22321
- reasoningEffort: entry.reasoningEffort ?? null,
22322
- reasoningEffortAvailable: entry.reasoningEffortAvailable ?? null,
22323
- pricingModelKey: entry.pricingModelKey ?? null,
22324
- pricingTierKey: normalizePricingTier(entry.pricingTierKey),
22325
- tokenUsageJson: entry.tokenUsageJson ?? null,
22326
- createdAt: entry.createdAt ?? null
22327
- }
22328
- ])
22329
- );
22162
+ const turnMetadataById = listThreadTurnMetadataMap(this.db, localThreadId);
22330
22163
  const cachedDetail = await this.detailAssembler.buildCacheEntry({
22331
22164
  localThreadId,
22332
22165
  record,
@@ -24081,64 +23914,137 @@ async function registerThreadRoutes(app) {
24081
23914
  }
24082
23915
 
24083
23916
  // src/routes/workspaces.ts
24084
- import fs18 from "fs/promises";
23917
+ import fs20 from "fs/promises";
24085
23918
  import { createReadStream } from "fs";
24086
- import os4 from "os";
24087
- import path19 from "path";
24088
- import { spawn as spawn4 } from "child_process";
23919
+ import path21 from "path";
24089
23920
  import { Readable } from "stream";
24090
23921
  import { z as z6 } from "zod";
24091
- var createWorkspaceSchema = z6.union([
24092
- z6.object({
24093
- absPath: z6.string().min(1),
24094
- label: z6.string().min(1).optional()
24095
- }),
24096
- z6.object({
24097
- gitUrl: z6.string().min(1),
24098
- label: z6.string().min(1).optional()
24099
- })
24100
- ]);
24101
- var updateFavoriteSchema = z6.object({
24102
- isFavorite: z6.boolean()
24103
- });
24104
- var updateWorkspaceSchema = z6.object({
24105
- label: z6.string().min(1)
24106
- });
24107
- var workspaceFilePathSchema = z6.string().trim().min(1).max(4096);
24108
- var writeWorkspaceFileSchema = z6.object({
24109
- path: workspaceFilePathSchema,
24110
- content: z6.string()
24111
- });
24112
- var moveWorkspaceFileSchema = z6.object({
24113
- fromPath: workspaceFilePathSchema,
24114
- toPath: workspaceFilePathSchema,
24115
- overwrite: z6.boolean().optional()
24116
- });
24117
- var deleteWorkspaceFileSchema = z6.object({
24118
- path: workspaceFilePathSchema,
24119
- recursive: z6.boolean().optional()
24120
- });
24121
- var workspaceArtifactIdSchema = z6.string().trim().min(1).max(160).regex(/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/);
24122
- var workspaceArtifactNameSchema = z6.string().trim().min(1).max(255);
24123
- var createWorkspaceArtifactSchema = z6.object({
24124
- id: workspaceArtifactIdSchema.optional(),
24125
- name: workspaceArtifactNameSchema,
24126
- mediaType: z6.string().trim().min(1).max(255).default("application/octet-stream"),
24127
- contentBase64: z6.string().min(1),
24128
- metadata: z6.record(z6.string(), z6.unknown()).optional()
24129
- });
24130
- var treeQuerySchema = z6.object({
24131
- path: z6.string().optional(),
24132
- showHidden: z6.coerce.boolean().optional()
24133
- });
24134
- var workspaceFileQuerySchema = z6.object({
24135
- path: z6.string().optional().default("")
24136
- });
24137
- var workspacePreviewQuerySchema = z6.object({
24138
- path: z6.string().min(1),
24139
- offset: z6.coerce.number().int().min(0).optional(),
24140
- limit: z6.coerce.number().int().positive().max(25e4).optional()
24141
- });
23922
+
23923
+ // src/workspace-artifact-service.ts
23924
+ import fs18 from "fs/promises";
23925
+ import path19 from "path";
23926
+ function artifactRoot(record) {
23927
+ return path19.join(record.absPath, ".remote-codex", "artifacts");
23928
+ }
23929
+ function artifactFilePath(record, artifactId) {
23930
+ return path19.join(artifactRoot(record), artifactId, "artifact.bin");
23931
+ }
23932
+ function artifactMetadataPath(record, artifactId) {
23933
+ return path19.join(artifactRoot(record), artifactId, "metadata.json");
23934
+ }
23935
+ function safeArtifactFileName(value) {
23936
+ return path19.basename(value).replace(/[^a-zA-Z0-9_. -]/g, "_") || "artifact.bin";
23937
+ }
23938
+ function artifactIdFromName(name) {
23939
+ const base = path19.basename(name).replace(/[^a-zA-Z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 96);
23940
+ return `${base || "artifact"}-${Date.now().toString(36)}`;
23941
+ }
23942
+ async function readWorkspaceArtifactMetadata(record, artifactId) {
23943
+ try {
23944
+ const raw = await fs18.readFile(artifactMetadataPath(record, artifactId), "utf8");
23945
+ return JSON.parse(raw);
23946
+ } catch (error) {
23947
+ if (error.code === "ENOENT") {
23948
+ throw new HttpError(404, {
23949
+ code: "not_found",
23950
+ message: "Workspace artifact was not found."
23951
+ });
23952
+ }
23953
+ throw error;
23954
+ }
23955
+ }
23956
+ async function listWorkspaceArtifacts(record) {
23957
+ let entries;
23958
+ try {
23959
+ entries = await fs18.readdir(artifactRoot(record));
23960
+ } catch (error) {
23961
+ if (error.code === "ENOENT") {
23962
+ return [];
23963
+ }
23964
+ throw error;
23965
+ }
23966
+ const artifacts = [];
23967
+ for (const entry of entries) {
23968
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(entry)) {
23969
+ continue;
23970
+ }
23971
+ try {
23972
+ artifacts.push(await readWorkspaceArtifactMetadata(record, entry));
23973
+ } catch (error) {
23974
+ if (!(error instanceof HttpError && error.statusCode === 404)) {
23975
+ throw error;
23976
+ }
23977
+ }
23978
+ }
23979
+ return artifacts.sort((left, right) => right.createdAt.localeCompare(left.createdAt));
23980
+ }
23981
+ async function createWorkspaceArtifact({
23982
+ record,
23983
+ artifactId,
23984
+ name,
23985
+ mediaType,
23986
+ content,
23987
+ metadata
23988
+ }) {
23989
+ if (content.length === 0) {
23990
+ throw new HttpError(400, {
23991
+ code: "bad_request",
23992
+ message: "Artifact content must not be empty."
23993
+ });
23994
+ }
23995
+ const dir = path19.dirname(artifactFilePath(record, artifactId));
23996
+ await fs18.mkdir(dir, { recursive: true, mode: 448 });
23997
+ const filePath = artifactFilePath(record, artifactId);
23998
+ await fs18.writeFile(filePath, content, { flag: "wx" }).catch((error) => {
23999
+ if (error.code === "EEXIST") {
24000
+ throw new HttpError(409, {
24001
+ code: "conflict",
24002
+ message: "Workspace artifact already exists."
24003
+ });
24004
+ }
24005
+ throw error;
24006
+ });
24007
+ const now = (/* @__PURE__ */ new Date()).toISOString();
24008
+ const artifact = {
24009
+ id: artifactId,
24010
+ workspaceId: record.id,
24011
+ name: safeArtifactFileName(name),
24012
+ mediaType,
24013
+ size: content.length,
24014
+ createdAt: now,
24015
+ updatedAt: now,
24016
+ metadata: metadata ?? {}
24017
+ };
24018
+ await fs18.writeFile(artifactMetadataPath(record, artifactId), JSON.stringify(artifact, null, 2));
24019
+ return artifact;
24020
+ }
24021
+ async function readWorkspaceArtifactContent(record, artifactId) {
24022
+ try {
24023
+ return await fs18.readFile(artifactFilePath(record, artifactId));
24024
+ } catch (error) {
24025
+ if (error.code === "ENOENT") {
24026
+ throw new HttpError(404, {
24027
+ code: "not_found",
24028
+ message: "Workspace artifact content was not found."
24029
+ });
24030
+ }
24031
+ throw error;
24032
+ }
24033
+ }
24034
+ async function deleteWorkspaceArtifact(record, artifactId) {
24035
+ const artifact = await readWorkspaceArtifactMetadata(record, artifactId);
24036
+ await fs18.rm(path19.dirname(artifactFilePath(record, artifactId)), {
24037
+ recursive: true,
24038
+ force: true
24039
+ });
24040
+ return artifact;
24041
+ }
24042
+
24043
+ // src/workspace-file-service.ts
24044
+ import { spawn as spawn4 } from "child_process";
24045
+ import fs19 from "fs/promises";
24046
+ import os4 from "os";
24047
+ import path20 from "path";
24142
24048
  var PREVIEW_DEFAULT_LIMIT_BYTES = 5e4;
24143
24049
  var WORKSPACE_UPLOAD_MAX_BYTES = 50 * 1024 * 1024;
24144
24050
  var WORKSPACE_FOLDER_DOWNLOAD_MAX_BYTES = 100 * 1024 * 1024;
@@ -24174,7 +24080,7 @@ function toWorkspaceFileDto(file) {
24174
24080
  };
24175
24081
  }
24176
24082
  function languageForPath(filePath) {
24177
- const extension = path19.extname(filePath).slice(1).toLowerCase();
24083
+ const extension = path20.extname(filePath).slice(1).toLowerCase();
24178
24084
  switch (extension) {
24179
24085
  case "js":
24180
24086
  case "jsx":
@@ -24220,18 +24126,18 @@ function languageForPath(filePath) {
24220
24126
  }
24221
24127
  }
24222
24128
  function relativeWorkspacePath(rootPath, absPath) {
24223
- const relative = path19.relative(rootPath, absPath);
24224
- return relative === "" ? "" : relative.split(path19.sep).join("/");
24129
+ const relative = path20.relative(rootPath, absPath);
24130
+ return relative === "" ? "" : relative.split(path20.sep).join("/");
24225
24131
  }
24226
24132
  async function resolveWorkspaceItemPath(rootPath, relativePath = "") {
24227
- const candidate = path19.resolve(rootPath, relativePath || ".");
24133
+ const candidate = path20.resolve(rootPath, relativePath || ".");
24228
24134
  const comparable = await assertPathWithinRoot(rootPath, candidate);
24229
24135
  return comparable;
24230
24136
  }
24231
24137
  async function buildWorkspaceTreeNode(rootPath, absPath) {
24232
- const stats = await fs18.stat(absPath);
24138
+ const stats = await fs19.stat(absPath);
24233
24139
  const relativePath = relativeWorkspacePath(rootPath, absPath);
24234
- const name = relativePath ? path19.basename(absPath) : path19.basename(rootPath);
24140
+ const name = relativePath ? path20.basename(absPath) : path20.basename(rootPath);
24235
24141
  if (!stats.isDirectory()) {
24236
24142
  return {
24237
24143
  name,
@@ -24249,7 +24155,7 @@ async function buildWorkspaceTreeNode(rootPath, absPath) {
24249
24155
  };
24250
24156
  const visible = [];
24251
24157
  try {
24252
- const directory = await fs18.opendir(absPath);
24158
+ const directory = await fs19.opendir(absPath);
24253
24159
  let scanned = 0;
24254
24160
  for await (const entry of directory) {
24255
24161
  scanned += 1;
@@ -24284,7 +24190,7 @@ async function buildWorkspaceTreeNode(rootPath, absPath) {
24284
24190
  });
24285
24191
  node.children = (await Promise.all(
24286
24192
  visible.map(async (entry) => {
24287
- const childPath = path19.join(absPath, entry.name);
24193
+ const childPath = path20.join(absPath, entry.name);
24288
24194
  try {
24289
24195
  const childRelativePath = relativeWorkspacePath(rootPath, childPath);
24290
24196
  if (entry.isDirectory()) {
@@ -24296,7 +24202,7 @@ async function buildWorkspaceTreeNode(rootPath, absPath) {
24296
24202
  childrenLoaded: false
24297
24203
  };
24298
24204
  }
24299
- const childStats = await fs18.stat(childPath);
24205
+ const childStats = await fs19.stat(childPath);
24300
24206
  return {
24301
24207
  name: entry.name,
24302
24208
  path: childRelativePath,
@@ -24310,73 +24216,8 @@ async function buildWorkspaceTreeNode(rootPath, absPath) {
24310
24216
  )).filter((child) => child !== null);
24311
24217
  return node;
24312
24218
  }
24313
- function requireWorkspaceRecord(app, workspaceId) {
24314
- const record = getWorkspaceRecordById(app.services.database.db, workspaceId);
24315
- if (!record) {
24316
- throw new HttpError(404, {
24317
- code: "not_found",
24318
- message: "Workspace was not found."
24319
- });
24320
- }
24321
- return record;
24322
- }
24323
- function artifactRoot(record) {
24324
- return path19.join(record.absPath, ".remote-codex", "artifacts");
24325
- }
24326
- function artifactFilePath(record, artifactId) {
24327
- return path19.join(artifactRoot(record), artifactId, "artifact.bin");
24328
- }
24329
- function artifactMetadataPath(record, artifactId) {
24330
- return path19.join(artifactRoot(record), artifactId, "metadata.json");
24331
- }
24332
- function safeArtifactFileName(value) {
24333
- return path19.basename(value).replace(/[^a-zA-Z0-9_. -]/g, "_") || "artifact.bin";
24334
- }
24335
- function artifactIdFromName(name) {
24336
- const base = path19.basename(name).replace(/[^a-zA-Z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 96);
24337
- return `${base || "artifact"}-${Date.now().toString(36)}`;
24338
- }
24339
- async function readArtifactMetadata(record, artifactId) {
24340
- try {
24341
- const raw = await fs18.readFile(artifactMetadataPath(record, artifactId), "utf8");
24342
- return JSON.parse(raw);
24343
- } catch (error) {
24344
- if (error.code === "ENOENT") {
24345
- throw new HttpError(404, {
24346
- code: "not_found",
24347
- message: "Workspace artifact was not found."
24348
- });
24349
- }
24350
- throw error;
24351
- }
24352
- }
24353
- async function listWorkspaceArtifacts(record) {
24354
- let entries;
24355
- try {
24356
- entries = await fs18.readdir(artifactRoot(record));
24357
- } catch (error) {
24358
- if (error.code === "ENOENT") {
24359
- return [];
24360
- }
24361
- throw error;
24362
- }
24363
- const artifacts = [];
24364
- for (const entry of entries) {
24365
- if (!workspaceArtifactIdSchema.safeParse(entry).success) {
24366
- continue;
24367
- }
24368
- try {
24369
- artifacts.push(await readArtifactMetadata(record, entry));
24370
- } catch (error) {
24371
- if (!(error instanceof HttpError && error.statusCode === 404)) {
24372
- throw error;
24373
- }
24374
- }
24375
- }
24376
- return artifacts.sort((left, right) => right.createdAt.localeCompare(left.createdAt));
24377
- }
24378
24219
  function contentTypeForPath(filePath) {
24379
- switch (path19.extname(filePath).slice(1).toLowerCase()) {
24220
+ switch (path20.extname(filePath).slice(1).toLowerCase()) {
24380
24221
  case "png":
24381
24222
  return "image/png";
24382
24223
  case "jpg":
@@ -24406,15 +24247,15 @@ function contentTypeForPath(filePath) {
24406
24247
  }
24407
24248
  }
24408
24249
  async function collectFolderZipEntries(rootPath, folderPath) {
24409
- const folderName = path19.basename(folderPath) || "workspace-folder";
24250
+ const folderName = path20.basename(folderPath) || "workspace-folder";
24410
24251
  const entries = [];
24411
24252
  let totalBytes = 0;
24412
24253
  const pending = [folderPath];
24413
24254
  while (pending.length > 0) {
24414
24255
  const current = pending.pop();
24415
- const children = await fs18.readdir(current, { withFileTypes: true });
24256
+ const children = await fs19.readdir(current, { withFileTypes: true });
24416
24257
  for (const child of children) {
24417
- const childPath = await resolveWorkspaceItemPath(rootPath, path19.relative(rootPath, path19.join(current, child.name)));
24258
+ const childPath = await resolveWorkspaceItemPath(rootPath, path20.relative(rootPath, path20.join(current, child.name)));
24418
24259
  if (child.isDirectory()) {
24419
24260
  pending.push(childPath);
24420
24261
  continue;
@@ -24422,7 +24263,7 @@ async function collectFolderZipEntries(rootPath, folderPath) {
24422
24263
  if (!child.isFile()) {
24423
24264
  continue;
24424
24265
  }
24425
- const stats = await fs18.stat(childPath);
24266
+ const stats = await fs19.stat(childPath);
24426
24267
  totalBytes += stats.size;
24427
24268
  entries.push({
24428
24269
  absPath: childPath,
@@ -24473,8 +24314,8 @@ async function createFolderZipFile(rootPath, folderPath) {
24473
24314
  const centralParts = [];
24474
24315
  let offset = 0;
24475
24316
  for (const entry of entries) {
24476
- const data = await fs18.readFile(entry.absPath);
24477
- const name = Buffer.from(entry.archivePath.split(path19.sep).join("/"), "utf8");
24317
+ const data = await fs19.readFile(entry.absPath);
24318
+ const name = Buffer.from(entry.archivePath.split(path20.sep).join("/"), "utf8");
24478
24319
  const checksum = crc32(data);
24479
24320
  const { dosDate, dosTime } = zipDosDateTime(entry.updatedAt);
24480
24321
  const localHeader = Buffer.alloc(30);
@@ -24521,19 +24362,19 @@ async function createFolderZipFile(rootPath, folderPath) {
24521
24362
  endRecord.writeUInt32LE(centralSize, 12);
24522
24363
  endRecord.writeUInt32LE(offset, 16);
24523
24364
  endRecord.writeUInt16LE(0, 20);
24524
- const tempDir = await fs18.mkdtemp(path19.join(os4.tmpdir(), "remote-codex-folder-download-"));
24525
- const zipPath = path19.join(tempDir, `${path19.basename(folderPath) || "workspace-folder"}.zip`);
24526
- await fs18.writeFile(zipPath, Buffer.concat([...localParts, ...centralParts, endRecord]));
24365
+ const tempDir = await fs19.mkdtemp(path20.join(os4.tmpdir(), "remote-codex-folder-download-"));
24366
+ const zipPath = path20.join(tempDir, `${path20.basename(folderPath) || "workspace-folder"}.zip`);
24367
+ await fs19.writeFile(zipPath, Buffer.concat([...localParts, ...centralParts, endRecord]));
24527
24368
  return { zipPath, tempDir };
24528
24369
  }
24529
24370
  function cleanupTemporaryZip(zipPath, tempDir) {
24530
24371
  return async () => {
24531
- await fs18.rm(zipPath, { force: true }).catch(() => void 0);
24532
- await fs18.rm(tempDir, { recursive: true, force: true }).catch(() => void 0);
24372
+ await fs19.rm(zipPath, { force: true }).catch(() => void 0);
24373
+ await fs19.rm(tempDir, { recursive: true, force: true }).catch(() => void 0);
24533
24374
  };
24534
24375
  }
24535
24376
  function sanitizeUploadFilename(filename) {
24536
- const baseName = path19.basename(filename?.trim() || "upload");
24377
+ const baseName = path20.basename(filename?.trim() || "upload");
24537
24378
  if (!baseName || baseName === "." || baseName === "..") {
24538
24379
  return "upload";
24539
24380
  }
@@ -24545,7 +24386,7 @@ function inferGitRepoName(gitUrl) {
24545
24386
  const normalized = withoutQuery.replace(/[\\/]+$/, "");
24546
24387
  const rawName = normalized.split(/[/:]/).filter(Boolean).at(-1) ?? "";
24547
24388
  const repoName = rawName.endsWith(".git") ? rawName.slice(0, -4) : rawName;
24548
- if (!repoName || repoName === "." || repoName === ".." || repoName.includes(path19.sep)) {
24389
+ if (!repoName || repoName === "." || repoName === ".." || repoName.includes(path20.sep)) {
24549
24390
  throw new HttpError(400, {
24550
24391
  code: "bad_request",
24551
24392
  message: "Unable to infer a target directory from the Git URL."
@@ -24555,7 +24396,7 @@ function inferGitRepoName(gitUrl) {
24555
24396
  }
24556
24397
  async function pathExists4(absPath) {
24557
24398
  try {
24558
- await fs18.stat(absPath);
24399
+ await fs19.stat(absPath);
24559
24400
  return true;
24560
24401
  } catch (error) {
24561
24402
  if (error.code === "ENOENT") {
@@ -24603,6 +24444,69 @@ function cloneRepository(gitUrl, targetPath) {
24603
24444
  });
24604
24445
  });
24605
24446
  }
24447
+
24448
+ // src/routes/workspaces.ts
24449
+ var createWorkspaceSchema = z6.union([
24450
+ z6.object({
24451
+ absPath: z6.string().min(1),
24452
+ label: z6.string().min(1).optional()
24453
+ }),
24454
+ z6.object({
24455
+ gitUrl: z6.string().min(1),
24456
+ label: z6.string().min(1).optional()
24457
+ })
24458
+ ]);
24459
+ var updateFavoriteSchema = z6.object({
24460
+ isFavorite: z6.boolean()
24461
+ });
24462
+ var updateWorkspaceSchema = z6.object({
24463
+ label: z6.string().min(1)
24464
+ });
24465
+ var workspaceFilePathSchema = z6.string().trim().min(1).max(4096);
24466
+ var writeWorkspaceFileSchema = z6.object({
24467
+ path: workspaceFilePathSchema,
24468
+ content: z6.string()
24469
+ });
24470
+ var moveWorkspaceFileSchema = z6.object({
24471
+ fromPath: workspaceFilePathSchema,
24472
+ toPath: workspaceFilePathSchema,
24473
+ overwrite: z6.boolean().optional()
24474
+ });
24475
+ var deleteWorkspaceFileSchema = z6.object({
24476
+ path: workspaceFilePathSchema,
24477
+ recursive: z6.boolean().optional()
24478
+ });
24479
+ var workspaceArtifactIdSchema = z6.string().trim().min(1).max(160).regex(/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/);
24480
+ var workspaceArtifactNameSchema = z6.string().trim().min(1).max(255);
24481
+ var createWorkspaceArtifactSchema = z6.object({
24482
+ id: workspaceArtifactIdSchema.optional(),
24483
+ name: workspaceArtifactNameSchema,
24484
+ mediaType: z6.string().trim().min(1).max(255).default("application/octet-stream"),
24485
+ contentBase64: z6.string().min(1),
24486
+ metadata: z6.record(z6.string(), z6.unknown()).optional()
24487
+ });
24488
+ var treeQuerySchema = z6.object({
24489
+ path: z6.string().optional(),
24490
+ showHidden: z6.coerce.boolean().optional()
24491
+ });
24492
+ var workspaceFileQuerySchema = z6.object({
24493
+ path: z6.string().optional().default("")
24494
+ });
24495
+ var workspacePreviewQuerySchema = z6.object({
24496
+ path: z6.string().min(1),
24497
+ offset: z6.coerce.number().int().min(0).optional(),
24498
+ limit: z6.coerce.number().int().positive().max(25e4).optional()
24499
+ });
24500
+ function requireWorkspaceRecord(app, workspaceId) {
24501
+ const record = getWorkspaceRecordById(app.services.database.db, workspaceId);
24502
+ if (!record) {
24503
+ throw new HttpError(404, {
24504
+ code: "not_found",
24505
+ message: "Workspace was not found."
24506
+ });
24507
+ }
24508
+ return record;
24509
+ }
24606
24510
  async function registerWorkspaceRoutes(app) {
24607
24511
  app.get("/api/workspaces", async () => {
24608
24512
  const records = listWorkspaceRecords(app.services.database.db);
@@ -24610,7 +24514,7 @@ async function registerWorkspaceRoutes(app) {
24610
24514
  });
24611
24515
  app.get("/api/workspaces/tree", async (request) => {
24612
24516
  const query = treeQuerySchema.parse(request.query);
24613
- const requestedPath = query.path ? path19.resolve(query.path) : app.services.config.workspaceRoot;
24517
+ const requestedPath = query.path ? path21.resolve(query.path) : app.services.config.workspaceRoot;
24614
24518
  const tree = await readWorkspaceTree({
24615
24519
  rootPath: app.services.config.workspaceRoot,
24616
24520
  targetPath: requestedPath,
@@ -24637,7 +24541,7 @@ async function registerWorkspaceRoutes(app) {
24637
24541
  const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
24638
24542
  const query = workspaceFileQuerySchema.parse(request.query);
24639
24543
  const record = requireWorkspaceRecord(app, params.id);
24640
- const rootPath = await fs18.realpath(record.absPath);
24544
+ const rootPath = await fs20.realpath(record.absPath);
24641
24545
  const targetPath = await resolveWorkspaceItemPath(rootPath, query.path);
24642
24546
  return buildWorkspaceTreeNode(rootPath, targetPath);
24643
24547
  });
@@ -24657,9 +24561,9 @@ async function registerWorkspaceRoutes(app) {
24657
24561
  const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
24658
24562
  const query = workspacePreviewQuerySchema.parse(request.query);
24659
24563
  const record = requireWorkspaceRecord(app, params.id);
24660
- const rootPath = await fs18.realpath(record.absPath);
24564
+ const rootPath = await fs20.realpath(record.absPath);
24661
24565
  const filePath = await resolveWorkspaceItemPath(rootPath, query.path);
24662
- const stats = await fs18.stat(filePath);
24566
+ const stats = await fs20.stat(filePath);
24663
24567
  if (!stats.isFile()) {
24664
24568
  throw new HttpError(400, {
24665
24569
  code: "bad_request",
@@ -24668,7 +24572,7 @@ async function registerWorkspaceRoutes(app) {
24668
24572
  }
24669
24573
  const offset = query.offset ?? 0;
24670
24574
  const limit = query.limit ?? PREVIEW_DEFAULT_LIMIT_BYTES;
24671
- const handle = await fs18.open(filePath, "r");
24575
+ const handle = await fs20.open(filePath, "r");
24672
24576
  try {
24673
24577
  const length = Math.min(limit, Math.max(0, stats.size - offset));
24674
24578
  const buffer = Buffer.alloc(length);
@@ -24676,7 +24580,7 @@ async function registerWorkspaceRoutes(app) {
24676
24580
  const nextOffset = offset + read.bytesRead;
24677
24581
  return {
24678
24582
  path: relativeWorkspacePath(rootPath, filePath),
24679
- name: path19.basename(filePath),
24583
+ name: path21.basename(filePath),
24680
24584
  content: buffer.subarray(0, read.bytesRead).toString("utf8"),
24681
24585
  language: languageForPath(filePath),
24682
24586
  size: stats.size,
@@ -24691,9 +24595,9 @@ async function registerWorkspaceRoutes(app) {
24691
24595
  const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
24692
24596
  const query = workspacePreviewQuerySchema.pick({ path: true }).parse(request.query);
24693
24597
  const record = requireWorkspaceRecord(app, params.id);
24694
- const rootPath = await fs18.realpath(record.absPath);
24598
+ const rootPath = await fs20.realpath(record.absPath);
24695
24599
  const filePath = await resolveWorkspaceItemPath(rootPath, query.path);
24696
- const stats = await fs18.stat(filePath);
24600
+ const stats = await fs20.stat(filePath);
24697
24601
  if (!stats.isFile()) {
24698
24602
  throw new HttpError(400, {
24699
24603
  code: "bad_request",
@@ -24701,18 +24605,18 @@ async function registerWorkspaceRoutes(app) {
24701
24605
  });
24702
24606
  }
24703
24607
  reply.header("content-type", contentTypeForPath(filePath));
24704
- return reply.send(Readable.from(await fs18.readFile(filePath)));
24608
+ return reply.send(Readable.from(await fs20.readFile(filePath)));
24705
24609
  });
24706
24610
  app.get("/api/workspaces/:id/files/download", async (request, reply) => {
24707
24611
  const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
24708
24612
  const query = workspaceFileQuerySchema.parse(request.query);
24709
24613
  const record = requireWorkspaceRecord(app, params.id);
24710
- const rootPath = await fs18.realpath(record.absPath);
24614
+ const rootPath = await fs20.realpath(record.absPath);
24711
24615
  const itemPath = await resolveWorkspaceItemPath(rootPath, query.path);
24712
- const stats = await fs18.stat(itemPath);
24616
+ const stats = await fs20.stat(itemPath);
24713
24617
  if (stats.isDirectory()) {
24714
24618
  const { zipPath, tempDir } = await createFolderZipFile(rootPath, itemPath);
24715
- const filename2 = `${path19.basename(itemPath) || "workspace-folder"}.zip`;
24619
+ const filename2 = `${path21.basename(itemPath) || "workspace-folder"}.zip`;
24716
24620
  const cleanup = cleanupTemporaryZip(zipPath, tempDir);
24717
24621
  reply.raw.once("finish", () => void cleanup());
24718
24622
  reply.raw.once("close", () => void cleanup());
@@ -24728,18 +24632,18 @@ async function registerWorkspaceRoutes(app) {
24728
24632
  message: "Only file and folder downloads are supported from this endpoint."
24729
24633
  });
24730
24634
  }
24731
- const filename = path19.basename(itemPath);
24635
+ const filename = path21.basename(itemPath);
24732
24636
  reply.header("content-type", contentTypeForPath(itemPath)).header(
24733
24637
  "content-disposition",
24734
24638
  `attachment; filename="${filename}"; filename*=UTF-8''${encodeURIComponent(filename)}`
24735
24639
  );
24736
- return reply.send(Readable.from(await fs18.readFile(itemPath)));
24640
+ return reply.send(Readable.from(await fs20.readFile(itemPath)));
24737
24641
  });
24738
24642
  app.post("/api/workspaces/:id/files/upload", async (request) => {
24739
24643
  requireWorkerScope(request, "file:write");
24740
24644
  const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
24741
24645
  const record = requireWorkspaceRecord(app, params.id);
24742
- const rootPath = await fs18.realpath(record.absPath);
24646
+ const rootPath = await fs20.realpath(record.absPath);
24743
24647
  const uploadRequest = request;
24744
24648
  if (!uploadRequest.isMultipart()) {
24745
24649
  throw new HttpError(400, {
@@ -24794,7 +24698,7 @@ async function registerWorkspaceRoutes(app) {
24794
24698
  kind: "file",
24795
24699
  file: {
24796
24700
  path: file.path,
24797
- name: path19.basename(file.path),
24701
+ name: path21.basename(file.path),
24798
24702
  size: file.size
24799
24703
  }
24800
24704
  };
@@ -24830,36 +24734,14 @@ async function registerWorkspaceRoutes(app) {
24830
24734
  const record = requireWorkspaceRecord(app, params.id);
24831
24735
  const artifactId = body.id ?? artifactIdFromName(body.name);
24832
24736
  const content = Buffer.from(body.contentBase64, "base64");
24833
- if (content.length === 0) {
24834
- throw new HttpError(400, {
24835
- code: "bad_request",
24836
- message: "Artifact content must not be empty."
24837
- });
24838
- }
24839
- const dir = path19.dirname(artifactFilePath(record, artifactId));
24840
- await fs18.mkdir(dir, { recursive: true, mode: 448 });
24841
- const filePath = artifactFilePath(record, artifactId);
24842
- await fs18.writeFile(filePath, content, { flag: "wx" }).catch((error) => {
24843
- if (error.code === "EEXIST") {
24844
- throw new HttpError(409, {
24845
- code: "conflict",
24846
- message: "Workspace artifact already exists."
24847
- });
24848
- }
24849
- throw error;
24850
- });
24851
- const now = (/* @__PURE__ */ new Date()).toISOString();
24852
- const artifact = {
24853
- id: artifactId,
24854
- workspaceId: record.id,
24855
- name: safeArtifactFileName(body.name),
24737
+ const artifact = await createWorkspaceArtifact({
24738
+ record,
24739
+ artifactId,
24740
+ name: body.name,
24856
24741
  mediaType: body.mediaType,
24857
- size: content.length,
24858
- createdAt: now,
24859
- updatedAt: now,
24860
- metadata: body.metadata ?? {}
24861
- };
24862
- await fs18.writeFile(artifactMetadataPath(record, artifactId), JSON.stringify(artifact, null, 2));
24742
+ content,
24743
+ ...body.metadata !== void 0 ? { metadata: body.metadata } : {}
24744
+ });
24863
24745
  return { artifact };
24864
24746
  });
24865
24747
  app.get("/api/workspaces/:id/artifacts", async (request) => {
@@ -24872,25 +24754,14 @@ async function registerWorkspaceRoutes(app) {
24872
24754
  requireWorkerScope(request, "artifact:read");
24873
24755
  const params = z6.object({ id: z6.string().uuid(), artifactId: workspaceArtifactIdSchema }).parse(request.params);
24874
24756
  const record = requireWorkspaceRecord(app, params.id);
24875
- return { artifact: await readArtifactMetadata(record, params.artifactId) };
24757
+ return { artifact: await readWorkspaceArtifactMetadata(record, params.artifactId) };
24876
24758
  });
24877
24759
  app.get("/api/workspaces/:id/artifacts/:artifactId/download", async (request, reply) => {
24878
24760
  requireWorkerScope(request, "artifact:read");
24879
24761
  const params = z6.object({ id: z6.string().uuid(), artifactId: workspaceArtifactIdSchema }).parse(request.params);
24880
24762
  const record = requireWorkspaceRecord(app, params.id);
24881
- const artifact = await readArtifactMetadata(record, params.artifactId);
24882
- let content;
24883
- try {
24884
- content = await fs18.readFile(artifactFilePath(record, params.artifactId));
24885
- } catch (error) {
24886
- if (error.code === "ENOENT") {
24887
- throw new HttpError(404, {
24888
- code: "not_found",
24889
- message: "Workspace artifact content was not found."
24890
- });
24891
- }
24892
- throw error;
24893
- }
24763
+ const artifact = await readWorkspaceArtifactMetadata(record, params.artifactId);
24764
+ const content = await readWorkspaceArtifactContent(record, params.artifactId);
24894
24765
  reply.header("content-type", artifact.mediaType).header("content-length", String(content.length)).header("content-disposition", `attachment; filename="${artifact.name.replace(/"/g, "")}"`);
24895
24766
  return reply.send(content);
24896
24767
  });
@@ -24898,11 +24769,7 @@ async function registerWorkspaceRoutes(app) {
24898
24769
  requireWorkerScope(request, "artifact:write");
24899
24770
  const params = z6.object({ id: z6.string().uuid(), artifactId: workspaceArtifactIdSchema }).parse(request.params);
24900
24771
  const record = requireWorkspaceRecord(app, params.id);
24901
- const artifact = await readArtifactMetadata(record, params.artifactId);
24902
- await fs18.rm(path19.dirname(artifactFilePath(record, params.artifactId)), {
24903
- recursive: true,
24904
- force: true
24905
- });
24772
+ const artifact = await deleteWorkspaceArtifact(record, params.artifactId);
24906
24773
  return { deleted: true, artifact };
24907
24774
  });
24908
24775
  app.post("/api/workspaces", async (request) => {
@@ -24914,7 +24781,7 @@ async function registerWorkspaceRoutes(app) {
24914
24781
  let validated;
24915
24782
  if ("gitUrl" in body) {
24916
24783
  const repoName = inferGitRepoName(body.gitUrl);
24917
- const targetPath = path19.join(settings.devHome, repoName);
24784
+ const targetPath = path21.join(settings.devHome, repoName);
24918
24785
  if (await pathExists4(targetPath)) {
24919
24786
  throw new HttpError(409, {
24920
24787
  code: "conflict",
@@ -25177,8 +25044,8 @@ async function registerAuthRoutes(app) {
25177
25044
  }
25178
25045
 
25179
25046
  // src/provider-host-config-service.ts
25180
- import fs19 from "fs/promises";
25181
- import path20 from "path";
25047
+ import fs21 from "fs/promises";
25048
+ import path22 from "path";
25182
25049
  import { randomUUID as randomUUID4 } from "crypto";
25183
25050
  function providerError(message, statusCode = 404) {
25184
25051
  const error = new Error(message);
@@ -25186,23 +25053,23 @@ function providerError(message, statusCode = 404) {
25186
25053
  return error;
25187
25054
  }
25188
25055
  function resolveProviderHostFilePath(providerHome, name) {
25189
- return path20.join(providerHome, name);
25056
+ return path22.join(providerHome, name);
25190
25057
  }
25191
25058
  function resolveArchiveRoot(providerHome) {
25192
- return path20.join(providerHome, "supervisor-config-archives");
25059
+ return path22.join(providerHome, "supervisor-config-archives");
25193
25060
  }
25194
25061
  function resolveArchiveIndexPath(providerHome) {
25195
- return path20.join(resolveArchiveRoot(providerHome), "index.json");
25062
+ return path22.join(resolveArchiveRoot(providerHome), "index.json");
25196
25063
  }
25197
25064
  function resolveArchivePath(providerHome, archiveId) {
25198
- return path20.join(resolveArchiveRoot(providerHome), archiveId);
25065
+ return path22.join(resolveArchiveRoot(providerHome), archiveId);
25199
25066
  }
25200
25067
  function defaultArchiveLabel(createdAt) {
25201
25068
  return `Backup ${createdAt.replace("T", " ").replace(/\.\d{3}Z$/, " UTC")}`;
25202
25069
  }
25203
25070
  async function readArchiveIndex(providerHome) {
25204
25071
  try {
25205
- const raw = await fs19.readFile(resolveArchiveIndexPath(providerHome), "utf8");
25072
+ const raw = await fs21.readFile(resolveArchiveIndexPath(providerHome), "utf8");
25206
25073
  const parsed = JSON.parse(raw);
25207
25074
  return {
25208
25075
  archives: Array.isArray(parsed.archives) ? parsed.archives : []
@@ -25216,8 +25083,8 @@ async function readArchiveIndex(providerHome) {
25216
25083
  }
25217
25084
  async function writeArchiveIndex(providerHome, index) {
25218
25085
  const root = resolveArchiveRoot(providerHome);
25219
- await fs19.mkdir(root, { recursive: true });
25220
- await fs19.writeFile(
25086
+ await fs21.mkdir(root, { recursive: true });
25087
+ await fs21.writeFile(
25221
25088
  resolveArchiveIndexPath(providerHome),
25222
25089
  `${JSON.stringify(index, null, 2)}
25223
25090
  `,
@@ -25288,7 +25155,7 @@ var ProviderHostConfigService = class {
25288
25155
  const fileName = this.assertHostFile(provider2, name);
25289
25156
  const filePath = resolveProviderHostFilePath(providerHome, fileName);
25290
25157
  try {
25291
- const content = await fs19.readFile(filePath, "utf8");
25158
+ const content = await fs21.readFile(filePath, "utf8");
25292
25159
  return {
25293
25160
  name: fileName,
25294
25161
  path: filePath,
@@ -25311,8 +25178,8 @@ var ProviderHostConfigService = class {
25311
25178
  const providerHome = this.providerHome(provider2);
25312
25179
  const fileName = this.assertHostFile(provider2, name);
25313
25180
  const filePath = resolveProviderHostFilePath(providerHome, fileName);
25314
- await fs19.mkdir(path20.dirname(filePath), { recursive: true });
25315
- await fs19.writeFile(filePath, input.content, "utf8");
25181
+ await fs21.mkdir(path22.dirname(filePath), { recursive: true });
25182
+ await fs21.writeFile(filePath, input.content, "utf8");
25316
25183
  return this.readFile(provider2, fileName);
25317
25184
  }
25318
25185
  async listArchives(provider2) {
@@ -25338,7 +25205,7 @@ var ProviderHostConfigService = class {
25338
25205
  }
25339
25206
  ])
25340
25207
  );
25341
- await fs19.mkdir(archivePath, { recursive: true });
25208
+ await fs21.mkdir(archivePath, { recursive: true });
25342
25209
  for (const name of fileNames) {
25343
25210
  const hostFile = await this.readFile(provider2, name);
25344
25211
  files[name] = {
@@ -25346,7 +25213,7 @@ var ProviderHostConfigService = class {
25346
25213
  exists: hostFile.exists
25347
25214
  };
25348
25215
  if (hostFile.exists) {
25349
- await fs19.writeFile(path20.join(archivePath, name), hostFile.content, "utf8");
25216
+ await fs21.writeFile(path22.join(archivePath, name), hostFile.content, "utf8");
25350
25217
  }
25351
25218
  }
25352
25219
  const archive = {
@@ -25382,14 +25249,14 @@ var ProviderHostConfigService = class {
25382
25249
  const fileNames = this.archiveFileNames(provider2);
25383
25250
  const { archive } = await findArchiveOrThrow(providerHome, id);
25384
25251
  const archivePath = resolveArchivePath(providerHome, archive.id);
25385
- await fs19.mkdir(providerHome, { recursive: true });
25252
+ await fs21.mkdir(providerHome, { recursive: true });
25386
25253
  for (const name of fileNames) {
25387
25254
  const hostPath = resolveProviderHostFilePath(providerHome, name);
25388
25255
  if (archive.files[name]?.exists) {
25389
- const content = await fs19.readFile(path20.join(archivePath, name), "utf8");
25390
- await fs19.writeFile(hostPath, content, "utf8");
25256
+ const content = await fs21.readFile(path22.join(archivePath, name), "utf8");
25257
+ await fs21.writeFile(hostPath, content, "utf8");
25391
25258
  } else {
25392
- await fs19.rm(hostPath, { force: true });
25259
+ await fs21.rm(hostPath, { force: true });
25393
25260
  }
25394
25261
  }
25395
25262
  await runtime.stop();
@@ -25402,12 +25269,12 @@ var ProviderHostConfigService = class {
25402
25269
  };
25403
25270
 
25404
25271
  // src/shell/shell-session-service.ts
25405
- import fs21 from "fs/promises";
25272
+ import fs23 from "fs/promises";
25406
25273
 
25407
25274
  // src/shell/shell-prompt.ts
25408
- import fs20 from "fs/promises";
25275
+ import fs22 from "fs/promises";
25409
25276
  import os5 from "os";
25410
- import path21 from "path";
25277
+ import path23 from "path";
25411
25278
  function basenameFromPath2(filePath) {
25412
25279
  if (!filePath) {
25413
25280
  return "";
@@ -25416,7 +25283,7 @@ function basenameFromPath2(filePath) {
25416
25283
  if (!normalized) {
25417
25284
  return "";
25418
25285
  }
25419
- return path21.basename(normalized) || normalized;
25286
+ return path23.basename(normalized) || normalized;
25420
25287
  }
25421
25288
  function isInteractiveShellCommand(command) {
25422
25289
  const normalized = (command ?? "").trim().toLowerCase();
@@ -25577,11 +25444,11 @@ function buildShellPromptInitScriptContents(command) {
25577
25444
  async function ensureShellPromptInitScript(command) {
25578
25445
  const normalized = command.trim().toLowerCase();
25579
25446
  const extension = normalized === "zsh" ? "zsh" : "sh";
25580
- const filePath = path21.join(
25447
+ const filePath = path23.join(
25581
25448
  os5.tmpdir(),
25582
25449
  `remote-codex-shell-prompt.${extension}`
25583
25450
  );
25584
- await fs20.writeFile(filePath, buildShellPromptInitScriptContents(command), "utf8");
25451
+ await fs22.writeFile(filePath, buildShellPromptInitScriptContents(command), "utf8");
25585
25452
  return filePath;
25586
25453
  }
25587
25454
  async function buildShellPromptInitCommand(command, options = {}) {
@@ -25597,7 +25464,7 @@ clear
25597
25464
  // src/shell/shell-session-service.ts
25598
25465
  async function pathExists5(filePath) {
25599
25466
  try {
25600
- await fs21.access(filePath);
25467
+ await fs23.access(filePath);
25601
25468
  return true;
25602
25469
  } catch {
25603
25470
  return false;
@@ -26255,8 +26122,8 @@ var builtinPlugins = [
26255
26122
  ];
26256
26123
 
26257
26124
  // src/plugins/plugin-service.ts
26258
- import fs22 from "fs/promises";
26259
- import path22 from "path";
26125
+ import fs24 from "fs/promises";
26126
+ import path24 from "path";
26260
26127
  var MANAGED_CODEX_MCP_BEGIN = "# BEGIN remote-codex managed plugin MCP servers";
26261
26128
  var MANAGED_CODEX_MCP_END = "# END remote-codex managed plugin MCP servers";
26262
26129
  var REMOTE_CODEX_MOLECULE_MCP_TOOL_NAME = "remote_codex_render_molecule";
@@ -26268,7 +26135,7 @@ function normalizeManagedCommand(server, repoRoot) {
26268
26135
  if (server.name === "remote_codex_plugins") {
26269
26136
  return {
26270
26137
  command: process.execPath,
26271
- args: [path22.join(repoRoot, "bin", "remote-codex-plugin-mcp.mjs")]
26138
+ args: [path24.join(repoRoot, "bin", "remote-codex-plugin-mcp.mjs")]
26272
26139
  };
26273
26140
  }
26274
26141
  return {
@@ -26455,10 +26322,10 @@ var PluginService = class {
26455
26322
  if (!input.codexHome) {
26456
26323
  return;
26457
26324
  }
26458
- const configPath = path22.join(input.codexHome, "config.toml");
26325
+ const configPath = path24.join(input.codexHome, "config.toml");
26459
26326
  let current = "";
26460
26327
  try {
26461
- current = await fs22.readFile(configPath, "utf8");
26328
+ current = await fs24.readFile(configPath, "utf8");
26462
26329
  } catch (error) {
26463
26330
  if (error.code !== "ENOENT") {
26464
26331
  throw error;
@@ -26473,8 +26340,8 @@ var PluginService = class {
26473
26340
  if (next === current) {
26474
26341
  return;
26475
26342
  }
26476
- await fs22.mkdir(path22.dirname(configPath), { recursive: true });
26477
- await fs22.writeFile(configPath, next, "utf8");
26343
+ await fs24.mkdir(path24.dirname(configPath), { recursive: true });
26344
+ await fs24.writeFile(configPath, next, "utf8");
26478
26345
  }
26479
26346
  async importPlugin(input) {
26480
26347
  const manifestInput = input.manifest ?? (input.manifestUrl ? await this.fetchManifestUrl(input.manifestUrl) : this.parseManifestJson(input.manifestJson));
@@ -26713,8 +26580,8 @@ var PluginSettingsStore = class {
26713
26580
  };
26714
26581
 
26715
26582
  // src/worker-bootstrap.ts
26716
- import fs23 from "fs/promises";
26717
- import path23 from "path";
26583
+ import fs25 from "fs/promises";
26584
+ import path25 from "path";
26718
26585
  function trimTrailingSlash(value) {
26719
26586
  return value.replace(/\/+$/, "");
26720
26587
  }
@@ -26725,9 +26592,9 @@ function tomlString(value) {
26725
26592
  return JSON.stringify(value);
26726
26593
  }
26727
26594
  async function writePrivateFile(filePath, content) {
26728
- await fs23.mkdir(path23.dirname(filePath), { recursive: true, mode: 448 });
26729
- await fs23.writeFile(filePath, content, { encoding: "utf8", mode: 384 });
26730
- await fs23.chmod(filePath, 384);
26595
+ await fs25.mkdir(path25.dirname(filePath), { recursive: true, mode: 448 });
26596
+ await fs25.writeFile(filePath, content, { encoding: "utf8", mode: 384 });
26597
+ await fs25.chmod(filePath, 384);
26731
26598
  }
26732
26599
  async function configureWorkerProviderGateway(config) {
26733
26600
  if (config.runtimeRole !== "worker" || !config.llmGatewayBaseUrl || !config.llmGatewayToken) {
@@ -26741,7 +26608,7 @@ async function configureWorkerProviderGateway(config) {
26741
26608
  process.env.ANTHROPIC_BASE_URL = anthropicBaseUrl;
26742
26609
  if (config.agentProviders.codex.enabled) {
26743
26610
  await writePrivateFile(
26744
- path23.join(config.agentProviders.codex.home, "config.toml"),
26611
+ path25.join(config.agentProviders.codex.home, "config.toml"),
26745
26612
  [
26746
26613
  'model_provider = "sub2api"',
26747
26614
  'forced_login_method = "api"',
@@ -26757,7 +26624,7 @@ async function configureWorkerProviderGateway(config) {
26757
26624
  ].join("\n")
26758
26625
  );
26759
26626
  await writePrivateFile(
26760
- path23.join(config.agentProviders.codex.home, "auth.json"),
26627
+ path25.join(config.agentProviders.codex.home, "auth.json"),
26761
26628
  `${JSON.stringify(
26762
26629
  {
26763
26630
  OPENAI_API_KEY: config.llmGatewayToken
@@ -26770,7 +26637,7 @@ async function configureWorkerProviderGateway(config) {
26770
26637
  }
26771
26638
  if (config.agentProviders.claude.enabled) {
26772
26639
  await writePrivateFile(
26773
- path23.join(config.agentProviders.claude.home, "settings.json"),
26640
+ path25.join(config.agentProviders.claude.home, "settings.json"),
26774
26641
  `${JSON.stringify(
26775
26642
  {
26776
26643
  env: {
@@ -26786,7 +26653,7 @@ async function configureWorkerProviderGateway(config) {
26786
26653
  }
26787
26654
  if (config.agentProviders.opencode.enabled) {
26788
26655
  await writePrivateFile(
26789
- path23.join(config.agentProviders.opencode.home, "opencode.json"),
26656
+ path25.join(config.agentProviders.opencode.home, "opencode.json"),
26790
26657
  `${JSON.stringify(
26791
26658
  {
26792
26659
  provider: {
@@ -26833,27 +26700,27 @@ var BackendPluginHost = class {
26833
26700
  };
26834
26701
 
26835
26702
  // src/shell/pty-shell-backend.ts
26836
- import path24 from "path";
26703
+ import path26 from "path";
26837
26704
  import { spawn as spawn5 } from "@homebridge/node-pty-prebuilt-multiarch";
26838
26705
 
26839
26706
  // src/shell/default-shell.ts
26840
- import fs24 from "fs";
26707
+ import fs26 from "fs";
26841
26708
  var POSIX_SHELL_CANDIDATES = ["/bin/bash", "/usr/bin/bash", "/bin/sh"];
26842
26709
  function resolveDefaultShell(env = process.env) {
26843
26710
  if (process.platform === "win32") {
26844
26711
  return env.COMSPEC ?? "cmd.exe";
26845
26712
  }
26846
- if (env.SHELL && fs24.existsSync(env.SHELL)) {
26713
+ if (env.SHELL && fs26.existsSync(env.SHELL)) {
26847
26714
  return env.SHELL;
26848
26715
  }
26849
- return POSIX_SHELL_CANDIDATES.find((candidate) => fs24.existsSync(candidate)) ?? "/bin/sh";
26716
+ return POSIX_SHELL_CANDIDATES.find((candidate) => fs26.existsSync(candidate)) ?? "/bin/sh";
26850
26717
  }
26851
26718
 
26852
26719
  // src/shell/pty-shell-backend.ts
26853
26720
  var MAX_SCROLLBACK_BYTES = 512 * 1024;
26854
26721
  var ANSI_ESCAPE_PATTERN = new RegExp(String.raw`\u001B\[[0-?]*[ -/]*[@-~]`, "g");
26855
26722
  function shellArgs(shell) {
26856
- const shellName = path24.basename(shell).toLowerCase();
26723
+ const shellName = path26.basename(shell).toLowerCase();
26857
26724
  if (process.platform === "win32") {
26858
26725
  return [];
26859
26726
  }
@@ -26875,7 +26742,7 @@ function lastVisibleLine(snapshot) {
26875
26742
  }
26876
26743
  function inferRuntime(session) {
26877
26744
  const promptLine = lastVisibleLine(session.scrollback);
26878
- const shell = path24.basename(session.shell);
26745
+ const shell = path26.basename(session.shell);
26879
26746
  const isCommandRunning = session.exitCode !== null ? false : !/[$#>]\s*$/.test(promptLine.trimEnd());
26880
26747
  return {
26881
26748
  panePid: session.pty.pid,
@@ -27021,8 +26888,8 @@ var PtyShellBackend = class {
27021
26888
  };
27022
26889
 
27023
26890
  // src/shell/tmux-manager.ts
27024
- import fs25 from "fs";
27025
- import path25 from "path";
26891
+ import fs27 from "fs";
26892
+ import path27 from "path";
27026
26893
  import { spawn as spawnChild } from "child_process";
27027
26894
  async function defaultExecCommand(command, args) {
27028
26895
  return await new Promise((resolve, reject) => {
@@ -27049,17 +26916,17 @@ async function defaultExecCommand(command, args) {
27049
26916
  });
27050
26917
  }
27051
26918
  function resolveExecutablePath(command) {
27052
- if (command.includes(path25.sep)) {
26919
+ if (command.includes(path27.sep)) {
27053
26920
  return command;
27054
26921
  }
27055
26922
  const searchPath = process.env.PATH ?? "";
27056
- for (const entry of searchPath.split(path25.delimiter)) {
26923
+ for (const entry of searchPath.split(path27.delimiter)) {
27057
26924
  const trimmed = entry.trim();
27058
26925
  if (!trimmed) {
27059
26926
  continue;
27060
26927
  }
27061
- const candidate = path25.join(trimmed, command);
27062
- if (fs25.existsSync(candidate)) {
26928
+ const candidate = path27.join(trimmed, command);
26929
+ if (fs27.existsSync(candidate)) {
27063
26930
  return candidate;
27064
26931
  }
27065
26932
  }
@@ -27820,22 +27687,22 @@ function payloadItems(payload, fields) {
27820
27687
  const record = recordFrom(payload);
27821
27688
  return arrayField(record, fields) ?? (record ? [record] : []);
27822
27689
  }
27823
- function artifactPreviewKind(type, format, path27) {
27690
+ function artifactPreviewKind(type, format, path29) {
27824
27691
  const candidates = [
27825
27692
  type,
27826
27693
  format,
27827
- path27?.split(".").pop() ?? null
27694
+ path29?.split(".").pop() ?? null
27828
27695
  ].map((value) => value?.trim().toLowerCase()).filter(Boolean);
27829
27696
  return candidates.some((value) => MOLECULE_ARTIFACT_TYPES.has(value)) ? "molecule" : "file";
27830
27697
  }
27831
27698
  function normalizeArtifactRef(value) {
27832
27699
  const record = recordFrom(value);
27833
- const path27 = stringField3(record, ["path", "filePath", "file_path", "filename", "fileName", "name"]);
27700
+ const path29 = stringField3(record, ["path", "filePath", "file_path", "filename", "fileName", "name"]);
27834
27701
  const type = stringField3(record, ["type", "artifactType", "artifact_type", "format", "extension"]);
27835
- const title = stringField3(record, ["title", "label", "name", "filename", "fileName"]) ?? path27 ?? "artifact";
27702
+ const title = stringField3(record, ["title", "label", "name", "filename", "fileName"]) ?? path29 ?? "artifact";
27836
27703
  return {
27837
27704
  title,
27838
- path: path27,
27705
+ path: path29,
27839
27706
  type,
27840
27707
  downloadUrl: stringField3(record, ["downloadUrl", "download_url", "url", "href"])
27841
27708
  };
@@ -27869,21 +27736,21 @@ function normalizeArtifact(module, runId, value) {
27869
27736
  if (!record) {
27870
27737
  return null;
27871
27738
  }
27872
- const path27 = stringField3(record, ["path", "filePath", "file_path", "filename", "fileName", "name"]);
27739
+ const path29 = stringField3(record, ["path", "filePath", "file_path", "filename", "fileName", "name"]);
27873
27740
  const type = stringField3(record, ["type", "artifactType", "artifact_type"]);
27874
27741
  const format = stringField3(record, ["format", "fileFormat", "file_format", "extension"]) ?? type;
27875
- const title = stringField3(record, ["title", "label", "name", "filename", "fileName"]) ?? path27 ?? `${module} artifact`;
27742
+ const title = stringField3(record, ["title", "label", "name", "filename", "fileName"]) ?? path29 ?? `${module} artifact`;
27876
27743
  return {
27877
27744
  module,
27878
27745
  runId,
27879
27746
  title,
27880
- path: path27,
27747
+ path: path29,
27881
27748
  type,
27882
27749
  format,
27883
27750
  mimeType: stringField3(record, ["mimeType", "mime_type", "contentType", "content_type"]),
27884
27751
  sizeBytes: numberField2(record, ["sizeBytes", "size_bytes", "bytes"]),
27885
27752
  downloadUrl: stringField3(record, ["downloadUrl", "download_url", "url", "href"]),
27886
- previewKind: artifactPreviewKind(type, format, path27)
27753
+ previewKind: artifactPreviewKind(type, format, path29)
27887
27754
  };
27888
27755
  }
27889
27756
  function parseTomlScalar(raw) {
@@ -28113,9 +27980,9 @@ var WorkerHarnessClient = class {
28113
27980
  }
28114
27981
  return { baseUrl, apiKey };
28115
27982
  }
28116
- async fetchText(path27) {
27983
+ async fetchText(path29) {
28117
27984
  const config = this.requireHarnessConfig();
28118
- const response = await this.fetchImpl(`${config.baseUrl}${path27}`, {
27985
+ const response = await this.fetchImpl(`${config.baseUrl}${path29}`, {
28119
27986
  headers: {
28120
27987
  "x-api-key": config.apiKey
28121
27988
  }
@@ -28126,11 +27993,11 @@ var WorkerHarnessClient = class {
28126
27993
  }
28127
27994
  return { text: text2 };
28128
27995
  }
28129
- async fetchPayload(path27, init = {}) {
27996
+ async fetchPayload(path29, init = {}) {
28130
27997
  const config = this.requireHarnessConfig();
28131
27998
  const headers = new Headers(init.headers);
28132
27999
  headers.set("x-api-key", config.apiKey);
28133
- const response = await this.fetchImpl(`${config.baseUrl}${path27}`, {
28000
+ const response = await this.fetchImpl(`${config.baseUrl}${path29}`, {
28134
28001
  ...init,
28135
28002
  headers
28136
28003
  });
@@ -28144,9 +28011,9 @@ var WorkerHarnessClient = class {
28144
28011
  return { text: text2 };
28145
28012
  }
28146
28013
  }
28147
- async fetchBinary(path27) {
28014
+ async fetchBinary(path29) {
28148
28015
  const config = this.requireHarnessConfig();
28149
- const response = await this.fetchImpl(`${config.baseUrl}${path27}`, {
28016
+ const response = await this.fetchImpl(`${config.baseUrl}${path29}`, {
28150
28017
  headers: {
28151
28018
  "x-api-key": config.apiKey
28152
28019
  }
@@ -28990,16 +28857,16 @@ var HttpError = class extends Error {
28990
28857
  };
28991
28858
  function findRepoRoot(start = process.cwd()) {
28992
28859
  if (process.env.REMOTE_CODEX_REPO_ROOT) {
28993
- return path26.resolve(process.env.REMOTE_CODEX_REPO_ROOT);
28860
+ return path28.resolve(process.env.REMOTE_CODEX_REPO_ROOT);
28994
28861
  }
28995
- let current = path26.resolve(start);
28996
- while (current !== path26.dirname(current)) {
28997
- if (fs26.existsSync(path26.join(current, "pnpm-workspace.yaml")) && fs26.existsSync(path26.join(current, "scripts", "service-restart.mjs"))) {
28862
+ let current = path28.resolve(start);
28863
+ while (current !== path28.dirname(current)) {
28864
+ if (fs28.existsSync(path28.join(current, "pnpm-workspace.yaml")) && fs28.existsSync(path28.join(current, "scripts", "service-restart.mjs"))) {
28998
28865
  return current;
28999
28866
  }
29000
- current = path26.dirname(current);
28867
+ current = path28.dirname(current);
29001
28868
  }
29002
- return path26.resolve(process.cwd());
28869
+ return path28.resolve(process.cwd());
29003
28870
  }
29004
28871
  function createServiceLifecycle() {
29005
28872
  return {
@@ -29011,8 +28878,8 @@ function createServiceLifecycle() {
29011
28878
  });
29012
28879
  }
29013
28880
  const repoRoot = findRepoRoot();
29014
- const restartScript = path26.join(repoRoot, "scripts", "service-restart.mjs");
29015
- if (!fs26.existsSync(restartScript) || !fs26.existsSync(path26.join(repoRoot, "pnpm-workspace.yaml"))) {
28881
+ const restartScript = path28.join(repoRoot, "scripts", "service-restart.mjs");
28882
+ if (!fs28.existsSync(restartScript) || !fs28.existsSync(path28.join(repoRoot, "pnpm-workspace.yaml"))) {
29016
28883
  throw new HttpError(503, {
29017
28884
  code: "service_unavailable",
29018
28885
  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."
@@ -29055,7 +28922,7 @@ function buildApp(options = {}) {
29055
28922
  const shellService = options.shellService ?? new ShellSessionService(
29056
28923
  database.db,
29057
28924
  eventBus,
29058
- createTerminalShellBackend(options.env)
28925
+ options.shellBackend ?? createTerminalShellBackend(options.env)
29059
28926
  );
29060
28927
  const providerHostConfigService = new ProviderHostConfigService(
29061
28928
  agentRuntimes,