remote-codex 0.11.12 → 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.
@@ -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",
@@ -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 = [
@@ -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;
@@ -21005,11 +21115,18 @@ function harnessDeveloperInstructions(config) {
21005
21115
  return null;
21006
21116
  }
21007
21117
  const baseUrl = config.harnessBaseUrl.replace(/\/+$/, "");
21008
- return [
21118
+ const lines = [
21009
21119
  `ElAgente Harness chemistry tools are available at ${baseUrl}.`,
21010
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.",
21011
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."
21012
- ].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(" ");
21013
21130
  }
21014
21131
  function combineDeveloperInstructions(parts) {
21015
21132
  const normalized = parts.map((part) => part?.trim()).filter((part) => Boolean(part));
@@ -22690,6 +22807,14 @@ var harnessToolParamSchema = harnessModuleParamSchema.extend({
22690
22807
  tool: z4.string().trim().min(1).max(160).regex(/^[a-zA-Z0-9_-]+$/)
22691
22808
  });
22692
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
+ });
22693
22818
  var harnessInvokeContextSchema = z4.object({
22694
22819
  workspaceId: z4.string().uuid().nullable().optional(),
22695
22820
  sessionId: z4.string().uuid().nullable().optional(),
@@ -23033,6 +23158,19 @@ async function registerSystemRoutes(app) {
23033
23158
  metadata: harnessUsageMetadata(payload, attributionSource)
23034
23159
  }).catch(() => void 0);
23035
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
+ }
23036
23174
  return payload;
23037
23175
  } catch (error) {
23038
23176
  if (error instanceof HttpError) {
@@ -23044,6 +23182,68 @@ async function registerSystemRoutes(app) {
23044
23182
  });
23045
23183
  }
