remote-codex 0.11.11 → 0.11.13
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/apps/relay-server/dist/index.js +15 -2
- package/apps/supervisor-api/dist/{chunk-2QAVWDYV.js → chunk-ZTQPMDTP.js} +856 -171
- package/apps/supervisor-api/dist/index.js +1 -1
- package/apps/supervisor-api/dist/worker-index.js +1 -1
- package/apps/supervisor-web/dist/assets/index-D3I41SIH.css +1 -0
- package/apps/supervisor-web/dist/assets/index-Z5yu-xV3.js +6 -0
- package/apps/supervisor-web/dist/assets/thread-ui-q6mjcjXn.js +3614 -0
- package/apps/supervisor-web/dist/index.html +3 -3
- package/package.json +1 -1
- package/packages/db/migrations/0027_harness_job_watches.sql +24 -0
- package/packages/db/src/repositories.ts +123 -0
- package/packages/db/src/schema.ts +29 -0
- package/packages/shared/src/index.ts +1 -0
- package/apps/supervisor-web/dist/assets/index-Cp9GkemI.css +0 -1
- package/apps/supervisor-web/dist/assets/index-Dsq8QmDr.js +0 -6
- package/apps/supervisor-web/dist/assets/thread-ui-DldLSgqC.js +0 -3604
|
@@ -8,9 +8,9 @@ var __export = (target, all) => {
|
|
|
8
8
|
import Fastify from "fastify";
|
|
9
9
|
import multipart from "@fastify/multipart";
|
|
10
10
|
import websocket from "@fastify/websocket";
|
|
11
|
-
import { spawn as
|
|
11
|
+
import { spawn as spawn6 } from "child_process";
|
|
12
12
|
import fs26 from "fs";
|
|
13
|
-
import
|
|
13
|
+
import path26 from "path";
|
|
14
14
|
import { ZodError } from "zod";
|
|
15
15
|
|
|
16
16
|
// ../../packages/config/src/index.ts
|
|
@@ -98,6 +98,7 @@ var envSchema = z.object({
|
|
|
98
98
|
ELAGENTE_HARNESS_BASE_URL: z.string().url().optional(),
|
|
99
99
|
INACT_X_APP_KEY: z.string().min(1).optional(),
|
|
100
100
|
REMOTE_CODEX_CHEMISTRY_TOOLS_ENABLED: z.string().optional(),
|
|
101
|
+
REMOTE_CODEX_HARNESS_WAKEUP_CALLBACK_BASE_URL: z.string().url().optional(),
|
|
101
102
|
REMOTE_CODEX_WORKER_RUNTIME_MANIFEST: z.string().min(1).optional(),
|
|
102
103
|
APP_NAME: z.string().min(1).optional(),
|
|
103
104
|
APP_VERSION: z.string().min(1).optional(),
|
|
@@ -209,6 +210,7 @@ function loadRuntimeConfig(env = process.env) {
|
|
|
209
210
|
harnessBaseUrl: parsed.ELAGENTE_HARNESS_BASE_URL ?? null,
|
|
210
211
|
harnessEnabled: Boolean(parsed.ELAGENTE_HARNESS_BASE_URL && parsed.INACT_X_APP_KEY),
|
|
211
212
|
chemistryToolsEnabled: parseBoolean(parsed.REMOTE_CODEX_CHEMISTRY_TOOLS_ENABLED, false),
|
|
213
|
+
harnessWakeupCallbackBaseUrl: parsed.REMOTE_CODEX_HARNESS_WAKEUP_CALLBACK_BASE_URL?.replace(/\/+$/, "") ?? null,
|
|
212
214
|
workerRuntimeManifestPath: parsed.REMOTE_CODEX_WORKER_RUNTIME_MANIFEST ? path.resolve(parsed.REMOTE_CODEX_WORKER_RUNTIME_MANIFEST) : runtimeRole === "worker" ? "/opt/remote-codex/worker-runtime-manifest.json" : null,
|
|
213
215
|
appName: parsed.APP_NAME ?? (runtimeRole === "worker" ? "Remote Codex Worker" : "Remote Codex Supervisor"),
|
|
214
216
|
appVersion: parsed.APP_VERSION ?? "0.1.0",
|
|
@@ -2316,7 +2318,7 @@ Subquery.prototype.getSQL = function() {
|
|
|
2316
2318
|
function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
2317
2319
|
const nullifyMap = {};
|
|
2318
2320
|
const result = columns.reduce(
|
|
2319
|
-
(result2, { path:
|
|
2321
|
+
(result2, { path: path27, field }, columnIndex) => {
|
|
2320
2322
|
let decoder;
|
|
2321
2323
|
if (is(field, Column)) {
|
|
2322
2324
|
decoder = field;
|
|
@@ -2326,8 +2328,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
|
2326
2328
|
decoder = field.sql.decoder;
|
|
2327
2329
|
}
|
|
2328
2330
|
let node = result2;
|
|
2329
|
-
for (const [pathChunkIndex, pathChunk] of
|
|
2330
|
-
if (pathChunkIndex <
|
|
2331
|
+
for (const [pathChunkIndex, pathChunk] of path27.entries()) {
|
|
2332
|
+
if (pathChunkIndex < path27.length - 1) {
|
|
2331
2333
|
if (!(pathChunk in node)) {
|
|
2332
2334
|
node[pathChunk] = {};
|
|
2333
2335
|
}
|
|
@@ -2335,8 +2337,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
|
2335
2337
|
} else {
|
|
2336
2338
|
const rawValue = row[columnIndex];
|
|
2337
2339
|
const value = node[pathChunk] = rawValue === null ? null : decoder.mapFromDriverValue(rawValue);
|
|
2338
|
-
if (joinsNotNullableMap && is(field, Column) &&
|
|
2339
|
-
const objectName =
|
|
2340
|
+
if (joinsNotNullableMap && is(field, Column) && path27.length === 2) {
|
|
2341
|
+
const objectName = path27[0];
|
|
2340
2342
|
if (!(objectName in nullifyMap)) {
|
|
2341
2343
|
nullifyMap[objectName] = value === null ? getTableName(field.table) : false;
|
|
2342
2344
|
} else if (typeof nullifyMap[objectName] === "string" && nullifyMap[objectName] !== getTableName(field.table)) {
|
|
@@ -6390,6 +6392,8 @@ __export(schema_exports, {
|
|
|
6390
6392
|
controlUsageImportState: () => controlUsageImportState,
|
|
6391
6393
|
controlUsers: () => controlUsers,
|
|
6392
6394
|
controlWorkspaces: () => controlWorkspaces,
|
|
6395
|
+
harnessJobWatches: () => harnessJobWatches,
|
|
6396
|
+
harnessNotifyRegistrations: () => harnessNotifyRegistrations,
|
|
6393
6397
|
hosts: () => hosts,
|
|
6394
6398
|
notifications: () => notifications,
|
|
6395
6399
|
policies: () => policies,
|
|
@@ -6855,6 +6859,33 @@ var controlAuditLogs = sqliteTable("control_audit_logs", {
|
|
|
6855
6859
|
metadataJson: text("metadata_json").notNull(),
|
|
6856
6860
|
createdAt: text("created_at").notNull()
|
|
6857
6861
|
});
|
|
6862
|
+
var harnessNotifyRegistrations = sqliteTable("harness_notify_registrations", {
|
|
6863
|
+
id: text("id").primaryKey(),
|
|
6864
|
+
agentId: text("agent_id").notNull(),
|
|
6865
|
+
hookToken: text("hook_token").notNull(),
|
|
6866
|
+
secret: text("secret").notNull(),
|
|
6867
|
+
callbackUrl: text("callback_url").notNull(),
|
|
6868
|
+
registeredAt: text("registered_at").notNull(),
|
|
6869
|
+
updatedAt: text("updated_at").notNull()
|
|
6870
|
+
});
|
|
6871
|
+
var harnessJobWatches = sqliteTable(
|
|
6872
|
+
"harness_job_watches",
|
|
6873
|
+
{
|
|
6874
|
+
id: text("id").primaryKey(),
|
|
6875
|
+
jobId: text("job_id").notNull(),
|
|
6876
|
+
threadId: text("thread_id").notNull(),
|
|
6877
|
+
title: text("title"),
|
|
6878
|
+
status: text("status").notNull().default("pending"),
|
|
6879
|
+
lastJobStatus: text("last_job_status"),
|
|
6880
|
+
lastError: text("last_error"),
|
|
6881
|
+
createdAt: text("created_at").notNull(),
|
|
6882
|
+
updatedAt: text("updated_at").notNull(),
|
|
6883
|
+
deliveredAt: text("delivered_at")
|
|
6884
|
+
},
|
|
6885
|
+
(table) => ({
|
|
6886
|
+
jobIdUnique: uniqueIndex("harness_job_watches_job_id_idx").on(table.jobId)
|
|
6887
|
+
})
|
|
6888
|
+
);
|
|
6858
6889
|
|
|
6859
6890
|
// ../../packages/db/src/client.ts
|
|
6860
6891
|
function resolvePlatform() {
|
|
@@ -7394,6 +7425,72 @@ function deleteNotificationsByThreadId(db, threadId) {
|
|
|
7394
7425
|
function deleteWorkspaceRecord(db, id) {
|
|
7395
7426
|
db.delete(workspaces).where(eq(workspaces.id, id)).run();
|
|
7396
7427
|
}
|
|
7428
|
+
function getHarnessNotifyRegistration(db) {
|
|
7429
|
+
return db.select().from(harnessNotifyRegistrations).where(eq(harnessNotifyRegistrations.id, "default")).get();
|
|
7430
|
+
}
|
|
7431
|
+
function upsertHarnessNotifyRegistration(db, input) {
|
|
7432
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7433
|
+
const existing = getHarnessNotifyRegistration(db);
|
|
7434
|
+
if (existing) {
|
|
7435
|
+
db.update(harnessNotifyRegistrations).set({
|
|
7436
|
+
agentId: input.agentId,
|
|
7437
|
+
hookToken: input.hookToken,
|
|
7438
|
+
secret: input.secret,
|
|
7439
|
+
callbackUrl: input.callbackUrl,
|
|
7440
|
+
updatedAt: now
|
|
7441
|
+
}).where(eq(harnessNotifyRegistrations.id, "default")).run();
|
|
7442
|
+
return getHarnessNotifyRegistration(db);
|
|
7443
|
+
}
|
|
7444
|
+
const record = {
|
|
7445
|
+
id: "default",
|
|
7446
|
+
agentId: input.agentId,
|
|
7447
|
+
hookToken: input.hookToken,
|
|
7448
|
+
secret: input.secret,
|
|
7449
|
+
callbackUrl: input.callbackUrl,
|
|
7450
|
+
registeredAt: now,
|
|
7451
|
+
updatedAt: now
|
|
7452
|
+
};
|
|
7453
|
+
db.insert(harnessNotifyRegistrations).values(record).run();
|
|
7454
|
+
return record;
|
|
7455
|
+
}
|
|
7456
|
+
function getHarnessJobWatchByJobId(db, jobId) {
|
|
7457
|
+
return db.select().from(harnessJobWatches).where(eq(harnessJobWatches.jobId, jobId)).get();
|
|
7458
|
+
}
|
|
7459
|
+
function listHarnessJobWatches(db) {
|
|
7460
|
+
return db.select().from(harnessJobWatches).orderBy(desc(harnessJobWatches.createdAt)).all();
|
|
7461
|
+
}
|
|
7462
|
+
function listPendingHarnessJobWatches(db) {
|
|
7463
|
+
return db.select().from(harnessJobWatches).where(eq(harnessJobWatches.status, "pending")).all();
|
|
7464
|
+
}
|
|
7465
|
+
function upsertHarnessJobWatch(db, input) {
|
|
7466
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7467
|
+
const existing = getHarnessJobWatchByJobId(db, input.jobId);
|
|
7468
|
+
if (existing) {
|
|
7469
|
+
db.update(harnessJobWatches).set({
|
|
7470
|
+
threadId: input.threadId,
|
|
7471
|
+
title: input.title ?? existing.title,
|
|
7472
|
+
updatedAt: now
|
|
7473
|
+
}).where(eq(harnessJobWatches.id, existing.id)).run();
|
|
7474
|
+
return getHarnessJobWatchByJobId(db, input.jobId);
|
|
7475
|
+
}
|
|
7476
|
+
const record = {
|
|
7477
|
+
id: randomUUID(),
|
|
7478
|
+
jobId: input.jobId,
|
|
7479
|
+
threadId: input.threadId,
|
|
7480
|
+
title: input.title ?? null,
|
|
7481
|
+
status: "pending",
|
|
7482
|
+
lastJobStatus: null,
|
|
7483
|
+
lastError: null,
|
|
7484
|
+
createdAt: now,
|
|
7485
|
+
updatedAt: now,
|
|
7486
|
+
deliveredAt: null
|
|
7487
|
+
};
|
|
7488
|
+
db.insert(harnessJobWatches).values(record).run();
|
|
7489
|
+
return record;
|
|
7490
|
+
}
|
|
7491
|
+
function updateHarnessJobWatch(db, id, input) {
|
|
7492
|
+
db.update(harnessJobWatches).set({ ...input, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq(harnessJobWatches.id, id)).run();
|
|
7493
|
+
}
|
|
7397
7494
|
|
|
7398
7495
|
// ../../packages/db/src/seed.ts
|
|
7399
7496
|
var defaultPolicies = [
|
|
@@ -9151,7 +9248,7 @@ function extractFileChangeEntries(item) {
|
|
|
9151
9248
|
isRecord4(entry.summary) ? entry.summary : null,
|
|
9152
9249
|
isRecord4(entry.diff) ? entry.diff : null
|
|
9153
9250
|
].filter((candidate) => Boolean(candidate));
|
|
9154
|
-
const
|
|
9251
|
+
const path27 = uniqueStrings([
|
|
9155
9252
|
stringOrNull(valueFromRecords(nestedRecords, ["path", "filePath", "targetPath"])),
|
|
9156
9253
|
stringOrNull(
|
|
9157
9254
|
valueFromRecords(nestedRecords, [
|
|
@@ -9198,7 +9295,7 @@ function extractFileChangeEntries(item) {
|
|
|
9198
9295
|
const diffStats = explicitAdditions === 0 && explicitDeletions === 0 && diffText ? countUnifiedDiffStats(diffText) : null;
|
|
9199
9296
|
const additions = explicitAdditions || diffStats?.additions || 0;
|
|
9200
9297
|
const deletions = explicitDeletions || diffStats?.deletions || 0;
|
|
9201
|
-
const normalizedPath =
|
|
9298
|
+
const normalizedPath = path27 ?? (diffText ? projectRelativePathLabel(extractPathFromDiffText(diffText)) : null);
|
|
9202
9299
|
if (!normalizedPath && additions === 0 && deletions === 0) {
|
|
9203
9300
|
return null;
|
|
9204
9301
|
}
|
|
@@ -13508,14 +13605,14 @@ function displayPath(pathValue, options) {
|
|
|
13508
13605
|
}
|
|
13509
13606
|
return relativePath;
|
|
13510
13607
|
}
|
|
13511
|
-
function toolIsLowInformationPatch(normalized, state, input, patchText,
|
|
13608
|
+
function toolIsLowInformationPatch(normalized, state, input, patchText, path27, metadataStats) {
|
|
13512
13609
|
if (normalized !== "applypatch" && normalized !== "patch") {
|
|
13513
13610
|
return false;
|
|
13514
13611
|
}
|
|
13515
13612
|
if (toolStateStatus(state) !== "running") {
|
|
13516
13613
|
return false;
|
|
13517
13614
|
}
|
|
13518
|
-
if (
|
|
13615
|
+
if (path27 || patchText || metadataStats || stringValue2(state.output)) {
|
|
13519
13616
|
return false;
|
|
13520
13617
|
}
|
|
13521
13618
|
return !isRecord9(input) || Object.keys(input).length === 0;
|
|
@@ -13558,7 +13655,7 @@ function fileChangeStatsFromMetadata(metadata) {
|
|
|
13558
13655
|
if (files.length === 0) {
|
|
13559
13656
|
return null;
|
|
13560
13657
|
}
|
|
13561
|
-
const paths = files.map((file) => stringValue2(file.filePath) ?? stringValue2(file.path) ?? stringValue2(file.relativePath)).filter((
|
|
13658
|
+
const paths = files.map((file) => stringValue2(file.filePath) ?? stringValue2(file.path) ?? stringValue2(file.relativePath)).filter((path27) => Boolean(path27));
|
|
13562
13659
|
const addedLines = files.reduce((total, file) => total + (numberValue(file.additions) ?? numberValue(file.addedLines) ?? numberValue(file.added) ?? 0), 0);
|
|
13563
13660
|
const removedLines = files.reduce((total, file) => total + (numberValue(file.deletions) ?? numberValue(file.removedLines) ?? numberValue(file.removed) ?? 0), 0);
|
|
13564
13661
|
return {
|
|
@@ -13706,28 +13803,28 @@ function mapAssistantTool(messageId2, tool, options) {
|
|
|
13706
13803
|
].includes(normalized)) {
|
|
13707
13804
|
const metadataStats = fileChangeStatsFromMetadata(state.metadata);
|
|
13708
13805
|
const patchText = isRecord9(input) ? stringValue2(input.patchText) ?? stringValue2(input.patch) ?? stringValue2(input.diff) : null;
|
|
13709
|
-
const
|
|
13710
|
-
if (toolIsLowInformationPatch(normalized, state, input, patchText,
|
|
13806
|
+
const path27 = metadataStats?.path ?? filePathFromInput(input) ?? extractPathFromPatchText(patchText);
|
|
13807
|
+
if (toolIsLowInformationPatch(normalized, state, input, patchText, path27, metadataStats)) {
|
|
13711
13808
|
return null;
|
|
13712
13809
|
}
|
|
13713
13810
|
const output = stringValue2(state.output);
|
|
13714
13811
|
const diffStats = countUnifiedDiffStats2(patchText);
|
|
13715
|
-
const displayFilePath = displayPath(
|
|
13812
|
+
const displayFilePath = displayPath(path27, options);
|
|
13716
13813
|
return {
|
|
13717
13814
|
id,
|
|
13718
13815
|
kind: "fileChange",
|
|
13719
13816
|
text: metadataStats ? metadataStats.changedFiles > 1 ? `${metadataStats.changedFiles} changed files` : displayFilePath ?? metadataStats.previewText : displayFilePath ?? output ?? summary ?? name,
|
|
13720
13817
|
previewText: metadataStats ? metadataStats.changedFiles > 1 ? `${metadataStats.changedFiles} changed files` : displayFilePath ?? metadataStats.previewText : displayFilePath ? `${name}: ${displayFilePath}` : output ?? summary ?? name,
|
|
13721
13818
|
detailText,
|
|
13722
|
-
changedFiles: metadataStats?.changedFiles ?? (
|
|
13819
|
+
changedFiles: metadataStats?.changedFiles ?? (path27 ? 1 : null),
|
|
13723
13820
|
addedLines: metadataStats?.addedLines ?? diffStats?.addedLines ?? null,
|
|
13724
13821
|
removedLines: metadataStats?.removedLines ?? diffStats?.removedLines ?? null,
|
|
13725
13822
|
status: toolStateStatus(state)
|
|
13726
13823
|
};
|
|
13727
13824
|
}
|
|
13728
13825
|
if (["read", "grep", "glob", "list", "ls", "bashoutput"].includes(normalized)) {
|
|
13729
|
-
const
|
|
13730
|
-
const text2 = displayPath(
|
|
13826
|
+
const path27 = filePathFromInput(input);
|
|
13827
|
+
const text2 = displayPath(path27, options) ?? summary ?? name;
|
|
13731
13828
|
return {
|
|
13732
13829
|
id,
|
|
13733
13830
|
kind: "fileRead",
|
|
@@ -16200,6 +16297,15 @@ function parseUuidV7Timestamp2(id) {
|
|
|
16200
16297
|
}
|
|
16201
16298
|
return new Date(millis).toISOString();
|
|
16202
16299
|
}
|
|
16300
|
+
function normalizeHistoryItemCreatedAt(item, fallback) {
|
|
16301
|
+
if (item.createdAt) {
|
|
16302
|
+
return item;
|
|
16303
|
+
}
|
|
16304
|
+
return {
|
|
16305
|
+
...item,
|
|
16306
|
+
createdAt: parseUuidV7Timestamp2(item.id) ?? fallback
|
|
16307
|
+
};
|
|
16308
|
+
}
|
|
16203
16309
|
function summarizeText(text2, fallback) {
|
|
16204
16310
|
const lines = text2.replace(/\r\n/g, "\n").split("\n");
|
|
16205
16311
|
while (lines.length > 1 && lines.at(-1)?.trim() === "") {
|
|
@@ -16552,13 +16658,17 @@ function mergePersistedHistoryItemsIntoTurns(turns, persistedItemsByTurnId, defe
|
|
|
16552
16658
|
});
|
|
16553
16659
|
}
|
|
16554
16660
|
function agentTurnToThreadTurnDto(turn, deferredDetails) {
|
|
16661
|
+
const startedAt = turn.startedAt ?? parseUuidV7Timestamp2(turn.providerTurnId);
|
|
16555
16662
|
const baseTurn = {
|
|
16556
16663
|
id: turn.providerTurnId,
|
|
16557
|
-
startedAt
|
|
16664
|
+
startedAt,
|
|
16558
16665
|
status: turn.status,
|
|
16559
16666
|
error: turn.error?.message ?? null,
|
|
16560
16667
|
items: visibleRuntimeTurnItems(turn.items).map(
|
|
16561
|
-
(item, transcriptIndex) =>
|
|
16668
|
+
(item, transcriptIndex) => normalizeHistoryItemCreatedAt(
|
|
16669
|
+
item.transcriptOrder === transcriptIndex ? item : { ...item, transcriptOrder: transcriptIndex },
|
|
16670
|
+
startedAt
|
|
16671
|
+
)
|
|
16562
16672
|
)
|
|
16563
16673
|
};
|
|
16564
16674
|
return deferredDetails ? deferLargeHistoryItemDetails(baseTurn, deferredDetails) : baseTurn;
|
|
@@ -19357,7 +19467,11 @@ var ThreadDeletionCoordinator = class {
|
|
|
19357
19467
|
};
|
|
19358
19468
|
|
|
19359
19469
|
// src/exports/thread-pdf-export.ts
|
|
19470
|
+
import { spawn as spawn2 } from "child_process";
|
|
19360
19471
|
import fs11 from "fs";
|
|
19472
|
+
import os3 from "os";
|
|
19473
|
+
import path14 from "path";
|
|
19474
|
+
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
19361
19475
|
import puppeteer from "puppeteer-core";
|
|
19362
19476
|
import { marked } from "marked";
|
|
19363
19477
|
var MAX_TEXT_CHARS = 12e3;
|
|
@@ -19371,6 +19485,8 @@ var EMBEDDED_CJK_FONT_CANDIDATES = [
|
|
|
19371
19485
|
{ path: "/mnt/c/Windows/Fonts/msyh.ttc", format: "truetype", weight: 400 }
|
|
19372
19486
|
];
|
|
19373
19487
|
var PUPPETEER_CHANNEL = "chrome";
|
|
19488
|
+
var PDF_EXPORT_TIMEOUT_MS = 45e3;
|
|
19489
|
+
var MAX_CHROME_STDERR_CHARS = 4e3;
|
|
19374
19490
|
var embeddedCjkFontCss = null;
|
|
19375
19491
|
function escapeHtml(value) {
|
|
19376
19492
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -20397,35 +20513,15 @@ endobj
|
|
|
20397
20513
|
`
|
|
20398
20514
|
);
|
|
20399
20515
|
}
|
|
20400
|
-
|
|
20401
|
-
try {
|
|
20402
|
-
const page = await browser.newPage();
|
|
20403
|
-
await page.setContent(renderThreadExportHtml(snapshot), {
|
|
20404
|
-
waitUntil: "load"
|
|
20405
|
-
});
|
|
20406
|
-
return Buffer.from(
|
|
20407
|
-
await page.pdf({
|
|
20408
|
-
format: "Letter",
|
|
20409
|
-
printBackground: true,
|
|
20410
|
-
preferCSSPageSize: true
|
|
20411
|
-
})
|
|
20412
|
-
);
|
|
20413
|
-
} finally {
|
|
20414
|
-
await browser.close();
|
|
20415
|
-
}
|
|
20516
|
+
return renderPdfWithChromeCli(renderThreadExportHtml(snapshot));
|
|
20416
20517
|
}
|
|
20417
|
-
|
|
20418
|
-
const launchOptions = {
|
|
20419
|
-
headless: true,
|
|
20420
|
-
args: ["--no-sandbox", "--disable-setuid-sandbox", "--font-render-hinting=none"]
|
|
20421
|
-
};
|
|
20422
|
-
if (process.env.PUPPETEER_EXECUTABLE_PATH) {
|
|
20423
|
-
launchOptions.executablePath = process.env.PUPPETEER_EXECUTABLE_PATH;
|
|
20424
|
-
} else {
|
|
20425
|
-
launchOptions.channel = PUPPETEER_CHANNEL;
|
|
20426
|
-
}
|
|
20518
|
+
function resolvePdfBrowserExecutablePath() {
|
|
20427
20519
|
try {
|
|
20428
|
-
|
|
20520
|
+
const executablePath = process.env.PUPPETEER_EXECUTABLE_PATH ? process.env.PUPPETEER_EXECUTABLE_PATH : puppeteer.executablePath(PUPPETEER_CHANNEL);
|
|
20521
|
+
if (!fs11.existsSync(executablePath)) {
|
|
20522
|
+
throw new Error(`Browser executable was not found at ${executablePath}`);
|
|
20523
|
+
}
|
|
20524
|
+
return executablePath;
|
|
20429
20525
|
} catch (error) {
|
|
20430
20526
|
const detail = error instanceof Error ? error.message : String(error);
|
|
20431
20527
|
throw new Error(
|
|
@@ -20433,6 +20529,89 @@ async function launchPdfBrowser() {
|
|
|
20433
20529
|
);
|
|
20434
20530
|
}
|
|
20435
20531
|
}
|
|
20532
|
+
async function renderPdfWithChromeCli(html) {
|
|
20533
|
+
const executablePath = resolvePdfBrowserExecutablePath();
|
|
20534
|
+
const tempDir = await fs11.promises.mkdtemp(path14.join(os3.tmpdir(), "remote-codex-pdf-"));
|
|
20535
|
+
const htmlPath = path14.join(tempDir, "thread-export.html");
|
|
20536
|
+
const pdfPath = path14.join(tempDir, "thread-export.pdf");
|
|
20537
|
+
try {
|
|
20538
|
+
await fs11.promises.writeFile(htmlPath, html, "utf8");
|
|
20539
|
+
await printHtmlToPdf({
|
|
20540
|
+
executablePath,
|
|
20541
|
+
htmlPath,
|
|
20542
|
+
pdfPath
|
|
20543
|
+
});
|
|
20544
|
+
const pdf = await fs11.promises.readFile(pdfPath);
|
|
20545
|
+
if (pdf.length === 0 || !pdf.subarray(0, 4).equals(Buffer.from("%PDF"))) {
|
|
20546
|
+
throw new Error("Chrome did not produce a valid PDF file.");
|
|
20547
|
+
}
|
|
20548
|
+
return pdf;
|
|
20549
|
+
} finally {
|
|
20550
|
+
await fs11.promises.rm(tempDir, { force: true, recursive: true });
|
|
20551
|
+
}
|
|
20552
|
+
}
|
|
20553
|
+
async function printHtmlToPdf(input) {
|
|
20554
|
+
const args = [
|
|
20555
|
+
"--headless",
|
|
20556
|
+
"--disable-gpu",
|
|
20557
|
+
"--disable-dev-shm-usage",
|
|
20558
|
+
"--no-sandbox",
|
|
20559
|
+
"--disable-setuid-sandbox",
|
|
20560
|
+
"--font-render-hinting=none",
|
|
20561
|
+
"--no-pdf-header-footer",
|
|
20562
|
+
"--print-to-pdf-no-header",
|
|
20563
|
+
`--print-to-pdf=${input.pdfPath}`,
|
|
20564
|
+
pathToFileURL3(input.htmlPath).href
|
|
20565
|
+
];
|
|
20566
|
+
await new Promise((resolve, reject) => {
|
|
20567
|
+
const child = spawn2(input.executablePath, args, {
|
|
20568
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
20569
|
+
});
|
|
20570
|
+
let settled = false;
|
|
20571
|
+
let stderr = "";
|
|
20572
|
+
const complete = (error) => {
|
|
20573
|
+
if (settled) {
|
|
20574
|
+
return;
|
|
20575
|
+
}
|
|
20576
|
+
settled = true;
|
|
20577
|
+
clearTimeout(timeout);
|
|
20578
|
+
child.kill("SIGTERM");
|
|
20579
|
+
if (error) {
|
|
20580
|
+
reject(error);
|
|
20581
|
+
} else {
|
|
20582
|
+
resolve();
|
|
20583
|
+
}
|
|
20584
|
+
};
|
|
20585
|
+
const timeout = setTimeout(() => {
|
|
20586
|
+
child.kill("SIGKILL");
|
|
20587
|
+
complete(new Error(`Chrome PDF export timed out after ${PDF_EXPORT_TIMEOUT_MS}ms.`));
|
|
20588
|
+
}, PDF_EXPORT_TIMEOUT_MS);
|
|
20589
|
+
child.stderr.on("data", (chunk) => {
|
|
20590
|
+
stderr = truncateChromeStderr(`${stderr}${chunk.toString("utf8")}`);
|
|
20591
|
+
if (stderr.includes("bytes written to file")) {
|
|
20592
|
+
complete();
|
|
20593
|
+
}
|
|
20594
|
+
});
|
|
20595
|
+
child.on("error", (error) => {
|
|
20596
|
+
complete(error);
|
|
20597
|
+
});
|
|
20598
|
+
child.on("close", (code, signal) => {
|
|
20599
|
+
if (code === 0) {
|
|
20600
|
+
complete();
|
|
20601
|
+
return;
|
|
20602
|
+
}
|
|
20603
|
+
const reason = signal ? `signal ${signal}` : `exit code ${code ?? "unknown"}`;
|
|
20604
|
+
const detail = stderr ? ` ${stderr}` : "";
|
|
20605
|
+
complete(new Error(`Chrome PDF export failed with ${reason}.${detail}`));
|
|
20606
|
+
});
|
|
20607
|
+
});
|
|
20608
|
+
}
|
|
20609
|
+
function truncateChromeStderr(value) {
|
|
20610
|
+
if (value.length <= MAX_CHROME_STDERR_CHARS) {
|
|
20611
|
+
return value;
|
|
20612
|
+
}
|
|
20613
|
+
return value.slice(value.length - MAX_CHROME_STDERR_CHARS);
|
|
20614
|
+
}
|
|
20436
20615
|
|
|
20437
20616
|
// src/thread-export-coordinator.ts
|
|
20438
20617
|
function userPromptPreviewFromTurn(turn) {
|
|
@@ -20703,7 +20882,7 @@ var ThreadForkCoordinator = class {
|
|
|
20703
20882
|
// src/thread-attachment-coordinator.ts
|
|
20704
20883
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
20705
20884
|
import fs12 from "fs/promises";
|
|
20706
|
-
import
|
|
20885
|
+
import path15 from "path";
|
|
20707
20886
|
async function pathExists(absPath) {
|
|
20708
20887
|
try {
|
|
20709
20888
|
await fs12.access(absPath);
|
|
@@ -20713,8 +20892,8 @@ async function pathExists(absPath) {
|
|
|
20713
20892
|
}
|
|
20714
20893
|
}
|
|
20715
20894
|
function sanitizeAttachmentFileName(originalName) {
|
|
20716
|
-
const basename =
|
|
20717
|
-
const extension =
|
|
20895
|
+
const basename = path15.basename(originalName).trim() || "attachment";
|
|
20896
|
+
const extension = path15.extname(basename).replace(/[^a-zA-Z0-9.]/g, "");
|
|
20718
20897
|
const rawStem = extension ? basename.slice(0, -extension.length) : basename;
|
|
20719
20898
|
const sanitizedStem = rawStem.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 64);
|
|
20720
20899
|
const stem = sanitizedStem || "attachment";
|
|
@@ -20722,7 +20901,7 @@ function sanitizeAttachmentFileName(originalName) {
|
|
|
20722
20901
|
return `${stem}-${randomUUID3().slice(0, 8)}${normalizedExtension}`;
|
|
20723
20902
|
}
|
|
20724
20903
|
function threadTempDirectoryPath2(workspacePath, localThreadId) {
|
|
20725
|
-
return
|
|
20904
|
+
return path15.join(workspacePath, ".temp", "threads", localThreadId);
|
|
20726
20905
|
}
|
|
20727
20906
|
var ThreadAttachmentCoordinator = class {
|
|
20728
20907
|
constructor(db) {
|
|
@@ -20763,7 +20942,7 @@ var ThreadAttachmentCoordinator = class {
|
|
|
20763
20942
|
const savedFileName = sanitizeAttachmentFileName(
|
|
20764
20943
|
attachment.manifest.originalName
|
|
20765
20944
|
);
|
|
20766
|
-
await fs12.writeFile(
|
|
20945
|
+
await fs12.writeFile(path15.join(tempDirectory, savedFileName), attachment.buffer);
|
|
20767
20946
|
const relativePath = `./.temp/threads/${localThreadId}/${savedFileName}`;
|
|
20768
20947
|
const replacementToken = attachment.manifest.kind === "photo" ? `[PHOTO ${relativePath}]` : `[FILE ${relativePath}]`;
|
|
20769
20948
|
rewrittenPrompt = rewrittenPrompt.split(attachment.manifest.placeholder).join(replacementToken);
|
|
@@ -20777,7 +20956,7 @@ var ThreadAttachmentCoordinator = class {
|
|
|
20777
20956
|
|
|
20778
20957
|
// src/thread-import-coordinator.ts
|
|
20779
20958
|
import fs13 from "fs/promises";
|
|
20780
|
-
import
|
|
20959
|
+
import path16 from "path";
|
|
20781
20960
|
async function pathExists2(absPath) {
|
|
20782
20961
|
try {
|
|
20783
20962
|
await fs13.access(absPath);
|
|
@@ -20787,19 +20966,19 @@ async function pathExists2(absPath) {
|
|
|
20787
20966
|
}
|
|
20788
20967
|
}
|
|
20789
20968
|
async function resolveComparablePath2(absPath) {
|
|
20790
|
-
const resolved =
|
|
20969
|
+
const resolved = path16.resolve(absPath);
|
|
20791
20970
|
if (await pathExists2(resolved)) {
|
|
20792
20971
|
return fs13.realpath(resolved);
|
|
20793
20972
|
}
|
|
20794
|
-
const parentPath =
|
|
20973
|
+
const parentPath = path16.dirname(resolved);
|
|
20795
20974
|
if (parentPath === resolved) {
|
|
20796
20975
|
return resolved;
|
|
20797
20976
|
}
|
|
20798
20977
|
const resolvedParent = await resolveComparablePath2(parentPath);
|
|
20799
|
-
return
|
|
20978
|
+
return path16.join(resolvedParent, path16.basename(resolved));
|
|
20800
20979
|
}
|
|
20801
20980
|
async function resolveImportedWorkspacePath(workspaceRoot, candidatePath) {
|
|
20802
|
-
if (!
|
|
20981
|
+
if (!path16.isAbsolute(candidatePath)) {
|
|
20803
20982
|
throw new HttpError(400, {
|
|
20804
20983
|
code: "bad_request",
|
|
20805
20984
|
message: "Imported session path must be absolute."
|
|
@@ -20807,7 +20986,7 @@ async function resolveImportedWorkspacePath(workspaceRoot, candidatePath) {
|
|
|
20807
20986
|
}
|
|
20808
20987
|
const resolvedRoot = await resolveComparablePath2(workspaceRoot);
|
|
20809
20988
|
const resolvedCandidate = await resolveComparablePath2(candidatePath);
|
|
20810
|
-
const normalizedRoot = resolvedRoot.endsWith(
|
|
20989
|
+
const normalizedRoot = resolvedRoot.endsWith(path16.sep) ? resolvedRoot : `${resolvedRoot}${path16.sep}`;
|
|
20811
20990
|
if (resolvedCandidate !== resolvedRoot && !resolvedCandidate.startsWith(normalizedRoot)) {
|
|
20812
20991
|
throw new HttpError(403, {
|
|
20813
20992
|
code: "forbidden",
|
|
@@ -20860,7 +21039,7 @@ var ThreadImportCoordinator = class {
|
|
|
20860
21039
|
if (!workspace) {
|
|
20861
21040
|
workspace = createWorkspaceRecord(this.db, {
|
|
20862
21041
|
absPath: importedPath,
|
|
20863
|
-
label:
|
|
21042
|
+
label: path16.basename(importedPath) || "workspace"
|
|
20864
21043
|
});
|
|
20865
21044
|
}
|
|
20866
21045
|
const created = createThreadRecord(this.db, {
|
|
@@ -20936,11 +21115,18 @@ function harnessDeveloperInstructions(config) {
|
|
|
20936
21115
|
return null;
|
|
20937
21116
|
}
|
|
20938
21117
|
const baseUrl = config.harnessBaseUrl.replace(/\/+$/, "");
|
|
20939
|
-
|
|
21118
|
+
const lines = [
|
|
20940
21119
|
`ElAgente Harness chemistry tools are available at ${baseUrl}.`,
|
|
20941
21120
|
"For chemistry tasks, call its HTTP API directly using the sandbox env var INACT_X_APP_KEY as the x-api-key header; never print or expose that key.",
|
|
20942
21121
|
"Discover tools with GET /, GET /farmaco/tools, GET /farmaco/.help, GET /quntur/tools, or GET /estructural/tools; invoke approved tools with POST /{module}/tools/{tool} using JSON input."
|
|
20943
|
-
]
|
|
21122
|
+
];
|
|
21123
|
+
if (config.harnessWakeupCallbackBaseUrl) {
|
|
21124
|
+
const supervisorBaseUrl = `http://127.0.0.1:${config.port}`;
|
|
21125
|
+
lines.push(
|
|
21126
|
+
`For long-running compute jobs you do not need to stay running: first GET ${supervisorBaseUrl}/api/harness/wakeup and read "notifyTo"; submit the job with "notify_to" set to that value. If you submit directly to Harness rather than through the Remote Codex Harness invoke proxy, immediately register POST ${supervisorBaseUrl}/api/harness/job-watches with JSON {"jobId": "<job id>"}. After that you may end your turn; this thread is woken with a new message when the job reaches a terminal status.`
|
|
21127
|
+
);
|
|
21128
|
+
}
|
|
21129
|
+
return lines.join(" ");
|
|
20944
21130
|
}
|
|
20945
21131
|
function combineDeveloperInstructions(parts) {
|
|
20946
21132
|
const normalized = parts.map((part) => part?.trim()).filter((part) => Boolean(part));
|
|
@@ -22118,8 +22304,8 @@ var ThreadService = class {
|
|
|
22118
22304
|
|
|
22119
22305
|
// src/routes/agent-runtimes.ts
|
|
22120
22306
|
import fs15 from "fs/promises";
|
|
22121
|
-
import { spawn as
|
|
22122
|
-
import
|
|
22307
|
+
import { spawn as spawn3 } from "child_process";
|
|
22308
|
+
import path17 from "path";
|
|
22123
22309
|
import { z as z3 } from "zod";
|
|
22124
22310
|
|
|
22125
22311
|
// src/provider-schemas.ts
|
|
@@ -22340,7 +22526,7 @@ async function refreshBackendInstallation(app, runtime) {
|
|
|
22340
22526
|
async function installedPackageVersion(packageName) {
|
|
22341
22527
|
const globalRoot = await npmGlobalRoot3();
|
|
22342
22528
|
if (globalRoot) {
|
|
22343
|
-
const global = await packageVersionFromPath(
|
|
22529
|
+
const global = await packageVersionFromPath(path17.join(globalRoot, packageName, "package.json"));
|
|
22344
22530
|
if (global) {
|
|
22345
22531
|
return global;
|
|
22346
22532
|
}
|
|
@@ -22391,7 +22577,7 @@ function versionStringContains(installedVersion, latestVersion) {
|
|
|
22391
22577
|
return Boolean(installedVersion && latestVersion && installedVersion.includes(latestVersion));
|
|
22392
22578
|
}
|
|
22393
22579
|
async function packageVersionFromNode(packageName) {
|
|
22394
|
-
return packageVersionFromPath(
|
|
22580
|
+
return packageVersionFromPath(path17.resolve("node_modules", packageName, "package.json"));
|
|
22395
22581
|
}
|
|
22396
22582
|
async function packageVersionFromPath(packageJsonPath) {
|
|
22397
22583
|
try {
|
|
@@ -22418,14 +22604,14 @@ async function npmGlobalBin() {
|
|
|
22418
22604
|
return firstLine(result.stdout);
|
|
22419
22605
|
}
|
|
22420
22606
|
const prefix = await npmGlobalPrefix();
|
|
22421
|
-
return prefix ?
|
|
22607
|
+
return prefix ? path17.join(prefix, "bin") : null;
|
|
22422
22608
|
}
|
|
22423
22609
|
async function npmGlobalPrefix() {
|
|
22424
22610
|
const result = await runShellCommand("npm prefix -g", 3e3);
|
|
22425
22611
|
return result.code === 0 ? firstLine(result.stdout) : null;
|
|
22426
22612
|
}
|
|
22427
22613
|
async function commandPathFor(command) {
|
|
22428
|
-
if (
|
|
22614
|
+
if (path17.isAbsolute(command)) {
|
|
22429
22615
|
return command;
|
|
22430
22616
|
}
|
|
22431
22617
|
const result = await runShellCommand(`command -v ${shellQuote(command)}`, 3e3);
|
|
@@ -22450,7 +22636,7 @@ function commandFailureMessage(command, result) {
|
|
|
22450
22636
|
}
|
|
22451
22637
|
function runShellCommand(command, timeoutMs = 0) {
|
|
22452
22638
|
return new Promise((resolve) => {
|
|
22453
|
-
const child =
|
|
22639
|
+
const child = spawn3(command, {
|
|
22454
22640
|
shell: true,
|
|
22455
22641
|
stdio: ["ignore", "pipe", "pipe"]
|
|
22456
22642
|
});
|
|
@@ -22621,6 +22807,14 @@ var harnessToolParamSchema = harnessModuleParamSchema.extend({
|
|
|
22621
22807
|
tool: z4.string().trim().min(1).max(160).regex(/^[a-zA-Z0-9_-]+$/)
|
|
22622
22808
|
});
|
|
22623
22809
|
var harnessInvokeBodySchema = z4.record(z4.string(), z4.unknown());
|
|
22810
|
+
var harnessJobWatchBodySchema = z4.object({
|
|
22811
|
+
jobId: z4.string().trim().min(1).max(200).regex(/^[a-zA-Z0-9_.:-]+$/),
|
|
22812
|
+
threadId: z4.string().trim().min(1).max(200).optional(),
|
|
22813
|
+
title: z4.string().trim().min(1).max(300).optional()
|
|
22814
|
+
});
|
|
22815
|
+
var harnessHookParamSchema = z4.object({
|
|
22816
|
+
token: z4.string().trim().min(1).max(200).regex(/^[a-zA-Z0-9_-]+$/)
|
|
22817
|
+
});
|
|
22624
22818
|
var harnessInvokeContextSchema = z4.object({
|
|
22625
22819
|
workspaceId: z4.string().uuid().nullable().optional(),
|
|
22626
22820
|
sessionId: z4.string().uuid().nullable().optional(),
|
|
@@ -22964,6 +23158,19 @@ async function registerSystemRoutes(app) {
|
|
|
22964
23158
|
metadata: harnessUsageMetadata(payload, attributionSource)
|
|
22965
23159
|
}).catch(() => void 0);
|
|
22966
23160
|
}
|
|
23161
|
+
const invokeJobId = stringField2(result, ["job_id", "jobId", "compute_job_id", "computeJobId"]);
|
|
23162
|
+
if (invokeJobId && context.threadId && app.services.harnessWakeupService.enabled()) {
|
|
23163
|
+
await app.services.harnessWakeupService.watchJob({
|
|
23164
|
+
jobId: invokeJobId,
|
|
23165
|
+
threadId: context.threadId,
|
|
23166
|
+
title: `${params.module}/${params.tool}`
|
|
23167
|
+
}).catch((watchError) => {
|
|
23168
|
+
request.log.warn(
|
|
23169
|
+
{ err: watchError, jobId: invokeJobId },
|
|
23170
|
+
"Harness wakeup auto-watch failed."
|
|
23171
|
+
);
|
|
23172
|
+
});
|
|
23173
|
+
}
|
|
22967
23174
|
return payload;
|
|
22968
23175
|
} catch (error) {
|
|
22969
23176
|
if (error instanceof HttpError) {
|
|
@@ -22975,6 +23182,68 @@ async function registerSystemRoutes(app) {
|
|
|
22975
23182
|
});
|
|
22976
23183
|
}
|
|
22977
23184
|
});
|
|
23185
|
+
app.get("/api/harness/wakeup", async () => {
|
|
23186
|
+
try {
|
|
23187
|
+
return await app.services.harnessWakeupService.getWakeupInfo();
|
|
23188
|
+
} catch (error) {
|
|
23189
|
+
if (error instanceof HttpError) {
|
|
23190
|
+
throw error;
|
|
23191
|
+
}
|
|
23192
|
+
throw new HttpError(503, {
|
|
23193
|
+
code: "harness_unavailable",
|
|
23194
|
+
message: error instanceof Error ? error.message : "ElAgenteHarness is unavailable."
|
|
23195
|
+
});
|
|
23196
|
+
}
|
|
23197
|
+
});
|
|
23198
|
+
app.get("/api/harness/job-watches", async () => {
|
|
23199
|
+
return {
|
|
23200
|
+
watches: listHarnessJobWatches(app.services.database.db)
|
|
23201
|
+
};
|
|
23202
|
+
});
|
|
23203
|
+
app.post("/api/harness/job-watches", async (request, reply) => {
|
|
23204
|
+
const body = harnessJobWatchBodySchema.parse(request.body ?? {});
|
|
23205
|
+
try {
|
|
23206
|
+
const result = await app.services.harnessWakeupService.watchJob({
|
|
23207
|
+
jobId: body.jobId,
|
|
23208
|
+
threadId: body.threadId ?? null,
|
|
23209
|
+
title: body.title ?? null
|
|
23210
|
+
});
|
|
23211
|
+
reply.status(201);
|
|
23212
|
+
return result;
|
|
23213
|
+
} catch (error) {
|
|
23214
|
+
if (error instanceof HttpError) {
|
|
23215
|
+
throw error;
|
|
23216
|
+
}
|
|
23217
|
+
throw new HttpError(503, {
|
|
23218
|
+
code: "harness_unavailable",
|
|
23219
|
+
message: error instanceof Error ? error.message : "ElAgenteHarness is unavailable."
|
|
23220
|
+
});
|
|
23221
|
+
}
|
|
23222
|
+
});
|
|
23223
|
+
app.register(async (hookApp) => {
|
|
23224
|
+
hookApp.addContentTypeParser(
|
|
23225
|
+
["application/json", "text/plain"],
|
|
23226
|
+
{ parseAs: "buffer" },
|
|
23227
|
+
(_request, body, done) => done(null, body)
|
|
23228
|
+
);
|
|
23229
|
+
hookApp.addContentTypeParser(
|
|
23230
|
+
"*",
|
|
23231
|
+
{ parseAs: "buffer" },
|
|
23232
|
+
(_request, body, done) => done(null, body)
|
|
23233
|
+
);
|
|
23234
|
+
hookApp.post("/api/hooks/harness-notify/:token", async (request, reply) => {
|
|
23235
|
+
const params = harnessHookParamSchema.parse(request.params);
|
|
23236
|
+
const signatureHeader = request.headers["x-webhook-signature"];
|
|
23237
|
+
const rawBody = Buffer.isBuffer(request.body) ? request.body : Buffer.from(typeof request.body === "string" ? request.body : "");
|
|
23238
|
+
const result = app.services.harnessWakeupService.handleCallback({
|
|
23239
|
+
hookToken: params.token,
|
|
23240
|
+
rawBody,
|
|
23241
|
+
signature: typeof signatureHeader === "string" ? signatureHeader : null
|
|
23242
|
+
});
|
|
23243
|
+
reply.status(202);
|
|
23244
|
+
return result;
|
|
23245
|
+
});
|
|
23246
|
+
});
|
|
22978
23247
|
app.get("/api/config/workspace-settings", async () => {
|
|
22979
23248
|
return getWorkspaceSettings(
|
|
22980
23249
|
app.services.database.db,
|
|
@@ -23097,7 +23366,7 @@ async function registerSystemRoutes(app) {
|
|
|
23097
23366
|
|
|
23098
23367
|
// src/routes/threads.ts
|
|
23099
23368
|
import fs17 from "fs/promises";
|
|
23100
|
-
import
|
|
23369
|
+
import path18 from "path";
|
|
23101
23370
|
import { z as z5 } from "zod";
|
|
23102
23371
|
|
|
23103
23372
|
// src/worker-identity.ts
|
|
@@ -23569,7 +23838,7 @@ async function registerThreadRoutes(app) {
|
|
|
23569
23838
|
message: "Workspace was not found for this thread."
|
|
23570
23839
|
});
|
|
23571
23840
|
}
|
|
23572
|
-
const candidatePath =
|
|
23841
|
+
const candidatePath = path18.isAbsolute(query.path) ? query.path : path18.resolve(workspace.absPath, query.path);
|
|
23573
23842
|
const requestedPath = await fs17.realpath(candidatePath).catch(() => null);
|
|
23574
23843
|
if (!requestedPath) {
|
|
23575
23844
|
throw new HttpError(404, {
|
|
@@ -23577,8 +23846,8 @@ async function registerThreadRoutes(app) {
|
|
|
23577
23846
|
message: "Image file was not found."
|
|
23578
23847
|
});
|
|
23579
23848
|
}
|
|
23580
|
-
const resolvedWorkspaceRoot = await fs17.realpath(app.services.config.workspaceRoot).catch(() =>
|
|
23581
|
-
const workspacePrefix = resolvedWorkspaceRoot.endsWith(
|
|
23849
|
+
const resolvedWorkspaceRoot = await fs17.realpath(app.services.config.workspaceRoot).catch(() => path18.resolve(app.services.config.workspaceRoot));
|
|
23850
|
+
const workspacePrefix = resolvedWorkspaceRoot.endsWith(path18.sep) ? resolvedWorkspaceRoot : `${resolvedWorkspaceRoot}${path18.sep}`;
|
|
23582
23851
|
if (requestedPath !== resolvedWorkspaceRoot && !requestedPath.startsWith(workspacePrefix)) {
|
|
23583
23852
|
throw new HttpError(403, {
|
|
23584
23853
|
code: "forbidden",
|
|
@@ -23775,9 +24044,9 @@ async function registerThreadRoutes(app) {
|
|
|
23775
24044
|
// src/routes/workspaces.ts
|
|
23776
24045
|
import fs18 from "fs/promises";
|
|
23777
24046
|
import { createReadStream } from "fs";
|
|
23778
|
-
import
|
|
23779
|
-
import
|
|
23780
|
-
import { spawn as
|
|
24047
|
+
import os4 from "os";
|
|
24048
|
+
import path19 from "path";
|
|
24049
|
+
import { spawn as spawn4 } from "child_process";
|
|
23781
24050
|
import { Readable } from "stream";
|
|
23782
24051
|
import { z as z6 } from "zod";
|
|
23783
24052
|
var createWorkspaceSchema = z6.union([
|
|
@@ -23864,7 +24133,7 @@ function toWorkspaceFileDto(file) {
|
|
|
23864
24133
|
};
|
|
23865
24134
|
}
|
|
23866
24135
|
function languageForPath(filePath) {
|
|
23867
|
-
const extension =
|
|
24136
|
+
const extension = path19.extname(filePath).slice(1).toLowerCase();
|
|
23868
24137
|
switch (extension) {
|
|
23869
24138
|
case "js":
|
|
23870
24139
|
case "jsx":
|
|
@@ -23910,18 +24179,18 @@ function languageForPath(filePath) {
|
|
|
23910
24179
|
}
|
|
23911
24180
|
}
|
|
23912
24181
|
function relativeWorkspacePath(rootPath, absPath) {
|
|
23913
|
-
const relative =
|
|
23914
|
-
return relative === "" ? "" : relative.split(
|
|
24182
|
+
const relative = path19.relative(rootPath, absPath);
|
|
24183
|
+
return relative === "" ? "" : relative.split(path19.sep).join("/");
|
|
23915
24184
|
}
|
|
23916
24185
|
async function resolveWorkspaceItemPath(rootPath, relativePath = "") {
|
|
23917
|
-
const candidate =
|
|
24186
|
+
const candidate = path19.resolve(rootPath, relativePath || ".");
|
|
23918
24187
|
const comparable = await assertPathWithinRoot(rootPath, candidate);
|
|
23919
24188
|
return comparable;
|
|
23920
24189
|
}
|
|
23921
24190
|
async function buildWorkspaceTreeNode(rootPath, absPath, depth = 0) {
|
|
23922
24191
|
const stats = await fs18.stat(absPath);
|
|
23923
24192
|
const relativePath = relativeWorkspacePath(rootPath, absPath);
|
|
23924
|
-
const name = relativePath ?
|
|
24193
|
+
const name = relativePath ? path19.basename(absPath) : path19.basename(rootPath);
|
|
23925
24194
|
if (!stats.isDirectory()) {
|
|
23926
24195
|
return {
|
|
23927
24196
|
name,
|
|
@@ -23956,7 +24225,7 @@ async function buildWorkspaceTreeNode(rootPath, absPath, depth = 0) {
|
|
|
23956
24225
|
}).slice(0, 400);
|
|
23957
24226
|
node.children = (await Promise.all(
|
|
23958
24227
|
visible.map(async (entry) => {
|
|
23959
|
-
const childPath =
|
|
24228
|
+
const childPath = path19.join(absPath, entry.name);
|
|
23960
24229
|
try {
|
|
23961
24230
|
if (!entry.isDirectory() && !entry.isFile()) {
|
|
23962
24231
|
return null;
|
|
@@ -23980,19 +24249,19 @@ function requireWorkspaceRecord(app, workspaceId) {
|
|
|
23980
24249
|
return record;
|
|
23981
24250
|
}
|
|
23982
24251
|
function artifactRoot(record) {
|
|
23983
|
-
return
|
|
24252
|
+
return path19.join(record.absPath, ".remote-codex", "artifacts");
|
|
23984
24253
|
}
|
|
23985
24254
|
function artifactFilePath(record, artifactId) {
|
|
23986
|
-
return
|
|
24255
|
+
return path19.join(artifactRoot(record), artifactId, "artifact.bin");
|
|
23987
24256
|
}
|
|
23988
24257
|
function artifactMetadataPath(record, artifactId) {
|
|
23989
|
-
return
|
|
24258
|
+
return path19.join(artifactRoot(record), artifactId, "metadata.json");
|
|
23990
24259
|
}
|
|
23991
24260
|
function safeArtifactFileName(value) {
|
|
23992
|
-
return
|
|
24261
|
+
return path19.basename(value).replace(/[^a-zA-Z0-9_. -]/g, "_") || "artifact.bin";
|
|
23993
24262
|
}
|
|
23994
24263
|
function artifactIdFromName(name) {
|
|
23995
|
-
const base =
|
|
24264
|
+
const base = path19.basename(name).replace(/[^a-zA-Z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 96);
|
|
23996
24265
|
return `${base || "artifact"}-${Date.now().toString(36)}`;
|
|
23997
24266
|
}
|
|
23998
24267
|
async function readArtifactMetadata(record, artifactId) {
|
|
@@ -24035,7 +24304,7 @@ async function listWorkspaceArtifacts(record) {
|
|
|
24035
24304
|
return artifacts.sort((left, right) => right.createdAt.localeCompare(left.createdAt));
|
|
24036
24305
|
}
|
|
24037
24306
|
function contentTypeForPath(filePath) {
|
|
24038
|
-
switch (
|
|
24307
|
+
switch (path19.extname(filePath).slice(1).toLowerCase()) {
|
|
24039
24308
|
case "png":
|
|
24040
24309
|
return "image/png";
|
|
24041
24310
|
case "jpg":
|
|
@@ -24065,7 +24334,7 @@ function contentTypeForPath(filePath) {
|
|
|
24065
24334
|
}
|
|
24066
24335
|
}
|
|
24067
24336
|
async function collectFolderZipEntries(rootPath, folderPath) {
|
|
24068
|
-
const folderName =
|
|
24337
|
+
const folderName = path19.basename(folderPath) || "workspace-folder";
|
|
24069
24338
|
const entries = [];
|
|
24070
24339
|
let totalBytes = 0;
|
|
24071
24340
|
const pending = [folderPath];
|
|
@@ -24073,7 +24342,7 @@ async function collectFolderZipEntries(rootPath, folderPath) {
|
|
|
24073
24342
|
const current = pending.pop();
|
|
24074
24343
|
const children = await fs18.readdir(current, { withFileTypes: true });
|
|
24075
24344
|
for (const child of children) {
|
|
24076
|
-
const childPath = await resolveWorkspaceItemPath(rootPath,
|
|
24345
|
+
const childPath = await resolveWorkspaceItemPath(rootPath, path19.relative(rootPath, path19.join(current, child.name)));
|
|
24077
24346
|
if (child.isDirectory()) {
|
|
24078
24347
|
pending.push(childPath);
|
|
24079
24348
|
continue;
|
|
@@ -24133,7 +24402,7 @@ async function createFolderZipFile(rootPath, folderPath) {
|
|
|
24133
24402
|
let offset = 0;
|
|
24134
24403
|
for (const entry of entries) {
|
|
24135
24404
|
const data = await fs18.readFile(entry.absPath);
|
|
24136
|
-
const name = Buffer.from(entry.archivePath.split(
|
|
24405
|
+
const name = Buffer.from(entry.archivePath.split(path19.sep).join("/"), "utf8");
|
|
24137
24406
|
const checksum = crc32(data);
|
|
24138
24407
|
const { dosDate, dosTime } = zipDosDateTime(entry.updatedAt);
|
|
24139
24408
|
const localHeader = Buffer.alloc(30);
|
|
@@ -24180,8 +24449,8 @@ async function createFolderZipFile(rootPath, folderPath) {
|
|
|
24180
24449
|
endRecord.writeUInt32LE(centralSize, 12);
|
|
24181
24450
|
endRecord.writeUInt32LE(offset, 16);
|
|
24182
24451
|
endRecord.writeUInt16LE(0, 20);
|
|
24183
|
-
const tempDir = await fs18.mkdtemp(
|
|
24184
|
-
const zipPath =
|
|
24452
|
+
const tempDir = await fs18.mkdtemp(path19.join(os4.tmpdir(), "remote-codex-folder-download-"));
|
|
24453
|
+
const zipPath = path19.join(tempDir, `${path19.basename(folderPath) || "workspace-folder"}.zip`);
|
|
24185
24454
|
await fs18.writeFile(zipPath, Buffer.concat([...localParts, ...centralParts, endRecord]));
|
|
24186
24455
|
return { zipPath, tempDir };
|
|
24187
24456
|
}
|
|
@@ -24192,7 +24461,7 @@ function cleanupTemporaryZip(zipPath, tempDir) {
|
|
|
24192
24461
|
};
|
|
24193
24462
|
}
|
|
24194
24463
|
function sanitizeUploadFilename(filename) {
|
|
24195
|
-
const baseName =
|
|
24464
|
+
const baseName = path19.basename(filename?.trim() || "upload");
|
|
24196
24465
|
if (!baseName || baseName === "." || baseName === "..") {
|
|
24197
24466
|
return "upload";
|
|
24198
24467
|
}
|
|
@@ -24204,7 +24473,7 @@ function inferGitRepoName(gitUrl) {
|
|
|
24204
24473
|
const normalized = withoutQuery.replace(/[\\/]+$/, "");
|
|
24205
24474
|
const rawName = normalized.split(/[/:]/).filter(Boolean).at(-1) ?? "";
|
|
24206
24475
|
const repoName = rawName.endsWith(".git") ? rawName.slice(0, -4) : rawName;
|
|
24207
|
-
if (!repoName || repoName === "." || repoName === ".." || repoName.includes(
|
|
24476
|
+
if (!repoName || repoName === "." || repoName === ".." || repoName.includes(path19.sep)) {
|
|
24208
24477
|
throw new HttpError(400, {
|
|
24209
24478
|
code: "bad_request",
|
|
24210
24479
|
message: "Unable to infer a target directory from the Git URL."
|
|
@@ -24225,7 +24494,7 @@ async function pathExists4(absPath) {
|
|
|
24225
24494
|
}
|
|
24226
24495
|
function cloneRepository(gitUrl, targetPath) {
|
|
24227
24496
|
return new Promise((resolve, reject) => {
|
|
24228
|
-
const child =
|
|
24497
|
+
const child = spawn4("git", ["clone", gitUrl, targetPath], {
|
|
24229
24498
|
stdio: ["ignore", "ignore", "pipe"]
|
|
24230
24499
|
});
|
|
24231
24500
|
let stderr = "";
|
|
@@ -24269,7 +24538,7 @@ async function registerWorkspaceRoutes(app) {
|
|
|
24269
24538
|
});
|
|
24270
24539
|
app.get("/api/workspaces/tree", async (request) => {
|
|
24271
24540
|
const query = treeQuerySchema.parse(request.query);
|
|
24272
|
-
const requestedPath = query.path ?
|
|
24541
|
+
const requestedPath = query.path ? path19.resolve(query.path) : app.services.config.workspaceRoot;
|
|
24273
24542
|
const tree = await readWorkspaceTree({
|
|
24274
24543
|
rootPath: app.services.config.workspaceRoot,
|
|
24275
24544
|
targetPath: requestedPath,
|
|
@@ -24335,7 +24604,7 @@ async function registerWorkspaceRoutes(app) {
|
|
|
24335
24604
|
const nextOffset = offset + read.bytesRead;
|
|
24336
24605
|
return {
|
|
24337
24606
|
path: relativeWorkspacePath(rootPath, filePath),
|
|
24338
|
-
name:
|
|
24607
|
+
name: path19.basename(filePath),
|
|
24339
24608
|
content: buffer.subarray(0, read.bytesRead).toString("utf8"),
|
|
24340
24609
|
language: languageForPath(filePath),
|
|
24341
24610
|
size: stats.size,
|
|
@@ -24371,7 +24640,7 @@ async function registerWorkspaceRoutes(app) {
|
|
|
24371
24640
|
const stats = await fs18.stat(itemPath);
|
|
24372
24641
|
if (stats.isDirectory()) {
|
|
24373
24642
|
const { zipPath, tempDir } = await createFolderZipFile(rootPath, itemPath);
|
|
24374
|
-
const filename2 = `${
|
|
24643
|
+
const filename2 = `${path19.basename(itemPath) || "workspace-folder"}.zip`;
|
|
24375
24644
|
const cleanup = cleanupTemporaryZip(zipPath, tempDir);
|
|
24376
24645
|
reply.raw.once("finish", () => void cleanup());
|
|
24377
24646
|
reply.raw.once("close", () => void cleanup());
|
|
@@ -24387,7 +24656,7 @@ async function registerWorkspaceRoutes(app) {
|
|
|
24387
24656
|
message: "Only file and folder downloads are supported from this endpoint."
|
|
24388
24657
|
});
|
|
24389
24658
|
}
|
|
24390
|
-
const filename =
|
|
24659
|
+
const filename = path19.basename(itemPath);
|
|
24391
24660
|
reply.header("content-type", contentTypeForPath(itemPath)).header(
|
|
24392
24661
|
"content-disposition",
|
|
24393
24662
|
`attachment; filename="${filename}"; filename*=UTF-8''${encodeURIComponent(filename)}`
|
|
@@ -24453,7 +24722,7 @@ async function registerWorkspaceRoutes(app) {
|
|
|
24453
24722
|
kind: "file",
|
|
24454
24723
|
file: {
|
|
24455
24724
|
path: file.path,
|
|
24456
|
-
name:
|
|
24725
|
+
name: path19.basename(file.path),
|
|
24457
24726
|
size: file.size
|
|
24458
24727
|
}
|
|
24459
24728
|
};
|
|
@@ -24495,7 +24764,7 @@ async function registerWorkspaceRoutes(app) {
|
|
|
24495
24764
|
message: "Artifact content must not be empty."
|
|
24496
24765
|
});
|
|
24497
24766
|
}
|
|
24498
|
-
const dir =
|
|
24767
|
+
const dir = path19.dirname(artifactFilePath(record, artifactId));
|
|
24499
24768
|
await fs18.mkdir(dir, { recursive: true, mode: 448 });
|
|
24500
24769
|
const filePath = artifactFilePath(record, artifactId);
|
|
24501
24770
|
await fs18.writeFile(filePath, content, { flag: "wx" }).catch((error) => {
|
|
@@ -24558,7 +24827,7 @@ async function registerWorkspaceRoutes(app) {
|
|
|
24558
24827
|
const params = z6.object({ id: z6.string().uuid(), artifactId: workspaceArtifactIdSchema }).parse(request.params);
|
|
24559
24828
|
const record = requireWorkspaceRecord(app, params.id);
|
|
24560
24829
|
const artifact = await readArtifactMetadata(record, params.artifactId);
|
|
24561
|
-
await fs18.rm(
|
|
24830
|
+
await fs18.rm(path19.dirname(artifactFilePath(record, params.artifactId)), {
|
|
24562
24831
|
recursive: true,
|
|
24563
24832
|
force: true
|
|
24564
24833
|
});
|
|
@@ -24573,7 +24842,7 @@ async function registerWorkspaceRoutes(app) {
|
|
|
24573
24842
|
let validated;
|
|
24574
24843
|
if ("gitUrl" in body) {
|
|
24575
24844
|
const repoName = inferGitRepoName(body.gitUrl);
|
|
24576
|
-
const targetPath =
|
|
24845
|
+
const targetPath = path19.join(settings.devHome, repoName);
|
|
24577
24846
|
if (await pathExists4(targetPath)) {
|
|
24578
24847
|
throw new HttpError(409, {
|
|
24579
24848
|
code: "conflict",
|
|
@@ -24837,7 +25106,7 @@ async function registerAuthRoutes(app) {
|
|
|
24837
25106
|
|
|
24838
25107
|
// src/provider-host-config-service.ts
|
|
24839
25108
|
import fs19 from "fs/promises";
|
|
24840
|
-
import
|
|
25109
|
+
import path20 from "path";
|
|
24841
25110
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
24842
25111
|
function providerError(message, statusCode = 404) {
|
|
24843
25112
|
const error = new Error(message);
|
|
@@ -24845,16 +25114,16 @@ function providerError(message, statusCode = 404) {
|
|
|
24845
25114
|
return error;
|
|
24846
25115
|
}
|
|
24847
25116
|
function resolveProviderHostFilePath(providerHome, name) {
|
|
24848
|
-
return
|
|
25117
|
+
return path20.join(providerHome, name);
|
|
24849
25118
|
}
|
|
24850
25119
|
function resolveArchiveRoot(providerHome) {
|
|
24851
|
-
return
|
|
25120
|
+
return path20.join(providerHome, "supervisor-config-archives");
|
|
24852
25121
|
}
|
|
24853
25122
|
function resolveArchiveIndexPath(providerHome) {
|
|
24854
|
-
return
|
|
25123
|
+
return path20.join(resolveArchiveRoot(providerHome), "index.json");
|
|
24855
25124
|
}
|
|
24856
25125
|
function resolveArchivePath(providerHome, archiveId) {
|
|
24857
|
-
return
|
|
25126
|
+
return path20.join(resolveArchiveRoot(providerHome), archiveId);
|
|
24858
25127
|
}
|
|
24859
25128
|
function defaultArchiveLabel(createdAt) {
|
|
24860
25129
|
return `Backup ${createdAt.replace("T", " ").replace(/\.\d{3}Z$/, " UTC")}`;
|
|
@@ -24970,7 +25239,7 @@ var ProviderHostConfigService = class {
|
|
|
24970
25239
|
const providerHome = this.providerHome(provider2);
|
|
24971
25240
|
const fileName = this.assertHostFile(provider2, name);
|
|
24972
25241
|
const filePath = resolveProviderHostFilePath(providerHome, fileName);
|
|
24973
|
-
await fs19.mkdir(
|
|
25242
|
+
await fs19.mkdir(path20.dirname(filePath), { recursive: true });
|
|
24974
25243
|
await fs19.writeFile(filePath, input.content, "utf8");
|
|
24975
25244
|
return this.readFile(provider2, fileName);
|
|
24976
25245
|
}
|
|
@@ -25005,7 +25274,7 @@ var ProviderHostConfigService = class {
|
|
|
25005
25274
|
exists: hostFile.exists
|
|
25006
25275
|
};
|
|
25007
25276
|
if (hostFile.exists) {
|
|
25008
|
-
await fs19.writeFile(
|
|
25277
|
+
await fs19.writeFile(path20.join(archivePath, name), hostFile.content, "utf8");
|
|
25009
25278
|
}
|
|
25010
25279
|
}
|
|
25011
25280
|
const archive = {
|
|
@@ -25045,7 +25314,7 @@ var ProviderHostConfigService = class {
|
|
|
25045
25314
|
for (const name of fileNames) {
|
|
25046
25315
|
const hostPath = resolveProviderHostFilePath(providerHome, name);
|
|
25047
25316
|
if (archive.files[name]?.exists) {
|
|
25048
|
-
const content = await fs19.readFile(
|
|
25317
|
+
const content = await fs19.readFile(path20.join(archivePath, name), "utf8");
|
|
25049
25318
|
await fs19.writeFile(hostPath, content, "utf8");
|
|
25050
25319
|
} else {
|
|
25051
25320
|
await fs19.rm(hostPath, { force: true });
|
|
@@ -25065,8 +25334,8 @@ import fs21 from "fs/promises";
|
|
|
25065
25334
|
|
|
25066
25335
|
// src/shell/shell-prompt.ts
|
|
25067
25336
|
import fs20 from "fs/promises";
|
|
25068
|
-
import
|
|
25069
|
-
import
|
|
25337
|
+
import os5 from "os";
|
|
25338
|
+
import path21 from "path";
|
|
25070
25339
|
function basenameFromPath2(filePath) {
|
|
25071
25340
|
if (!filePath) {
|
|
25072
25341
|
return "";
|
|
@@ -25075,7 +25344,7 @@ function basenameFromPath2(filePath) {
|
|
|
25075
25344
|
if (!normalized) {
|
|
25076
25345
|
return "";
|
|
25077
25346
|
}
|
|
25078
|
-
return
|
|
25347
|
+
return path21.basename(normalized) || normalized;
|
|
25079
25348
|
}
|
|
25080
25349
|
function isInteractiveShellCommand(command) {
|
|
25081
25350
|
const normalized = (command ?? "").trim().toLowerCase();
|
|
@@ -25236,8 +25505,8 @@ function buildShellPromptInitScriptContents(command) {
|
|
|
25236
25505
|
async function ensureShellPromptInitScript(command) {
|
|
25237
25506
|
const normalized = command.trim().toLowerCase();
|
|
25238
25507
|
const extension = normalized === "zsh" ? "zsh" : "sh";
|
|
25239
|
-
const filePath =
|
|
25240
|
-
|
|
25508
|
+
const filePath = path21.join(
|
|
25509
|
+
os5.tmpdir(),
|
|
25241
25510
|
`remote-codex-shell-prompt.${extension}`
|
|
25242
25511
|
);
|
|
25243
25512
|
await fs20.writeFile(filePath, buildShellPromptInitScriptContents(command), "utf8");
|
|
@@ -25860,13 +26129,13 @@ var terminalPluginManifest = {
|
|
|
25860
26129
|
}
|
|
25861
26130
|
};
|
|
25862
26131
|
|
|
25863
|
-
//
|
|
26132
|
+
// src/plugins/xyz-viewer-plugin-manifest.ts
|
|
25864
26133
|
var XYZ_MOLECULE_ARTIFACT_TYPE = "chemistry.molecule3d";
|
|
25865
26134
|
var xyzViewerPluginManifest = {
|
|
25866
26135
|
id: "remote-codex.xyz-viewer",
|
|
25867
26136
|
name: "XYZ Molecule Viewer",
|
|
25868
26137
|
version: "0.1.0",
|
|
25869
|
-
description: "A
|
|
26138
|
+
description: "A built-in plugin for previewing xyz, extxyz, cif, and pdb molecular structures.",
|
|
25870
26139
|
remoteCodex: "^0.11.0",
|
|
25871
26140
|
capabilities: {
|
|
25872
26141
|
artifactTypes: [
|
|
@@ -25897,11 +26166,7 @@ var xyzViewerPluginManifest = {
|
|
|
25897
26166
|
command: "node",
|
|
25898
26167
|
args: ["bin/remote-codex-plugin-mcp.mjs"]
|
|
25899
26168
|
}
|
|
25900
|
-
]
|
|
25901
|
-
frontend: {
|
|
25902
|
-
entry: "./dist/index.js",
|
|
25903
|
-
style: "./src/styles.css"
|
|
25904
|
-
}
|
|
26169
|
+
]
|
|
25905
26170
|
}
|
|
25906
26171
|
};
|
|
25907
26172
|
|
|
@@ -25919,7 +26184,7 @@ var builtinPlugins = [
|
|
|
25919
26184
|
|
|
25920
26185
|
// src/plugins/plugin-service.ts
|
|
25921
26186
|
import fs22 from "fs/promises";
|
|
25922
|
-
import
|
|
26187
|
+
import path22 from "path";
|
|
25923
26188
|
var MANAGED_CODEX_MCP_BEGIN = "# BEGIN remote-codex managed plugin MCP servers";
|
|
25924
26189
|
var MANAGED_CODEX_MCP_END = "# END remote-codex managed plugin MCP servers";
|
|
25925
26190
|
var REMOTE_CODEX_MOLECULE_MCP_TOOL_NAME = "remote_codex_render_molecule";
|
|
@@ -25931,7 +26196,7 @@ function normalizeManagedCommand(server, repoRoot) {
|
|
|
25931
26196
|
if (server.name === "remote_codex_plugins") {
|
|
25932
26197
|
return {
|
|
25933
26198
|
command: process.execPath,
|
|
25934
|
-
args: [
|
|
26199
|
+
args: [path22.join(repoRoot, "bin", "remote-codex-plugin-mcp.mjs")]
|
|
25935
26200
|
};
|
|
25936
26201
|
}
|
|
25937
26202
|
return {
|
|
@@ -26118,7 +26383,7 @@ var PluginService = class {
|
|
|
26118
26383
|
if (!input.codexHome) {
|
|
26119
26384
|
return;
|
|
26120
26385
|
}
|
|
26121
|
-
const configPath =
|
|
26386
|
+
const configPath = path22.join(input.codexHome, "config.toml");
|
|
26122
26387
|
let current = "";
|
|
26123
26388
|
try {
|
|
26124
26389
|
current = await fs22.readFile(configPath, "utf8");
|
|
@@ -26136,7 +26401,7 @@ var PluginService = class {
|
|
|
26136
26401
|
if (next === current) {
|
|
26137
26402
|
return;
|
|
26138
26403
|
}
|
|
26139
|
-
await fs22.mkdir(
|
|
26404
|
+
await fs22.mkdir(path22.dirname(configPath), { recursive: true });
|
|
26140
26405
|
await fs22.writeFile(configPath, next, "utf8");
|
|
26141
26406
|
}
|
|
26142
26407
|
async importPlugin(input) {
|
|
@@ -26377,7 +26642,7 @@ var PluginSettingsStore = class {
|
|
|
26377
26642
|
|
|
26378
26643
|
// src/worker-bootstrap.ts
|
|
26379
26644
|
import fs23 from "fs/promises";
|
|
26380
|
-
import
|
|
26645
|
+
import path23 from "path";
|
|
26381
26646
|
function trimTrailingSlash(value) {
|
|
26382
26647
|
return value.replace(/\/+$/, "");
|
|
26383
26648
|
}
|
|
@@ -26388,7 +26653,7 @@ function tomlString(value) {
|
|
|
26388
26653
|
return JSON.stringify(value);
|
|
26389
26654
|
}
|
|
26390
26655
|
async function writePrivateFile(filePath, content) {
|
|
26391
|
-
await fs23.mkdir(
|
|
26656
|
+
await fs23.mkdir(path23.dirname(filePath), { recursive: true, mode: 448 });
|
|
26392
26657
|
await fs23.writeFile(filePath, content, { encoding: "utf8", mode: 384 });
|
|
26393
26658
|
await fs23.chmod(filePath, 384);
|
|
26394
26659
|
}
|
|
@@ -26404,7 +26669,7 @@ async function configureWorkerProviderGateway(config) {
|
|
|
26404
26669
|
process.env.ANTHROPIC_BASE_URL = anthropicBaseUrl;
|
|
26405
26670
|
if (config.agentProviders.codex.enabled) {
|
|
26406
26671
|
await writePrivateFile(
|
|
26407
|
-
|
|
26672
|
+
path23.join(config.agentProviders.codex.home, "config.toml"),
|
|
26408
26673
|
[
|
|
26409
26674
|
'model_provider = "sub2api"',
|
|
26410
26675
|
'forced_login_method = "api"',
|
|
@@ -26420,7 +26685,7 @@ async function configureWorkerProviderGateway(config) {
|
|
|
26420
26685
|
].join("\n")
|
|
26421
26686
|
);
|
|
26422
26687
|
await writePrivateFile(
|
|
26423
|
-
|
|
26688
|
+
path23.join(config.agentProviders.codex.home, "auth.json"),
|
|
26424
26689
|
`${JSON.stringify(
|
|
26425
26690
|
{
|
|
26426
26691
|
OPENAI_API_KEY: config.llmGatewayToken
|
|
@@ -26433,7 +26698,7 @@ async function configureWorkerProviderGateway(config) {
|
|
|
26433
26698
|
}
|
|
26434
26699
|
if (config.agentProviders.claude.enabled) {
|
|
26435
26700
|
await writePrivateFile(
|
|
26436
|
-
|
|
26701
|
+
path23.join(config.agentProviders.claude.home, "settings.json"),
|
|
26437
26702
|
`${JSON.stringify(
|
|
26438
26703
|
{
|
|
26439
26704
|
env: {
|
|
@@ -26449,7 +26714,7 @@ async function configureWorkerProviderGateway(config) {
|
|
|
26449
26714
|
}
|
|
26450
26715
|
if (config.agentProviders.opencode.enabled) {
|
|
26451
26716
|
await writePrivateFile(
|
|
26452
|
-
|
|
26717
|
+
path23.join(config.agentProviders.opencode.home, "opencode.json"),
|
|
26453
26718
|
`${JSON.stringify(
|
|
26454
26719
|
{
|
|
26455
26720
|
provider: {
|
|
@@ -26496,8 +26761,8 @@ var BackendPluginHost = class {
|
|
|
26496
26761
|
};
|
|
26497
26762
|
|
|
26498
26763
|
// src/shell/pty-shell-backend.ts
|
|
26499
|
-
import
|
|
26500
|
-
import { spawn as
|
|
26764
|
+
import path24 from "path";
|
|
26765
|
+
import { spawn as spawn5 } from "@homebridge/node-pty-prebuilt-multiarch";
|
|
26501
26766
|
|
|
26502
26767
|
// src/shell/default-shell.ts
|
|
26503
26768
|
import fs24 from "fs";
|
|
@@ -26516,7 +26781,7 @@ function resolveDefaultShell(env = process.env) {
|
|
|
26516
26781
|
var MAX_SCROLLBACK_BYTES = 512 * 1024;
|
|
26517
26782
|
var ANSI_ESCAPE_PATTERN = new RegExp(String.raw`\u001B\[[0-?]*[ -/]*[@-~]`, "g");
|
|
26518
26783
|
function shellArgs(shell) {
|
|
26519
|
-
const shellName =
|
|
26784
|
+
const shellName = path24.basename(shell).toLowerCase();
|
|
26520
26785
|
if (process.platform === "win32") {
|
|
26521
26786
|
return [];
|
|
26522
26787
|
}
|
|
@@ -26538,7 +26803,7 @@ function lastVisibleLine(snapshot) {
|
|
|
26538
26803
|
}
|
|
26539
26804
|
function inferRuntime(session) {
|
|
26540
26805
|
const promptLine = lastVisibleLine(session.scrollback);
|
|
26541
|
-
const shell =
|
|
26806
|
+
const shell = path24.basename(session.shell);
|
|
26542
26807
|
const isCommandRunning = session.exitCode !== null ? false : !/[$#>]\s*$/.test(promptLine.trimEnd());
|
|
26543
26808
|
return {
|
|
26544
26809
|
panePid: session.pty.pid,
|
|
@@ -26566,7 +26831,7 @@ var PtyShellBackend = class {
|
|
|
26566
26831
|
if (this.sessions.has(input.sessionId)) {
|
|
26567
26832
|
return;
|
|
26568
26833
|
}
|
|
26569
|
-
const pty =
|
|
26834
|
+
const pty = spawn5(this.shell, shellArgs(this.shell), {
|
|
26570
26835
|
name: "xterm-256color",
|
|
26571
26836
|
cwd: input.cwd,
|
|
26572
26837
|
cols: input.cols ?? 120,
|
|
@@ -26685,7 +26950,7 @@ var PtyShellBackend = class {
|
|
|
26685
26950
|
|
|
26686
26951
|
// src/shell/tmux-manager.ts
|
|
26687
26952
|
import fs25 from "fs";
|
|
26688
|
-
import
|
|
26953
|
+
import path25 from "path";
|
|
26689
26954
|
import { spawn as spawnChild } from "child_process";
|
|
26690
26955
|
async function defaultExecCommand(command, args) {
|
|
26691
26956
|
return await new Promise((resolve, reject) => {
|
|
@@ -26712,16 +26977,16 @@ async function defaultExecCommand(command, args) {
|
|
|
26712
26977
|
});
|
|
26713
26978
|
}
|
|
26714
26979
|
function resolveExecutablePath(command) {
|
|
26715
|
-
if (command.includes(
|
|
26980
|
+
if (command.includes(path25.sep)) {
|
|
26716
26981
|
return command;
|
|
26717
26982
|
}
|
|
26718
26983
|
const searchPath = process.env.PATH ?? "";
|
|
26719
|
-
for (const entry of searchPath.split(
|
|
26984
|
+
for (const entry of searchPath.split(path25.delimiter)) {
|
|
26720
26985
|
const trimmed = entry.trim();
|
|
26721
26986
|
if (!trimmed) {
|
|
26722
26987
|
continue;
|
|
26723
26988
|
}
|
|
26724
|
-
const candidate =
|
|
26989
|
+
const candidate = path25.join(trimmed, command);
|
|
26725
26990
|
if (fs25.existsSync(candidate)) {
|
|
26726
26991
|
return candidate;
|
|
26727
26992
|
}
|
|
@@ -27427,6 +27692,9 @@ function makeShellErrorEnvelope(shellId, error) {
|
|
|
27427
27692
|
var HARNESS_MODULES = ["estructural", "quntur", "farmaco"];
|
|
27428
27693
|
var HARNESS_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
27429
27694
|
var HARNESS_RUN_ID_PATTERN = /^[a-zA-Z0-9_.-]+$/;
|
|
27695
|
+
var HARNESS_JOB_ID_PATTERN = /^[a-zA-Z0-9_.:-]+$/;
|
|
27696
|
+
var HARNESS_NOTIFICATION_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
27697
|
+
var HARNESS_TERMINAL_JOB_STATUSES = /* @__PURE__ */ new Set(["done", "failed", "cancelled"]);
|
|
27430
27698
|
var MOLECULE_ARTIFACT_TYPES = /* @__PURE__ */ new Set(["xyz", "extxyz", "pdb", "cif"]);
|
|
27431
27699
|
function recordFrom(value) {
|
|
27432
27700
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -27480,22 +27748,22 @@ function payloadItems(payload, fields) {
|
|
|
27480
27748
|
const record = recordFrom(payload);
|
|
27481
27749
|
return arrayField(record, fields) ?? (record ? [record] : []);
|
|
27482
27750
|
}
|
|
27483
|
-
function artifactPreviewKind(type, format,
|
|
27751
|
+
function artifactPreviewKind(type, format, path27) {
|
|
27484
27752
|
const candidates = [
|
|
27485
27753
|
type,
|
|
27486
27754
|
format,
|
|
27487
|
-
|
|
27755
|
+
path27?.split(".").pop() ?? null
|
|
27488
27756
|
].map((value) => value?.trim().toLowerCase()).filter(Boolean);
|
|
27489
27757
|
return candidates.some((value) => MOLECULE_ARTIFACT_TYPES.has(value)) ? "molecule" : "file";
|
|
27490
27758
|
}
|
|
27491
27759
|
function normalizeArtifactRef(value) {
|
|
27492
27760
|
const record = recordFrom(value);
|
|
27493
|
-
const
|
|
27761
|
+
const path27 = stringField3(record, ["path", "filePath", "file_path", "filename", "fileName", "name"]);
|
|
27494
27762
|
const type = stringField3(record, ["type", "artifactType", "artifact_type", "format", "extension"]);
|
|
27495
|
-
const title = stringField3(record, ["title", "label", "name", "filename", "fileName"]) ??
|
|
27763
|
+
const title = stringField3(record, ["title", "label", "name", "filename", "fileName"]) ?? path27 ?? "artifact";
|
|
27496
27764
|
return {
|
|
27497
27765
|
title,
|
|
27498
|
-
path:
|
|
27766
|
+
path: path27,
|
|
27499
27767
|
type,
|
|
27500
27768
|
downloadUrl: stringField3(record, ["downloadUrl", "download_url", "url", "href"])
|
|
27501
27769
|
};
|
|
@@ -27529,23 +27797,68 @@ function normalizeArtifact(module, runId, value) {
|
|
|
27529
27797
|
if (!record) {
|
|
27530
27798
|
return null;
|
|
27531
27799
|
}
|
|
27532
|
-
const
|
|
27800
|
+
const path27 = stringField3(record, ["path", "filePath", "file_path", "filename", "fileName", "name"]);
|
|
27533
27801
|
const type = stringField3(record, ["type", "artifactType", "artifact_type"]);
|
|
27534
27802
|
const format = stringField3(record, ["format", "fileFormat", "file_format", "extension"]) ?? type;
|
|
27535
|
-
const title = stringField3(record, ["title", "label", "name", "filename", "fileName"]) ??
|
|
27803
|
+
const title = stringField3(record, ["title", "label", "name", "filename", "fileName"]) ?? path27 ?? `${module} artifact`;
|
|
27536
27804
|
return {
|
|
27537
27805
|
module,
|
|
27538
27806
|
runId,
|
|
27539
27807
|
title,
|
|
27540
|
-
path:
|
|
27808
|
+
path: path27,
|
|
27541
27809
|
type,
|
|
27542
27810
|
format,
|
|
27543
27811
|
mimeType: stringField3(record, ["mimeType", "mime_type", "contentType", "content_type"]),
|
|
27544
27812
|
sizeBytes: numberField2(record, ["sizeBytes", "size_bytes", "bytes"]),
|
|
27545
27813
|
downloadUrl: stringField3(record, ["downloadUrl", "download_url", "url", "href"]),
|
|
27546
|
-
previewKind: artifactPreviewKind(type, format,
|
|
27814
|
+
previewKind: artifactPreviewKind(type, format, path27)
|
|
27547
27815
|
};
|
|
27548
27816
|
}
|
|
27817
|
+
function parseTomlScalar(raw) {
|
|
27818
|
+
const value = raw.trim();
|
|
27819
|
+
if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
|
|
27820
|
+
try {
|
|
27821
|
+
return JSON.parse(value);
|
|
27822
|
+
} catch {
|
|
27823
|
+
return value.slice(1, -1);
|
|
27824
|
+
}
|
|
27825
|
+
}
|
|
27826
|
+
return value;
|
|
27827
|
+
}
|
|
27828
|
+
function parseTomlLines(text2) {
|
|
27829
|
+
const record = {};
|
|
27830
|
+
for (const line of text2.split("\n")) {
|
|
27831
|
+
const match = /^([A-Za-z0-9_]+)\s*=\s*(.+)$/.exec(line.trim());
|
|
27832
|
+
if (match && !(match[1] in record)) {
|
|
27833
|
+
record[match[1]] = parseTomlScalar(match[2]);
|
|
27834
|
+
}
|
|
27835
|
+
}
|
|
27836
|
+
return record;
|
|
27837
|
+
}
|
|
27838
|
+
function parseTomlBlocks(text2, blockName) {
|
|
27839
|
+
const marker = `[[${blockName}]]`;
|
|
27840
|
+
const blocks = [];
|
|
27841
|
+
let current = null;
|
|
27842
|
+
for (const line of text2.split("\n")) {
|
|
27843
|
+
if (line.trim() === marker) {
|
|
27844
|
+
if (current) {
|
|
27845
|
+
blocks.push(parseTomlLines(current.join("\n")));
|
|
27846
|
+
}
|
|
27847
|
+
current = [];
|
|
27848
|
+
continue;
|
|
27849
|
+
}
|
|
27850
|
+
if (line.trim().startsWith("[[") && current) {
|
|
27851
|
+
blocks.push(parseTomlLines(current.join("\n")));
|
|
27852
|
+
current = null;
|
|
27853
|
+
continue;
|
|
27854
|
+
}
|
|
27855
|
+
current?.push(line);
|
|
27856
|
+
}
|
|
27857
|
+
if (current) {
|
|
27858
|
+
blocks.push(parseTomlLines(current.join("\n")));
|
|
27859
|
+
}
|
|
27860
|
+
return blocks;
|
|
27861
|
+
}
|
|
27549
27862
|
function normalizeRuns(module, result) {
|
|
27550
27863
|
const runs = payloadItems(result.payload, ["runs", "items", "results"]).map((item) => normalizeRun(module, item)).filter(Boolean);
|
|
27551
27864
|
return { runs };
|
|
@@ -27589,6 +27902,57 @@ var WorkerHarnessClient = class {
|
|
|
27589
27902
|
async me() {
|
|
27590
27903
|
return this.fetchText("/members/.me");
|
|
27591
27904
|
}
|
|
27905
|
+
async whoami() {
|
|
27906
|
+
const { text: text2 } = await this.fetchText("/members/.me");
|
|
27907
|
+
const record = parseTomlLines(text2);
|
|
27908
|
+
const agentId = record.id?.trim();
|
|
27909
|
+
if (!agentId) {
|
|
27910
|
+
throw new Error("ElAgenteHarness /members/.me response did not include an id.");
|
|
27911
|
+
}
|
|
27912
|
+
return { agentId };
|
|
27913
|
+
}
|
|
27914
|
+
async registerNotifyCallback(input) {
|
|
27915
|
+
return this.fetchPayload("/notify/register", {
|
|
27916
|
+
method: "POST",
|
|
27917
|
+
headers: {
|
|
27918
|
+
"content-type": "application/json"
|
|
27919
|
+
},
|
|
27920
|
+
body: JSON.stringify({
|
|
27921
|
+
agent_id: input.agentId,
|
|
27922
|
+
callback: input.callback,
|
|
27923
|
+
secret: input.secret
|
|
27924
|
+
})
|
|
27925
|
+
});
|
|
27926
|
+
}
|
|
27927
|
+
async getComputeJob(jobId) {
|
|
27928
|
+
const id = this.requireJobId(jobId);
|
|
27929
|
+
const { text: text2 } = await this.fetchText(`/compute/jobs/${encodeURIComponent(id)}`);
|
|
27930
|
+
const record = parseTomlLines(text2);
|
|
27931
|
+
const status = record.status?.trim() ?? null;
|
|
27932
|
+
return {
|
|
27933
|
+
jobId: record.id?.trim() ?? id,
|
|
27934
|
+
status,
|
|
27935
|
+
terminal: status !== null && HARNESS_TERMINAL_JOB_STATUSES.has(status),
|
|
27936
|
+
title: record.title?.trim() || null,
|
|
27937
|
+
reason: record.reason?.trim() || null,
|
|
27938
|
+
raw: record
|
|
27939
|
+
};
|
|
27940
|
+
}
|
|
27941
|
+
async listUnreadNotifications() {
|
|
27942
|
+
const { text: text2 } = await this.fetchText("/notify/inbox");
|
|
27943
|
+
return parseTomlBlocks(text2, "notifications").filter((entry) => entry.id?.trim()).map((entry) => ({
|
|
27944
|
+
id: entry.id.trim(),
|
|
27945
|
+
from: entry.from?.trim() ?? "",
|
|
27946
|
+
message: entry.message ?? ""
|
|
27947
|
+
}));
|
|
27948
|
+
}
|
|
27949
|
+
async markNotificationRead(notificationId) {
|
|
27950
|
+
const id = notificationId.trim();
|
|
27951
|
+
if (!HARNESS_NOTIFICATION_ID_PATTERN.test(id)) {
|
|
27952
|
+
throw new Error(`Unsupported Harness notification id: ${notificationId}`);
|
|
27953
|
+
}
|
|
27954
|
+
return this.fetchText(`/notify/inbox/${encodeURIComponent(id)}`);
|
|
27955
|
+
}
|
|
27592
27956
|
async home() {
|
|
27593
27957
|
return this.fetchPayload("/");
|
|
27594
27958
|
}
|
|
@@ -27655,6 +28019,13 @@ var WorkerHarnessClient = class {
|
|
|
27655
28019
|
}
|
|
27656
28020
|
return normalized;
|
|
27657
28021
|
}
|
|
28022
|
+
requireJobId(jobId) {
|
|
28023
|
+
const normalized = jobId.trim();
|
|
28024
|
+
if (!HARNESS_JOB_ID_PATTERN.test(normalized)) {
|
|
28025
|
+
throw new Error(`Unsupported Harness job id: ${jobId}`);
|
|
28026
|
+
}
|
|
28027
|
+
return normalized;
|
|
28028
|
+
}
|
|
27658
28029
|
requireRunId(runId) {
|
|
27659
28030
|
const normalized = runId.trim();
|
|
27660
28031
|
if (!HARNESS_RUN_ID_PATTERN.test(normalized)) {
|
|
@@ -27670,9 +28041,9 @@ var WorkerHarnessClient = class {
|
|
|
27670
28041
|
}
|
|
27671
28042
|
return { baseUrl, apiKey };
|
|
27672
28043
|
}
|
|
27673
|
-
async fetchText(
|
|
28044
|
+
async fetchText(path27) {
|
|
27674
28045
|
const config = this.requireHarnessConfig();
|
|
27675
|
-
const response = await this.fetchImpl(`${config.baseUrl}${
|
|
28046
|
+
const response = await this.fetchImpl(`${config.baseUrl}${path27}`, {
|
|
27676
28047
|
headers: {
|
|
27677
28048
|
"x-api-key": config.apiKey
|
|
27678
28049
|
}
|
|
@@ -27683,11 +28054,11 @@ var WorkerHarnessClient = class {
|
|
|
27683
28054
|
}
|
|
27684
28055
|
return { text: text2 };
|
|
27685
28056
|
}
|
|
27686
|
-
async fetchPayload(
|
|
28057
|
+
async fetchPayload(path27, init = {}) {
|
|
27687
28058
|
const config = this.requireHarnessConfig();
|
|
27688
28059
|
const headers = new Headers(init.headers);
|
|
27689
28060
|
headers.set("x-api-key", config.apiKey);
|
|
27690
|
-
const response = await this.fetchImpl(`${config.baseUrl}${
|
|
28061
|
+
const response = await this.fetchImpl(`${config.baseUrl}${path27}`, {
|
|
27691
28062
|
...init,
|
|
27692
28063
|
headers
|
|
27693
28064
|
});
|
|
@@ -27701,9 +28072,9 @@ var WorkerHarnessClient = class {
|
|
|
27701
28072
|
return { text: text2 };
|
|
27702
28073
|
}
|
|
27703
28074
|
}
|
|
27704
|
-
async fetchBinary(
|
|
28075
|
+
async fetchBinary(path27) {
|
|
27705
28076
|
const config = this.requireHarnessConfig();
|
|
27706
|
-
const response = await this.fetchImpl(`${config.baseUrl}${
|
|
28077
|
+
const response = await this.fetchImpl(`${config.baseUrl}${path27}`, {
|
|
27707
28078
|
headers: {
|
|
27708
28079
|
"x-api-key": config.apiKey
|
|
27709
28080
|
}
|
|
@@ -27728,6 +28099,297 @@ var WorkerHarnessClient = class {
|
|
|
27728
28099
|
}
|
|
27729
28100
|
};
|
|
27730
28101
|
|
|
28102
|
+
// src/harness-wakeup-service.ts
|
|
28103
|
+
import crypto3 from "crypto";
|
|
28104
|
+
var JOB_ID_FROM_MESSAGE_PATTERN = /^id:\s*(\S+)\s*$/m;
|
|
28105
|
+
function timingSafeEqualString2(left, right) {
|
|
28106
|
+
const leftBuffer = Buffer.from(left);
|
|
28107
|
+
const rightBuffer = Buffer.from(right);
|
|
28108
|
+
return leftBuffer.length === rightBuffer.length && crypto3.timingSafeEqual(leftBuffer, rightBuffer);
|
|
28109
|
+
}
|
|
28110
|
+
var HarnessWakeupService = class {
|
|
28111
|
+
constructor(config, db, harnessClient, threadService, logger) {
|
|
28112
|
+
this.config = config;
|
|
28113
|
+
this.db = db;
|
|
28114
|
+
this.harnessClient = harnessClient;
|
|
28115
|
+
this.threadService = threadService;
|
|
28116
|
+
this.logger = logger;
|
|
28117
|
+
}
|
|
28118
|
+
config;
|
|
28119
|
+
db;
|
|
28120
|
+
harnessClient;
|
|
28121
|
+
threadService;
|
|
28122
|
+
logger;
|
|
28123
|
+
reconcileInFlight = null;
|
|
28124
|
+
reconcileQueued = false;
|
|
28125
|
+
disabledReasonFor(keyPresent) {
|
|
28126
|
+
if (!this.config.harnessBaseUrl) {
|
|
28127
|
+
return "missing_harness_base_url";
|
|
28128
|
+
}
|
|
28129
|
+
if (!keyPresent) {
|
|
28130
|
+
return "missing_harness_key";
|
|
28131
|
+
}
|
|
28132
|
+
if (!this.config.harnessWakeupCallbackBaseUrl) {
|
|
28133
|
+
return "missing_callback_base_url";
|
|
28134
|
+
}
|
|
28135
|
+
return null;
|
|
28136
|
+
}
|
|
28137
|
+
disabledReason() {
|
|
28138
|
+
return this.disabledReasonFor(this.harnessClient.configured().keyPresent);
|
|
28139
|
+
}
|
|
28140
|
+
status() {
|
|
28141
|
+
const keyPresent = this.harnessClient.configured().keyPresent;
|
|
28142
|
+
const reason = this.disabledReasonFor(keyPresent);
|
|
28143
|
+
return {
|
|
28144
|
+
enabled: reason === null,
|
|
28145
|
+
reason,
|
|
28146
|
+
harnessBaseUrl: this.config.harnessBaseUrl,
|
|
28147
|
+
callbackBaseUrl: this.config.harnessWakeupCallbackBaseUrl,
|
|
28148
|
+
keyPresent
|
|
28149
|
+
};
|
|
28150
|
+
}
|
|
28151
|
+
enabled() {
|
|
28152
|
+
return this.disabledReason() === null;
|
|
28153
|
+
}
|
|
28154
|
+
requireEnabled() {
|
|
28155
|
+
if (!this.enabled()) {
|
|
28156
|
+
throw new HttpError(409, {
|
|
28157
|
+
code: "conflict",
|
|
28158
|
+
message: "Harness wakeup is not configured. REMOTE_CODEX_HARNESS_WAKEUP_CALLBACK_BASE_URL and the Harness key are required."
|
|
28159
|
+
});
|
|
28160
|
+
}
|
|
28161
|
+
}
|
|
28162
|
+
buildCallbackUrl(hookToken) {
|
|
28163
|
+
const base = this.config.harnessWakeupCallbackBaseUrl.replace(/\/+$/, "");
|
|
28164
|
+
const userSuffix = this.config.userId ? `?u=${encodeURIComponent(this.config.userId)}` : "";
|
|
28165
|
+
return `${base}/harness-notify/${hookToken}${userSuffix}`;
|
|
28166
|
+
}
|
|
28167
|
+
async ensureRegistration() {
|
|
28168
|
+
this.requireEnabled();
|
|
28169
|
+
const existing = getHarnessNotifyRegistration(this.db);
|
|
28170
|
+
if (existing) {
|
|
28171
|
+
const desiredUrl = this.buildCallbackUrl(existing.hookToken);
|
|
28172
|
+
if (existing.callbackUrl === desiredUrl) {
|
|
28173
|
+
return existing;
|
|
28174
|
+
}
|
|
28175
|
+
await this.harnessClient.registerNotifyCallback({
|
|
28176
|
+
agentId: existing.agentId,
|
|
28177
|
+
callback: desiredUrl,
|
|
28178
|
+
secret: existing.secret
|
|
28179
|
+
});
|
|
28180
|
+
return upsertHarnessNotifyRegistration(this.db, {
|
|
28181
|
+
agentId: existing.agentId,
|
|
28182
|
+
hookToken: existing.hookToken,
|
|
28183
|
+
secret: existing.secret,
|
|
28184
|
+
callbackUrl: desiredUrl
|
|
28185
|
+
});
|
|
28186
|
+
}
|
|
28187
|
+
const { agentId } = await this.harnessClient.whoami();
|
|
28188
|
+
const hookToken = crypto3.randomBytes(32).toString("hex");
|
|
28189
|
+
const secret = crypto3.randomBytes(32).toString("hex");
|
|
28190
|
+
const callbackUrl = this.buildCallbackUrl(hookToken);
|
|
28191
|
+
await this.harnessClient.registerNotifyCallback({
|
|
28192
|
+
agentId,
|
|
28193
|
+
callback: callbackUrl,
|
|
28194
|
+
secret
|
|
28195
|
+
});
|
|
28196
|
+
return upsertHarnessNotifyRegistration(this.db, {
|
|
28197
|
+
agentId,
|
|
28198
|
+
hookToken,
|
|
28199
|
+
secret,
|
|
28200
|
+
callbackUrl
|
|
28201
|
+
});
|
|
28202
|
+
}
|
|
28203
|
+
async getWakeupInfo() {
|
|
28204
|
+
const status = this.status();
|
|
28205
|
+
if (!status.enabled) {
|
|
28206
|
+
return {
|
|
28207
|
+
...status,
|
|
28208
|
+
enabled: false
|
|
28209
|
+
};
|
|
28210
|
+
}
|
|
28211
|
+
const registration = await this.ensureRegistration();
|
|
28212
|
+
return {
|
|
28213
|
+
...status,
|
|
28214
|
+
enabled: true,
|
|
28215
|
+
notifyTo: registration.agentId,
|
|
28216
|
+
registered: true
|
|
28217
|
+
};
|
|
28218
|
+
}
|
|
28219
|
+
async watchJob(input) {
|
|
28220
|
+
this.requireEnabled();
|
|
28221
|
+
const jobId = input.jobId.trim();
|
|
28222
|
+
if (!jobId) {
|
|
28223
|
+
throw new HttpError(400, {
|
|
28224
|
+
code: "bad_request",
|
|
28225
|
+
message: "jobId is required."
|
|
28226
|
+
});
|
|
28227
|
+
}
|
|
28228
|
+
let threadId = input.threadId?.trim() || null;
|
|
28229
|
+
if (!threadId) {
|
|
28230
|
+
const runningThreads = listThreadRecords(this.db).filter(
|
|
28231
|
+
(thread) => thread.status === "running"
|
|
28232
|
+
);
|
|
28233
|
+
if (runningThreads.length === 1) {
|
|
28234
|
+
threadId = runningThreads[0].id;
|
|
28235
|
+
}
|
|
28236
|
+
}
|
|
28237
|
+
if (!threadId) {
|
|
28238
|
+
throw new HttpError(400, {
|
|
28239
|
+
code: "bad_request",
|
|
28240
|
+
message: "threadId is required when it cannot be inferred from a single running thread."
|
|
28241
|
+
});
|
|
28242
|
+
}
|
|
28243
|
+
if (!getThreadRecordById(this.db, threadId)) {
|
|
28244
|
+
throw new HttpError(404, {
|
|
28245
|
+
code: "not_found",
|
|
28246
|
+
message: "Thread was not found."
|
|
28247
|
+
});
|
|
28248
|
+
}
|
|
28249
|
+
const registration = await this.ensureRegistration();
|
|
28250
|
+
const watch = upsertHarnessJobWatch(this.db, {
|
|
28251
|
+
jobId,
|
|
28252
|
+
threadId,
|
|
28253
|
+
title: input.title ?? null
|
|
28254
|
+
});
|
|
28255
|
+
return {
|
|
28256
|
+
watch,
|
|
28257
|
+
notifyTo: registration.agentId
|
|
28258
|
+
};
|
|
28259
|
+
}
|
|
28260
|
+
verifyCallback(input) {
|
|
28261
|
+
const registration = getHarnessNotifyRegistration(this.db);
|
|
28262
|
+
if (!registration || !timingSafeEqualString2(registration.hookToken, input.hookToken)) {
|
|
28263
|
+
throw new HttpError(404, {
|
|
28264
|
+
code: "not_found",
|
|
28265
|
+
message: "Unknown harness hook."
|
|
28266
|
+
});
|
|
28267
|
+
}
|
|
28268
|
+
const expected = crypto3.createHmac("sha256", registration.secret).update(input.rawBody).digest("hex");
|
|
28269
|
+
if (!input.signature || !timingSafeEqualString2(expected, input.signature.trim())) {
|
|
28270
|
+
throw new HttpError(403, {
|
|
28271
|
+
code: "forbidden",
|
|
28272
|
+
message: "Invalid harness hook signature."
|
|
28273
|
+
});
|
|
28274
|
+
}
|
|
28275
|
+
let payload = {};
|
|
28276
|
+
try {
|
|
28277
|
+
const parsed = JSON.parse(input.rawBody.toString("utf8"));
|
|
28278
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
28279
|
+
payload = parsed;
|
|
28280
|
+
}
|
|
28281
|
+
} catch {
|
|
28282
|
+
}
|
|
28283
|
+
return payload;
|
|
28284
|
+
}
|
|
28285
|
+
handleCallback(input) {
|
|
28286
|
+
const payload = this.verifyCallback(input);
|
|
28287
|
+
this.scheduleReconcile();
|
|
28288
|
+
return {
|
|
28289
|
+
accepted: true,
|
|
28290
|
+
type: typeof payload.type === "string" ? payload.type : null
|
|
28291
|
+
};
|
|
28292
|
+
}
|
|
28293
|
+
scheduleReconcile() {
|
|
28294
|
+
if (this.reconcileInFlight) {
|
|
28295
|
+
this.reconcileQueued = true;
|
|
28296
|
+
return;
|
|
28297
|
+
}
|
|
28298
|
+
this.reconcileInFlight = this.reconcile().catch((error) => {
|
|
28299
|
+
this.logger.error({ err: error }, "Harness wakeup reconcile failed.");
|
|
28300
|
+
}).finally(() => {
|
|
28301
|
+
this.reconcileInFlight = null;
|
|
28302
|
+
if (this.reconcileQueued) {
|
|
28303
|
+
this.reconcileQueued = false;
|
|
28304
|
+
this.scheduleReconcile();
|
|
28305
|
+
}
|
|
28306
|
+
});
|
|
28307
|
+
}
|
|
28308
|
+
async waitForReconcile() {
|
|
28309
|
+
while (this.reconcileInFlight) {
|
|
28310
|
+
await this.reconcileInFlight;
|
|
28311
|
+
}
|
|
28312
|
+
}
|
|
28313
|
+
async reconcile() {
|
|
28314
|
+
const watches = listPendingHarnessJobWatches(this.db);
|
|
28315
|
+
for (const watch of watches) {
|
|
28316
|
+
try {
|
|
28317
|
+
const job = await this.harnessClient.getComputeJob(watch.jobId);
|
|
28318
|
+
updateHarnessJobWatch(this.db, watch.id, {
|
|
28319
|
+
lastJobStatus: job.status
|
|
28320
|
+
});
|
|
28321
|
+
if (!job.terminal) {
|
|
28322
|
+
continue;
|
|
28323
|
+
}
|
|
28324
|
+
await this.wakeThread(watch, job);
|
|
28325
|
+
} catch (error) {
|
|
28326
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
28327
|
+
this.logger.warn(
|
|
28328
|
+
{ jobId: watch.jobId, threadId: watch.threadId, err: error },
|
|
28329
|
+
"Harness wakeup delivery attempt failed; will retry on the next callback."
|
|
28330
|
+
);
|
|
28331
|
+
updateHarnessJobWatch(this.db, watch.id, { lastError: message });
|
|
28332
|
+
}
|
|
28333
|
+
}
|
|
28334
|
+
await this.acknowledgeNotifications();
|
|
28335
|
+
}
|
|
28336
|
+
async wakeThread(watch, job) {
|
|
28337
|
+
const thread = getThreadRecordById(this.db, watch.threadId);
|
|
28338
|
+
if (!thread) {
|
|
28339
|
+
updateHarnessJobWatch(this.db, watch.id, {
|
|
28340
|
+
status: "failed",
|
|
28341
|
+
lastError: "Thread was not found."
|
|
28342
|
+
});
|
|
28343
|
+
return;
|
|
28344
|
+
}
|
|
28345
|
+
if (thread.isConnected === false) {
|
|
28346
|
+
await this.threadService.resumeThread(watch.threadId);
|
|
28347
|
+
}
|
|
28348
|
+
const title = watch.title ?? job.title;
|
|
28349
|
+
const prompt = [
|
|
28350
|
+
`[Harness job wakeup] Compute job ${job.jobId}${title ? ` ("${title}")` : ""} finished with status: ${job.status}.`,
|
|
28351
|
+
job.reason ? `Reason: ${job.reason}.` : null,
|
|
28352
|
+
`Retrieve details and outputs from the ElAgente Harness API (GET /compute/jobs/${job.jobId}, output files under GET /compute/jobs/${job.jobId}/files/...) using the INACT_X_APP_KEY env var, then continue the original task.`
|
|
28353
|
+
].filter(Boolean).join(" ");
|
|
28354
|
+
await this.threadService.sendPrompt(watch.threadId, { prompt });
|
|
28355
|
+
updateHarnessJobWatch(this.db, watch.id, {
|
|
28356
|
+
status: "delivered",
|
|
28357
|
+
lastError: null,
|
|
28358
|
+
deliveredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
28359
|
+
});
|
|
28360
|
+
}
|
|
28361
|
+
async acknowledgeNotifications() {
|
|
28362
|
+
let notifications2;
|
|
28363
|
+
try {
|
|
28364
|
+
notifications2 = await this.harnessClient.listUnreadNotifications();
|
|
28365
|
+
} catch (error) {
|
|
28366
|
+
this.logger.warn({ err: error }, "Harness wakeup inbox listing failed.");
|
|
28367
|
+
return;
|
|
28368
|
+
}
|
|
28369
|
+
for (const notification of notifications2) {
|
|
28370
|
+
if (!notification.from.includes("jobs")) {
|
|
28371
|
+
continue;
|
|
28372
|
+
}
|
|
28373
|
+
const jobId = JOB_ID_FROM_MESSAGE_PATTERN.exec(notification.message)?.[1] ?? null;
|
|
28374
|
+
if (!jobId) {
|
|
28375
|
+
continue;
|
|
28376
|
+
}
|
|
28377
|
+
const watch = getHarnessJobWatchByJobId(this.db, jobId);
|
|
28378
|
+
if (watch && watch.status === "pending") {
|
|
28379
|
+
continue;
|
|
28380
|
+
}
|
|
28381
|
+
try {
|
|
28382
|
+
await this.harnessClient.markNotificationRead(notification.id);
|
|
28383
|
+
} catch (error) {
|
|
28384
|
+
this.logger.warn(
|
|
28385
|
+
{ notificationId: notification.id, err: error },
|
|
28386
|
+
"Harness wakeup notification acknowledgement failed."
|
|
28387
|
+
);
|
|
28388
|
+
}
|
|
28389
|
+
}
|
|
28390
|
+
}
|
|
28391
|
+
};
|
|
28392
|
+
|
|
27731
28393
|
// src/worker-control-plane-sync.ts
|
|
27732
28394
|
import { setTimeout as delay } from "timers/promises";
|
|
27733
28395
|
var WorkerControlPlaneSyncError = class extends Error {
|
|
@@ -27863,7 +28525,7 @@ var WorkerControlPlaneSyncClient = class {
|
|
|
27863
28525
|
};
|
|
27864
28526
|
|
|
27865
28527
|
// src/auth.ts
|
|
27866
|
-
import
|
|
28528
|
+
import crypto4 from "crypto";
|
|
27867
28529
|
var AUTH_COOKIE_NAME = "remote_codex_session";
|
|
27868
28530
|
var AuthService = class {
|
|
27869
28531
|
required;
|
|
@@ -27946,7 +28608,7 @@ var AuthService = class {
|
|
|
27946
28608
|
const payload = {
|
|
27947
28609
|
username,
|
|
27948
28610
|
expiresAt: expiresAtMs,
|
|
27949
|
-
nonce:
|
|
28611
|
+
nonce: crypto4.randomBytes(16).toString("base64url")
|
|
27950
28612
|
};
|
|
27951
28613
|
const payloadText = Buffer.from(JSON.stringify(payload), "utf8").toString(
|
|
27952
28614
|
"base64url"
|
|
@@ -27994,7 +28656,7 @@ var AuthService = class {
|
|
|
27994
28656
|
};
|
|
27995
28657
|
}
|
|
27996
28658
|
sign(payloadText) {
|
|
27997
|
-
return
|
|
28659
|
+
return crypto4.createHmac("sha256", this.secret ?? "").update(payloadText).digest("base64url");
|
|
27998
28660
|
}
|
|
27999
28661
|
};
|
|
28000
28662
|
function unauthorizedPayload() {
|
|
@@ -28051,7 +28713,7 @@ function constantTimeEqual(left, right) {
|
|
|
28051
28713
|
if (leftBuffer.length !== rightBuffer.length) {
|
|
28052
28714
|
return false;
|
|
28053
28715
|
}
|
|
28054
|
-
return
|
|
28716
|
+
return crypto4.timingSafeEqual(leftBuffer, rightBuffer);
|
|
28055
28717
|
}
|
|
28056
28718
|
|
|
28057
28719
|
// src/relay-tunnel-client.ts
|
|
@@ -28218,6 +28880,14 @@ var RelayTunnelClient = class {
|
|
|
28218
28880
|
var MAX_PROMPT_ATTACHMENTS2 = 10;
|
|
28219
28881
|
var MAX_PROMPT_ATTACHMENT_BYTES2 = 25 * 1024 * 1024;
|
|
28220
28882
|
var WORKER_AUTH_EXEMPT_PATHS = /* @__PURE__ */ new Set(["/healthz", "/readyz"]);
|
|
28883
|
+
var WORKER_AUTH_HOOK_PATH_PREFIX = "/api/hooks/";
|
|
28884
|
+
var WORKER_AUTH_LOOPBACK_PATHS = /* @__PURE__ */ new Set([
|
|
28885
|
+
"/api/harness/wakeup",
|
|
28886
|
+
"/api/harness/job-watches"
|
|
28887
|
+
]);
|
|
28888
|
+
function isLoopbackAddress(ip) {
|
|
28889
|
+
return ip === "127.0.0.1" || ip === "::1" || ip === "::ffff:127.0.0.1";
|
|
28890
|
+
}
|
|
28221
28891
|
var RELAY_FORWARD_HEADER = "x-remote-codex-relay-forwarded";
|
|
28222
28892
|
var SUPERVISOR_LOG_REDACTION_PATHS = [
|
|
28223
28893
|
"req.headers.authorization",
|
|
@@ -28248,16 +28918,16 @@ var HttpError = class extends Error {
|
|
|
28248
28918
|
};
|
|
28249
28919
|
function findRepoRoot(start = process.cwd()) {
|
|
28250
28920
|
if (process.env.REMOTE_CODEX_REPO_ROOT) {
|
|
28251
|
-
return
|
|
28921
|
+
return path26.resolve(process.env.REMOTE_CODEX_REPO_ROOT);
|
|
28252
28922
|
}
|
|
28253
|
-
let current =
|
|
28254
|
-
while (current !==
|
|
28255
|
-
if (fs26.existsSync(
|
|
28923
|
+
let current = path26.resolve(start);
|
|
28924
|
+
while (current !== path26.dirname(current)) {
|
|
28925
|
+
if (fs26.existsSync(path26.join(current, "pnpm-workspace.yaml")) && fs26.existsSync(path26.join(current, "scripts", "service-restart.mjs"))) {
|
|
28256
28926
|
return current;
|
|
28257
28927
|
}
|
|
28258
|
-
current =
|
|
28928
|
+
current = path26.dirname(current);
|
|
28259
28929
|
}
|
|
28260
|
-
return
|
|
28930
|
+
return path26.resolve(process.cwd());
|
|
28261
28931
|
}
|
|
28262
28932
|
function createServiceLifecycle() {
|
|
28263
28933
|
return {
|
|
@@ -28269,14 +28939,14 @@ function createServiceLifecycle() {
|
|
|
28269
28939
|
});
|
|
28270
28940
|
}
|
|
28271
28941
|
const repoRoot = findRepoRoot();
|
|
28272
|
-
const restartScript =
|
|
28273
|
-
if (!fs26.existsSync(restartScript) || !fs26.existsSync(
|
|
28942
|
+
const restartScript = path26.join(repoRoot, "scripts", "service-restart.mjs");
|
|
28943
|
+
if (!fs26.existsSync(restartScript) || !fs26.existsSync(path26.join(repoRoot, "pnpm-workspace.yaml"))) {
|
|
28274
28944
|
throw new HttpError(503, {
|
|
28275
28945
|
code: "service_unavailable",
|
|
28276
28946
|
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."
|
|
28277
28947
|
});
|
|
28278
28948
|
}
|
|
28279
|
-
const child =
|
|
28949
|
+
const child = spawn6(process.execPath, [restartScript, "launch"], {
|
|
28280
28950
|
cwd: repoRoot,
|
|
28281
28951
|
detached: true,
|
|
28282
28952
|
env: process.env,
|
|
@@ -28334,7 +29004,8 @@ function buildApp(options = {}) {
|
|
|
28334
29004
|
disableRequestLogging: config.disableRequestLogging
|
|
28335
29005
|
});
|
|
28336
29006
|
app.addHook("onRequest", async (request) => {
|
|
28337
|
-
|
|
29007
|
+
const requestPath = request.url.split("?")[0] ?? request.url;
|
|
29008
|
+
if (config.runtimeRole !== "worker" || !config.workerAuthToken || WORKER_AUTH_EXEMPT_PATHS.has(requestPath) || requestPath.startsWith(WORKER_AUTH_HOOK_PATH_PREFIX) || WORKER_AUTH_LOOPBACK_PATHS.has(requestPath) && isLoopbackAddress(request.ip)) {
|
|
28338
29009
|
return;
|
|
28339
29010
|
}
|
|
28340
29011
|
const headerToken = request.headers["x-remote-codex-worker-token"];
|
|
@@ -28364,6 +29035,13 @@ function buildApp(options = {}) {
|
|
|
28364
29035
|
relaySocketBridge.handleMessage
|
|
28365
29036
|
) : null;
|
|
28366
29037
|
relayTunnelClient?.validateConfig();
|
|
29038
|
+
const harnessWakeupService = new HarnessWakeupService(
|
|
29039
|
+
config,
|
|
29040
|
+
database.db,
|
|
29041
|
+
harnessClient,
|
|
29042
|
+
threadService,
|
|
29043
|
+
app.log
|
|
29044
|
+
);
|
|
28367
29045
|
app.decorate("services", {
|
|
28368
29046
|
config,
|
|
28369
29047
|
database,
|
|
@@ -28376,6 +29054,7 @@ function buildApp(options = {}) {
|
|
|
28376
29054
|
pluginRegistry,
|
|
28377
29055
|
pluginService,
|
|
28378
29056
|
harnessClient,
|
|
29057
|
+
harnessWakeupService,
|
|
28379
29058
|
controlPlaneSyncClient,
|
|
28380
29059
|
authService,
|
|
28381
29060
|
relayTunnelClient,
|
|
@@ -28392,6 +29071,12 @@ function buildApp(options = {}) {
|
|
|
28392
29071
|
if (requestPath === "/api/auth/login" || requestPath === "/api/auth/logout" || requestPath === "/api/auth/session") {
|
|
28393
29072
|
return;
|
|
28394
29073
|
}
|
|
29074
|
+
if (requestPath.startsWith(WORKER_AUTH_HOOK_PATH_PREFIX)) {
|
|
29075
|
+
return;
|
|
29076
|
+
}
|
|
29077
|
+
if (WORKER_AUTH_LOOPBACK_PATHS.has(requestPath) && isLoopbackAddress(request.ip)) {
|
|
29078
|
+
return;
|
|
29079
|
+
}
|
|
28395
29080
|
if (config.mode === "relay" && request.headers[RELAY_FORWARD_HEADER] === "1") {
|
|
28396
29081
|
return;
|
|
28397
29082
|
}
|