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.
@@ -8,9 +8,9 @@ var __export = (target, all) => {
8
8
  import Fastify from "fastify";
9
9
  import multipart from "@fastify/multipart";
10
10
  import websocket from "@fastify/websocket";
11
- import { spawn as spawn5 } from "child_process";
11
+ import { spawn as spawn6 } from "child_process";
12
12
  import fs26 from "fs";
13
- import path25 from "path";
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: path26, field }, columnIndex) => {
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 path26.entries()) {
2330
- if (pathChunkIndex < path26.length - 1) {
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) && path26.length === 2) {
2339
- const objectName = path26[0];
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 path26 = uniqueStrings([
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 = path26 ?? (diffText ? projectRelativePathLabel(extractPathFromDiffText(diffText)) : null);
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, path26, metadataStats) {
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 (path26 || patchText || metadataStats || stringValue2(state.output)) {
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((path26) => Boolean(path26));
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 path26 = metadataStats?.path ?? filePathFromInput(input) ?? extractPathFromPatchText(patchText);
13710
- if (toolIsLowInformationPatch(normalized, state, input, patchText, path26, metadataStats)) {
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(path26, options);
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 ?? (path26 ? 1 : null),
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 path26 = filePathFromInput(input);
13730
- const text2 = displayPath(path26, options) ?? summary ?? name;
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: turn.startedAt ?? parseUuidV7Timestamp2(turn.providerTurnId),
16664
+ startedAt,
16558
16665
  status: turn.status,
16559
16666
  error: turn.error?.message ?? null,
16560
16667
  items: visibleRuntimeTurnItems(turn.items).map(
16561
- (item, transcriptIndex) => item.transcriptOrder === transcriptIndex ? item : { ...item, transcriptOrder: 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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
@@ -20397,35 +20513,15 @@ endobj
20397
20513
  `
20398
20514
  );
20399
20515
  }
20400
- const browser = await launchPdfBrowser();
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
- async function launchPdfBrowser() {
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
- return await puppeteer.launch(launchOptions);
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 path14 from "path";
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 = path14.basename(originalName).trim() || "attachment";
20717
- const extension = path14.extname(basename).replace(/[^a-zA-Z0-9.]/g, "");
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 path14.join(workspacePath, ".temp", "threads", localThreadId);
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(path14.join(tempDirectory, savedFileName), attachment.buffer);
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 path15 from "path";
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 = path15.resolve(absPath);
20969
+ const resolved = path16.resolve(absPath);
20791
20970
  if (await pathExists2(resolved)) {
20792
20971
  return fs13.realpath(resolved);
20793
20972
  }
20794
- const parentPath = path15.dirname(resolved);
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 path15.join(resolvedParent, path15.basename(resolved));
20978
+ return path16.join(resolvedParent, path16.basename(resolved));
20800
20979
  }
20801
20980
  async function resolveImportedWorkspacePath(workspaceRoot, candidatePath) {
20802
- if (!path15.isAbsolute(candidatePath)) {
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(path15.sep) ? resolvedRoot : `${resolvedRoot}${path15.sep}`;
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: path15.basename(importedPath) || "workspace"
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
- return [
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
- ].join(" ");
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 spawn2 } from "child_process";
22122
- import path16 from "path";
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(path16.join(globalRoot, packageName, "package.json"));
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(path16.resolve("node_modules", packageName, "package.json"));
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 ? path16.join(prefix, "bin") : null;
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 (path16.isAbsolute(command)) {
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 = spawn2(command, {
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 path17 from "path";
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 = path17.isAbsolute(query.path) ? query.path : path17.resolve(workspace.absPath, query.path);
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(() => path17.resolve(app.services.config.workspaceRoot));
23581
- const workspacePrefix = resolvedWorkspaceRoot.endsWith(path17.sep) ? resolvedWorkspaceRoot : `${resolvedWorkspaceRoot}${path17.sep}`;
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 os3 from "os";
23779
- import path18 from "path";
23780
- import { spawn as spawn3 } from "child_process";
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 = path18.extname(filePath).slice(1).toLowerCase();
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 = path18.relative(rootPath, absPath);
23914
- return relative === "" ? "" : relative.split(path18.sep).join("/");
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 = path18.resolve(rootPath, relativePath || ".");
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 ? path18.basename(absPath) : path18.basename(rootPath);
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 = path18.join(absPath, entry.name);
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 path18.join(record.absPath, ".remote-codex", "artifacts");
24252
+ return path19.join(record.absPath, ".remote-codex", "artifacts");
23984
24253
  }
23985
24254
  function artifactFilePath(record, artifactId) {
23986
- return path18.join(artifactRoot(record), artifactId, "artifact.bin");
24255
+ return path19.join(artifactRoot(record), artifactId, "artifact.bin");
23987
24256
  }
23988
24257
  function artifactMetadataPath(record, artifactId) {
23989
- return path18.join(artifactRoot(record), artifactId, "metadata.json");
24258
+ return path19.join(artifactRoot(record), artifactId, "metadata.json");
23990
24259
  }
23991
24260
  function safeArtifactFileName(value) {
23992
- return path18.basename(value).replace(/[^a-zA-Z0-9_. -]/g, "_") || "artifact.bin";
24261
+ return path19.basename(value).replace(/[^a-zA-Z0-9_. -]/g, "_") || "artifact.bin";
23993
24262
  }
23994
24263
  function artifactIdFromName(name) {
23995
- const base = path18.basename(name).replace(/[^a-zA-Z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 96);
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 (path18.extname(filePath).slice(1).toLowerCase()) {
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 = path18.basename(folderPath) || "workspace-folder";
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, path18.relative(rootPath, path18.join(current, child.name)));
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(path18.sep).join("/"), "utf8");
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(path18.join(os3.tmpdir(), "remote-codex-folder-download-"));
24184
- const zipPath = path18.join(tempDir, `${path18.basename(folderPath) || "workspace-folder"}.zip`);
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 = path18.basename(filename?.trim() || "upload");
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(path18.sep)) {
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 = spawn3("git", ["clone", gitUrl, targetPath], {
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 ? path18.resolve(query.path) : app.services.config.workspaceRoot;
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: path18.basename(filePath),
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 = `${path18.basename(itemPath) || "workspace-folder"}.zip`;
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 = path18.basename(itemPath);
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: path18.basename(file.path),
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 = path18.dirname(artifactFilePath(record, artifactId));
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(path18.dirname(artifactFilePath(record, params.artifactId)), {
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 = path18.join(settings.devHome, repoName);
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 path19 from "path";
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 path19.join(providerHome, name);
25117
+ return path20.join(providerHome, name);
24849
25118
  }
24850
25119
  function resolveArchiveRoot(providerHome) {
24851
- return path19.join(providerHome, "supervisor-config-archives");
25120
+ return path20.join(providerHome, "supervisor-config-archives");
24852
25121
  }
24853
25122
  function resolveArchiveIndexPath(providerHome) {
24854
- return path19.join(resolveArchiveRoot(providerHome), "index.json");
25123
+ return path20.join(resolveArchiveRoot(providerHome), "index.json");
24855
25124
  }
24856
25125
  function resolveArchivePath(providerHome, archiveId) {
24857
- return path19.join(resolveArchiveRoot(providerHome), archiveId);
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(path19.dirname(filePath), { recursive: true });
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(path19.join(archivePath, name), hostFile.content, "utf8");
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(path19.join(archivePath, name), "utf8");
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 os4 from "os";
25069
- import path20 from "path";
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 path20.basename(normalized) || normalized;
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 = path20.join(
25240
- os4.tmpdir(),
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
- // ../../node_modules/.pnpm/@remote-codex+plugin-xyz-viewer@file+..+remote-codex-thread-ui+packages+plugin-xyz-view_5227bf80a742872b523d08ba646669ff/node_modules/@remote-codex/plugin-xyz-viewer/dist/chunk-6FS7BLJV.js
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 draft built-in plugin for previewing xyz, extxyz, cif, and pdb molecular structures with 3Dmol.js.",
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 path21 from "path";
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: [path21.join(repoRoot, "bin", "remote-codex-plugin-mcp.mjs")]
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 = path21.join(input.codexHome, "config.toml");
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(path21.dirname(configPath), { recursive: true });
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 path22 from "path";
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(path22.dirname(filePath), { recursive: true, mode: 448 });
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
- path22.join(config.agentProviders.codex.home, "config.toml"),
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
- path22.join(config.agentProviders.codex.home, "auth.json"),
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
- path22.join(config.agentProviders.claude.home, "settings.json"),
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
- path22.join(config.agentProviders.opencode.home, "opencode.json"),
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 path23 from "path";
26500
- import { spawn as spawn4 } from "@homebridge/node-pty-prebuilt-multiarch";
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 = path23.basename(shell).toLowerCase();
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 = path23.basename(session.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 = spawn4(this.shell, shellArgs(this.shell), {
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 path24 from "path";
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(path24.sep)) {
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(path24.delimiter)) {
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 = path24.join(trimmed, command);
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, path26) {
27751
+ function artifactPreviewKind(type, format, path27) {
27484
27752
  const candidates = [
27485
27753
  type,
27486
27754
  format,
27487
- path26?.split(".").pop() ?? null
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 path26 = stringField3(record, ["path", "filePath", "file_path", "filename", "fileName", "name"]);
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"]) ?? path26 ?? "artifact";
27763
+ const title = stringField3(record, ["title", "label", "name", "filename", "fileName"]) ?? path27 ?? "artifact";
27496
27764
  return {
27497
27765
  title,
27498
- path: path26,
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 path26 = stringField3(record, ["path", "filePath", "file_path", "filename", "fileName", "name"]);
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"]) ?? path26 ?? `${module} artifact`;
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: path26,
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, path26)
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(path26) {
28044
+ async fetchText(path27) {
27674
28045
  const config = this.requireHarnessConfig();
27675
- const response = await this.fetchImpl(`${config.baseUrl}${path26}`, {
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(path26, init = {}) {
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}${path26}`, {
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(path26) {
28075
+ async fetchBinary(path27) {
27705
28076
  const config = this.requireHarnessConfig();
27706
- const response = await this.fetchImpl(`${config.baseUrl}${path26}`, {
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 crypto3 from "crypto";
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: crypto3.randomBytes(16).toString("base64url")
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 crypto3.createHmac("sha256", this.secret ?? "").update(payloadText).digest("base64url");
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 crypto3.timingSafeEqual(leftBuffer, rightBuffer);
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 path25.resolve(process.env.REMOTE_CODEX_REPO_ROOT);
28921
+ return path26.resolve(process.env.REMOTE_CODEX_REPO_ROOT);
28252
28922
  }
28253
- let current = path25.resolve(start);
28254
- while (current !== path25.dirname(current)) {
28255
- if (fs26.existsSync(path25.join(current, "pnpm-workspace.yaml")) && fs26.existsSync(path25.join(current, "scripts", "service-restart.mjs"))) {
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 = path25.dirname(current);
28928
+ current = path26.dirname(current);
28259
28929
  }
28260
- return path25.resolve(process.cwd());
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 = path25.join(repoRoot, "scripts", "service-restart.mjs");
28273
- if (!fs26.existsSync(restartScript) || !fs26.existsSync(path25.join(repoRoot, "pnpm-workspace.yaml"))) {
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 = spawn5(process.execPath, [restartScript, "launch"], {
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
- if (config.runtimeRole !== "worker" || !config.workerAuthToken || WORKER_AUTH_EXEMPT_PATHS.has(request.url.split("?")[0] ?? request.url)) {
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
  }