remote-codex 0.11.12 → 0.11.14

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() === "") {
@@ -16386,8 +16492,11 @@ function historyItemSequence(item) {
16386
16492
  function historyItemTranscriptOrder(item) {
16387
16493
  return typeof item.transcriptOrder === "number" && Number.isFinite(item.transcriptOrder) ? item.transcriptOrder : null;
16388
16494
  }
16389
- function copyPersistedOrderingHints(item, persistedItem) {
16495
+ function copyPersistedOrderingHints(item, persistedItem, turnStartedAt) {
16390
16496
  let nextItem = item;
16497
+ if (persistedItem.createdAt && (!nextItem.createdAt || nextItem.kind === "agentMessage" && nextItem.createdAt === turnStartedAt)) {
16498
+ nextItem = { ...nextItem, createdAt: persistedItem.createdAt };
16499
+ }
16391
16500
  if (hasHistoryItemSequence(persistedItem)) {
16392
16501
  const sequence = historyItemSequence(persistedItem);
16393
16502
  if (nextItem.sequence !== sequence) {
@@ -16525,7 +16634,8 @@ function mergePersistedHistoryItemsIntoTurns(turns, persistedItemsByTurnId, defe
16525
16634
  };
16526
16635
  const sequencedItem = copyPersistedOrderingHints(
16527
16636
  itemWithTranscriptOrder,
16528
- persistedItemWithTranscriptOrder
16637
+ persistedItemWithTranscriptOrder,
16638
+ turn.startedAt
16529
16639
  );
16530
16640
  if (sequencedItem !== item) {
16531
16641
  changed = true;
@@ -16552,13 +16662,17 @@ function mergePersistedHistoryItemsIntoTurns(turns, persistedItemsByTurnId, defe
16552
16662
  });
16553
16663
  }
16554
16664
  function agentTurnToThreadTurnDto(turn, deferredDetails) {
16665
+ const startedAt = turn.startedAt ?? parseUuidV7Timestamp2(turn.providerTurnId);
16555
16666
  const baseTurn = {
16556
16667
  id: turn.providerTurnId,
16557
- startedAt: turn.startedAt ?? parseUuidV7Timestamp2(turn.providerTurnId),
16668
+ startedAt,
16558
16669
  status: turn.status,
16559
16670
  error: turn.error?.message ?? null,
16560
16671
  items: visibleRuntimeTurnItems(turn.items).map(
16561
- (item, transcriptIndex) => item.transcriptOrder === transcriptIndex ? item : { ...item, transcriptOrder: transcriptIndex }
16672
+ (item, transcriptIndex) => normalizeHistoryItemCreatedAt(
16673
+ item.transcriptOrder === transcriptIndex ? item : { ...item, transcriptOrder: transcriptIndex },
16674
+ startedAt
16675
+ )
16562
16676
  )
16563
16677
  };
16564
16678
  return deferredDetails ? deferLargeHistoryItemDetails(baseTurn, deferredDetails) : baseTurn;
@@ -16702,6 +16816,11 @@ var ThreadLiveStateStore = class {
16702
16816
  return this.threadTurnItemOrder.get(localThreadId) ?? /* @__PURE__ */ new Map();
16703
16817
  }
16704
16818
  finalTurnAgentMessageOrderingHints(localThreadId, turnId, items, options = {}) {
16819
+ return new Map(
16820
+ [...this.finalTurnAgentMessageOrderingMetadata(localThreadId, turnId, items, options)].map(([itemId, metadata]) => [itemId, metadata.sequence])
16821
+ );
16822
+ }
16823
+ finalTurnAgentMessageOrderingMetadata(localThreadId, turnId, items, options = {}) {
16705
16824
  const hints = /* @__PURE__ */ new Map();
16706
16825
  const turnOrder = this.threadTurnItemOrder.get(localThreadId)?.get(turnId);
16707
16826
  const liveAgentMessages = [
@@ -16709,17 +16828,21 @@ var ThreadLiveStateStore = class {
16709
16828
  ].map((item) => ({
16710
16829
  id: item.id,
16711
16830
  text: normalizeAgentMessageForMatching(item.text),
16712
- sequence: item.sequence
16831
+ sequence: item.sequence,
16832
+ createdAt: item.createdAt
16713
16833
  }));
16714
16834
  const usedLiveAgentIds = /* @__PURE__ */ new Set();
16715
16835
  const finalAgentItems = items.filter((item) => item.kind === "agentMessage");
16716
16836
  for (const item of finalAgentItems) {
16717
16837
  const existingSequence = turnOrder?.get(item.id);
16718
16838
  if (existingSequence !== void 0) {
16719
- hints.set(item.id, existingSequence);
16720
16839
  const matchingLiveAgent = liveAgentMessages.find(
16721
16840
  (liveAgent) => liveAgent.id === item.id || liveAgent.sequence === existingSequence
16722
16841
  );
16842
+ hints.set(item.id, {
16843
+ sequence: existingSequence,
16844
+ createdAt: matchingLiveAgent?.createdAt ?? null
16845
+ });
16723
16846
  if (matchingLiveAgent) {
16724
16847
  usedLiveAgentIds.add(matchingLiveAgent.id);
16725
16848
  }
@@ -16741,12 +16864,16 @@ var ThreadLiveStateStore = class {
16741
16864
  bestMatch = {
16742
16865
  id: liveAgent.id,
16743
16866
  sequence: liveAgent.sequence,
16867
+ createdAt: liveAgent.createdAt,
16744
16868
  score
16745
16869
  };
16746
16870
  }
16747
16871
  if (bestMatch) {
16748
16872
  usedLiveAgentIds.add(bestMatch.id);
16749
- hints.set(item.id, bestMatch.sequence);
16873
+ hints.set(item.id, {
16874
+ sequence: bestMatch.sequence,
16875
+ createdAt: bestMatch.createdAt
16876
+ });
16750
16877
  }
16751
16878
  }
16752
16879
  if (options.allowUnmatchedFallback ?? true) {
@@ -16760,7 +16887,10 @@ var ThreadLiveStateStore = class {
16760
16887
  if (!liveAgent) {
16761
16888
  break;
16762
16889
  }
16763
- hints.set(item.id, liveAgent.sequence);
16890
+ hints.set(item.id, {
16891
+ sequence: liveAgent.sequence,
16892
+ createdAt: liveAgent.createdAt
16893
+ });
16764
16894
  remainingLiveAgentIndex += 1;
16765
16895
  }
16766
16896
  }
@@ -16799,7 +16929,7 @@ var ThreadLiveStateStore = class {
16799
16929
  sequence: input.sequence
16800
16930
  } : {
16801
16931
  id: input.itemId,
16802
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
16932
+ createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
16803
16933
  kind: "agentMessage",
16804
16934
  text: input.delta,
16805
16935
  sequence: input.sequence
@@ -16838,7 +16968,8 @@ var ThreadLiveStateStore = class {
16838
16968
  turnHints.set(item.id, {
16839
16969
  id: item.id,
16840
16970
  text: item.text,
16841
- sequence
16971
+ sequence,
16972
+ createdAt: item.createdAt ?? null
16842
16973
  });
16843
16974
  }
16844
16975
  reconcileLiveItems(localThreadId, turns) {
@@ -17395,8 +17526,9 @@ var ThreadRuntimeEventProjector = class {
17395
17526
  if (!turnId) {
17396
17527
  return;
17397
17528
  }
17529
+ const eventTimestamp = (/* @__PURE__ */ new Date()).toISOString();
17398
17530
  const liveItem = {
17399
- ...event.item,
17531
+ ...withHistoryItemCreatedAt(event.item, eventTimestamp),
17400
17532
  sequence: liveState.recordTurnItemOrder(record.id, turnId, event.item.id)
17401
17533
  };
17402
17534
  const transportLiveItem = deferHistoryItemDetailForTransport(liveItem);
@@ -17427,8 +17559,9 @@ var ThreadRuntimeEventProjector = class {
17427
17559
  displayTurnId,
17428
17560
  event.item.id
17429
17561
  );
17562
+ const eventTimestamp = (/* @__PURE__ */ new Date()).toISOString();
17430
17563
  const orderedLiveItem = {
17431
- ...event.item,
17564
+ ...withHistoryItemCreatedAt(event.item, eventTimestamp),
17432
17565
  sequence
17433
17566
  };
17434
17567
  const transportLiveItem = deferHistoryItemDetailForTransport(orderedLiveItem);
@@ -17480,18 +17613,21 @@ var ThreadRuntimeEventProjector = class {
17480
17613
  displayTurnId,
17481
17614
  event.itemId
17482
17615
  );
17616
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
17483
17617
  callbacks.appendLiveAgentMessageDelta({
17484
17618
  localThreadId: record.id,
17485
17619
  turnId: displayTurnId,
17486
17620
  itemId: event.itemId,
17487
17621
  delta: event.delta,
17488
- sequence
17622
+ sequence,
17623
+ createdAt
17489
17624
  });
17490
17625
  callbacks.emitThreadEvent("thread.output.delta", record.id, {
17491
17626
  turnId: displayTurnId,
17492
17627
  itemId: event.itemId,
17493
17628
  sequence,
17494
- delta: event.delta
17629
+ delta: event.delta,
17630
+ createdAt
17495
17631
  });
17496
17632
  return;
17497
17633
  }
@@ -17591,6 +17727,9 @@ var ThreadRuntimeEventProjector = class {
17591
17727
  return typeof value === "object" && value !== null && !Array.isArray(value);
17592
17728
  }
17593
17729
  };
17730
+ function withHistoryItemCreatedAt(item, createdAt) {
17731
+ return item.createdAt ? item : { ...item, createdAt };
17732
+ }
17594
17733
 
17595
17734
  // src/provider-request-coordinator.ts
17596
17735
  var LOCAL_PLAN_DECISION_PREFIX = "plan-decision:";
@@ -19230,7 +19369,7 @@ var ThreadHistoryPersistenceCoordinator = class {
19230
19369
  deleteThreadHistoryItemRecordsByThreadAndTurnId(this.db, localThreadId, turnId);
19231
19370
  }
19232
19371
  persistFinalTurnOrderingHints(localThreadId, turnId, items) {
19233
- const orderingHints = this.liveState.finalTurnAgentMessageOrderingHints(
19372
+ const orderingHints = this.liveState.finalTurnAgentMessageOrderingMetadata(
19234
19373
  localThreadId,
19235
19374
  turnId,
19236
19375
  items
@@ -19239,8 +19378,8 @@ var ThreadHistoryPersistenceCoordinator = class {
19239
19378
  if (item.kind !== "agentMessage" || !shouldPersistRuntimeFinalHistoryItem(item)) {
19240
19379
  continue;
19241
19380
  }
19242
- const sequence = orderingHints.get(item.id);
19243
- if (sequence === void 0) {
19381
+ const metadata = orderingHints.get(item.id);
19382
+ if (!metadata) {
19244
19383
  continue;
19245
19384
  }
19246
19385
  upsertThreadHistoryItemRecord(this.db, {
@@ -19249,7 +19388,8 @@ var ThreadHistoryPersistenceCoordinator = class {
19249
19388
  itemId: item.id,
19250
19389
  itemJson: JSON.stringify({
19251
19390
  ...item,
19252
- sequence,
19391
+ sequence: metadata.sequence,
19392
+ createdAt: item.createdAt ?? metadata.createdAt,
19253
19393
  sourceTurnId: turnId
19254
19394
  })
19255
19395
  });
@@ -21005,11 +21145,18 @@ function harnessDeveloperInstructions(config) {
21005
21145
  return null;
21006
21146
  }
21007
21147
  const baseUrl = config.harnessBaseUrl.replace(/\/+$/, "");
21008
- return [
21148
+ const lines = [
21009
21149
  `ElAgente Harness chemistry tools are available at ${baseUrl}.`,
21010
21150
  "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
21151
  "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(" ");
21152
+ ];
21153
+ if (config.harnessWakeupCallbackBaseUrl) {
21154
+ const supervisorBaseUrl = `http://127.0.0.1:${config.port}`;
21155
+ lines.push(
21156
+ `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.`
21157
+ );
21158
+ }
21159
+ return lines.join(" ");
21013
21160
  }
21014
21161
  function combineDeveloperInstructions(parts) {
21015
21162
  const normalized = parts.map((part) => part?.trim()).filter((part) => Boolean(part));
@@ -21196,7 +21343,8 @@ var ThreadService = class {
21196
21343
  input.turnId,
21197
21344
  input.itemId,
21198
21345
  input.delta,
21199
- input.sequence
21346
+ input.sequence,
21347
+ input.createdAt
21200
21348
  ),
21201
21349
  clearPendingPlanDecisionRequests: (localThreadId, emitEvents) => this.clearPendingPlanDecisionRequests(localThreadId, emitEvents),
21202
21350
  clearPendingSteersForTurn: (localThreadId, turnId) => this.auxiliaryState.clearPendingSteersForTurn(localThreadId, turnId),
@@ -22108,13 +22256,14 @@ var ThreadService = class {
22108
22256
  listPendingRequests(localThreadId, options = {}) {
22109
22257
  return this.requestCoordinator.listPendingRequests(localThreadId, options);
22110
22258
  }
22111
- appendLiveAgentMessageDelta(localThreadId, turnId, itemId, delta, sequence) {
22259
+ appendLiveAgentMessageDelta(localThreadId, turnId, itemId, delta, sequence, createdAt) {
22112
22260
  this.liveState.appendLiveAgentMessageDelta({
22113
22261
  localThreadId,
22114
22262
  turnId,
22115
22263
  itemId,
22116
22264
  delta,
22117
- sequence
22265
+ sequence,
22266
+ createdAt
22118
22267
  });
22119
22268
  }
22120
22269
  createPendingPlanDecisionRequest(localThreadId, turnId, emitEvents) {
@@ -22690,6 +22839,14 @@ var harnessToolParamSchema = harnessModuleParamSchema.extend({
22690
22839
  tool: z4.string().trim().min(1).max(160).regex(/^[a-zA-Z0-9_-]+$/)
22691
22840
  });
22692
22841
  var harnessInvokeBodySchema = z4.record(z4.string(), z4.unknown());
22842
+ var harnessJobWatchBodySchema = z4.object({
22843
+ jobId: z4.string().trim().min(1).max(200).regex(/^[a-zA-Z0-9_.:-]+$/),
22844
+ threadId: z4.string().trim().min(1).max(200).optional(),
22845
+ title: z4.string().trim().min(1).max(300).optional()
22846
+ });
22847
+ var harnessHookParamSchema = z4.object({
22848
+ token: z4.string().trim().min(1).max(200).regex(/^[a-zA-Z0-9_-]+$/)
22849
+ });
22693
22850
  var harnessInvokeContextSchema = z4.object({
22694
22851
  workspaceId: z4.string().uuid().nullable().optional(),
22695
22852
  sessionId: z4.string().uuid().nullable().optional(),
@@ -23033,6 +23190,19 @@ async function registerSystemRoutes(app) {
23033
23190
  metadata: harnessUsageMetadata(payload, attributionSource)
23034
23191
  }).catch(() => void 0);
23035
23192
  }
23193
+ const invokeJobId = stringField2(result, ["job_id", "jobId", "compute_job_id", "computeJobId"]);
23194
+ if (invokeJobId && context.threadId && app.services.harnessWakeupService.enabled()) {
23195
+ await app.services.harnessWakeupService.watchJob({
23196
+ jobId: invokeJobId,
23197
+ threadId: context.threadId,
23198
+ title: `${params.module}/${params.tool}`
23199
+ }).catch((watchError) => {
23200
+ request.log.warn(
23201
+ { err: watchError, jobId: invokeJobId },
23202
+ "Harness wakeup auto-watch failed."
23203
+ );
23204
+ });
23205
+ }
23036
23206
  return payload;
23037
23207
  } catch (error) {
23038
23208
  if (error instanceof HttpError) {
@@ -23044,6 +23214,68 @@ async function registerSystemRoutes(app) {
23044
23214
  });
23045
23215
  }
23046
23216
  });
23217
+ app.get("/api/harness/wakeup", async () => {
23218
+ try {
23219
+ return await app.services.harnessWakeupService.getWakeupInfo();
23220
+ } catch (error) {
23221
+ if (error instanceof HttpError) {
23222
+ throw error;
23223
+ }
23224
+ throw new HttpError(503, {
23225
+ code: "harness_unavailable",
23226
+ message: error instanceof Error ? error.message : "ElAgenteHarness is unavailable."
23227
+ });
23228
+ }
23229
+ });
23230
+ app.get("/api/harness/job-watches", async () => {
23231
+ return {
23232
+ watches: listHarnessJobWatches(app.services.database.db)
23233
+ };
23234
+ });
23235
+ app.post("/api/harness/job-watches", async (request, reply) => {
23236
+ const body = harnessJobWatchBodySchema.parse(request.body ?? {});
23237
+ try {
23238
+ const result = await app.services.harnessWakeupService.watchJob({
23239
+ jobId: body.jobId,
23240
+ threadId: body.threadId ?? null,
23241
+ title: body.title ?? null
23242
+ });
23243
+ reply.status(201);
23244
+ return result;
23245
+ } catch (error) {
23246
+ if (error instanceof HttpError) {
23247
+ throw error;
23248
+ }
23249
+ throw new HttpError(503, {
23250
+ code: "harness_unavailable",
23251
+ message: error instanceof Error ? error.message : "ElAgenteHarness is unavailable."
23252
+ });
23253
+ }
23254
+ });
23255
+ app.register(async (hookApp) => {
23256
+ hookApp.addContentTypeParser(
23257
+ ["application/json", "text/plain"],
23258
+ { parseAs: "buffer" },
23259
+ (_request, body, done) => done(null, body)
23260
+ );
23261
+ hookApp.addContentTypeParser(
23262
+ "*",
23263
+ { parseAs: "buffer" },
23264
+ (_request, body, done) => done(null, body)
23265
+ );
23266
+ hookApp.post("/api/hooks/harness-notify/:token", async (request, reply) => {
23267
+ const params = harnessHookParamSchema.parse(request.params);
23268
+ const signatureHeader = request.headers["x-webhook-signature"];
23269
+ const rawBody = Buffer.isBuffer(request.body) ? request.body : Buffer.from(typeof request.body === "string" ? request.body : "");
23270
+ const result = app.services.harnessWakeupService.handleCallback({
23271
+ hookToken: params.token,
23272
+ rawBody,
23273
+ signature: typeof signatureHeader === "string" ? signatureHeader : null
23274
+ });
23275
+ reply.status(202);
23276
+ return result;
23277
+ });
23278
+ });
23047
23279
  app.get("/api/config/workspace-settings", async () => {
23048
23280
  return getWorkspaceSettings(
23049
23281
  app.services.database.db,
@@ -25929,13 +26161,13 @@ var terminalPluginManifest = {
25929
26161
  }
25930
26162
  };
25931
26163
 
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
26164
+ // src/plugins/xyz-viewer-plugin-manifest.ts
25933
26165
  var XYZ_MOLECULE_ARTIFACT_TYPE = "chemistry.molecule3d";
25934
26166
  var xyzViewerPluginManifest = {
25935
26167
  id: "remote-codex.xyz-viewer",
25936
26168
  name: "XYZ Molecule Viewer",
25937
26169
  version: "0.1.0",
25938
- description: "A draft built-in plugin for previewing xyz, extxyz, cif, and pdb molecular structures with 3Dmol.js.",
26170
+ description: "A built-in plugin for previewing xyz, extxyz, cif, and pdb molecular structures.",
25939
26171
  remoteCodex: "^0.11.0",
25940
26172
  capabilities: {
25941
26173
  artifactTypes: [
@@ -25966,11 +26198,7 @@ var xyzViewerPluginManifest = {
25966
26198
  command: "node",
25967
26199
  args: ["bin/remote-codex-plugin-mcp.mjs"]
25968
26200
  }
25969
- ],
25970
- frontend: {
25971
- entry: "./dist/index.js",
25972
- style: "./src/styles.css"
25973
- }
26201
+ ]
25974
26202
  }
25975
26203
  };
25976
26204
 
@@ -27496,6 +27724,9 @@ function makeShellErrorEnvelope(shellId, error) {
27496
27724
  var HARNESS_MODULES = ["estructural", "quntur", "farmaco"];
27497
27725
  var HARNESS_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
27498
27726
  var HARNESS_RUN_ID_PATTERN = /^[a-zA-Z0-9_.-]+$/;
27727
+ var HARNESS_JOB_ID_PATTERN = /^[a-zA-Z0-9_.:-]+$/;
27728
+ var HARNESS_NOTIFICATION_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
27729
+ var HARNESS_TERMINAL_JOB_STATUSES = /* @__PURE__ */ new Set(["done", "failed", "cancelled"]);
27499
27730
  var MOLECULE_ARTIFACT_TYPES = /* @__PURE__ */ new Set(["xyz", "extxyz", "pdb", "cif"]);
27500
27731
  function recordFrom(value) {
27501
27732
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -27615,6 +27846,51 @@ function normalizeArtifact(module, runId, value) {
27615
27846
  previewKind: artifactPreviewKind(type, format, path27)
27616
27847
  };
27617
27848
  }
27849
+ function parseTomlScalar(raw) {
27850
+ const value = raw.trim();
27851
+ if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
27852
+ try {
27853
+ return JSON.parse(value);
27854
+ } catch {
27855
+ return value.slice(1, -1);
27856
+ }
27857
+ }
27858
+ return value;
27859
+ }
27860
+ function parseTomlLines(text2) {
27861
+ const record = {};
27862
+ for (const line of text2.split("\n")) {
27863
+ const match = /^([A-Za-z0-9_]+)\s*=\s*(.+)$/.exec(line.trim());
27864
+ if (match && !(match[1] in record)) {
27865
+ record[match[1]] = parseTomlScalar(match[2]);
27866
+ }
27867
+ }
27868
+ return record;
27869
+ }
27870
+ function parseTomlBlocks(text2, blockName) {
27871
+ const marker = `[[${blockName}]]`;
27872
+ const blocks = [];
27873
+ let current = null;
27874
+ for (const line of text2.split("\n")) {
27875
+ if (line.trim() === marker) {
27876
+ if (current) {
27877
+ blocks.push(parseTomlLines(current.join("\n")));
27878
+ }
27879
+ current = [];
27880
+ continue;
27881
+ }
27882
+ if (line.trim().startsWith("[[") && current) {
27883
+ blocks.push(parseTomlLines(current.join("\n")));
27884
+ current = null;
27885
+ continue;
27886
+ }
27887
+ current?.push(line);
27888
+ }
27889
+ if (current) {
27890
+ blocks.push(parseTomlLines(current.join("\n")));
27891
+ }
27892
+ return blocks;
27893
+ }
27618
27894
  function normalizeRuns(module, result) {
27619
27895
  const runs = payloadItems(result.payload, ["runs", "items", "results"]).map((item) => normalizeRun(module, item)).filter(Boolean);
27620
27896
  return { runs };
@@ -27658,6 +27934,57 @@ var WorkerHarnessClient = class {
27658
27934
  async me() {
27659
27935
  return this.fetchText("/members/.me");
27660
27936
  }
27937
+ async whoami() {
27938
+ const { text: text2 } = await this.fetchText("/members/.me");
27939
+ const record = parseTomlLines(text2);
27940
+ const agentId = record.id?.trim();
27941
+ if (!agentId) {
27942
+ throw new Error("ElAgenteHarness /members/.me response did not include an id.");
27943
+ }
27944
+ return { agentId };
27945
+ }
27946
+ async registerNotifyCallback(input) {
27947
+ return this.fetchPayload("/notify/register", {
27948
+ method: "POST",
27949
+ headers: {
27950
+ "content-type": "application/json"
27951
+ },
27952
+ body: JSON.stringify({
27953
+ agent_id: input.agentId,
27954
+ callback: input.callback,
27955
+ secret: input.secret
27956
+ })
27957
+ });
27958
+ }
27959
+ async getComputeJob(jobId) {
27960
+ const id = this.requireJobId(jobId);
27961
+ const { text: text2 } = await this.fetchText(`/compute/jobs/${encodeURIComponent(id)}`);
27962
+ const record = parseTomlLines(text2);
27963
+ const status = record.status?.trim() ?? null;
27964
+ return {
27965
+ jobId: record.id?.trim() ?? id,
27966
+ status,
27967
+ terminal: status !== null && HARNESS_TERMINAL_JOB_STATUSES.has(status),
27968
+ title: record.title?.trim() || null,
27969
+ reason: record.reason?.trim() || null,
27970
+ raw: record
27971
+ };
27972
+ }
27973
+ async listUnreadNotifications() {
27974
+ const { text: text2 } = await this.fetchText("/notify/inbox");
27975
+ return parseTomlBlocks(text2, "notifications").filter((entry) => entry.id?.trim()).map((entry) => ({
27976
+ id: entry.id.trim(),
27977
+ from: entry.from?.trim() ?? "",
27978
+ message: entry.message ?? ""
27979
+ }));
27980
+ }
27981
+ async markNotificationRead(notificationId) {
27982
+ const id = notificationId.trim();
27983
+ if (!HARNESS_NOTIFICATION_ID_PATTERN.test(id)) {
27984
+ throw new Error(`Unsupported Harness notification id: ${notificationId}`);
27985
+ }
27986
+ return this.fetchText(`/notify/inbox/${encodeURIComponent(id)}`);
27987
+ }
27661
27988
  async home() {
27662
27989
  return this.fetchPayload("/");
27663
27990
  }
@@ -27724,6 +28051,13 @@ var WorkerHarnessClient = class {
27724
28051
  }
27725
28052
  return normalized;
27726
28053
  }
28054
+ requireJobId(jobId) {
28055
+ const normalized = jobId.trim();
28056
+ if (!HARNESS_JOB_ID_PATTERN.test(normalized)) {
28057
+ throw new Error(`Unsupported Harness job id: ${jobId}`);
28058
+ }
28059
+ return normalized;
28060
+ }
27727
28061
  requireRunId(runId) {
27728
28062
  const normalized = runId.trim();
27729
28063
  if (!HARNESS_RUN_ID_PATTERN.test(normalized)) {
@@ -27797,6 +28131,297 @@ var WorkerHarnessClient = class {
27797
28131
  }
27798
28132
  };
27799
28133
 
28134
+ // src/harness-wakeup-service.ts
28135
+ import crypto3 from "crypto";
28136
+ var JOB_ID_FROM_MESSAGE_PATTERN = /^id:\s*(\S+)\s*$/m;
28137
+ function timingSafeEqualString2(left, right) {
28138
+ const leftBuffer = Buffer.from(left);
28139
+ const rightBuffer = Buffer.from(right);
28140
+ return leftBuffer.length === rightBuffer.length && crypto3.timingSafeEqual(leftBuffer, rightBuffer);
28141
+ }
28142
+ var HarnessWakeupService = class {
28143
+ constructor(config, db, harnessClient, threadService, logger) {
28144
+ this.config = config;
28145
+ this.db = db;
28146
+ this.harnessClient = harnessClient;
28147
+ this.threadService = threadService;
28148
+ this.logger = logger;
28149
+ }
28150
+ config;
28151
+ db;
28152
+ harnessClient;
28153
+ threadService;
28154
+ logger;
28155
+ reconcileInFlight = null;
28156
+ reconcileQueued = false;
28157
+ disabledReasonFor(keyPresent) {
28158
+ if (!this.config.harnessBaseUrl) {
28159
+ return "missing_harness_base_url";
28160
+ }
28161
+ if (!keyPresent) {
28162
+ return "missing_harness_key";
28163
+ }
28164
+ if (!this.config.harnessWakeupCallbackBaseUrl) {
28165
+ return "missing_callback_base_url";
28166
+ }
28167
+ return null;
28168
+ }
28169
+ disabledReason() {
28170
+ return this.disabledReasonFor(this.harnessClient.configured().keyPresent);
28171
+ }
28172
+ status() {
28173
+ const keyPresent = this.harnessClient.configured().keyPresent;
28174
+ const reason = this.disabledReasonFor(keyPresent);
28175
+ return {
28176
+ enabled: reason === null,
28177
+ reason,
28178
+ harnessBaseUrl: this.config.harnessBaseUrl,
28179
+ callbackBaseUrl: this.config.harnessWakeupCallbackBaseUrl,
28180
+ keyPresent
28181
+ };
28182
+ }
28183
+ enabled() {
28184
+ return this.disabledReason() === null;
28185
+ }
28186
+ requireEnabled() {
28187
+ if (!this.enabled()) {
28188
+ throw new HttpError(409, {
28189
+ code: "conflict",
28190
+ message: "Harness wakeup is not configured. REMOTE_CODEX_HARNESS_WAKEUP_CALLBACK_BASE_URL and the Harness key are required."
28191
+ });
28192
+ }
28193
+ }
28194
+ buildCallbackUrl(hookToken) {
28195
+ const base = this.config.harnessWakeupCallbackBaseUrl.replace(/\/+$/, "");
28196
+ const userSuffix = this.config.userId ? `?u=${encodeURIComponent(this.config.userId)}` : "";
28197
+ return `${base}/harness-notify/${hookToken}${userSuffix}`;
28198
+ }
28199
+ async ensureRegistration() {
28200
+ this.requireEnabled();
28201
+ const existing = getHarnessNotifyRegistration(this.db);
28202
+ if (existing) {
28203
+ const desiredUrl = this.buildCallbackUrl(existing.hookToken);
28204
+ if (existing.callbackUrl === desiredUrl) {
28205
+ return existing;
28206
+ }
28207
+ await this.harnessClient.registerNotifyCallback({
28208
+ agentId: existing.agentId,
28209
+ callback: desiredUrl,
28210
+ secret: existing.secret
28211
+ });
28212
+ return upsertHarnessNotifyRegistration(this.db, {
28213
+ agentId: existing.agentId,
28214
+ hookToken: existing.hookToken,
28215
+ secret: existing.secret,
28216
+ callbackUrl: desiredUrl
28217
+ });
28218
+ }
28219
+ const { agentId } = await this.harnessClient.whoami();
28220
+ const hookToken = crypto3.randomBytes(32).toString("hex");
28221
+ const secret = crypto3.randomBytes(32).toString("hex");
28222
+ const callbackUrl = this.buildCallbackUrl(hookToken);
28223
+ await this.harnessClient.registerNotifyCallback({
28224
+ agentId,
28225
+ callback: callbackUrl,
28226
+ secret
28227
+ });
28228
+ return upsertHarnessNotifyRegistration(this.db, {
28229
+ agentId,
28230
+ hookToken,
28231
+ secret,
28232
+ callbackUrl
28233
+ });
28234
+ }
28235
+ async getWakeupInfo() {
28236
+ const status = this.status();
28237
+ if (!status.enabled) {
28238
+ return {
28239
+ ...status,
28240
+ enabled: false
28241
+ };
28242
+ }
28243
+ const registration = await this.ensureRegistration();
28244
+ return {
28245
+ ...status,
28246
+ enabled: true,
28247
+ notifyTo: registration.agentId,
28248
+ registered: true
28249
+ };
28250
+ }
28251
+ async watchJob(input) {
28252
+ this.requireEnabled();
28253
+ const jobId = input.jobId.trim();
28254
+ if (!jobId) {
28255
+ throw new HttpError(400, {
28256
+ code: "bad_request",
28257
+ message: "jobId is required."
28258
+ });
28259
+ }
28260
+ let threadId = input.threadId?.trim() || null;
28261
+ if (!threadId) {
28262
+ const runningThreads = listThreadRecords(this.db).filter(
28263
+ (thread) => thread.status === "running"
28264
+ );
28265
+ if (runningThreads.length === 1) {
28266
+ threadId = runningThreads[0].id;
28267
+ }
28268
+ }
28269
+ if (!threadId) {
28270
+ throw new HttpError(400, {
28271
+ code: "bad_request",
28272
+ message: "threadId is required when it cannot be inferred from a single running thread."
28273
+ });
28274
+ }
28275
+ if (!getThreadRecordById(this.db, threadId)) {
28276
+ throw new HttpError(404, {
28277
+ code: "not_found",
28278
+ message: "Thread was not found."
28279
+ });
28280
+ }
28281
+ const registration = await this.ensureRegistration();
28282
+ const watch = upsertHarnessJobWatch(this.db, {
28283
+ jobId,
28284
+ threadId,
28285
+ title: input.title ?? null
28286
+ });
28287
+ return {
28288
+ watch,
28289
+ notifyTo: registration.agentId
28290
+ };
28291
+ }
28292
+ verifyCallback(input) {
28293
+ const registration = getHarnessNotifyRegistration(this.db);
28294
+ if (!registration || !timingSafeEqualString2(registration.hookToken, input.hookToken)) {
28295
+ throw new HttpError(404, {
28296
+ code: "not_found",
28297
+ message: "Unknown harness hook."
28298
+ });
28299
+ }
28300
+ const expected = crypto3.createHmac("sha256", registration.secret).update(input.rawBody).digest("hex");
28301
+ if (!input.signature || !timingSafeEqualString2(expected, input.signature.trim())) {
28302
+ throw new HttpError(403, {
28303
+ code: "forbidden",
28304
+ message: "Invalid harness hook signature."
28305
+ });
28306
+ }
28307
+ let payload = {};
28308
+ try {
28309
+ const parsed = JSON.parse(input.rawBody.toString("utf8"));
28310
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
28311
+ payload = parsed;
28312
+ }
28313
+ } catch {
28314
+ }
28315
+ return payload;
28316
+ }
28317
+ handleCallback(input) {
28318
+ const payload = this.verifyCallback(input);
28319
+ this.scheduleReconcile();
28320
+ return {
28321
+ accepted: true,
28322
+ type: typeof payload.type === "string" ? payload.type : null
28323
+ };
28324
+ }
28325
+ scheduleReconcile() {
28326
+ if (this.reconcileInFlight) {
28327
+ this.reconcileQueued = true;
28328
+ return;
28329
+ }
28330
+ this.reconcileInFlight = this.reconcile().catch((error) => {
28331
+ this.logger.error({ err: error }, "Harness wakeup reconcile failed.");
28332
+ }).finally(() => {
28333
+ this.reconcileInFlight = null;
28334
+ if (this.reconcileQueued) {
28335
+ this.reconcileQueued = false;
28336
+ this.scheduleReconcile();
28337
+ }
28338
+ });
28339
+ }
28340
+ async waitForReconcile() {
28341
+ while (this.reconcileInFlight) {
28342
+ await this.reconcileInFlight;
28343
+ }
28344
+ }
28345
+ async reconcile() {
28346
+ const watches = listPendingHarnessJobWatches(this.db);
28347
+ for (const watch of watches) {
28348
+ try {
28349
+ const job = await this.harnessClient.getComputeJob(watch.jobId);
28350
+ updateHarnessJobWatch(this.db, watch.id, {
28351
+ lastJobStatus: job.status
28352
+ });
28353
+ if (!job.terminal) {
28354
+ continue;
28355
+ }
28356
+ await this.wakeThread(watch, job);
28357
+ } catch (error) {
28358
+ const message = error instanceof Error ? error.message : String(error);
28359
+ this.logger.warn(
28360
+ { jobId: watch.jobId, threadId: watch.threadId, err: error },
28361
+ "Harness wakeup delivery attempt failed; will retry on the next callback."
28362
+ );
28363
+ updateHarnessJobWatch(this.db, watch.id, { lastError: message });
28364
+ }
28365
+ }
28366
+ await this.acknowledgeNotifications();
28367
+ }
28368
+ async wakeThread(watch, job) {
28369
+ const thread = getThreadRecordById(this.db, watch.threadId);
28370
+ if (!thread) {
28371
+ updateHarnessJobWatch(this.db, watch.id, {
28372
+ status: "failed",
28373
+ lastError: "Thread was not found."
28374
+ });
28375
+ return;
28376
+ }
28377
+ if (thread.isConnected === false) {
28378
+ await this.threadService.resumeThread(watch.threadId);
28379
+ }
28380
+ const title = watch.title ?? job.title;
28381
+ const prompt = [
28382
+ `[Harness job wakeup] Compute job ${job.jobId}${title ? ` ("${title}")` : ""} finished with status: ${job.status}.`,
28383
+ job.reason ? `Reason: ${job.reason}.` : null,
28384
+ `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.`
28385
+ ].filter(Boolean).join(" ");
28386
+ await this.threadService.sendPrompt(watch.threadId, { prompt });
28387
+ updateHarnessJobWatch(this.db, watch.id, {
28388
+ status: "delivered",
28389
+ lastError: null,
28390
+ deliveredAt: (/* @__PURE__ */ new Date()).toISOString()
28391
+ });
28392
+ }
28393
+ async acknowledgeNotifications() {
28394
+ let notifications2;
28395
+ try {
28396
+ notifications2 = await this.harnessClient.listUnreadNotifications();
28397
+ } catch (error) {
28398
+ this.logger.warn({ err: error }, "Harness wakeup inbox listing failed.");
28399
+ return;
28400
+ }
28401
+ for (const notification of notifications2) {
28402
+ if (!notification.from.includes("jobs")) {
28403
+ continue;
28404
+ }
28405
+ const jobId = JOB_ID_FROM_MESSAGE_PATTERN.exec(notification.message)?.[1] ?? null;
28406
+ if (!jobId) {
28407
+ continue;
28408
+ }
28409
+ const watch = getHarnessJobWatchByJobId(this.db, jobId);
28410
+ if (watch && watch.status === "pending") {
28411
+ continue;
28412
+ }
28413
+ try {
28414
+ await this.harnessClient.markNotificationRead(notification.id);
28415
+ } catch (error) {
28416
+ this.logger.warn(
28417
+ { notificationId: notification.id, err: error },
28418
+ "Harness wakeup notification acknowledgement failed."
28419
+ );
28420
+ }
28421
+ }
28422
+ }
28423
+ };
28424
+
27800
28425
  // src/worker-control-plane-sync.ts
27801
28426
  import { setTimeout as delay } from "timers/promises";
27802
28427
  var WorkerControlPlaneSyncError = class extends Error {
@@ -27932,7 +28557,7 @@ var WorkerControlPlaneSyncClient = class {
27932
28557
  };
27933
28558
 
27934
28559
  // src/auth.ts
27935
- import crypto3 from "crypto";
28560
+ import crypto4 from "crypto";
27936
28561
  var AUTH_COOKIE_NAME = "remote_codex_session";
27937
28562
  var AuthService = class {
27938
28563
  required;
@@ -28015,7 +28640,7 @@ var AuthService = class {
28015
28640
  const payload = {
28016
28641
  username,
28017
28642
  expiresAt: expiresAtMs,
28018
- nonce: crypto3.randomBytes(16).toString("base64url")
28643
+ nonce: crypto4.randomBytes(16).toString("base64url")
28019
28644
  };
28020
28645
  const payloadText = Buffer.from(JSON.stringify(payload), "utf8").toString(
28021
28646
  "base64url"
@@ -28063,7 +28688,7 @@ var AuthService = class {
28063
28688
  };
28064
28689
  }
28065
28690
  sign(payloadText) {
28066
- return crypto3.createHmac("sha256", this.secret ?? "").update(payloadText).digest("base64url");
28691
+ return crypto4.createHmac("sha256", this.secret ?? "").update(payloadText).digest("base64url");
28067
28692
  }
28068
28693
  };
28069
28694
  function unauthorizedPayload() {
@@ -28120,7 +28745,7 @@ function constantTimeEqual(left, right) {
28120
28745
  if (leftBuffer.length !== rightBuffer.length) {
28121
28746
  return false;
28122
28747
  }
28123
- return crypto3.timingSafeEqual(leftBuffer, rightBuffer);
28748
+ return crypto4.timingSafeEqual(leftBuffer, rightBuffer);
28124
28749
  }
28125
28750
 
28126
28751
  // src/relay-tunnel-client.ts
@@ -28287,6 +28912,14 @@ var RelayTunnelClient = class {
28287
28912
  var MAX_PROMPT_ATTACHMENTS2 = 10;
28288
28913
  var MAX_PROMPT_ATTACHMENT_BYTES2 = 25 * 1024 * 1024;
28289
28914
  var WORKER_AUTH_EXEMPT_PATHS = /* @__PURE__ */ new Set(["/healthz", "/readyz"]);
28915
+ var WORKER_AUTH_HOOK_PATH_PREFIX = "/api/hooks/";
28916
+ var WORKER_AUTH_LOOPBACK_PATHS = /* @__PURE__ */ new Set([
28917
+ "/api/harness/wakeup",
28918
+ "/api/harness/job-watches"
28919
+ ]);
28920
+ function isLoopbackAddress(ip) {
28921
+ return ip === "127.0.0.1" || ip === "::1" || ip === "::ffff:127.0.0.1";
28922
+ }
28290
28923
  var RELAY_FORWARD_HEADER = "x-remote-codex-relay-forwarded";
28291
28924
  var SUPERVISOR_LOG_REDACTION_PATHS = [
28292
28925
  "req.headers.authorization",
@@ -28403,7 +29036,8 @@ function buildApp(options = {}) {
28403
29036
  disableRequestLogging: config.disableRequestLogging
28404
29037
  });
28405
29038
  app.addHook("onRequest", async (request) => {
28406
- if (config.runtimeRole !== "worker" || !config.workerAuthToken || WORKER_AUTH_EXEMPT_PATHS.has(request.url.split("?")[0] ?? request.url)) {
29039
+ const requestPath = request.url.split("?")[0] ?? request.url;
29040
+ 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
29041
  return;
28408
29042
  }
28409
29043
  const headerToken = request.headers["x-remote-codex-worker-token"];
@@ -28433,6 +29067,13 @@ function buildApp(options = {}) {
28433
29067
  relaySocketBridge.handleMessage
28434
29068
  ) : null;
28435
29069
  relayTunnelClient?.validateConfig();
29070
+ const harnessWakeupService = new HarnessWakeupService(
29071
+ config,
29072
+ database.db,
29073
+ harnessClient,
29074
+ threadService,
29075
+ app.log
29076
+ );
28436
29077
  app.decorate("services", {
28437
29078
  config,
28438
29079
  database,
@@ -28445,6 +29086,7 @@ function buildApp(options = {}) {
28445
29086
  pluginRegistry,
28446
29087
  pluginService,
28447
29088
  harnessClient,
29089
+ harnessWakeupService,
28448
29090
  controlPlaneSyncClient,
28449
29091
  authService,
28450
29092
  relayTunnelClient,
@@ -28461,6 +29103,12 @@ function buildApp(options = {}) {
28461
29103
  if (requestPath === "/api/auth/login" || requestPath === "/api/auth/logout" || requestPath === "/api/auth/session") {
28462
29104
  return;
28463
29105
  }
29106
+ if (requestPath.startsWith(WORKER_AUTH_HOOK_PATH_PREFIX)) {
29107
+ return;
29108
+ }
29109
+ if (WORKER_AUTH_LOOPBACK_PATHS.has(requestPath) && isLoopbackAddress(request.ip)) {
29110
+ return;
29111
+ }
28464
29112
  if (config.mode === "relay" && request.headers[RELAY_FORWARD_HEADER] === "1") {
28465
29113
  return;
28466
29114
  }