remote-codex 0.11.31 → 0.11.32
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/README.md +12 -4
- package/apps/relay-server/dist/index.js +2956 -164
- package/apps/supervisor-api/dist/index.js +457 -54
- package/apps/supervisor-web/dist/assets/index-CGHHTNkM.js +21 -0
- package/apps/supervisor-web/dist/assets/index-CdjTdnJt.css +1 -0
- package/apps/supervisor-web/dist/assets/thread-ui-B9eC2H4u.js +3677 -0
- package/apps/supervisor-web/dist/index.html +3 -3
- package/bin/remote-codex.mjs +6 -3
- package/package.json +1 -1
- package/packages/agent-runtime/src/types.ts +13 -0
- package/packages/claude/src/runtimeAdapter.test.ts +64 -0
- package/packages/claude/src/runtimeAdapter.ts +60 -0
- package/packages/codex/src/appServerManager.ts +16 -0
- package/packages/codex/src/runtimeAdapter.test.ts +44 -0
- package/packages/codex/src/runtimeAdapter.ts +66 -0
- package/packages/db/migrations/0029_thread_turn_delivery.sql +19 -0
- package/packages/db/src/repositories.ts +96 -1
- package/packages/db/src/schema.ts +20 -0
- package/packages/shared/src/index.ts +199 -6
- package/scripts/run-web-service.mjs +2 -2
- package/scripts/service-manager.mjs +2 -2
- package/apps/supervisor-web/dist/assets/index-BkwJP0KB.js +0 -6
- package/apps/supervisor-web/dist/assets/index-CJFMmjP5.css +0 -1
- package/apps/supervisor-web/dist/assets/thread-ui-BOqbjoiB.js +0 -3677
|
@@ -6382,6 +6382,7 @@ __export(schema_exports, {
|
|
|
6382
6382
|
threadGoals: () => threadGoals,
|
|
6383
6383
|
threadHistoryItems: () => threadHistoryItems,
|
|
6384
6384
|
threadPendingSteers: () => threadPendingSteers,
|
|
6385
|
+
threadPromptRequests: () => threadPromptRequests,
|
|
6385
6386
|
threadTurnMetadata: () => threadTurnMetadata,
|
|
6386
6387
|
threads: () => threads,
|
|
6387
6388
|
viewerSessions: () => viewerSessions,
|
|
@@ -6418,6 +6419,7 @@ var threads = sqliteTable("threads", {
|
|
|
6418
6419
|
fastBaseModel: text("fast_base_model"),
|
|
6419
6420
|
fastBaseReasoningEffort: text("fast_base_reasoning_effort"),
|
|
6420
6421
|
collaborationMode: text("collaboration_mode").notNull().default("default"),
|
|
6422
|
+
activeTurnCollaborationMode: text("active_turn_collaboration_mode"),
|
|
6421
6423
|
approvalMode: text("approval_mode"),
|
|
6422
6424
|
sandboxMode: text("sandbox_mode"),
|
|
6423
6425
|
status: text("status"),
|
|
@@ -6493,9 +6495,27 @@ var threadPendingSteers = sqliteTable("thread_pending_steers", {
|
|
|
6493
6495
|
clientRequestId: text("client_request_id"),
|
|
6494
6496
|
displayPrompt: text("display_prompt").notNull(),
|
|
6495
6497
|
submittedPrompt: text("submitted_prompt").notNull(),
|
|
6498
|
+
delivery: text("delivery").notNull().default("steer"),
|
|
6499
|
+
turnConfigJson: text("turn_config_json"),
|
|
6496
6500
|
createdAt: text("created_at").notNull(),
|
|
6497
6501
|
updatedAt: text("updated_at").notNull()
|
|
6498
6502
|
});
|
|
6503
|
+
var threadPromptRequests = sqliteTable(
|
|
6504
|
+
"thread_prompt_requests",
|
|
6505
|
+
{
|
|
6506
|
+
id: text("id").primaryKey(),
|
|
6507
|
+
threadId: text("thread_id").notNull(),
|
|
6508
|
+
clientRequestId: text("client_request_id").notNull(),
|
|
6509
|
+
status: text("status").notNull(),
|
|
6510
|
+
createdAt: text("created_at").notNull(),
|
|
6511
|
+
updatedAt: text("updated_at").notNull()
|
|
6512
|
+
},
|
|
6513
|
+
(table) => ({
|
|
6514
|
+
threadClientRequestUnique: uniqueIndex(
|
|
6515
|
+
"thread_prompt_requests_thread_client_request_idx"
|
|
6516
|
+
).on(table.threadId, table.clientRequestId)
|
|
6517
|
+
})
|
|
6518
|
+
);
|
|
6499
6519
|
var threadHistoryItems = sqliteTable(
|
|
6500
6520
|
"thread_history_items",
|
|
6501
6521
|
{
|
|
@@ -6729,6 +6749,7 @@ function createThreadRecord(db, input) {
|
|
|
6729
6749
|
fastBaseModel: input.fastBaseModel ?? null,
|
|
6730
6750
|
fastBaseReasoningEffort: input.fastBaseReasoningEffort ?? null,
|
|
6731
6751
|
collaborationMode: input.collaborationMode ?? "default",
|
|
6752
|
+
activeTurnCollaborationMode: input.activeTurnCollaborationMode ?? null,
|
|
6732
6753
|
approvalMode: input.approvalMode,
|
|
6733
6754
|
sandboxMode: input.sandboxMode ?? null,
|
|
6734
6755
|
status: "idle",
|
|
@@ -6868,6 +6889,8 @@ function createThreadPendingSteerRecord(db, input) {
|
|
|
6868
6889
|
clientRequestId: input.clientRequestId ?? null,
|
|
6869
6890
|
displayPrompt: input.displayPrompt,
|
|
6870
6891
|
submittedPrompt: input.submittedPrompt,
|
|
6892
|
+
delivery: input.delivery ?? "steer",
|
|
6893
|
+
turnConfigJson: input.turnConfigJson ?? null,
|
|
6871
6894
|
createdAt: now,
|
|
6872
6895
|
updatedAt: now
|
|
6873
6896
|
};
|
|
@@ -6880,6 +6903,48 @@ function deleteThreadPendingSteerRecordById(db, id) {
|
|
|
6880
6903
|
function deleteThreadPendingSteerRecordsByThreadId(db, threadId) {
|
|
6881
6904
|
db.delete(threadPendingSteers).where(eq(threadPendingSteers.threadId, threadId)).run();
|
|
6882
6905
|
}
|
|
6906
|
+
function getThreadPromptRequestRecord(db, threadId, clientRequestId) {
|
|
6907
|
+
return db.select().from(threadPromptRequests).where(
|
|
6908
|
+
and(
|
|
6909
|
+
eq(threadPromptRequests.threadId, threadId),
|
|
6910
|
+
eq(threadPromptRequests.clientRequestId, clientRequestId)
|
|
6911
|
+
)
|
|
6912
|
+
).get();
|
|
6913
|
+
}
|
|
6914
|
+
function createThreadPromptRequestRecord(db, threadId, clientRequestId) {
|
|
6915
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6916
|
+
db.insert(threadPromptRequests).values({
|
|
6917
|
+
id: randomUUID(),
|
|
6918
|
+
threadId,
|
|
6919
|
+
clientRequestId,
|
|
6920
|
+
status: "processing",
|
|
6921
|
+
createdAt: now,
|
|
6922
|
+
updatedAt: now
|
|
6923
|
+
}).onConflictDoNothing().run();
|
|
6924
|
+
return getThreadPromptRequestRecord(db, threadId, clientRequestId);
|
|
6925
|
+
}
|
|
6926
|
+
function markThreadPromptRequestAccepted(db, threadId, clientRequestId) {
|
|
6927
|
+
db.update(threadPromptRequests).set({ status: "accepted", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
6928
|
+
and(
|
|
6929
|
+
eq(threadPromptRequests.threadId, threadId),
|
|
6930
|
+
eq(threadPromptRequests.clientRequestId, clientRequestId)
|
|
6931
|
+
)
|
|
6932
|
+
).run();
|
|
6933
|
+
}
|
|
6934
|
+
function deleteExpiredThreadPromptRequestRecords(db, cutoff) {
|
|
6935
|
+
return db.delete(threadPromptRequests).where(lt(threadPromptRequests.updatedAt, cutoff)).run();
|
|
6936
|
+
}
|
|
6937
|
+
function deleteThreadPromptRequestRecord(db, threadId, clientRequestId) {
|
|
6938
|
+
db.delete(threadPromptRequests).where(
|
|
6939
|
+
and(
|
|
6940
|
+
eq(threadPromptRequests.threadId, threadId),
|
|
6941
|
+
eq(threadPromptRequests.clientRequestId, clientRequestId)
|
|
6942
|
+
)
|
|
6943
|
+
).run();
|
|
6944
|
+
}
|
|
6945
|
+
function deleteThreadPromptRequestRecordsByThreadId(db, threadId) {
|
|
6946
|
+
db.delete(threadPromptRequests).where(eq(threadPromptRequests.threadId, threadId)).run();
|
|
6947
|
+
}
|
|
6883
6948
|
function listThreadActivityNotesByThreadId(db, threadId) {
|
|
6884
6949
|
return db.select().from(threadActivityNotes).where(eq(threadActivityNotes.threadId, threadId)).orderBy(threadActivityNotes.createdAt).all();
|
|
6885
6950
|
}
|
|
@@ -7863,6 +7928,14 @@ var CodexAppServerManager = class extends EventEmitter3 {
|
|
|
7863
7928
|
});
|
|
7864
7929
|
return response.data.map(mapModel);
|
|
7865
7930
|
}
|
|
7931
|
+
async readAccount() {
|
|
7932
|
+
await this.ensureReady();
|
|
7933
|
+
return this.client.request("account/read", { refreshToken: false });
|
|
7934
|
+
}
|
|
7935
|
+
async readAccountRateLimits() {
|
|
7936
|
+
await this.ensureReady();
|
|
7937
|
+
return this.client.request("account/rateLimits/read", null);
|
|
7938
|
+
}
|
|
7866
7939
|
async listThreads() {
|
|
7867
7940
|
await this.ensureReady();
|
|
7868
7941
|
const response = await this.client.request("thread/list", {
|
|
@@ -10550,6 +10623,16 @@ function mapCodexNotification(event) {
|
|
|
10550
10623
|
return null;
|
|
10551
10624
|
}
|
|
10552
10625
|
}
|
|
10626
|
+
function formatRateLimitWindowLabel(durationMinutes, fallback) {
|
|
10627
|
+
if (durationMinutes === null) return fallback;
|
|
10628
|
+
if (durationMinutes % (60 * 24) === 0) {
|
|
10629
|
+
return `${durationMinutes / (60 * 24)}d`;
|
|
10630
|
+
}
|
|
10631
|
+
if (durationMinutes % 60 === 0) {
|
|
10632
|
+
return `${durationMinutes / 60}h`;
|
|
10633
|
+
}
|
|
10634
|
+
return `${durationMinutes}m`;
|
|
10635
|
+
}
|
|
10553
10636
|
function mapCodexRuntimeError(error) {
|
|
10554
10637
|
if (error instanceof AgentRuntimeError) {
|
|
10555
10638
|
throw error;
|
|
@@ -10667,6 +10750,51 @@ var CodexRuntimeAdapter = class extends EventEmitter4 {
|
|
|
10667
10750
|
getStatus() {
|
|
10668
10751
|
return mapStatus(this.manager.getStatus());
|
|
10669
10752
|
}
|
|
10753
|
+
async getSubscriptionUsage() {
|
|
10754
|
+
const account = await codexRuntimeCall(() => this.manager.readAccount());
|
|
10755
|
+
if (account.account?.type === "apiKey") {
|
|
10756
|
+
return {
|
|
10757
|
+
provider: "codex",
|
|
10758
|
+
authKind: "apiKey",
|
|
10759
|
+
observedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10760
|
+
stale: false,
|
|
10761
|
+
windows: []
|
|
10762
|
+
};
|
|
10763
|
+
}
|
|
10764
|
+
if (account.account?.type !== "chatgpt") {
|
|
10765
|
+
return null;
|
|
10766
|
+
}
|
|
10767
|
+
const response = await codexRuntimeCall(
|
|
10768
|
+
() => this.manager.readAccountRateLimits()
|
|
10769
|
+
);
|
|
10770
|
+
const buckets = response.rateLimitsByLimitId;
|
|
10771
|
+
const snapshot = (buckets && (buckets.codex ?? Object.values(buckets)[0])) ?? response.rateLimits;
|
|
10772
|
+
const record = snapshot && typeof snapshot === "object" ? snapshot : {};
|
|
10773
|
+
const windows = ["primary", "secondary"].flatMap((id) => {
|
|
10774
|
+
const value = record[id];
|
|
10775
|
+
if (!value || typeof value !== "object") return [];
|
|
10776
|
+
const window = value;
|
|
10777
|
+
const usedPercent = Number(window.usedPercent);
|
|
10778
|
+
if (!Number.isFinite(usedPercent)) return [];
|
|
10779
|
+
const duration = Number(window.windowDurationMins);
|
|
10780
|
+
const durationMinutes = Number.isFinite(duration) ? duration : null;
|
|
10781
|
+
const resetsAt = Number(window.resetsAt);
|
|
10782
|
+
return [{
|
|
10783
|
+
id,
|
|
10784
|
+
durationMinutes,
|
|
10785
|
+
label: formatRateLimitWindowLabel(durationMinutes, id),
|
|
10786
|
+
usedPercent: Math.max(0, Math.min(100, usedPercent)),
|
|
10787
|
+
resetsAt: Number.isFinite(resetsAt) ? new Date(resetsAt * 1e3).toISOString() : null
|
|
10788
|
+
}];
|
|
10789
|
+
});
|
|
10790
|
+
return {
|
|
10791
|
+
provider: "codex",
|
|
10792
|
+
authKind: "subscription",
|
|
10793
|
+
observedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10794
|
+
stale: false,
|
|
10795
|
+
windows
|
|
10796
|
+
};
|
|
10797
|
+
}
|
|
10670
10798
|
start() {
|
|
10671
10799
|
return codexRuntimeCall(() => this.manager.start());
|
|
10672
10800
|
}
|
|
@@ -12373,11 +12501,44 @@ var ClaudeRuntimeAdapter = class extends EventEmitter5 {
|
|
|
12373
12501
|
sessionModels = /* @__PURE__ */ new Map();
|
|
12374
12502
|
sessionApprovalModes = /* @__PURE__ */ new Map();
|
|
12375
12503
|
liveUserPrompts = /* @__PURE__ */ new Map();
|
|
12504
|
+
subscriptionUsageWindows = /* @__PURE__ */ new Map();
|
|
12505
|
+
subscriptionUsageObservedAt = null;
|
|
12376
12506
|
clientApp;
|
|
12377
12507
|
sdkLoadError = null;
|
|
12378
12508
|
getStatus() {
|
|
12379
12509
|
return { ...this.status };
|
|
12380
12510
|
}
|
|
12511
|
+
async getSubscriptionUsage() {
|
|
12512
|
+
const observedAt = this.subscriptionUsageObservedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
12513
|
+
const windows = [...this.subscriptionUsageWindows.entries()].map(([id, window]) => ({
|
|
12514
|
+
id,
|
|
12515
|
+
durationMinutes: id === "five_hour" ? 300 : 10080,
|
|
12516
|
+
label: id === "five_hour" ? "5h" : "7d",
|
|
12517
|
+
usedPercent: window.usedPercent,
|
|
12518
|
+
resetsAt: window.resetsAt
|
|
12519
|
+
}));
|
|
12520
|
+
return {
|
|
12521
|
+
provider: "claude",
|
|
12522
|
+
authKind: windows.length > 0 ? "subscription" : "unknown",
|
|
12523
|
+
observedAt,
|
|
12524
|
+
stale: false,
|
|
12525
|
+
windows
|
|
12526
|
+
};
|
|
12527
|
+
}
|
|
12528
|
+
captureRateLimit(message) {
|
|
12529
|
+
if (message.type !== "rate_limit_event") {
|
|
12530
|
+
return;
|
|
12531
|
+
}
|
|
12532
|
+
const info = message.rate_limit_info;
|
|
12533
|
+
if (!info || info.rateLimitType !== "five_hour" && info.rateLimitType !== "seven_day" || typeof info.utilization !== "number" || !Number.isFinite(info.utilization)) {
|
|
12534
|
+
return;
|
|
12535
|
+
}
|
|
12536
|
+
this.subscriptionUsageWindows.set(info.rateLimitType, {
|
|
12537
|
+
usedPercent: Math.max(0, Math.min(100, info.utilization * 100)),
|
|
12538
|
+
resetsAt: typeof info.resetsAt === "number" ? new Date(info.resetsAt * 1e3).toISOString() : null
|
|
12539
|
+
});
|
|
12540
|
+
this.subscriptionUsageObservedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
12541
|
+
}
|
|
12381
12542
|
updateToolboxItemsFromSystemInit(message) {
|
|
12382
12543
|
this.managementSchema.toolboxItems = buildClaudeToolboxItems(
|
|
12383
12544
|
normalizeClaudeSlashCommands(message.slash_commands)
|
|
@@ -12525,6 +12686,7 @@ var ClaudeRuntimeAdapter = class extends EventEmitter5 {
|
|
|
12525
12686
|
try {
|
|
12526
12687
|
for await (const message of query) {
|
|
12527
12688
|
rawMessages.push(message);
|
|
12689
|
+
this.captureRateLimit(message);
|
|
12528
12690
|
if (message.type === "system" && message.subtype === "init") {
|
|
12529
12691
|
this.updateToolboxItemsFromSystemInit(message);
|
|
12530
12692
|
const sessionId = message.session_id;
|
|
@@ -12772,6 +12934,7 @@ var ClaudeRuntimeAdapter = class extends EventEmitter5 {
|
|
|
12772
12934
|
try {
|
|
12773
12935
|
for await (const message of state.query) {
|
|
12774
12936
|
rawMessages.push(message);
|
|
12937
|
+
this.captureRateLimit(message);
|
|
12775
12938
|
this.consumeMessage(state, message);
|
|
12776
12939
|
const status = queryResultStatus(message);
|
|
12777
12940
|
if (status) {
|
|
@@ -16074,6 +16237,7 @@ var ThreadAuxiliaryStateStore = class {
|
|
|
16074
16237
|
clientRequestId: record.clientRequestId ?? null,
|
|
16075
16238
|
turnId: record.turnId,
|
|
16076
16239
|
prompt: record.displayPrompt,
|
|
16240
|
+
delivery: record.delivery === "continuation" ? "continuation" : "steer",
|
|
16077
16241
|
createdAt: record.createdAt
|
|
16078
16242
|
}));
|
|
16079
16243
|
}
|
|
@@ -16090,6 +16254,11 @@ var ThreadAuxiliaryStateStore = class {
|
|
|
16090
16254
|
hasPendingSteersForTurn(localThreadId, turnId) {
|
|
16091
16255
|
return this.listPendingSteerRecordsForTurn(localThreadId, turnId).length > 0;
|
|
16092
16256
|
}
|
|
16257
|
+
hasQueuedContinuationsForTurn(localThreadId, turnId) {
|
|
16258
|
+
return this.listPendingSteerRecordsForTurn(localThreadId, turnId).some(
|
|
16259
|
+
(record) => record.delivery === "continuation"
|
|
16260
|
+
);
|
|
16261
|
+
}
|
|
16093
16262
|
deletePendingSteerRecord(localThreadId, id, turnId) {
|
|
16094
16263
|
deleteThreadPendingSteerRecordById(this.db, id);
|
|
16095
16264
|
this.callbacks.invalidateThreadDetailCache(localThreadId);
|
|
@@ -17889,6 +18058,7 @@ var ThreadRuntimeEventProjector = class {
|
|
|
17889
18058
|
const turnItems = event.turn.items;
|
|
17890
18059
|
updateThreadRecord(db, record.id, {
|
|
17891
18060
|
providerTurnId: null,
|
|
18061
|
+
activeTurnCollaborationMode: null,
|
|
17892
18062
|
status: event.turn.status === "failed" ? "failed" : event.turn.status === "interrupted" ? "interrupted" : "idle",
|
|
17893
18063
|
lastError: event.turn.error?.message ?? null,
|
|
17894
18064
|
lastTurnCompletedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -17910,7 +18080,9 @@ var ThreadRuntimeEventProjector = class {
|
|
|
17910
18080
|
}
|
|
17911
18081
|
}
|
|
17912
18082
|
callbacks.clearTerminalPendingRequests(record.id, true);
|
|
17913
|
-
if (event.turn.status === "completed" &&
|
|
18083
|
+
if (event.turn.status === "completed" && !preservePendingSteers && callbacks.normalizeCollaborationMode(
|
|
18084
|
+
record.activeTurnCollaborationMode ?? record.collaborationMode
|
|
18085
|
+
) === "plan" && turnItems.some((item) => item.kind === "plan") && !callbacks.hasPendingAskUserQuestion(record.id)) {
|
|
17914
18086
|
callbacks.createPendingPlanDecisionRequest(record.id, turnId, true);
|
|
17915
18087
|
} else {
|
|
17916
18088
|
callbacks.dismissPlanDecisionTurn(record.id);
|
|
@@ -17942,6 +18114,7 @@ var ThreadRuntimeEventProjector = class {
|
|
|
17942
18114
|
const turnId = liveState.displayTurnIdForRuntimeTurn(record.id, event.providerTurnId) ?? event.providerTurnId;
|
|
17943
18115
|
updateThreadRecord(db, record.id, {
|
|
17944
18116
|
providerTurnId: null,
|
|
18117
|
+
activeTurnCollaborationMode: null,
|
|
17945
18118
|
status: "failed",
|
|
17946
18119
|
lastError: event.error,
|
|
17947
18120
|
lastTurnCompletedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -19274,6 +19447,7 @@ var ThreadPromptTurnCoordinator = class {
|
|
|
19274
19447
|
model: input.effectiveModel,
|
|
19275
19448
|
reasoningEffort: input.normalizedReasoning,
|
|
19276
19449
|
collaborationMode: input.collaborationMode,
|
|
19450
|
+
activeTurnCollaborationMode: input.collaborationMode,
|
|
19277
19451
|
sandboxMode: input.sandboxMode
|
|
19278
19452
|
};
|
|
19279
19453
|
if (isAutoGeneratedTitle(record.title)) {
|
|
@@ -19396,7 +19570,8 @@ var ThreadPromptTurnCoordinator = class {
|
|
|
19396
19570
|
turnId: steerTurnId,
|
|
19397
19571
|
clientRequestId: input.clientRequestId,
|
|
19398
19572
|
displayPrompt: input.displayPrompt,
|
|
19399
|
-
submittedPrompt: input.prompt
|
|
19573
|
+
submittedPrompt: input.prompt,
|
|
19574
|
+
delivery: "steer"
|
|
19400
19575
|
});
|
|
19401
19576
|
this.callbacks.invalidateThreadDetailCache(localThreadId);
|
|
19402
19577
|
this.callbacks.emitThreadUpdated(localThreadId, {
|
|
@@ -19445,7 +19620,16 @@ var ThreadPromptTurnCoordinator = class {
|
|
|
19445
19620
|
turnId: displayTurnId,
|
|
19446
19621
|
clientRequestId: input.clientRequestId,
|
|
19447
19622
|
displayPrompt: input.displayPrompt,
|
|
19448
|
-
submittedPrompt: input.prompt
|
|
19623
|
+
submittedPrompt: input.prompt,
|
|
19624
|
+
delivery: "continuation",
|
|
19625
|
+
turnConfigJson: JSON.stringify({
|
|
19626
|
+
effectiveModel: input.effectiveModel,
|
|
19627
|
+
normalizedReasoning: input.normalizedReasoning,
|
|
19628
|
+
collaborationMode: input.collaborationMode,
|
|
19629
|
+
sandboxMode: input.sandboxMode,
|
|
19630
|
+
performanceMode: input.performanceMode,
|
|
19631
|
+
startNewTurn: input.collaborationMode !== (record.activeTurnCollaborationMode === "plan" ? "plan" : "default")
|
|
19632
|
+
})
|
|
19449
19633
|
});
|
|
19450
19634
|
this.callbacks.invalidateThreadDetailCache(localThreadId);
|
|
19451
19635
|
this.callbacks.emitThreadUpdated(localThreadId, {
|
|
@@ -19651,7 +19835,7 @@ var ThreadSessionCoordinator = class {
|
|
|
19651
19835
|
if (!isRemoteThreadBootstrapError(error)) {
|
|
19652
19836
|
throw error;
|
|
19653
19837
|
}
|
|
19654
|
-
return { status: "bootstrap_unavailable" };
|
|
19838
|
+
return { status: "bootstrap_unavailable", error };
|
|
19655
19839
|
}
|
|
19656
19840
|
const effectiveModel = input.resumeInput.model ?? input.currentModel ?? response.model ?? null;
|
|
19657
19841
|
const resumedReasoning = this.providerRuntime.normalizeReasoningForModel(
|
|
@@ -19827,6 +20011,37 @@ var ThreadSessionLifecycleCoordinator = class {
|
|
|
19827
20011
|
fastMode: record.fastMode
|
|
19828
20012
|
});
|
|
19829
20013
|
if (resumed.status === "bootstrap_unavailable") {
|
|
20014
|
+
if (!this.canRecreateUnmaterializedThread(record, resumed.error)) {
|
|
20015
|
+
return;
|
|
20016
|
+
}
|
|
20017
|
+
const workspace = getWorkspaceRecordById(this.db, record.workspaceId);
|
|
20018
|
+
const model = input.model ?? record.model;
|
|
20019
|
+
if (!workspace || !model) {
|
|
20020
|
+
return;
|
|
20021
|
+
}
|
|
20022
|
+
const recreated = await this.sessionCoordinator.startThreadSession({
|
|
20023
|
+
workspacePath: workspace.absPath,
|
|
20024
|
+
threadInput: {
|
|
20025
|
+
workspaceId: workspace.id,
|
|
20026
|
+
title: record.title,
|
|
20027
|
+
provider: record.provider,
|
|
20028
|
+
model,
|
|
20029
|
+
reasoningEffort: record.reasoningEffort,
|
|
20030
|
+
approvalMode: record.approvalMode ?? "yolo"
|
|
20031
|
+
},
|
|
20032
|
+
defaultTitle: record.title
|
|
20033
|
+
});
|
|
20034
|
+
updateThreadRecord(this.db, record.id, {
|
|
20035
|
+
...buildThreadPatch(
|
|
20036
|
+
recreated.response.session,
|
|
20037
|
+
model,
|
|
20038
|
+
recreated.response.reasoningEffort ?? recreated.reasoningEffort
|
|
20039
|
+
),
|
|
20040
|
+
providerSessionId: recreated.response.providerSessionId,
|
|
20041
|
+
sandboxMode: recreated.sandboxMode,
|
|
20042
|
+
isConnected: true
|
|
20043
|
+
});
|
|
20044
|
+
this.callbacks.invalidateThreadDetailCache(localThreadId);
|
|
19830
20045
|
return;
|
|
19831
20046
|
}
|
|
19832
20047
|
updateThreadRecord(
|
|
@@ -19850,6 +20065,9 @@ var ThreadSessionLifecycleCoordinator = class {
|
|
|
19850
20065
|
}
|
|
19851
20066
|
this.callbacks.invalidateThreadDetailCache(localThreadId);
|
|
19852
20067
|
}
|
|
20068
|
+
canRecreateUnmaterializedThread(record, error) {
|
|
20069
|
+
return record.provider === "codex" && record.source === "supervisor" && listThreadTurnMetadataByThreadId(this.db, record.id).length === 0 && error instanceof AgentRuntimeError && error.provider === "codex" && error.code === "remote_error" && /thread not loaded|no rollout found/i.test(error.message);
|
|
20070
|
+
}
|
|
19853
20071
|
disconnectThread(localThreadId) {
|
|
19854
20072
|
const record = getThreadRecordById(this.db, localThreadId);
|
|
19855
20073
|
if (!record) {
|
|
@@ -20049,6 +20267,7 @@ var ThreadDeletionCoordinator = class {
|
|
|
20049
20267
|
deleteThreadGoalRecordsByThreadId(this.db, localThreadId);
|
|
20050
20268
|
deleteThreadHistoryItemRecordsByThreadId(this.db, localThreadId);
|
|
20051
20269
|
deleteThreadPendingSteerRecordsByThreadId(this.db, localThreadId);
|
|
20270
|
+
deleteThreadPromptRequestRecordsByThreadId(this.db, localThreadId);
|
|
20052
20271
|
deleteThreadTurnMetadataByThreadId(this.db, localThreadId);
|
|
20053
20272
|
deleteThreadRecord(this.db, localThreadId);
|
|
20054
20273
|
return { id: localThreadId };
|
|
@@ -21974,7 +22193,7 @@ var ThreadService = class {
|
|
|
21974
22193
|
normalizeCollaborationMode,
|
|
21975
22194
|
normalizeReasoningEffort: normalizeReasoningEffort2,
|
|
21976
22195
|
normalizeThreadGoalStatusForThread: (goal, record) => this.goalCoordinator.normalizeThreadGoalStatusForThread(goal, record),
|
|
21977
|
-
shouldPreservePendingSteersForCompletedTurn: (record, turnId) =>
|
|
22196
|
+
shouldPreservePendingSteersForCompletedTurn: (record, turnId) => this.shouldPreserveCompletedPendingSteer(record.id, turnId),
|
|
21978
22197
|
scheduleQueuedContinuationDrain: (localThreadId, turnId) => this.scheduleQueuedContinuationDrain(localThreadId, turnId),
|
|
21979
22198
|
persistLiveHistoryItem: (localThreadId, turnId, item) => this.historyPersistence.persistLiveHistoryItem(localThreadId, turnId, item),
|
|
21980
22199
|
persistFinalTurnOrderingHints: (localThreadId, turnId, items) => this.historyPersistence.persistFinalTurnOrderingHints(localThreadId, turnId, items),
|
|
@@ -22008,6 +22227,7 @@ var ThreadService = class {
|
|
|
22008
22227
|
config;
|
|
22009
22228
|
liveState = new ThreadLiveStateStore();
|
|
22010
22229
|
queuedContinuationDrains = /* @__PURE__ */ new Set();
|
|
22230
|
+
promptRequestsInFlight = /* @__PURE__ */ new Map();
|
|
22011
22231
|
detailAssembler;
|
|
22012
22232
|
usageAccounting;
|
|
22013
22233
|
requestCoordinator;
|
|
@@ -22326,6 +22546,57 @@ var ThreadService = class {
|
|
|
22326
22546
|
return this.getThreadDetail(localThreadId);
|
|
22327
22547
|
}
|
|
22328
22548
|
async sendPrompt(localThreadId, input, options = {}) {
|
|
22549
|
+
const clientRequestId = input.clientRequestId?.trim();
|
|
22550
|
+
if (!clientRequestId) {
|
|
22551
|
+
return this.sendPromptOnce(localThreadId, input, options);
|
|
22552
|
+
}
|
|
22553
|
+
const requestKey = `${localThreadId}:${clientRequestId}`;
|
|
22554
|
+
const activeRequest = this.promptRequestsInFlight.get(requestKey);
|
|
22555
|
+
if (activeRequest) {
|
|
22556
|
+
return activeRequest;
|
|
22557
|
+
}
|
|
22558
|
+
const request = this.sendPromptIdempotently(
|
|
22559
|
+
localThreadId,
|
|
22560
|
+
{ ...input, clientRequestId },
|
|
22561
|
+
options
|
|
22562
|
+
);
|
|
22563
|
+
this.promptRequestsInFlight.set(requestKey, request);
|
|
22564
|
+
try {
|
|
22565
|
+
return await request;
|
|
22566
|
+
} finally {
|
|
22567
|
+
if (this.promptRequestsInFlight.get(requestKey) === request) {
|
|
22568
|
+
this.promptRequestsInFlight.delete(requestKey);
|
|
22569
|
+
}
|
|
22570
|
+
}
|
|
22571
|
+
}
|
|
22572
|
+
async sendPromptIdempotently(localThreadId, input, options) {
|
|
22573
|
+
deleteExpiredThreadPromptRequestRecords(
|
|
22574
|
+
this.db,
|
|
22575
|
+
new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3).toISOString()
|
|
22576
|
+
);
|
|
22577
|
+
const existing = getThreadPromptRequestRecord(
|
|
22578
|
+
this.db,
|
|
22579
|
+
localThreadId,
|
|
22580
|
+
input.clientRequestId
|
|
22581
|
+
);
|
|
22582
|
+
if (existing) {
|
|
22583
|
+
const record = this.requireThreadRecord(localThreadId);
|
|
22584
|
+
return this.toThreadDto(
|
|
22585
|
+
record,
|
|
22586
|
+
await this.listLoadedProviderSessionIds(record.provider)
|
|
22587
|
+
);
|
|
22588
|
+
}
|
|
22589
|
+
createThreadPromptRequestRecord(this.db, localThreadId, input.clientRequestId);
|
|
22590
|
+
try {
|
|
22591
|
+
const result = await this.sendPromptOnce(localThreadId, input, options);
|
|
22592
|
+
markThreadPromptRequestAccepted(this.db, localThreadId, input.clientRequestId);
|
|
22593
|
+
return result;
|
|
22594
|
+
} catch (error) {
|
|
22595
|
+
deleteThreadPromptRequestRecord(this.db, localThreadId, input.clientRequestId);
|
|
22596
|
+
throw error;
|
|
22597
|
+
}
|
|
22598
|
+
}
|
|
22599
|
+
async sendPromptOnce(localThreadId, input, options = {}) {
|
|
22329
22600
|
let record = this.requireThreadRecord(localThreadId);
|
|
22330
22601
|
await this.importCoordinator.assertImportedThreadReadyForPrompt({
|
|
22331
22602
|
source: record.source,
|
|
@@ -22391,7 +22662,10 @@ var ThreadService = class {
|
|
|
22391
22662
|
};
|
|
22392
22663
|
const hasActiveProviderTurn = Boolean(record.providerTurnId) && (record.status === "running" || !turnConfig.supportsRunningTurnInput && !record.lastTurnCompletedAt) && record.status !== "failed" && record.status !== "interrupted";
|
|
22393
22664
|
if (hasActiveProviderTurn && record.providerTurnId) {
|
|
22394
|
-
|
|
22665
|
+
const activeTurnCollaborationMode = normalizeCollaborationMode(
|
|
22666
|
+
record.activeTurnCollaborationMode ?? record.collaborationMode
|
|
22667
|
+
);
|
|
22668
|
+
if (!turnConfig.supportsRunningTurnInput || activeTurnCollaborationMode !== turnConfig.collaborationMode) {
|
|
22395
22669
|
return this.promptTurnCoordinator.queueContinuationPromptTurn(localThreadId, {
|
|
22396
22670
|
...connectedRecord,
|
|
22397
22671
|
providerTurnId: record.providerTurnId
|
|
@@ -22735,7 +23009,7 @@ var ThreadService = class {
|
|
|
22735
23009
|
if (!record) {
|
|
22736
23010
|
return false;
|
|
22737
23011
|
}
|
|
22738
|
-
return
|
|
23012
|
+
return this.auxiliaryState.hasQueuedContinuationsForTurn(localThreadId, turnId);
|
|
22739
23013
|
}
|
|
22740
23014
|
shouldPreserveMissingPendingSteer(localThreadId, turnId) {
|
|
22741
23015
|
const record = getThreadRecordById(this.db, localThreadId);
|
|
@@ -22745,14 +23019,11 @@ var ThreadService = class {
|
|
|
22745
23019
|
if (record.status === "failed" || record.status === "interrupted") {
|
|
22746
23020
|
return false;
|
|
22747
23021
|
}
|
|
22748
|
-
if (this.runtimeSupportsLiveRunningTurnInput(record.provider)) {
|
|
22749
|
-
return false;
|
|
22750
|
-
}
|
|
22751
23022
|
const activeDisplayTurnId = this.liveState.displayTurnIdForRuntimeTurn(
|
|
22752
23023
|
localThreadId,
|
|
22753
23024
|
record.providerTurnId
|
|
22754
23025
|
);
|
|
22755
|
-
return record.providerTurnId === turnId || activeDisplayTurnId === turnId;
|
|
23026
|
+
return (record.providerTurnId === turnId || activeDisplayTurnId === turnId) && this.auxiliaryState.hasQueuedContinuationsForTurn(localThreadId, turnId);
|
|
22756
23027
|
}
|
|
22757
23028
|
scheduleQueuedContinuationDrain(localThreadId, turnId) {
|
|
22758
23029
|
const key = `${localThreadId}:${turnId}`;
|
|
@@ -22776,7 +23047,7 @@ var ThreadService = class {
|
|
|
22776
23047
|
const pending = this.auxiliaryState.listPendingSteerRecordsForTurn(
|
|
22777
23048
|
localThreadId,
|
|
22778
23049
|
turnId
|
|
22779
|
-
)
|
|
23050
|
+
).find((entry) => entry.delivery === "continuation");
|
|
22780
23051
|
if (!pending) {
|
|
22781
23052
|
return;
|
|
22782
23053
|
}
|
|
@@ -22795,7 +23066,8 @@ var ThreadService = class {
|
|
|
22795
23066
|
const developerInstructions = combineDeveloperInstructions([
|
|
22796
23067
|
pluginDeveloperInstructions(this.pluginService)
|
|
22797
23068
|
]);
|
|
22798
|
-
const
|
|
23069
|
+
const queuedConfig = parseQueuedTurnConfig(pending.turnConfigJson);
|
|
23070
|
+
const turnConfig = queuedConfig ?? await this.sessionCoordinator.resolvePromptTurnConfig({
|
|
22799
23071
|
provider: record.provider,
|
|
22800
23072
|
currentModel: record.model,
|
|
22801
23073
|
currentReasoningEffort: record.reasoningEffort,
|
|
@@ -22805,14 +23077,16 @@ var ThreadService = class {
|
|
|
22805
23077
|
approvalMode: record.approvalMode ?? "yolo",
|
|
22806
23078
|
promptInput: {}
|
|
22807
23079
|
});
|
|
22808
|
-
|
|
22809
|
-
|
|
22810
|
-
|
|
22811
|
-
|
|
22812
|
-
|
|
22813
|
-
|
|
22814
|
-
|
|
22815
|
-
|
|
23080
|
+
if (!queuedConfig?.startNewTurn) {
|
|
23081
|
+
const queuedUserItemId = `queued-continuation:${pending.id}:user`;
|
|
23082
|
+
this.historyPersistence.persistProjectedHistoryItem(localThreadId, turnId, {
|
|
23083
|
+
id: queuedUserItemId,
|
|
23084
|
+
kind: "userMessage",
|
|
23085
|
+
text: pending.displayPrompt,
|
|
23086
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
23087
|
+
sequence: this.liveState.recordTurnItemOrder(localThreadId, turnId, queuedUserItemId)
|
|
23088
|
+
});
|
|
23089
|
+
}
|
|
22816
23090
|
await this.promptTurnCoordinator.startPromptTurn(localThreadId, {
|
|
22817
23091
|
...record,
|
|
22818
23092
|
providerSessionId
|
|
@@ -22826,8 +23100,7 @@ var ThreadService = class {
|
|
|
22826
23100
|
sandboxMode: turnConfig.sandboxMode,
|
|
22827
23101
|
performanceMode: turnConfig.performanceMode,
|
|
22828
23102
|
workspacePath: workspace.absPath,
|
|
22829
|
-
hidden: true,
|
|
22830
|
-
displayTurnId: turnId
|
|
23103
|
+
...queuedConfig?.startNewTurn ? {} : { hidden: true, displayTurnId: turnId }
|
|
22831
23104
|
});
|
|
22832
23105
|
this.auxiliaryState.deletePendingSteerRecord(localThreadId, pending.id, turnId);
|
|
22833
23106
|
}
|
|
@@ -22915,6 +23188,27 @@ var ThreadService = class {
|
|
|
22915
23188
|
);
|
|
22916
23189
|
}
|
|
22917
23190
|
};
|
|
23191
|
+
function parseQueuedTurnConfig(value) {
|
|
23192
|
+
if (!value) {
|
|
23193
|
+
return null;
|
|
23194
|
+
}
|
|
23195
|
+
try {
|
|
23196
|
+
const parsed = JSON.parse(value);
|
|
23197
|
+
if (parsed.collaborationMode !== "default" && parsed.collaborationMode !== "plan" || parsed.performanceMode !== "fast" && parsed.performanceMode !== "standard" || typeof parsed.startNewTurn !== "boolean") {
|
|
23198
|
+
return null;
|
|
23199
|
+
}
|
|
23200
|
+
return {
|
|
23201
|
+
effectiveModel: typeof parsed.effectiveModel === "string" ? parsed.effectiveModel : null,
|
|
23202
|
+
normalizedReasoning: parsed.normalizedReasoning ?? null,
|
|
23203
|
+
collaborationMode: parsed.collaborationMode,
|
|
23204
|
+
sandboxMode: parsed.sandboxMode ?? "workspace-write",
|
|
23205
|
+
performanceMode: parsed.performanceMode,
|
|
23206
|
+
startNewTurn: parsed.startNewTurn
|
|
23207
|
+
};
|
|
23208
|
+
} catch {
|
|
23209
|
+
return null;
|
|
23210
|
+
}
|
|
23211
|
+
}
|
|
22918
23212
|
|
|
22919
23213
|
// src/routes/agent-runtimes.ts
|
|
22920
23214
|
import fs15 from "fs/promises";
|
|
@@ -22987,6 +23281,17 @@ async function registerAgentRuntimeRoutes(app2) {
|
|
|
22987
23281
|
const { provider: provider2 } = providerParamSchema.parse(request.params);
|
|
22988
23282
|
return runtimeDto(app2, provider2);
|
|
22989
23283
|
});
|
|
23284
|
+
app2.get("/api/agent-runtimes/:provider/subscription-usage", async (request) => {
|
|
23285
|
+
const { provider: provider2 } = providerParamSchema.parse(request.params);
|
|
23286
|
+
const runtime = app2.services.agentRuntimes.getOptional(provider2);
|
|
23287
|
+
if (!runtime) {
|
|
23288
|
+
throw providerNotConfigured(provider2);
|
|
23289
|
+
}
|
|
23290
|
+
if (!runtime.getSubscriptionUsage) {
|
|
23291
|
+
return { usage: null };
|
|
23292
|
+
}
|
|
23293
|
+
return { usage: await runtime.getSubscriptionUsage() };
|
|
23294
|
+
});
|
|
22990
23295
|
app2.post("/api/agent-runtimes/:provider/restart", async (request) => {
|
|
22991
23296
|
const { provider: provider2 } = providerParamSchema.parse(request.params);
|
|
22992
23297
|
const runtime = app2.services.agentRuntimes.getOptional(provider2);
|
|
@@ -23393,9 +23698,11 @@ function parseProviderHostFileParams(params) {
|
|
|
23393
23698
|
}
|
|
23394
23699
|
async function registerSystemRoutes(app2) {
|
|
23395
23700
|
app2.get("/healthz", async () => {
|
|
23701
|
+
const activeTurnCount = app2.services.database.sqlite.prepare("SELECT COUNT(*) AS count FROM threads WHERE status = 'running'").get();
|
|
23396
23702
|
return {
|
|
23397
23703
|
status: "ok",
|
|
23398
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
23704
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
23705
|
+
activeTurnCount: activeTurnCount.count
|
|
23399
23706
|
};
|
|
23400
23707
|
});
|
|
23401
23708
|
app2.get("/readyz", async () => {
|
|
@@ -25017,7 +25324,16 @@ async function registerWorkspaceRoutes(app2) {
|
|
|
25017
25324
|
await cloneRepository(body.gitUrl.trim(), targetPath);
|
|
25018
25325
|
validated = await validateWorkspacePath(app2.services.config.workspaceRoot, targetPath);
|
|
25019
25326
|
} else {
|
|
25020
|
-
|
|
25327
|
+
const requestedPath = body.absPath.trim();
|
|
25328
|
+
const isWorkspaceName = !path22.isAbsolute(requestedPath) && /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(requestedPath) && requestedPath !== "." && requestedPath !== "..";
|
|
25329
|
+
if (!path22.isAbsolute(requestedPath) && !isWorkspaceName) {
|
|
25330
|
+
throw new HttpError(400, {
|
|
25331
|
+
code: "bad_request",
|
|
25332
|
+
message: "Use a simple directory name, an absolute path, or a Git URL."
|
|
25333
|
+
});
|
|
25334
|
+
}
|
|
25335
|
+
const targetPath = isWorkspaceName ? path22.join(settings.devHome, requestedPath) : requestedPath;
|
|
25336
|
+
validated = await validateWorkspacePath(app2.services.config.workspaceRoot, targetPath, {
|
|
25021
25337
|
devHome: settings.devHome,
|
|
25022
25338
|
createMissingLeaf: true
|
|
25023
25339
|
});
|
|
@@ -27969,6 +28285,7 @@ var RelayTunnelClient = class {
|
|
|
27969
28285
|
reconnectDelayMs = RELAY_RECONNECT_INITIAL_DELAY_MS;
|
|
27970
28286
|
stopped = false;
|
|
27971
28287
|
relayClientCleanup = /* @__PURE__ */ new Map();
|
|
28288
|
+
pendingActivity = /* @__PURE__ */ new Map();
|
|
27972
28289
|
validateConfig() {
|
|
27973
28290
|
if (!this.config.serverUrl || !this.config.agentToken) {
|
|
27974
28291
|
throw new Error(
|
|
@@ -27983,7 +28300,10 @@ var RelayTunnelClient = class {
|
|
|
27983
28300
|
if (this.socket) {
|
|
27984
28301
|
return;
|
|
27985
28302
|
}
|
|
27986
|
-
const url = new URL(
|
|
28303
|
+
const url = new URL(
|
|
28304
|
+
"/supervisor/tunnel",
|
|
28305
|
+
this.config.serverUrl ?? void 0
|
|
28306
|
+
);
|
|
27987
28307
|
url.searchParams.set("token", this.config.agentToken ?? "");
|
|
27988
28308
|
url.searchParams.set("deviceToken", this.config.agentToken ?? "");
|
|
27989
28309
|
const socket = new WebSocket(url);
|
|
@@ -27997,6 +28317,7 @@ var RelayTunnelClient = class {
|
|
|
27997
28317
|
this.clearConnectTimeout();
|
|
27998
28318
|
this.reconnectDelayMs = RELAY_RECONNECT_INITIAL_DELAY_MS;
|
|
27999
28319
|
this.sendHeartbeat();
|
|
28320
|
+
this.flushPendingActivity();
|
|
28000
28321
|
this.clearHeartbeat();
|
|
28001
28322
|
this.heartbeatHandle = setInterval(() => {
|
|
28002
28323
|
this.sendHeartbeat();
|
|
@@ -28021,6 +28342,29 @@ var RelayTunnelClient = class {
|
|
|
28021
28342
|
this.socket?.close();
|
|
28022
28343
|
this.socket = null;
|
|
28023
28344
|
}
|
|
28345
|
+
sendActivity(payload) {
|
|
28346
|
+
const key = `${payload.threadId}\0${payload.turnId}`;
|
|
28347
|
+
const socket = this.socket;
|
|
28348
|
+
if (socket?.readyState !== WebSocket.OPEN) {
|
|
28349
|
+
this.pendingActivity.set(key, payload);
|
|
28350
|
+
return;
|
|
28351
|
+
}
|
|
28352
|
+
const sent = this.sendEnvelope(socket, {
|
|
28353
|
+
type: "relay.activity",
|
|
28354
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
28355
|
+
payload
|
|
28356
|
+
});
|
|
28357
|
+
if (sent) {
|
|
28358
|
+
this.pendingActivity.delete(key);
|
|
28359
|
+
} else {
|
|
28360
|
+
this.pendingActivity.set(key, payload);
|
|
28361
|
+
}
|
|
28362
|
+
}
|
|
28363
|
+
flushPendingActivity() {
|
|
28364
|
+
for (const payload of this.pendingActivity.values()) {
|
|
28365
|
+
this.sendActivity(payload);
|
|
28366
|
+
}
|
|
28367
|
+
}
|
|
28024
28368
|
sendHeartbeat() {
|
|
28025
28369
|
const socket = this.socket;
|
|
28026
28370
|
if (socket?.readyState !== WebSocket.OPEN) {
|
|
@@ -28040,9 +28384,12 @@ var RelayTunnelClient = class {
|
|
|
28040
28384
|
}
|
|
28041
28385
|
if (parsed.type !== "relay.request") {
|
|
28042
28386
|
if (parsed.type === "relay.client.connected") {
|
|
28043
|
-
const cleanup = this.handleClientConnected(
|
|
28044
|
-
|
|
28045
|
-
|
|
28387
|
+
const cleanup = this.handleClientConnected(
|
|
28388
|
+
parsed.clientId,
|
|
28389
|
+
(message) => {
|
|
28390
|
+
this.sendClientMessage(parsed.clientId, message);
|
|
28391
|
+
}
|
|
28392
|
+
);
|
|
28046
28393
|
this.relayClientCleanup.set(parsed.clientId, cleanup);
|
|
28047
28394
|
return;
|
|
28048
28395
|
}
|
|
@@ -28052,9 +28399,13 @@ var RelayTunnelClient = class {
|
|
|
28052
28399
|
return;
|
|
28053
28400
|
}
|
|
28054
28401
|
if (parsed.type === "relay.client.message") {
|
|
28055
|
-
await this.handleClientMessage(
|
|
28056
|
-
|
|
28057
|
-
|
|
28402
|
+
await this.handleClientMessage(
|
|
28403
|
+
parsed.clientId,
|
|
28404
|
+
parsed.payload,
|
|
28405
|
+
(message) => {
|
|
28406
|
+
this.sendClientMessage(parsed.clientId, message);
|
|
28407
|
+
}
|
|
28408
|
+
);
|
|
28058
28409
|
return;
|
|
28059
28410
|
}
|
|
28060
28411
|
return;
|
|
@@ -28064,15 +28415,12 @@ var RelayTunnelClient = class {
|
|
|
28064
28415
|
if (socket?.readyState !== WebSocket.OPEN) {
|
|
28065
28416
|
return;
|
|
28066
28417
|
}
|
|
28067
|
-
this.sendEnvelope(
|
|
28068
|
-
|
|
28069
|
-
|
|
28070
|
-
|
|
28071
|
-
|
|
28072
|
-
|
|
28073
|
-
payload: response
|
|
28074
|
-
}
|
|
28075
|
-
);
|
|
28418
|
+
this.sendEnvelope(socket, {
|
|
28419
|
+
type: "relay.response",
|
|
28420
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
28421
|
+
requestId: parsed.requestId,
|
|
28422
|
+
payload: response
|
|
28423
|
+
});
|
|
28076
28424
|
}
|
|
28077
28425
|
sendClientMessage(clientId, message) {
|
|
28078
28426
|
const socket = this.socket;
|
|
@@ -28089,8 +28437,10 @@ var RelayTunnelClient = class {
|
|
|
28089
28437
|
sendEnvelope(socket, message) {
|
|
28090
28438
|
try {
|
|
28091
28439
|
socket.send(JSON.stringify(message));
|
|
28440
|
+
return true;
|
|
28092
28441
|
} catch {
|
|
28093
28442
|
this.closeAndReconnect(socket);
|
|
28443
|
+
return false;
|
|
28094
28444
|
}
|
|
28095
28445
|
}
|
|
28096
28446
|
closeAndReconnect(socket) {
|
|
@@ -28158,10 +28508,7 @@ var DEFAULT_WEBVIEW_CORS_ORIGINS = /* @__PURE__ */ new Set([
|
|
|
28158
28508
|
"https://localhost",
|
|
28159
28509
|
"https://appassets.androidplatform.net"
|
|
28160
28510
|
]);
|
|
28161
|
-
var WEBVIEW_CORS_ALLOW_HEADERS = [
|
|
28162
|
-
"authorization",
|
|
28163
|
-
"content-type"
|
|
28164
|
-
].join(", ");
|
|
28511
|
+
var WEBVIEW_CORS_ALLOW_HEADERS = ["authorization", "content-type"].join(", ");
|
|
28165
28512
|
var WEBVIEW_CORS_ALLOW_METHODS = [
|
|
28166
28513
|
"GET",
|
|
28167
28514
|
"POST",
|
|
@@ -28175,7 +28522,9 @@ function webViewCorsOrigins(env) {
|
|
|
28175
28522
|
return null;
|
|
28176
28523
|
}
|
|
28177
28524
|
const configured = env.REMOTE_CODEX_WEBVIEW_CORS_ORIGINS?.split(",").map((origin) => origin.trim()).filter(Boolean);
|
|
28178
|
-
return new Set(
|
|
28525
|
+
return new Set(
|
|
28526
|
+
configured?.length ? configured : DEFAULT_WEBVIEW_CORS_ORIGINS
|
|
28527
|
+
);
|
|
28179
28528
|
}
|
|
28180
28529
|
function applyWebViewCorsHeaders(reply, origin) {
|
|
28181
28530
|
reply.header("access-control-allow-origin", origin);
|
|
@@ -28224,7 +28573,11 @@ function createServiceLifecycle() {
|
|
|
28224
28573
|
});
|
|
28225
28574
|
}
|
|
28226
28575
|
const repoRoot = findRepoRoot();
|
|
28227
|
-
const restartScript = path28.join(
|
|
28576
|
+
const restartScript = path28.join(
|
|
28577
|
+
repoRoot,
|
|
28578
|
+
"scripts",
|
|
28579
|
+
"service-restart.mjs"
|
|
28580
|
+
);
|
|
28228
28581
|
if (!fs26.existsSync(restartScript) || !fs26.existsSync(path28.join(repoRoot, "pnpm-workspace.yaml"))) {
|
|
28229
28582
|
throw new HttpError(503, {
|
|
28230
28583
|
code: "service_unavailable",
|
|
@@ -28284,7 +28637,9 @@ function buildApp(options = {}) {
|
|
|
28284
28637
|
},
|
|
28285
28638
|
disableRequestLogging: config.disableRequestLogging
|
|
28286
28639
|
});
|
|
28287
|
-
const allowedWebViewCorsOrigins = webViewCorsOrigins(
|
|
28640
|
+
const allowedWebViewCorsOrigins = webViewCorsOrigins(
|
|
28641
|
+
options.env ?? process.env
|
|
28642
|
+
);
|
|
28288
28643
|
app2.addHook("onRequest", async (request, reply) => {
|
|
28289
28644
|
if (!allowedWebViewCorsOrigins) {
|
|
28290
28645
|
return;
|
|
@@ -28298,15 +28653,22 @@ function buildApp(options = {}) {
|
|
|
28298
28653
|
return reply.code(204).send();
|
|
28299
28654
|
}
|
|
28300
28655
|
});
|
|
28301
|
-
app2.register(
|
|
28302
|
-
|
|
28303
|
-
|
|
28304
|
-
|
|
28656
|
+
app2.register(
|
|
28657
|
+
multipart,
|
|
28658
|
+
{
|
|
28659
|
+
limits: {
|
|
28660
|
+
files: MAX_PROMPT_ATTACHMENTS2,
|
|
28661
|
+
fileSize: MAX_PROMPT_ATTACHMENT_BYTES2
|
|
28662
|
+
}
|
|
28305
28663
|
}
|
|
28306
|
-
|
|
28664
|
+
);
|
|
28307
28665
|
const backendPluginHost = new BackendPluginHost(app2);
|
|
28308
28666
|
backendPluginHost.register(createTerminalPluginBackendContribution());
|
|
28309
|
-
const relaySocketBridge = createRelaySocketBridge(
|
|
28667
|
+
const relaySocketBridge = createRelaySocketBridge(
|
|
28668
|
+
app2,
|
|
28669
|
+
eventBus,
|
|
28670
|
+
backendPluginHost
|
|
28671
|
+
);
|
|
28310
28672
|
const relayTunnelClient = config.mode === "relay" ? options.relayTunnelClient ?? new RelayTunnelClient(
|
|
28311
28673
|
config.relay,
|
|
28312
28674
|
createRelayRequestHandler(app2),
|
|
@@ -28314,6 +28676,31 @@ function buildApp(options = {}) {
|
|
|
28314
28676
|
relaySocketBridge.handleMessage
|
|
28315
28677
|
) : null;
|
|
28316
28678
|
relayTunnelClient?.validateConfig();
|
|
28679
|
+
const cleanupRelayActivity = relayTunnelClient ? eventBus.onThreadEvent((event) => {
|
|
28680
|
+
if (event.type === "thread.turn.started") {
|
|
28681
|
+
relayTunnelClient.sendActivity({
|
|
28682
|
+
kind: "turn_started",
|
|
28683
|
+
threadId: event.threadId,
|
|
28684
|
+
turnId: event.payload.turnId
|
|
28685
|
+
});
|
|
28686
|
+
return;
|
|
28687
|
+
}
|
|
28688
|
+
if (event.type === "thread.turn.completed") {
|
|
28689
|
+
relayTunnelClient.sendActivity({
|
|
28690
|
+
kind: "turn_terminal",
|
|
28691
|
+
threadId: event.threadId,
|
|
28692
|
+
turnId: event.payload.turnId
|
|
28693
|
+
});
|
|
28694
|
+
return;
|
|
28695
|
+
}
|
|
28696
|
+
if (event.type === "thread.turn.failed" && event.payload.willRetry !== true) {
|
|
28697
|
+
relayTunnelClient.sendActivity({
|
|
28698
|
+
kind: "turn_terminal",
|
|
28699
|
+
threadId: event.threadId,
|
|
28700
|
+
turnId: event.payload.turnId
|
|
28701
|
+
});
|
|
28702
|
+
}
|
|
28703
|
+
}) : null;
|
|
28317
28704
|
app2.decorate("services", {
|
|
28318
28705
|
config,
|
|
28319
28706
|
database,
|
|
@@ -28494,6 +28881,7 @@ function buildApp(options = {}) {
|
|
|
28494
28881
|
});
|
|
28495
28882
|
});
|
|
28496
28883
|
app2.addHook("onClose", async () => {
|
|
28884
|
+
cleanupRelayActivity?.();
|
|
28497
28885
|
await shellService.stop();
|
|
28498
28886
|
relayTunnelClient?.stop();
|
|
28499
28887
|
await Promise.all(agentRuntimes.all().map((runtime) => runtime.stop()));
|
|
@@ -28666,6 +29054,21 @@ if (fs27.existsSync(".env")) {
|
|
|
28666
29054
|
}
|
|
28667
29055
|
var app = buildApp();
|
|
28668
29056
|
var { host, port } = app.services.config;
|
|
29057
|
+
var closing = false;
|
|
29058
|
+
async function shutdown(signal) {
|
|
29059
|
+
if (closing) return;
|
|
29060
|
+
closing = true;
|
|
29061
|
+
app.log.info(`Supervisor API received ${signal}; closing cleanly.`);
|
|
29062
|
+
try {
|
|
29063
|
+
await app.close();
|
|
29064
|
+
process.exit(0);
|
|
29065
|
+
} catch (error) {
|
|
29066
|
+
app.log.error(error);
|
|
29067
|
+
process.exit(1);
|
|
29068
|
+
}
|
|
29069
|
+
}
|
|
29070
|
+
process.once("SIGTERM", () => void shutdown("SIGTERM"));
|
|
29071
|
+
process.once("SIGINT", () => void shutdown("SIGINT"));
|
|
28669
29072
|
app.listen({ host, port }).then(() => {
|
|
28670
29073
|
app.log.info(`Supervisor API listening on http://${host}:${port}`);
|
|
28671
29074
|
}).catch((error) => {
|