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.
- package/apps/relay-server/dist/index.js +15 -2
- package/apps/supervisor-api/dist/{chunk-TQUNANIS.js → chunk-B3T3GIKQ.js} +683 -35
- package/apps/supervisor-api/dist/index.js +1 -1
- package/apps/supervisor-api/dist/worker-index.js +1 -1
- package/apps/supervisor-web/dist/assets/index-C5VKPUvg.js +6 -0
- package/apps/supervisor-web/dist/assets/index-D3I41SIH.css +1 -0
- package/apps/supervisor-web/dist/assets/thread-ui-q6mjcjXn.js +3614 -0
- package/apps/supervisor-web/dist/index.html +3 -3
- package/package.json +1 -1
- package/packages/db/migrations/0027_harness_job_watches.sql +24 -0
- package/packages/db/src/repositories.ts +123 -0
- package/packages/db/src/schema.ts +29 -0
- package/packages/shared/src/index.ts +2 -0
- package/apps/supervisor-web/dist/assets/index-Cp9GkemI.css +0 -1
- package/apps/supervisor-web/dist/assets/index-Dsq8QmDr.js +0 -6
- package/apps/supervisor-web/dist/assets/thread-ui-DldLSgqC.js +0 -3604
|
@@ -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
|
|
16668
|
+
startedAt,
|
|
16558
16669
|
status: turn.status,
|
|
16559
16670
|
error: turn.error?.message ?? null,
|
|
16560
16671
|
items: visibleRuntimeTurnItems(turn.items).map(
|
|
16561
|
-
(item, 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,
|
|
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,
|
|
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.
|
|
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
|
|
19243
|
-
if (
|
|
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
|
-
|
|
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
|
-
]
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
}
|