remote-codex 0.11.17 → 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,68 +23914,143 @@ 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;
24145
24051
  var WORKSPACE_FOLDER_DOWNLOAD_MAX_FILES = 300;
24052
+ var WORKSPACE_TREE_DIRECTORY_ENTRY_LIMIT = 400;
24053
+ var WORKSPACE_TREE_DIRECTORY_SCAN_LIMIT = 2e3;
24146
24054
  var WORKSPACE_TREE_IGNORED_NAMES = /* @__PURE__ */ new Set([
24147
24055
  ".git",
24148
24056
  "node_modules",
@@ -24172,7 +24080,7 @@ function toWorkspaceFileDto(file) {
24172
24080
  };
24173
24081
  }
24174
24082
  function languageForPath(filePath) {
24175
- const extension = path19.extname(filePath).slice(1).toLowerCase();
24083
+ const extension = path20.extname(filePath).slice(1).toLowerCase();
24176
24084
  switch (extension) {
24177
24085
  case "js":
24178
24086
  case "jsx":
@@ -24218,18 +24126,18 @@ function languageForPath(filePath) {
24218
24126
  }
24219
24127
  }
24220
24128
  function relativeWorkspacePath(rootPath, absPath) {
24221
- const relative = path19.relative(rootPath, absPath);
24222
- return relative === "" ? "" : relative.split(path19.sep).join("/");
24129
+ const relative = path20.relative(rootPath, absPath);
24130
+ return relative === "" ? "" : relative.split(path20.sep).join("/");
24223
24131
  }
24224
24132
  async function resolveWorkspaceItemPath(rootPath, relativePath = "") {
24225
- const candidate = path19.resolve(rootPath, relativePath || ".");
24133
+ const candidate = path20.resolve(rootPath, relativePath || ".");
24226
24134
  const comparable = await assertPathWithinRoot(rootPath, candidate);
24227
24135
  return comparable;
24228
24136
  }
24229
- async function buildWorkspaceTreeNode(rootPath, absPath, depth = 0) {
24230
- const stats = await fs18.stat(absPath);
24137
+ async function buildWorkspaceTreeNode(rootPath, absPath) {
24138
+ const stats = await fs19.stat(absPath);
24231
24139
  const relativePath = relativeWorkspacePath(rootPath, absPath);
24232
- const name = relativePath ? path19.basename(absPath) : path19.basename(rootPath);
24140
+ const name = relativePath ? path20.basename(absPath) : path20.basename(rootPath);
24233
24141
  if (!stats.isDirectory()) {
24234
24142
  return {
24235
24143
  name,
@@ -24242,18 +24150,36 @@ async function buildWorkspaceTreeNode(rootPath, absPath, depth = 0) {
24242
24150
  name,
24243
24151
  path: relativePath,
24244
24152
  kind: "directory",
24153
+ childrenLoaded: true,
24245
24154
  children: []
24246
24155
  };
24247
- if (depth >= 6) {
24248
- return node;
24249
- }
24250
- let entries;
24156
+ const visible = [];
24251
24157
  try {
24252
- entries = await fs18.readdir(absPath, { withFileTypes: true });
24158
+ const directory = await fs19.opendir(absPath);
24159
+ let scanned = 0;
24160
+ for await (const entry of directory) {
24161
+ scanned += 1;
24162
+ if (scanned > WORKSPACE_TREE_DIRECTORY_SCAN_LIMIT) {
24163
+ node.truncated = true;
24164
+ break;
24165
+ }
24166
+ if (entry.name.startsWith(".") || WORKSPACE_TREE_IGNORED_NAMES.has(entry.name)) {
24167
+ continue;
24168
+ }
24169
+ if (!entry.isDirectory() && !entry.isFile()) {
24170
+ continue;
24171
+ }
24172
+ if (visible.length >= WORKSPACE_TREE_DIRECTORY_ENTRY_LIMIT) {
24173
+ node.truncated = true;
24174
+ break;
24175
+ }
24176
+ visible.push(entry);
24177
+ }
24253
24178
  } catch {
24254
24179
  return node;
24255
24180
  }
24256
- const visible = entries.filter((entry) => !entry.name.startsWith(".")).filter((entry) => !WORKSPACE_TREE_IGNORED_NAMES.has(entry.name)).sort((left, right) => {
24181
+ node.hasChildren = visible.length > 0;
24182
+ visible.sort((left, right) => {
24257
24183
  if (left.isDirectory() && !right.isDirectory()) {
24258
24184
  return -1;
24259
24185
  }
@@ -24261,15 +24187,28 @@ async function buildWorkspaceTreeNode(rootPath, absPath, depth = 0) {
24261
24187
  return 1;
24262
24188
  }
24263
24189
  return left.name.localeCompare(right.name);
24264
- }).slice(0, 400);
24190
+ });
24265
24191
  node.children = (await Promise.all(
24266
24192
  visible.map(async (entry) => {
24267
- const childPath = path19.join(absPath, entry.name);
24193
+ const childPath = path20.join(absPath, entry.name);
24268
24194
  try {
24269
- if (!entry.isDirectory() && !entry.isFile()) {
24270
- return null;
24195
+ const childRelativePath = relativeWorkspacePath(rootPath, childPath);
24196
+ if (entry.isDirectory()) {
24197
+ return {
24198
+ name: entry.name,
24199
+ path: childRelativePath,
24200
+ kind: "directory",
24201
+ hasChildren: true,
24202
+ childrenLoaded: false
24203
+ };
24271
24204
  }
24272
- return await buildWorkspaceTreeNode(rootPath, childPath, depth + 1);
24205
+ const childStats = await fs19.stat(childPath);
24206
+ return {
24207
+ name: entry.name,
24208
+ path: childRelativePath,
24209
+ kind: "file",
24210
+ size: childStats.size
24211
+ };
24273
24212
  } catch {
24274
24213
  return null;
24275
24214
  }
@@ -24277,73 +24216,8 @@ async function buildWorkspaceTreeNode(rootPath, absPath, depth = 0) {
24277
24216
  )).filter((child) => child !== null);
24278
24217
  return node;
24279
24218
  }
24280
- function requireWorkspaceRecord(app, workspaceId) {
24281
- const record = getWorkspaceRecordById(app.services.database.db, workspaceId);
24282
- if (!record) {
24283
- throw new HttpError(404, {
24284
- code: "not_found",
24285
- message: "Workspace was not found."
24286
- });
24287
- }
24288
- return record;
24289
- }
24290
- function artifactRoot(record) {
24291
- return path19.join(record.absPath, ".remote-codex", "artifacts");
24292
- }
24293
- function artifactFilePath(record, artifactId) {
24294
- return path19.join(artifactRoot(record), artifactId, "artifact.bin");
24295
- }
24296
- function artifactMetadataPath(record, artifactId) {
24297
- return path19.join(artifactRoot(record), artifactId, "metadata.json");
24298
- }
24299
- function safeArtifactFileName(value) {
24300
- return path19.basename(value).replace(/[^a-zA-Z0-9_. -]/g, "_") || "artifact.bin";
24301
- }
24302
- function artifactIdFromName(name) {
24303
- const base = path19.basename(name).replace(/[^a-zA-Z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 96);
24304
- return `${base || "artifact"}-${Date.now().toString(36)}`;
24305
- }
24306
- async function readArtifactMetadata(record, artifactId) {
24307
- try {
24308
- const raw = await fs18.readFile(artifactMetadataPath(record, artifactId), "utf8");
24309
- return JSON.parse(raw);
24310
- } catch (error) {
24311
- if (error.code === "ENOENT") {
24312
- throw new HttpError(404, {
24313
- code: "not_found",
24314
- message: "Workspace artifact was not found."
24315
- });
24316
- }
24317
- throw error;
24318
- }
24319
- }
24320
- async function listWorkspaceArtifacts(record) {
24321
- let entries;
24322
- try {
24323
- entries = await fs18.readdir(artifactRoot(record));
24324
- } catch (error) {
24325
- if (error.code === "ENOENT") {
24326
- return [];
24327
- }
24328
- throw error;
24329
- }
24330
- const artifacts = [];
24331
- for (const entry of entries) {
24332
- if (!workspaceArtifactIdSchema.safeParse(entry).success) {
24333
- continue;
24334
- }
24335
- try {
24336
- artifacts.push(await readArtifactMetadata(record, entry));
24337
- } catch (error) {
24338
- if (!(error instanceof HttpError && error.statusCode === 404)) {
24339
- throw error;
24340
- }
24341
- }
24342
- }
24343
- return artifacts.sort((left, right) => right.createdAt.localeCompare(left.createdAt));
24344
- }
24345
24219
  function contentTypeForPath(filePath) {
24346
- switch (path19.extname(filePath).slice(1).toLowerCase()) {
24220
+ switch (path20.extname(filePath).slice(1).toLowerCase()) {
24347
24221
  case "png":
24348
24222
  return "image/png";
24349
24223
  case "jpg":
@@ -24373,15 +24247,15 @@ function contentTypeForPath(filePath) {
24373
24247
  }
24374
24248
  }
24375
24249
  async function collectFolderZipEntries(rootPath, folderPath) {
24376
- const folderName = path19.basename(folderPath) || "workspace-folder";
24250
+ const folderName = path20.basename(folderPath) || "workspace-folder";
24377
24251
  const entries = [];
24378
24252
  let totalBytes = 0;
24379
24253
  const pending = [folderPath];
24380
24254
  while (pending.length > 0) {
24381
24255
  const current = pending.pop();
24382
- const children = await fs18.readdir(current, { withFileTypes: true });
24256
+ const children = await fs19.readdir(current, { withFileTypes: true });
24383
24257
  for (const child of children) {
24384
- 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)));
24385
24259
  if (child.isDirectory()) {
24386
24260
  pending.push(childPath);
24387
24261
  continue;
@@ -24389,7 +24263,7 @@ async function collectFolderZipEntries(rootPath, folderPath) {
24389
24263
  if (!child.isFile()) {
24390
24264
  continue;
24391
24265
  }
24392
- const stats = await fs18.stat(childPath);
24266
+ const stats = await fs19.stat(childPath);
24393
24267
  totalBytes += stats.size;
24394
24268
  entries.push({
24395
24269
  absPath: childPath,
@@ -24440,8 +24314,8 @@ async function createFolderZipFile(rootPath, folderPath) {
24440
24314
  const centralParts = [];
24441
24315
  let offset = 0;
24442
24316
  for (const entry of entries) {
24443
- const data = await fs18.readFile(entry.absPath);
24444
- 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");
24445
24319
  const checksum = crc32(data);
24446
24320
  const { dosDate, dosTime } = zipDosDateTime(entry.updatedAt);
24447
24321
  const localHeader = Buffer.alloc(30);
@@ -24488,19 +24362,19 @@ async function createFolderZipFile(rootPath, folderPath) {
24488
24362
  endRecord.writeUInt32LE(centralSize, 12);
24489
24363
  endRecord.writeUInt32LE(offset, 16);
24490
24364
  endRecord.writeUInt16LE(0, 20);
24491
- const tempDir = await fs18.mkdtemp(path19.join(os4.tmpdir(), "remote-codex-folder-download-"));
24492
- const zipPath = path19.join(tempDir, `${path19.basename(folderPath) || "workspace-folder"}.zip`);
24493
- 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]));
24494
24368
  return { zipPath, tempDir };
24495
24369
  }
24496
24370
  function cleanupTemporaryZip(zipPath, tempDir) {
24497
24371
  return async () => {
24498
- await fs18.rm(zipPath, { force: true }).catch(() => void 0);
24499
- 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);
24500
24374
  };
24501
24375
  }
24502
24376
  function sanitizeUploadFilename(filename) {
24503
- const baseName = path19.basename(filename?.trim() || "upload");
24377
+ const baseName = path20.basename(filename?.trim() || "upload");
24504
24378
  if (!baseName || baseName === "." || baseName === "..") {
24505
24379
  return "upload";
24506
24380
  }
@@ -24512,7 +24386,7 @@ function inferGitRepoName(gitUrl) {
24512
24386
  const normalized = withoutQuery.replace(/[\\/]+$/, "");
24513
24387
  const rawName = normalized.split(/[/:]/).filter(Boolean).at(-1) ?? "";
24514
24388
  const repoName = rawName.endsWith(".git") ? rawName.slice(0, -4) : rawName;
24515
- if (!repoName || repoName === "." || repoName === ".." || repoName.includes(path19.sep)) {
24389
+ if (!repoName || repoName === "." || repoName === ".." || repoName.includes(path20.sep)) {
24516
24390
  throw new HttpError(400, {
24517
24391
  code: "bad_request",
24518
24392
  message: "Unable to infer a target directory from the Git URL."
@@ -24522,7 +24396,7 @@ function inferGitRepoName(gitUrl) {
24522
24396
  }
24523
24397
  async function pathExists4(absPath) {
24524
24398
  try {
24525
- await fs18.stat(absPath);
24399
+ await fs19.stat(absPath);
24526
24400
  return true;
24527
24401
  } catch (error) {
24528
24402
  if (error.code === "ENOENT") {
@@ -24570,6 +24444,69 @@ function cloneRepository(gitUrl, targetPath) {
24570
24444
  });
24571
24445
  });
24572
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
+ }
24573
24510
  async function registerWorkspaceRoutes(app) {
24574
24511
  app.get("/api/workspaces", async () => {
24575
24512
  const records = listWorkspaceRecords(app.services.database.db);
@@ -24577,7 +24514,7 @@ async function registerWorkspaceRoutes(app) {
24577
24514
  });
24578
24515
  app.get("/api/workspaces/tree", async (request) => {
24579
24516
  const query = treeQuerySchema.parse(request.query);
24580
- 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;
24581
24518
  const tree = await readWorkspaceTree({
24582
24519
  rootPath: app.services.config.workspaceRoot,
24583
24520
  targetPath: requestedPath,
@@ -24604,7 +24541,7 @@ async function registerWorkspaceRoutes(app) {
24604
24541
  const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
24605
24542
  const query = workspaceFileQuerySchema.parse(request.query);
24606
24543
  const record = requireWorkspaceRecord(app, params.id);
24607
- const rootPath = await fs18.realpath(record.absPath);
24544
+ const rootPath = await fs20.realpath(record.absPath);
24608
24545
  const targetPath = await resolveWorkspaceItemPath(rootPath, query.path);
24609
24546
  return buildWorkspaceTreeNode(rootPath, targetPath);
24610
24547
  });
@@ -24624,9 +24561,9 @@ async function registerWorkspaceRoutes(app) {
24624
24561
  const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
24625
24562
  const query = workspacePreviewQuerySchema.parse(request.query);
24626
24563
  const record = requireWorkspaceRecord(app, params.id);
24627
- const rootPath = await fs18.realpath(record.absPath);
24564
+ const rootPath = await fs20.realpath(record.absPath);
24628
24565
  const filePath = await resolveWorkspaceItemPath(rootPath, query.path);
24629
- const stats = await fs18.stat(filePath);
24566
+ const stats = await fs20.stat(filePath);
24630
24567
  if (!stats.isFile()) {
24631
24568
  throw new HttpError(400, {
24632
24569
  code: "bad_request",
@@ -24635,7 +24572,7 @@ async function registerWorkspaceRoutes(app) {
24635
24572
  }
24636
24573
  const offset = query.offset ?? 0;
24637
24574
  const limit = query.limit ?? PREVIEW_DEFAULT_LIMIT_BYTES;
24638
- const handle = await fs18.open(filePath, "r");
24575
+ const handle = await fs20.open(filePath, "r");
24639
24576
  try {
24640
24577
  const length = Math.min(limit, Math.max(0, stats.size - offset));
24641
24578
  const buffer = Buffer.alloc(length);
@@ -24643,7 +24580,7 @@ async function registerWorkspaceRoutes(app) {
24643
24580
  const nextOffset = offset + read.bytesRead;
24644
24581
  return {
24645
24582
  path: relativeWorkspacePath(rootPath, filePath),
24646
- name: path19.basename(filePath),
24583
+ name: path21.basename(filePath),
24647
24584
  content: buffer.subarray(0, read.bytesRead).toString("utf8"),
24648
24585
  language: languageForPath(filePath),
24649
24586
  size: stats.size,
@@ -24658,9 +24595,9 @@ async function registerWorkspaceRoutes(app) {
24658
24595
  const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
24659
24596
  const query = workspacePreviewQuerySchema.pick({ path: true }).parse(request.query);
24660
24597
  const record = requireWorkspaceRecord(app, params.id);
24661
- const rootPath = await fs18.realpath(record.absPath);
24598
+ const rootPath = await fs20.realpath(record.absPath);
24662
24599
  const filePath = await resolveWorkspaceItemPath(rootPath, query.path);
24663
- const stats = await fs18.stat(filePath);
24600
+ const stats = await fs20.stat(filePath);
24664
24601
  if (!stats.isFile()) {
24665
24602
  throw new HttpError(400, {
24666
24603
  code: "bad_request",
@@ -24668,18 +24605,18 @@ async function registerWorkspaceRoutes(app) {
24668
24605
  });
24669
24606
  }
24670
24607
  reply.header("content-type", contentTypeForPath(filePath));
24671
- return reply.send(Readable.from(await fs18.readFile(filePath)));
24608
+ return reply.send(Readable.from(await fs20.readFile(filePath)));
24672
24609
  });
24673
24610
  app.get("/api/workspaces/:id/files/download", async (request, reply) => {
24674
24611
  const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
24675
24612
  const query = workspaceFileQuerySchema.parse(request.query);
24676
24613
  const record = requireWorkspaceRecord(app, params.id);
24677
- const rootPath = await fs18.realpath(record.absPath);
24614
+ const rootPath = await fs20.realpath(record.absPath);
24678
24615
  const itemPath = await resolveWorkspaceItemPath(rootPath, query.path);
24679
- const stats = await fs18.stat(itemPath);
24616
+ const stats = await fs20.stat(itemPath);
24680
24617
  if (stats.isDirectory()) {
24681
24618
  const { zipPath, tempDir } = await createFolderZipFile(rootPath, itemPath);
24682
- const filename2 = `${path19.basename(itemPath) || "workspace-folder"}.zip`;
24619
+ const filename2 = `${path21.basename(itemPath) || "workspace-folder"}.zip`;
24683
24620
  const cleanup = cleanupTemporaryZip(zipPath, tempDir);
24684
24621
  reply.raw.once("finish", () => void cleanup());
24685
24622
  reply.raw.once("close", () => void cleanup());
@@ -24695,18 +24632,18 @@ async function registerWorkspaceRoutes(app) {
24695
24632
  message: "Only file and folder downloads are supported from this endpoint."
24696
24633
  });
24697
24634
  }
24698
- const filename = path19.basename(itemPath);
24635
+ const filename = path21.basename(itemPath);
24699
24636
  reply.header("content-type", contentTypeForPath(itemPath)).header(
24700
24637
  "content-disposition",
24701
24638
  `attachment; filename="${filename}"; filename*=UTF-8''${encodeURIComponent(filename)}`
24702
24639
  );
24703
- return reply.send(Readable.from(await fs18.readFile(itemPath)));
24640
+ return reply.send(Readable.from(await fs20.readFile(itemPath)));
24704
24641
  });
24705
24642
  app.post("/api/workspaces/:id/files/upload", async (request) => {
24706
24643
  requireWorkerScope(request, "file:write");
24707
24644
  const params = z6.object({ id: z6.string().uuid() }).parse(request.params);
24708
24645
  const record = requireWorkspaceRecord(app, params.id);
24709
- const rootPath = await fs18.realpath(record.absPath);
24646
+ const rootPath = await fs20.realpath(record.absPath);
24710
24647
  const uploadRequest = request;
24711
24648
  if (!uploadRequest.isMultipart()) {
24712
24649
  throw new HttpError(400, {
@@ -24761,7 +24698,7 @@ async function registerWorkspaceRoutes(app) {
24761
24698
  kind: "file",
24762
24699
  file: {
24763
24700
  path: file.path,
24764
- name: path19.basename(file.path),
24701
+ name: path21.basename(file.path),
24765
24702
  size: file.size
24766
24703
  }
24767
24704
  };
@@ -24797,36 +24734,14 @@ async function registerWorkspaceRoutes(app) {
24797
24734
  const record = requireWorkspaceRecord(app, params.id);
24798
24735
  const artifactId = body.id ?? artifactIdFromName(body.name);
24799
24736
  const content = Buffer.from(body.contentBase64, "base64");
24800
- if (content.length === 0) {
24801
- throw new HttpError(400, {
24802
- code: "bad_request",
24803
- message: "Artifact content must not be empty."
24804
- });
24805
- }
24806
- const dir = path19.dirname(artifactFilePath(record, artifactId));
24807
- await fs18.mkdir(dir, { recursive: true, mode: 448 });
24808
- const filePath = artifactFilePath(record, artifactId);
24809
- await fs18.writeFile(filePath, content, { flag: "wx" }).catch((error) => {
24810
- if (error.code === "EEXIST") {
24811
- throw new HttpError(409, {
24812
- code: "conflict",
24813
- message: "Workspace artifact already exists."
24814
- });
24815
- }
24816
- throw error;
24817
- });
24818
- const now = (/* @__PURE__ */ new Date()).toISOString();
24819
- const artifact = {
24820
- id: artifactId,
24821
- workspaceId: record.id,
24822
- name: safeArtifactFileName(body.name),
24737
+ const artifact = await createWorkspaceArtifact({
24738
+ record,
24739
+ artifactId,
24740
+ name: body.name,
24823
24741
  mediaType: body.mediaType,
24824
- size: content.length,
24825
- createdAt: now,
24826
- updatedAt: now,
24827
- metadata: body.metadata ?? {}
24828
- };
24829
- await fs18.writeFile(artifactMetadataPath(record, artifactId), JSON.stringify(artifact, null, 2));
24742
+ content,
24743
+ ...body.metadata !== void 0 ? { metadata: body.metadata } : {}
24744
+ });
24830
24745
  return { artifact };
24831
24746
  });
24832
24747
  app.get("/api/workspaces/:id/artifacts", async (request) => {
@@ -24839,25 +24754,14 @@ async function registerWorkspaceRoutes(app) {
24839
24754
  requireWorkerScope(request, "artifact:read");
24840
24755
  const params = z6.object({ id: z6.string().uuid(), artifactId: workspaceArtifactIdSchema }).parse(request.params);
24841
24756
  const record = requireWorkspaceRecord(app, params.id);
24842
- return { artifact: await readArtifactMetadata(record, params.artifactId) };
24757
+ return { artifact: await readWorkspaceArtifactMetadata(record, params.artifactId) };
24843
24758
  });
24844
24759
  app.get("/api/workspaces/:id/artifacts/:artifactId/download", async (request, reply) => {
24845
24760
  requireWorkerScope(request, "artifact:read");
24846
24761
  const params = z6.object({ id: z6.string().uuid(), artifactId: workspaceArtifactIdSchema }).parse(request.params);
24847
24762
  const record = requireWorkspaceRecord(app, params.id);
24848
- const artifact = await readArtifactMetadata(record, params.artifactId);
24849
- let content;
24850
- try {
24851
- content = await fs18.readFile(artifactFilePath(record, params.artifactId));
24852
- } catch (error) {
24853
- if (error.code === "ENOENT") {
24854
- throw new HttpError(404, {
24855
- code: "not_found",
24856
- message: "Workspace artifact content was not found."
24857
- });
24858
- }
24859
- throw error;
24860
- }
24763
+ const artifact = await readWorkspaceArtifactMetadata(record, params.artifactId);
24764
+ const content = await readWorkspaceArtifactContent(record, params.artifactId);
24861
24765
  reply.header("content-type", artifact.mediaType).header("content-length", String(content.length)).header("content-disposition", `attachment; filename="${artifact.name.replace(/"/g, "")}"`);
24862
24766
  return reply.send(content);
24863
24767
  });
@@ -24865,11 +24769,7 @@ async function registerWorkspaceRoutes(app) {
24865
24769
  requireWorkerScope(request, "artifact:write");
24866
24770
  const params = z6.object({ id: z6.string().uuid(), artifactId: workspaceArtifactIdSchema }).parse(request.params);
24867
24771
  const record = requireWorkspaceRecord(app, params.id);
24868
- const artifact = await readArtifactMetadata(record, params.artifactId);
24869
- await fs18.rm(path19.dirname(artifactFilePath(record, params.artifactId)), {
24870
- recursive: true,
24871
- force: true
24872
- });
24772
+ const artifact = await deleteWorkspaceArtifact(record, params.artifactId);
24873
24773
  return { deleted: true, artifact };
24874
24774
  });
24875
24775
  app.post("/api/workspaces", async (request) => {
@@ -24881,7 +24781,7 @@ async function registerWorkspaceRoutes(app) {
24881
24781
  let validated;
24882
24782
  if ("gitUrl" in body) {
24883
24783
  const repoName = inferGitRepoName(body.gitUrl);
24884
- const targetPath = path19.join(settings.devHome, repoName);
24784
+ const targetPath = path21.join(settings.devHome, repoName);
24885
24785
  if (await pathExists4(targetPath)) {
24886
24786
  throw new HttpError(409, {
24887
24787
  code: "conflict",
@@ -25144,8 +25044,8 @@ async function registerAuthRoutes(app) {
25144
25044
  }
25145
25045
 
25146
25046
  // src/provider-host-config-service.ts
25147
- import fs19 from "fs/promises";
25148
- import path20 from "path";
25047
+ import fs21 from "fs/promises";
25048
+ import path22 from "path";
25149
25049
  import { randomUUID as randomUUID4 } from "crypto";
25150
25050
  function providerError(message, statusCode = 404) {
25151
25051
  const error = new Error(message);
@@ -25153,23 +25053,23 @@ function providerError(message, statusCode = 404) {
25153
25053
  return error;
25154
25054
  }
25155
25055
  function resolveProviderHostFilePath(providerHome, name) {
25156
- return path20.join(providerHome, name);
25056
+ return path22.join(providerHome, name);
25157
25057
  }
25158
25058
  function resolveArchiveRoot(providerHome) {
25159
- return path20.join(providerHome, "supervisor-config-archives");
25059
+ return path22.join(providerHome, "supervisor-config-archives");
25160
25060
  }
25161
25061
  function resolveArchiveIndexPath(providerHome) {
25162
- return path20.join(resolveArchiveRoot(providerHome), "index.json");
25062
+ return path22.join(resolveArchiveRoot(providerHome), "index.json");
25163
25063
  }
25164
25064
  function resolveArchivePath(providerHome, archiveId) {
25165
- return path20.join(resolveArchiveRoot(providerHome), archiveId);
25065
+ return path22.join(resolveArchiveRoot(providerHome), archiveId);
25166
25066
  }
25167
25067
  function defaultArchiveLabel(createdAt) {
25168
25068
  return `Backup ${createdAt.replace("T", " ").replace(/\.\d{3}Z$/, " UTC")}`;
25169
25069
  }
25170
25070
  async function readArchiveIndex(providerHome) {
25171
25071
  try {
25172
- const raw = await fs19.readFile(resolveArchiveIndexPath(providerHome), "utf8");
25072
+ const raw = await fs21.readFile(resolveArchiveIndexPath(providerHome), "utf8");
25173
25073
  const parsed = JSON.parse(raw);
25174
25074
  return {
25175
25075
  archives: Array.isArray(parsed.archives) ? parsed.archives : []
@@ -25183,8 +25083,8 @@ async function readArchiveIndex(providerHome) {
25183
25083
  }
25184
25084
  async function writeArchiveIndex(providerHome, index) {
25185
25085
  const root = resolveArchiveRoot(providerHome);
25186
- await fs19.mkdir(root, { recursive: true });
25187
- await fs19.writeFile(
25086
+ await fs21.mkdir(root, { recursive: true });
25087
+ await fs21.writeFile(
25188
25088
  resolveArchiveIndexPath(providerHome),
25189
25089
  `${JSON.stringify(index, null, 2)}
25190
25090
  `,
@@ -25255,7 +25155,7 @@ var ProviderHostConfigService = class {
25255
25155
  const fileName = this.assertHostFile(provider2, name);
25256
25156
  const filePath = resolveProviderHostFilePath(providerHome, fileName);
25257
25157
  try {
25258
- const content = await fs19.readFile(filePath, "utf8");
25158
+ const content = await fs21.readFile(filePath, "utf8");
25259
25159
  return {
25260
25160
  name: fileName,
25261
25161
  path: filePath,
@@ -25278,8 +25178,8 @@ var ProviderHostConfigService = class {
25278
25178
  const providerHome = this.providerHome(provider2);
25279
25179
  const fileName = this.assertHostFile(provider2, name);
25280
25180
  const filePath = resolveProviderHostFilePath(providerHome, fileName);
25281
- await fs19.mkdir(path20.dirname(filePath), { recursive: true });
25282
- await fs19.writeFile(filePath, input.content, "utf8");
25181
+ await fs21.mkdir(path22.dirname(filePath), { recursive: true });
25182
+ await fs21.writeFile(filePath, input.content, "utf8");
25283
25183
  return this.readFile(provider2, fileName);
25284
25184
  }
25285
25185
  async listArchives(provider2) {
@@ -25305,7 +25205,7 @@ var ProviderHostConfigService = class {
25305
25205
  }
25306
25206
  ])
25307
25207
  );
25308
- await fs19.mkdir(archivePath, { recursive: true });
25208
+ await fs21.mkdir(archivePath, { recursive: true });
25309
25209
  for (const name of fileNames) {
25310
25210
  const hostFile = await this.readFile(provider2, name);
25311
25211
  files[name] = {
@@ -25313,7 +25213,7 @@ var ProviderHostConfigService = class {
25313
25213
  exists: hostFile.exists
25314
25214
  };
25315
25215
  if (hostFile.exists) {
25316
- await fs19.writeFile(path20.join(archivePath, name), hostFile.content, "utf8");
25216
+ await fs21.writeFile(path22.join(archivePath, name), hostFile.content, "utf8");
25317
25217
  }
25318
25218
  }
25319
25219
  const archive = {
@@ -25349,14 +25249,14 @@ var ProviderHostConfigService = class {
25349
25249
  const fileNames = this.archiveFileNames(provider2);
25350
25250
  const { archive } = await findArchiveOrThrow(providerHome, id);
25351
25251
  const archivePath = resolveArchivePath(providerHome, archive.id);
25352
- await fs19.mkdir(providerHome, { recursive: true });
25252
+ await fs21.mkdir(providerHome, { recursive: true });
25353
25253
  for (const name of fileNames) {
25354
25254
  const hostPath = resolveProviderHostFilePath(providerHome, name);
25355
25255
  if (archive.files[name]?.exists) {
25356
- const content = await fs19.readFile(path20.join(archivePath, name), "utf8");
25357
- await fs19.writeFile(hostPath, content, "utf8");
25256
+ const content = await fs21.readFile(path22.join(archivePath, name), "utf8");
25257
+ await fs21.writeFile(hostPath, content, "utf8");
25358
25258
  } else {
25359
- await fs19.rm(hostPath, { force: true });
25259
+ await fs21.rm(hostPath, { force: true });
25360
25260
  }
25361
25261
  }
25362
25262
  await runtime.stop();
@@ -25369,12 +25269,12 @@ var ProviderHostConfigService = class {
25369
25269
  };
25370
25270
 
25371
25271
  // src/shell/shell-session-service.ts
25372
- import fs21 from "fs/promises";
25272
+ import fs23 from "fs/promises";
25373
25273
 
25374
25274
  // src/shell/shell-prompt.ts
25375
- import fs20 from "fs/promises";
25275
+ import fs22 from "fs/promises";
25376
25276
  import os5 from "os";
25377
- import path21 from "path";
25277
+ import path23 from "path";
25378
25278
  function basenameFromPath2(filePath) {
25379
25279
  if (!filePath) {
25380
25280
  return "";
@@ -25383,7 +25283,7 @@ function basenameFromPath2(filePath) {
25383
25283
  if (!normalized) {
25384
25284
  return "";
25385
25285
  }
25386
- return path21.basename(normalized) || normalized;
25286
+ return path23.basename(normalized) || normalized;
25387
25287
  }
25388
25288
  function isInteractiveShellCommand(command) {
25389
25289
  const normalized = (command ?? "").trim().toLowerCase();
@@ -25544,11 +25444,11 @@ function buildShellPromptInitScriptContents(command) {
25544
25444
  async function ensureShellPromptInitScript(command) {
25545
25445
  const normalized = command.trim().toLowerCase();
25546
25446
  const extension = normalized === "zsh" ? "zsh" : "sh";
25547
- const filePath = path21.join(
25447
+ const filePath = path23.join(
25548
25448
  os5.tmpdir(),
25549
25449
  `remote-codex-shell-prompt.${extension}`
25550
25450
  );
25551
- await fs20.writeFile(filePath, buildShellPromptInitScriptContents(command), "utf8");
25451
+ await fs22.writeFile(filePath, buildShellPromptInitScriptContents(command), "utf8");
25552
25452
  return filePath;
25553
25453
  }
25554
25454
  async function buildShellPromptInitCommand(command, options = {}) {
@@ -25564,7 +25464,7 @@ clear
25564
25464
  // src/shell/shell-session-service.ts
25565
25465
  async function pathExists5(filePath) {
25566
25466
  try {
25567
- await fs21.access(filePath);
25467
+ await fs23.access(filePath);
25568
25468
  return true;
25569
25469
  } catch {
25570
25470
  return false;
@@ -26222,8 +26122,8 @@ var builtinPlugins = [
26222
26122
  ];
26223
26123
 
26224
26124
  // src/plugins/plugin-service.ts
26225
- import fs22 from "fs/promises";
26226
- import path22 from "path";
26125
+ import fs24 from "fs/promises";
26126
+ import path24 from "path";
26227
26127
  var MANAGED_CODEX_MCP_BEGIN = "# BEGIN remote-codex managed plugin MCP servers";
26228
26128
  var MANAGED_CODEX_MCP_END = "# END remote-codex managed plugin MCP servers";
26229
26129
  var REMOTE_CODEX_MOLECULE_MCP_TOOL_NAME = "remote_codex_render_molecule";
@@ -26235,7 +26135,7 @@ function normalizeManagedCommand(server, repoRoot) {
26235
26135
  if (server.name === "remote_codex_plugins") {
26236
26136
  return {
26237
26137
  command: process.execPath,
26238
- args: [path22.join(repoRoot, "bin", "remote-codex-plugin-mcp.mjs")]
26138
+ args: [path24.join(repoRoot, "bin", "remote-codex-plugin-mcp.mjs")]
26239
26139
  };
26240
26140
  }
26241
26141
  return {
@@ -26422,10 +26322,10 @@ var PluginService = class {
26422
26322
  if (!input.codexHome) {
26423
26323
  return;
26424
26324
  }
26425
- const configPath = path22.join(input.codexHome, "config.toml");
26325
+ const configPath = path24.join(input.codexHome, "config.toml");
26426
26326
  let current = "";
26427
26327
  try {
26428
- current = await fs22.readFile(configPath, "utf8");
26328
+ current = await fs24.readFile(configPath, "utf8");
26429
26329
  } catch (error) {
26430
26330
  if (error.code !== "ENOENT") {
26431
26331
  throw error;
@@ -26440,8 +26340,8 @@ var PluginService = class {
26440
26340
  if (next === current) {
26441
26341
  return;
26442
26342
  }
26443
- await fs22.mkdir(path22.dirname(configPath), { recursive: true });
26444
- await fs22.writeFile(configPath, next, "utf8");
26343
+ await fs24.mkdir(path24.dirname(configPath), { recursive: true });
26344
+ await fs24.writeFile(configPath, next, "utf8");
26445
26345
  }
26446
26346
  async importPlugin(input) {
26447
26347
  const manifestInput = input.manifest ?? (input.manifestUrl ? await this.fetchManifestUrl(input.manifestUrl) : this.parseManifestJson(input.manifestJson));
@@ -26680,8 +26580,8 @@ var PluginSettingsStore = class {
26680
26580
  };
26681
26581
 
26682
26582
  // src/worker-bootstrap.ts
26683
- import fs23 from "fs/promises";
26684
- import path23 from "path";
26583
+ import fs25 from "fs/promises";
26584
+ import path25 from "path";
26685
26585
  function trimTrailingSlash(value) {
26686
26586
  return value.replace(/\/+$/, "");
26687
26587
  }
@@ -26692,9 +26592,9 @@ function tomlString(value) {
26692
26592
  return JSON.stringify(value);
26693
26593
  }
26694
26594
  async function writePrivateFile(filePath, content) {
26695
- await fs23.mkdir(path23.dirname(filePath), { recursive: true, mode: 448 });
26696
- await fs23.writeFile(filePath, content, { encoding: "utf8", mode: 384 });
26697
- 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);
26698
26598
  }
26699
26599
  async function configureWorkerProviderGateway(config) {
26700
26600
  if (config.runtimeRole !== "worker" || !config.llmGatewayBaseUrl || !config.llmGatewayToken) {
@@ -26708,7 +26608,7 @@ async function configureWorkerProviderGateway(config) {
26708
26608
  process.env.ANTHROPIC_BASE_URL = anthropicBaseUrl;
26709
26609
  if (config.agentProviders.codex.enabled) {
26710
26610
  await writePrivateFile(
26711
- path23.join(config.agentProviders.codex.home, "config.toml"),
26611
+ path25.join(config.agentProviders.codex.home, "config.toml"),
26712
26612
  [
26713
26613
  'model_provider = "sub2api"',
26714
26614
  'forced_login_method = "api"',
@@ -26724,7 +26624,7 @@ async function configureWorkerProviderGateway(config) {
26724
26624
  ].join("\n")
26725
26625
  );
26726
26626
  await writePrivateFile(
26727
- path23.join(config.agentProviders.codex.home, "auth.json"),
26627
+ path25.join(config.agentProviders.codex.home, "auth.json"),
26728
26628
  `${JSON.stringify(
26729
26629
  {
26730
26630
  OPENAI_API_KEY: config.llmGatewayToken
@@ -26737,7 +26637,7 @@ async function configureWorkerProviderGateway(config) {
26737
26637
  }
26738
26638
  if (config.agentProviders.claude.enabled) {
26739
26639
  await writePrivateFile(
26740
- path23.join(config.agentProviders.claude.home, "settings.json"),
26640
+ path25.join(config.agentProviders.claude.home, "settings.json"),
26741
26641
  `${JSON.stringify(
26742
26642
  {
26743
26643
  env: {
@@ -26753,7 +26653,7 @@ async function configureWorkerProviderGateway(config) {
26753
26653
  }
26754
26654
  if (config.agentProviders.opencode.enabled) {
26755
26655
  await writePrivateFile(
26756
- path23.join(config.agentProviders.opencode.home, "opencode.json"),
26656
+ path25.join(config.agentProviders.opencode.home, "opencode.json"),
26757
26657
  `${JSON.stringify(
26758
26658
  {
26759
26659
  provider: {
@@ -26800,27 +26700,27 @@ var BackendPluginHost = class {
26800
26700
  };
26801
26701
 
26802
26702
  // src/shell/pty-shell-backend.ts
26803
- import path24 from "path";
26703
+ import path26 from "path";
26804
26704
  import { spawn as spawn5 } from "@homebridge/node-pty-prebuilt-multiarch";
26805
26705
 
26806
26706
  // src/shell/default-shell.ts
26807
- import fs24 from "fs";
26707
+ import fs26 from "fs";
26808
26708
  var POSIX_SHELL_CANDIDATES = ["/bin/bash", "/usr/bin/bash", "/bin/sh"];
26809
26709
  function resolveDefaultShell(env = process.env) {
26810
26710
  if (process.platform === "win32") {
26811
26711
  return env.COMSPEC ?? "cmd.exe";
26812
26712
  }
26813
- if (env.SHELL && fs24.existsSync(env.SHELL)) {
26713
+ if (env.SHELL && fs26.existsSync(env.SHELL)) {
26814
26714
  return env.SHELL;
26815
26715
  }
26816
- return POSIX_SHELL_CANDIDATES.find((candidate) => fs24.existsSync(candidate)) ?? "/bin/sh";
26716
+ return POSIX_SHELL_CANDIDATES.find((candidate) => fs26.existsSync(candidate)) ?? "/bin/sh";
26817
26717
  }
26818
26718
 
26819
26719
  // src/shell/pty-shell-backend.ts
26820
26720
  var MAX_SCROLLBACK_BYTES = 512 * 1024;
26821
26721
  var ANSI_ESCAPE_PATTERN = new RegExp(String.raw`\u001B\[[0-?]*[ -/]*[@-~]`, "g");
26822
26722
  function shellArgs(shell) {
26823
- const shellName = path24.basename(shell).toLowerCase();
26723
+ const shellName = path26.basename(shell).toLowerCase();
26824
26724
  if (process.platform === "win32") {
26825
26725
  return [];
26826
26726
  }
@@ -26842,7 +26742,7 @@ function lastVisibleLine(snapshot) {
26842
26742
  }
26843
26743
  function inferRuntime(session) {
26844
26744
  const promptLine = lastVisibleLine(session.scrollback);
26845
- const shell = path24.basename(session.shell);
26745
+ const shell = path26.basename(session.shell);
26846
26746
  const isCommandRunning = session.exitCode !== null ? false : !/[$#>]\s*$/.test(promptLine.trimEnd());
26847
26747
  return {
26848
26748
  panePid: session.pty.pid,
@@ -26988,8 +26888,8 @@ var PtyShellBackend = class {
26988
26888
  };
26989
26889
 
26990
26890
  // src/shell/tmux-manager.ts
26991
- import fs25 from "fs";
26992
- import path25 from "path";
26891
+ import fs27 from "fs";
26892
+ import path27 from "path";
26993
26893
  import { spawn as spawnChild } from "child_process";
26994
26894
  async function defaultExecCommand(command, args) {
26995
26895
  return await new Promise((resolve, reject) => {
@@ -27016,17 +26916,17 @@ async function defaultExecCommand(command, args) {
27016
26916
  });
27017
26917
  }
27018
26918
  function resolveExecutablePath(command) {
27019
- if (command.includes(path25.sep)) {
26919
+ if (command.includes(path27.sep)) {
27020
26920
  return command;
27021
26921
  }
27022
26922
  const searchPath = process.env.PATH ?? "";
27023
- for (const entry of searchPath.split(path25.delimiter)) {
26923
+ for (const entry of searchPath.split(path27.delimiter)) {
27024
26924
  const trimmed = entry.trim();
27025
26925
  if (!trimmed) {
27026
26926
  continue;
27027
26927
  }
27028
- const candidate = path25.join(trimmed, command);
27029
- if (fs25.existsSync(candidate)) {
26928
+ const candidate = path27.join(trimmed, command);
26929
+ if (fs27.existsSync(candidate)) {
27030
26930
  return candidate;
27031
26931
  }
27032
26932
  }
@@ -27787,22 +27687,22 @@ function payloadItems(payload, fields) {
27787
27687
  const record = recordFrom(payload);
27788
27688
  return arrayField(record, fields) ?? (record ? [record] : []);
27789
27689
  }
27790
- function artifactPreviewKind(type, format, path27) {
27690
+ function artifactPreviewKind(type, format, path29) {
27791
27691
  const candidates = [
27792
27692
  type,
27793
27693
  format,
27794
- path27?.split(".").pop() ?? null
27694
+ path29?.split(".").pop() ?? null
27795
27695
  ].map((value) => value?.trim().toLowerCase()).filter(Boolean);
27796
27696
  return candidates.some((value) => MOLECULE_ARTIFACT_TYPES.has(value)) ? "molecule" : "file";
27797
27697
  }
27798
27698
  function normalizeArtifactRef(value) {
27799
27699
  const record = recordFrom(value);
27800
- const path27 = stringField3(record, ["path", "filePath", "file_path", "filename", "fileName", "name"]);
27700
+ const path29 = stringField3(record, ["path", "filePath", "file_path", "filename", "fileName", "name"]);
27801
27701
  const type = stringField3(record, ["type", "artifactType", "artifact_type", "format", "extension"]);
27802
- const title = stringField3(record, ["title", "label", "name", "filename", "fileName"]) ?? path27 ?? "artifact";
27702
+ const title = stringField3(record, ["title", "label", "name", "filename", "fileName"]) ?? path29 ?? "artifact";
27803
27703
  return {
27804
27704
  title,
27805
- path: path27,
27705
+ path: path29,
27806
27706
  type,
27807
27707
  downloadUrl: stringField3(record, ["downloadUrl", "download_url", "url", "href"])
27808
27708
  };
@@ -27836,21 +27736,21 @@ function normalizeArtifact(module, runId, value) {
27836
27736
  if (!record) {
27837
27737
  return null;
27838
27738
  }
27839
- const path27 = stringField3(record, ["path", "filePath", "file_path", "filename", "fileName", "name"]);
27739
+ const path29 = stringField3(record, ["path", "filePath", "file_path", "filename", "fileName", "name"]);
27840
27740
  const type = stringField3(record, ["type", "artifactType", "artifact_type"]);
27841
27741
  const format = stringField3(record, ["format", "fileFormat", "file_format", "extension"]) ?? type;
27842
- 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`;
27843
27743
  return {
27844
27744
  module,
27845
27745
  runId,
27846
27746
  title,
27847
- path: path27,
27747
+ path: path29,
27848
27748
  type,
27849
27749
  format,
27850
27750
  mimeType: stringField3(record, ["mimeType", "mime_type", "contentType", "content_type"]),
27851
27751
  sizeBytes: numberField2(record, ["sizeBytes", "size_bytes", "bytes"]),
27852
27752
  downloadUrl: stringField3(record, ["downloadUrl", "download_url", "url", "href"]),
27853
- previewKind: artifactPreviewKind(type, format, path27)
27753
+ previewKind: artifactPreviewKind(type, format, path29)
27854
27754
  };
27855
27755
  }
27856
27756
  function parseTomlScalar(raw) {
@@ -28080,9 +27980,9 @@ var WorkerHarnessClient = class {
28080
27980
  }
28081
27981
  return { baseUrl, apiKey };
28082
27982
  }
28083
- async fetchText(path27) {
27983
+ async fetchText(path29) {
28084
27984
  const config = this.requireHarnessConfig();
28085
- const response = await this.fetchImpl(`${config.baseUrl}${path27}`, {
27985
+ const response = await this.fetchImpl(`${config.baseUrl}${path29}`, {
28086
27986
  headers: {
28087
27987
  "x-api-key": config.apiKey
28088
27988
  }
@@ -28093,11 +27993,11 @@ var WorkerHarnessClient = class {
28093
27993
  }
28094
27994
  return { text: text2 };
28095
27995
  }
28096
- async fetchPayload(path27, init = {}) {
27996
+ async fetchPayload(path29, init = {}) {
28097
27997
  const config = this.requireHarnessConfig();
28098
27998
  const headers = new Headers(init.headers);
28099
27999
  headers.set("x-api-key", config.apiKey);
28100
- const response = await this.fetchImpl(`${config.baseUrl}${path27}`, {
28000
+ const response = await this.fetchImpl(`${config.baseUrl}${path29}`, {
28101
28001
  ...init,
28102
28002
  headers
28103
28003
  });
@@ -28111,9 +28011,9 @@ var WorkerHarnessClient = class {
28111
28011
  return { text: text2 };
28112
28012
  }
28113
28013
  }
28114
- async fetchBinary(path27) {
28014
+ async fetchBinary(path29) {
28115
28015
  const config = this.requireHarnessConfig();
28116
- const response = await this.fetchImpl(`${config.baseUrl}${path27}`, {
28016
+ const response = await this.fetchImpl(`${config.baseUrl}${path29}`, {
28117
28017
  headers: {
28118
28018
  "x-api-key": config.apiKey
28119
28019
  }
@@ -28957,16 +28857,16 @@ var HttpError = class extends Error {
28957
28857
  };
28958
28858
  function findRepoRoot(start = process.cwd()) {
28959
28859
  if (process.env.REMOTE_CODEX_REPO_ROOT) {
28960
- return path26.resolve(process.env.REMOTE_CODEX_REPO_ROOT);
28860
+ return path28.resolve(process.env.REMOTE_CODEX_REPO_ROOT);
28961
28861
  }
28962
- let current = path26.resolve(start);
28963
- while (current !== path26.dirname(current)) {
28964
- 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"))) {
28965
28865
  return current;
28966
28866
  }
28967
- current = path26.dirname(current);
28867
+ current = path28.dirname(current);
28968
28868
  }
28969
- return path26.resolve(process.cwd());
28869
+ return path28.resolve(process.cwd());
28970
28870
  }
28971
28871
  function createServiceLifecycle() {
28972
28872
  return {
@@ -28978,8 +28878,8 @@ function createServiceLifecycle() {
28978
28878
  });
28979
28879
  }
28980
28880
  const repoRoot = findRepoRoot();
28981
- const restartScript = path26.join(repoRoot, "scripts", "service-restart.mjs");
28982
- 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"))) {
28983
28883
  throw new HttpError(503, {
28984
28884
  code: "service_unavailable",
28985
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."
@@ -29022,7 +28922,7 @@ function buildApp(options = {}) {
29022
28922
  const shellService = options.shellService ?? new ShellSessionService(
29023
28923
  database.db,
29024
28924
  eventBus,
29025
- createTerminalShellBackend(options.env)
28925
+ options.shellBackend ?? createTerminalShellBackend(options.env)
29026
28926
  );
29027
28927
  const providerHostConfigService = new ProviderHostConfigService(
29028
28928
  agentRuntimes,