remote-codex 0.11.27 → 0.11.28
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.
- package/README.md +23 -0
- package/apps/supervisor-api/dist/index.js +221 -16
- package/apps/supervisor-web/dist/assets/{index-pMPBEcL2.js → index-Dpf8YNxk.js} +1 -1
- package/apps/supervisor-web/dist/assets/index-IJffwc2e.css +1 -0
- package/apps/supervisor-web/dist/assets/{thread-ui-CpukI6ql.js → thread-ui-BnLdOiXc.js} +24 -24
- package/apps/supervisor-web/dist/index.html +3 -3
- package/bin/remote-codex.mjs +216 -12
- package/package.json +1 -1
- package/apps/supervisor-web/dist/assets/index-BVYGmxwf.css +0 -1
package/README.md
CHANGED
|
@@ -103,6 +103,18 @@ remote-codex relay-supervisor
|
|
|
103
103
|
`45679` is used by default in copied setup commands to avoid common local
|
|
104
104
|
`8787` port conflicts. You can change it if needed.
|
|
105
105
|
|
|
106
|
+
By default, `remote-codex relay-supervisor` starts itself inside a detached
|
|
107
|
+
`tmux` session so closing the terminal does not take the device offline. Manage
|
|
108
|
+
it with:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
remote-codex relay-supervisor status
|
|
112
|
+
remote-codex relay-supervisor stop
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
If `tmux` is not installed it runs in the foreground. Use
|
|
116
|
+
`remote-codex relay-supervisor run` for explicit foreground/debug mode.
|
|
117
|
+
|
|
106
118
|
### Local Mode
|
|
107
119
|
|
|
108
120
|
```bash
|
|
@@ -247,6 +259,17 @@ remote-codex relay-supervisor
|
|
|
247
259
|
|
|
248
260
|
复制命令默认使用 `45679`,用于避开本机常见的 `8787` 端口冲突;需要时可以手动换。
|
|
249
261
|
|
|
262
|
+
默认情况下,`remote-codex relay-supervisor` 会尝试启动到 detached `tmux`
|
|
263
|
+
session 里,这样关闭终端窗口不会让设备下线。可以用下面命令管理:
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
remote-codex relay-supervisor status
|
|
267
|
+
remote-codex relay-supervisor stop
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
如果设备没有安装 `tmux`,它会自动退回前台运行。需要显式前台调试时使用
|
|
271
|
+
`remote-codex relay-supervisor run`。
|
|
272
|
+
|
|
250
273
|
### 本地模式
|
|
251
274
|
|
|
252
275
|
```bash
|
|
@@ -18362,6 +18362,9 @@ var ThreadDetailAssembler = class {
|
|
|
18362
18362
|
input.record.model,
|
|
18363
18363
|
input.record.reasoningEffort
|
|
18364
18364
|
);
|
|
18365
|
+
const persistedItemsByTurnIdForPatch = this.input.callbacks.listPersistedHistoryItemsByTurnId(
|
|
18366
|
+
input.localThreadId
|
|
18367
|
+
);
|
|
18365
18368
|
const activeDisplayTurnId = this.input.liveState.displayTurnIdForRuntimeTurn(
|
|
18366
18369
|
input.localThreadId,
|
|
18367
18370
|
input.record.providerTurnId
|
|
@@ -18373,6 +18376,15 @@ var ThreadDetailAssembler = class {
|
|
|
18373
18376
|
if (input.record.providerTurnId && threadPatch.status === "idle" && activeLiveItems && activeLiveItems.items.length > 0) {
|
|
18374
18377
|
threadPatch.status = "running";
|
|
18375
18378
|
}
|
|
18379
|
+
const latestPersistedFailure = latestPersistedFailureAfter(
|
|
18380
|
+
persistedItemsByTurnIdForPatch,
|
|
18381
|
+
input.turnMetadataById,
|
|
18382
|
+
newestRemoteTurnStartedAt(remoteSession.turns)
|
|
18383
|
+
);
|
|
18384
|
+
if (threadPatch.status !== "running" && latestPersistedFailure) {
|
|
18385
|
+
threadPatch.status = "failed";
|
|
18386
|
+
threadPatch.lastError = latestPersistedFailure.error;
|
|
18387
|
+
}
|
|
18376
18388
|
const nextThreadPatch = {
|
|
18377
18389
|
...threadPatch,
|
|
18378
18390
|
...threadPatch.status !== "running" ? { providerTurnId: null } : {}
|
|
@@ -18400,8 +18412,17 @@ var ThreadDetailAssembler = class {
|
|
|
18400
18412
|
this.input.liveState,
|
|
18401
18413
|
input.turnMetadataById
|
|
18402
18414
|
);
|
|
18403
|
-
const
|
|
18415
|
+
const visibleTurnsWithPersistedFailures = appendPersistedFailureTurnsIfMissing(
|
|
18404
18416
|
visibleTurnsWithActiveLiveTurn,
|
|
18417
|
+
persistedItemsByTurnId,
|
|
18418
|
+
input.turnMetadataById,
|
|
18419
|
+
{
|
|
18420
|
+
includeAllMissing: shouldCacheFullDetail,
|
|
18421
|
+
includeLatestMissing: options.beforeTurnId === void 0
|
|
18422
|
+
}
|
|
18423
|
+
);
|
|
18424
|
+
const orderedVisibleTurns = applyLiveAgentMessageOrderingHints(
|
|
18425
|
+
visibleTurnsWithPersistedFailures,
|
|
18405
18426
|
input.localThreadId,
|
|
18406
18427
|
this.input.liveState
|
|
18407
18428
|
);
|
|
@@ -18447,13 +18468,16 @@ var ThreadDetailAssembler = class {
|
|
|
18447
18468
|
input.record,
|
|
18448
18469
|
latestThreadTurnMetadata(input.turnMetadataById)
|
|
18449
18470
|
);
|
|
18450
|
-
const localTurns = localSession?.turns ?? [...persistedItemsByTurnId.
|
|
18451
|
-
|
|
18452
|
-
|
|
18453
|
-
|
|
18454
|
-
|
|
18455
|
-
|
|
18456
|
-
|
|
18471
|
+
const localTurns = localSession?.turns ?? [...persistedItemsByTurnId.entries()].map(([turnId, items]) => {
|
|
18472
|
+
const error = persistedTurnError(items);
|
|
18473
|
+
return {
|
|
18474
|
+
id: turnId,
|
|
18475
|
+
startedAt: input.turnMetadataById.get(turnId)?.createdAt ?? null,
|
|
18476
|
+
status: error ? "failed" : "completed",
|
|
18477
|
+
error,
|
|
18478
|
+
items: []
|
|
18479
|
+
};
|
|
18480
|
+
});
|
|
18457
18481
|
const turns = mergePersistedHistoryItemsIntoTurns(
|
|
18458
18482
|
applyRecordedTurnItemOrders(
|
|
18459
18483
|
localTurns,
|
|
@@ -18552,6 +18576,87 @@ function appendActiveLiveTurnIfMissing(turns, localThreadId, providerTurnId, liv
|
|
|
18552
18576
|
}
|
|
18553
18577
|
];
|
|
18554
18578
|
}
|
|
18579
|
+
function appendPersistedFailureTurnsIfMissing(turns, persistedItemsByTurnId, metadataById, options) {
|
|
18580
|
+
if (persistedItemsByTurnId.size === 0) {
|
|
18581
|
+
return turns;
|
|
18582
|
+
}
|
|
18583
|
+
const existingTurnIds = new Set(turns.map((turn) => turn.id));
|
|
18584
|
+
const newestVisibleStartedAt = newestStartedAt(turns);
|
|
18585
|
+
const missingFailureTurns = [];
|
|
18586
|
+
for (const [turnId, items] of persistedItemsByTurnId.entries()) {
|
|
18587
|
+
if (existingTurnIds.has(turnId)) {
|
|
18588
|
+
continue;
|
|
18589
|
+
}
|
|
18590
|
+
const error = persistedTurnError(items);
|
|
18591
|
+
if (!error) {
|
|
18592
|
+
continue;
|
|
18593
|
+
}
|
|
18594
|
+
const startedAt = metadataById.get(turnId)?.createdAt ?? earliestItemCreatedAt(items);
|
|
18595
|
+
const shouldInclude = options.includeAllMissing || options.includeLatestMissing && (!newestVisibleStartedAt || !startedAt || startedAt >= newestVisibleStartedAt);
|
|
18596
|
+
if (!shouldInclude) {
|
|
18597
|
+
continue;
|
|
18598
|
+
}
|
|
18599
|
+
missingFailureTurns.push({
|
|
18600
|
+
id: turnId,
|
|
18601
|
+
startedAt,
|
|
18602
|
+
status: "failed",
|
|
18603
|
+
error,
|
|
18604
|
+
items: []
|
|
18605
|
+
});
|
|
18606
|
+
}
|
|
18607
|
+
if (missingFailureTurns.length === 0) {
|
|
18608
|
+
return turns;
|
|
18609
|
+
}
|
|
18610
|
+
return sortTurnsByStartedAt([...turns, ...missingFailureTurns]);
|
|
18611
|
+
}
|
|
18612
|
+
function persistedTurnError(items) {
|
|
18613
|
+
const failedItem = [...items].reverse().find(
|
|
18614
|
+
(item) => item.status === "failed" || item.status === "error"
|
|
18615
|
+
);
|
|
18616
|
+
return failedItem?.text?.trim() || null;
|
|
18617
|
+
}
|
|
18618
|
+
function earliestItemCreatedAt(items) {
|
|
18619
|
+
return items.map((item) => item.createdAt).filter((value) => Boolean(value)).sort()[0] ?? null;
|
|
18620
|
+
}
|
|
18621
|
+
function newestStartedAt(turns) {
|
|
18622
|
+
return turns.map((turn) => turn.startedAt).filter((value) => Boolean(value)).sort().at(-1) ?? null;
|
|
18623
|
+
}
|
|
18624
|
+
function newestRemoteTurnStartedAt(turns) {
|
|
18625
|
+
return turns.map((turn) => turn.startedAt).filter((value) => Boolean(value)).sort().at(-1) ?? null;
|
|
18626
|
+
}
|
|
18627
|
+
function latestPersistedFailureAfter(persistedItemsByTurnId, metadataById, timestamp) {
|
|
18628
|
+
let latest = null;
|
|
18629
|
+
for (const [turnId, items] of persistedItemsByTurnId.entries()) {
|
|
18630
|
+
const error = persistedTurnError(items);
|
|
18631
|
+
if (!error) {
|
|
18632
|
+
continue;
|
|
18633
|
+
}
|
|
18634
|
+
const startedAt = metadataById.get(turnId)?.createdAt ?? earliestItemCreatedAt(items);
|
|
18635
|
+
if (timestamp && startedAt && startedAt < timestamp) {
|
|
18636
|
+
continue;
|
|
18637
|
+
}
|
|
18638
|
+
if (!latest || !latest.startedAt && startedAt || latest.startedAt && startedAt && startedAt > latest.startedAt) {
|
|
18639
|
+
latest = { error, startedAt };
|
|
18640
|
+
}
|
|
18641
|
+
}
|
|
18642
|
+
return latest;
|
|
18643
|
+
}
|
|
18644
|
+
function sortTurnsByStartedAt(turns) {
|
|
18645
|
+
return turns.map((turn, index) => ({ turn, index })).sort((left, right) => {
|
|
18646
|
+
const leftStartedAt = left.turn.startedAt;
|
|
18647
|
+
const rightStartedAt = right.turn.startedAt;
|
|
18648
|
+
if (!leftStartedAt && !rightStartedAt) {
|
|
18649
|
+
return left.index - right.index;
|
|
18650
|
+
}
|
|
18651
|
+
if (!leftStartedAt) {
|
|
18652
|
+
return 1;
|
|
18653
|
+
}
|
|
18654
|
+
if (!rightStartedAt) {
|
|
18655
|
+
return -1;
|
|
18656
|
+
}
|
|
18657
|
+
return leftStartedAt.localeCompare(rightStartedAt) || left.index - right.index;
|
|
18658
|
+
}).map(({ turn }) => turn);
|
|
18659
|
+
}
|
|
18555
18660
|
function buildTurnDto(turn, metadata) {
|
|
18556
18661
|
const tokenUsage = parseThreadTurnTokenUsageJson(metadata?.tokenUsageJson);
|
|
18557
18662
|
const displayPrompt = metadata?.displayPrompt?.trim();
|
|
@@ -19005,10 +19110,20 @@ var ThreadManagementCoordinator = class {
|
|
|
19005
19110
|
};
|
|
19006
19111
|
|
|
19007
19112
|
// src/thread-prompt-turn-coordinator.ts
|
|
19113
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
19008
19114
|
function isAutoGeneratedTitle(title) {
|
|
19009
19115
|
const normalized = title?.trim();
|
|
19010
19116
|
return !normalized || normalized === "Untitled thread" || normalized === "Thread";
|
|
19011
19117
|
}
|
|
19118
|
+
function errorMessage3(error) {
|
|
19119
|
+
return error instanceof Error ? error.message : String(error);
|
|
19120
|
+
}
|
|
19121
|
+
function promptStartFailureMessage(error, runtime, fallbackProvider) {
|
|
19122
|
+
const providerLabel = runtime.displayName || fallbackProvider || "Agent backend";
|
|
19123
|
+
const rawMessage = errorMessage3(error).trim() || "Unknown error.";
|
|
19124
|
+
const codeLabel = error instanceof AgentRuntimeError ? ` (${error.code})` : "";
|
|
19125
|
+
return `${providerLabel} failed to start this turn${codeLabel}: ${rawMessage}`;
|
|
19126
|
+
}
|
|
19012
19127
|
var ThreadPromptTurnCoordinator = class {
|
|
19013
19128
|
constructor(db, liveState, providerRuntime, callbacks) {
|
|
19014
19129
|
this.db = db;
|
|
@@ -19051,7 +19166,22 @@ var ThreadPromptTurnCoordinator = class {
|
|
|
19051
19166
|
if (input.displayTurnId) {
|
|
19052
19167
|
startTurnInput.displayTurnId = input.displayTurnId;
|
|
19053
19168
|
}
|
|
19054
|
-
|
|
19169
|
+
let turn;
|
|
19170
|
+
try {
|
|
19171
|
+
turn = await runtime.startTurn(startTurnInput);
|
|
19172
|
+
} catch (error) {
|
|
19173
|
+
const failureMessage = promptStartFailureMessage(error, runtime, record.provider);
|
|
19174
|
+
this.persistPromptStartFailure(localThreadId, record, input, {
|
|
19175
|
+
displayPrompt,
|
|
19176
|
+
message: failureMessage,
|
|
19177
|
+
modelRecords,
|
|
19178
|
+
pricingSnapshot
|
|
19179
|
+
});
|
|
19180
|
+
throw new HttpError(503, {
|
|
19181
|
+
code: "service_unavailable",
|
|
19182
|
+
message: failureMessage
|
|
19183
|
+
});
|
|
19184
|
+
}
|
|
19055
19185
|
const displayTurnId = input.displayTurnId ?? turn.providerTurnId;
|
|
19056
19186
|
if (displayTurnId !== turn.providerTurnId) {
|
|
19057
19187
|
this.liveState.setRuntimeDisplayTurnMapping(localThreadId, {
|
|
@@ -19104,6 +19234,80 @@ var ThreadPromptTurnCoordinator = class {
|
|
|
19104
19234
|
const updated = getThreadRecordById(this.db, localThreadId);
|
|
19105
19235
|
return this.callbacks.toThreadDto(updated, /* @__PURE__ */ new Set([record.providerSessionId]));
|
|
19106
19236
|
}
|
|
19237
|
+
persistPromptStartFailure(localThreadId, record, input, failure) {
|
|
19238
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
19239
|
+
const displayTurnId = input.displayTurnId ?? `local-failed-${randomUUID3()}`;
|
|
19240
|
+
const items = input.hidden ? [
|
|
19241
|
+
{
|
|
19242
|
+
id: `${displayTurnId}:error`,
|
|
19243
|
+
kind: "agentMessage",
|
|
19244
|
+
text: failure.message,
|
|
19245
|
+
status: "failed",
|
|
19246
|
+
createdAt: now,
|
|
19247
|
+
transcriptOrder: 0,
|
|
19248
|
+
sourceTurnId: displayTurnId
|
|
19249
|
+
}
|
|
19250
|
+
] : [
|
|
19251
|
+
{
|
|
19252
|
+
id: `${displayTurnId}:user`,
|
|
19253
|
+
kind: "userMessage",
|
|
19254
|
+
text: failure.displayPrompt,
|
|
19255
|
+
createdAt: now,
|
|
19256
|
+
transcriptOrder: 0
|
|
19257
|
+
},
|
|
19258
|
+
{
|
|
19259
|
+
id: `${displayTurnId}:error`,
|
|
19260
|
+
kind: "agentMessage",
|
|
19261
|
+
text: failure.message,
|
|
19262
|
+
status: "failed",
|
|
19263
|
+
createdAt: now,
|
|
19264
|
+
transcriptOrder: 1,
|
|
19265
|
+
sourceTurnId: displayTurnId
|
|
19266
|
+
}
|
|
19267
|
+
];
|
|
19268
|
+
upsertThreadTurnMetadata(this.db, {
|
|
19269
|
+
threadId: localThreadId,
|
|
19270
|
+
turnId: displayTurnId,
|
|
19271
|
+
model: input.effectiveModel,
|
|
19272
|
+
reasoningEffort: input.normalizedReasoning,
|
|
19273
|
+
reasoningEffortAvailable: this.providerRuntime.reasoningEffortAvailableForModel(
|
|
19274
|
+
failure.modelRecords,
|
|
19275
|
+
input.effectiveModel
|
|
19276
|
+
),
|
|
19277
|
+
pricingModelKey: failure.pricingSnapshot?.pricingModelKey ?? null,
|
|
19278
|
+
pricingTierKey: failure.pricingSnapshot?.pricingTierKey ?? null,
|
|
19279
|
+
displayPrompt: input.hidden ? null : failure.displayPrompt
|
|
19280
|
+
});
|
|
19281
|
+
for (const item of items) {
|
|
19282
|
+
upsertThreadHistoryItemRecord(this.db, {
|
|
19283
|
+
threadId: localThreadId,
|
|
19284
|
+
turnId: displayTurnId,
|
|
19285
|
+
itemId: item.id,
|
|
19286
|
+
itemJson: JSON.stringify(item)
|
|
19287
|
+
});
|
|
19288
|
+
}
|
|
19289
|
+
const patch = {
|
|
19290
|
+
providerTurnId: displayTurnId,
|
|
19291
|
+
status: "failed",
|
|
19292
|
+
lastError: failure.message,
|
|
19293
|
+
lastTurnStartedAt: now,
|
|
19294
|
+
lastTurnCompletedAt: now,
|
|
19295
|
+
model: input.effectiveModel,
|
|
19296
|
+
reasoningEffort: input.normalizedReasoning,
|
|
19297
|
+
collaborationMode: input.collaborationMode,
|
|
19298
|
+
sandboxMode: input.sandboxMode
|
|
19299
|
+
};
|
|
19300
|
+
if (!input.hidden) {
|
|
19301
|
+
patch.summaryText = failure.displayPrompt;
|
|
19302
|
+
if (isAutoGeneratedTitle(record.title)) {
|
|
19303
|
+
patch.title = truncateAutoThreadTitle(failure.displayPrompt);
|
|
19304
|
+
}
|
|
19305
|
+
}
|
|
19306
|
+
updateThreadRecord(this.db, localThreadId, patch);
|
|
19307
|
+
this.liveState.setLivePlan(localThreadId, null);
|
|
19308
|
+
this.liveState.setLiveItems(localThreadId, null);
|
|
19309
|
+
this.callbacks.invalidateThreadDetailCache(localThreadId);
|
|
19310
|
+
}
|
|
19107
19311
|
async steerOrStartPromptTurn(localThreadId, record, input) {
|
|
19108
19312
|
const runtime = this.callbacks.runtimeForProvider(record.provider);
|
|
19109
19313
|
let steerTurnId = record.providerTurnId;
|
|
@@ -19214,15 +19418,16 @@ function normalizeCollaborationMode(value) {
|
|
|
19214
19418
|
return value === "plan" ? "plan" : "default";
|
|
19215
19419
|
}
|
|
19216
19420
|
function buildThreadPatch(remoteSession, model, reasoningEffort) {
|
|
19217
|
-
const
|
|
19421
|
+
const latestTurn = "turns" in remoteSession ? remoteSession.turns.at(-1) ?? null : null;
|
|
19422
|
+
const latestStatus = latestTurn?.status === "failed" ? "failed" : latestTurn?.status === "interrupted" ? "interrupted" : remoteSession.status;
|
|
19218
19423
|
return {
|
|
19219
19424
|
provider: remoteSession.provider,
|
|
19220
19425
|
providerSessionId: remoteSession.providerSessionId,
|
|
19221
|
-
status:
|
|
19426
|
+
status: latestStatus,
|
|
19222
19427
|
summaryText: remoteSession.preview || null,
|
|
19223
19428
|
model: model ?? null,
|
|
19224
19429
|
reasoningEffort: normalizeReasoningEffort2(reasoningEffort),
|
|
19225
|
-
lastError:
|
|
19430
|
+
lastError: latestTurn?.status === "failed" ? latestTurn.error?.message ?? "Turn failed." : null,
|
|
19226
19431
|
updatedAt: remoteSession.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
19227
19432
|
};
|
|
19228
19433
|
}
|
|
@@ -21287,7 +21492,7 @@ var ThreadForkCoordinator = class {
|
|
|
21287
21492
|
};
|
|
21288
21493
|
|
|
21289
21494
|
// src/thread-attachment-coordinator.ts
|
|
21290
|
-
import { randomUUID as
|
|
21495
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
21291
21496
|
import fs12 from "fs/promises";
|
|
21292
21497
|
import path16 from "path";
|
|
21293
21498
|
async function pathExists(absPath) {
|
|
@@ -21305,7 +21510,7 @@ function sanitizeAttachmentFileName(originalName) {
|
|
|
21305
21510
|
const sanitizedStem = rawStem.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 64);
|
|
21306
21511
|
const stem = sanitizedStem || "attachment";
|
|
21307
21512
|
const normalizedExtension = extension.slice(0, 16);
|
|
21308
|
-
return `${stem}-${
|
|
21513
|
+
return `${stem}-${randomUUID4().slice(0, 8)}${normalizedExtension}`;
|
|
21309
21514
|
}
|
|
21310
21515
|
function threadTempDirectoryPath2(workspacePath, localThreadId) {
|
|
21311
21516
|
return path16.join(workspacePath, ".temp", "threads", localThreadId);
|
|
@@ -25010,7 +25215,7 @@ async function registerAuthRoutes(app2) {
|
|
|
25010
25215
|
// src/provider-host-config-service.ts
|
|
25011
25216
|
import fs20 from "fs/promises";
|
|
25012
25217
|
import path23 from "path";
|
|
25013
|
-
import { randomUUID as
|
|
25218
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
25014
25219
|
function providerError(message, statusCode = 404) {
|
|
25015
25220
|
const error = new Error(message);
|
|
25016
25221
|
error.statusCode = statusCode;
|
|
@@ -25158,7 +25363,7 @@ var ProviderHostConfigService = class {
|
|
|
25158
25363
|
const providerHome = this.providerHome(provider2);
|
|
25159
25364
|
const fileNames = this.archiveFileNames(provider2);
|
|
25160
25365
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
25161
|
-
const id = `${createdAt.replace(/[-:.TZ]/g, "").slice(0, 14)}-${
|
|
25366
|
+
const id = `${createdAt.replace(/[-:.TZ]/g, "").slice(0, 14)}-${randomUUID5().slice(0, 8)}`;
|
|
25162
25367
|
const archivePath = resolveArchivePath(providerHome, id);
|
|
25163
25368
|
const files = Object.fromEntries(
|
|
25164
25369
|
fileNames.map((name) => [
|