23046
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
+ });
23047
23247
  app.get("/api/config/workspace-settings", async () => {
23048
23248
  return getWorkspaceSettings(
23049
23249
  app.services.database.db,
@@ -25929,13 +26129,13 @@ var terminalPluginManifest = {
25929
26129
  }
25930
26130
  };
25931
26131
 
25932
- // ../../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
25933
26133
  var XYZ_MOLECULE_ARTIFACT_TYPE = "chemistry.molecule3d";
25934
26134
  var xyzViewerPluginManifest = {
25935
26135
  id: "remote-codex.xyz-viewer",
25936
26136
  name: "XYZ Molecule Viewer",
25937
26137
  version: "0.1.0",
25938
- 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.",
25939
26139
  remoteCodex: "^0.11.0",
25940
26140
  capabilities: {
25941
26141
  artifactTypes: [
@@ -25966,11 +26166,7 @@ var xyzViewerPluginManifest = {
25966
26166
  command: "node",
25967
26167
  args: ["bin/remote-codex-plugin-mcp.mjs"]
25968
26168
  }
25969
- ],
25970
- frontend: {
25971
- entry: "./dist/index.js",
25972
- style: "./src/styles.css"
25973
- }
26169
+ ]
25974
26170
  }
25975
26171
  };
25976
26172
 
@@ -27496,6 +27692,9 @@ function makeShellErrorEnvelope(shellId, error) {
27496
27692
  var HARNESS_MODULES = ["estructural", "quntur", "farmaco"];
27497
27693
  var HARNESS_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
27498
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"]);
27499
27698
  var MOLECULE_ARTIFACT_TYPES = /* @__PURE__ */ new Set(["xyz", "extxyz", "pdb", "cif"]);
27500
27699
  function recordFrom(value) {
27501
27700
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -27615,6 +27814,51 @@ function normalizeArtifact(module, runId, value) {
27615
27814
  previewKind: artifactPreviewKind(type, format, path27)
27616
27815
  };
27617
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
+ }
27618
27862
  function normalizeRuns(module, result) {
27619
27863
  const runs = payloadItems(result.payload, ["runs", "items", "results"]).map((item) => normalizeRun(module, item)).filter(Boolean);
27620
27864
  return { runs };
@@ -27658,6 +27902,57 @@ var WorkerHarnessClient = class {
27658
27902
  async me() {
27659
27903
  return this.fetchText("/members/.me");
27660
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
+ }
27661
27956
  async home() {
27662
27957
  return this.fetchPayload("/");
27663
27958
  }
@@ -27724,6 +28019,13 @@ var WorkerHarnessClient = class {
27724
28019
  }
27725
28020
  return normalized;
27726
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
+ }
27727
28029
  requireRunId(runId) {
27728
28030
  const normalized = runId.trim();
27729
28031
  if (!HARNESS_RUN_ID_PATTERN.test(normalized)) {
@@ -27797,6 +28099,297 @@ var WorkerHarnessClient = class {
27797
28099
  }
27798
28100
  };
27799
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
+
27800
28393
  // src/worker-control-plane-sync.ts
27801
28394
  import { setTimeout as delay } from "timers/promises";
27802
28395
  var WorkerControlPlaneSyncError = class extends Error {
@@ -27932,7 +28525,7 @@ var WorkerControlPlaneSyncClient = class {
27932
28525
  };
27933
28526
 
27934
28527
  // src/auth.ts
27935
- import crypto3 from "crypto";
28528
+ import crypto4 from "crypto";
27936
28529
  var AUTH_COOKIE_NAME = "remote_codex_session";
27937
28530
  var AuthService = class {
27938
28531
  required;
@@ -28015,7 +28608,7 @@ var AuthService = class {
28015
28608
  const payload = {
28016
28609
  username,
28017
28610
  expiresAt: expiresAtMs,
28018
- nonce: crypto3.randomBytes(16).toString("base64url")
28611
+ nonce: crypto4.randomBytes(16).toString("base64url")
28019
28612
  };
28020
28613
  const payloadText = Buffer.from(JSON.stringify(payload), "utf8").toString(
28021
28614
  "base64url"
@@ -28063,7 +28656,7 @@ var AuthService = class {
28063
28656
  };
28064
28657
  }
28065
28658
  sign(payloadText) {
28066
- return crypto3.createHmac("sha256", this.secret ?? "").update(payloadText).digest("base64url");
28659
+ return crypto4.createHmac("sha256", this.secret ?? "").update(payloadText).digest("base64url");
28067
28660
  }
28068
28661
  };
28069
28662
  function unauthorizedPayload() {
@@ -28120,7 +28713,7 @@ function constantTimeEqual(left, right) {
28120
28713
  if (leftBuffer.length !== rightBuffer.length) {
28121
28714
  return false;
28122
28715
  }
28123
- return crypto3.timingSafeEqual(leftBuffer, rightBuffer);
28716
+ return crypto4.timingSafeEqual(leftBuffer, rightBuffer);
28124
28717
  }
28125
28718
 
28126
28719
  // src/relay-tunnel-client.ts
@@ -28287,6 +28880,14 @@ var RelayTunnelClient = class {
28287
28880
  var MAX_PROMPT_ATTACHMENTS2 = 10;
28288
28881
  var MAX_PROMPT_ATTACHMENT_BYTES2 = 25 * 1024 * 1024;
28289
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
+ }
28290
28891
  var RELAY_FORWARD_HEADER = "x-remote-codex-relay-forwarded";
28291
28892
  var SUPERVISOR_LOG_REDACTION_PATHS = [
28292
28893
  "req.headers.authorization",
@@ -28403,7 +29004,8 @@ function buildApp(options = {}) {
28403
29004
  disableRequestLogging: config.disableRequestLogging
28404
29005
  });
28405
29006
  app.addHook("onRequest", async (request) => {
28406
- 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)) {
28407
29009
  return;
28408
29010
  }
28409
29011
  const headerToken = request.headers["x-remote-codex-worker-token"];
@@ -28433,6 +29035,13 @@ function buildApp(options = {}) {
28433
29035
  relaySocketBridge.handleMessage
28434
29036
  ) : null;
28435
29037
  relayTunnelClient?.validateConfig();
29038
+ const harnessWakeupService = new HarnessWakeupService(
29039
+ config,
29040
+ database.db,
29041
+ harnessClient,
29042
+ threadService,
29043
+ app.log
29044
+ );
28436
29045
  app.decorate("services", {
28437
29046
  config,
28438
29047
  database,
@@ -28445,6 +29054,7 @@ function buildApp(options = {}) {
28445
29054
  pluginRegistry,
28446
29055
  pluginService,
28447
29056
  harnessClient,
29057
+ harnessWakeupService,
28448
29058
  controlPlaneSyncClient,
28449
29059
  authService,
28450
29060
  relayTunnelClient,
@@ -28461,6 +29071,12 @@ function buildApp(options = {}) {
28461
29071
  if (requestPath === "/api/auth/login" || requestPath === "/api/auth/logout" || requestPath === "/api/auth/session") {
28462
29072
  return;
28463
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
+ }
28464
29080
  if (config.mode === "relay" && request.headers[RELAY_FORWARD_HEADER] === "1") {
28465
29081
  return;
28466
29082
  }