remote-codex 0.11.31 → 0.11.33
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 +497 -54
- package/apps/supervisor-web/dist/assets/index-CdjTdnJt.css +1 -0
- package/apps/supervisor-web/dist/assets/index-DIi8wTEY.js +21 -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 +121 -1
- package/packages/claude/src/runtimeAdapter.ts +131 -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,74 @@ 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
|
+
subscriptionAuthKind = "unknown";
|
|
12506
|
+
subscriptionUsageObservedAt = null;
|
|
12376
12507
|
clientApp;
|
|
12377
12508
|
sdkLoadError = null;
|
|
12378
12509
|
getStatus() {
|
|
12379
12510
|
return { ...this.status };
|
|
12380
12511
|
}
|
|
12512
|
+
async getSubscriptionUsage() {
|
|
12513
|
+
const observedAt = this.subscriptionUsageObservedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
12514
|
+
const windows = [...this.subscriptionUsageWindows.entries()].map(([id, window]) => ({
|
|
12515
|
+
id,
|
|
12516
|
+
durationMinutes: id === "five_hour" ? 300 : 10080,
|
|
12517
|
+
label: id === "five_hour" ? "5h" : "7d",
|
|
12518
|
+
usedPercent: window.usedPercent,
|
|
12519
|
+
resetsAt: window.resetsAt
|
|
12520
|
+
}));
|
|
12521
|
+
return {
|
|
12522
|
+
provider: "claude",
|
|
12523
|
+
authKind: this.subscriptionAuthKind,
|
|
12524
|
+
observedAt,
|
|
12525
|
+
stale: false,
|
|
12526
|
+
windows
|
|
12527
|
+
};
|
|
12528
|
+
}
|
|
12529
|
+
captureRateLimit(message) {
|
|
12530
|
+
if (message.type !== "rate_limit_event") {
|
|
12531
|
+
return;
|
|
12532
|
+
}
|
|
12533
|
+
const info = message.rate_limit_info;
|
|
12534
|
+
if (!info || info.rateLimitType !== "five_hour" && info.rateLimitType !== "seven_day" || typeof info.utilization !== "number" || !Number.isFinite(info.utilization)) {
|
|
12535
|
+
return;
|
|
12536
|
+
}
|
|
12537
|
+
this.subscriptionUsageWindows.set(info.rateLimitType, {
|
|
12538
|
+
usedPercent: Math.max(0, Math.min(100, info.utilization * 100)),
|
|
12539
|
+
resetsAt: typeof info.resetsAt === "number" ? new Date(info.resetsAt * 1e3).toISOString() : null
|
|
12540
|
+
});
|
|
12541
|
+
this.subscriptionAuthKind = "subscription";
|
|
12542
|
+
this.subscriptionUsageObservedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
12543
|
+
}
|
|
12544
|
+
async captureStructuredSubscriptionUsage(query) {
|
|
12545
|
+
const getUsage = query.usage_EXPERIMENTAL_MAY_CHANGE_DO_NOT_RELY_ON_THIS_API_YET;
|
|
12546
|
+
if (!getUsage) {
|
|
12547
|
+
return;
|
|
12548
|
+
}
|
|
12549
|
+
try {
|
|
12550
|
+
const usage = await getUsage.call(query);
|
|
12551
|
+
const nextWindows = /* @__PURE__ */ new Map();
|
|
12552
|
+
const addWindow = (id, window) => {
|
|
12553
|
+
if (typeof window?.utilization !== "number" || !Number.isFinite(window.utilization)) {
|
|
12554
|
+
return;
|
|
12555
|
+
}
|
|
12556
|
+
nextWindows.set(id, {
|
|
12557
|
+
usedPercent: Math.max(0, Math.min(100, window.utilization)),
|
|
12558
|
+
resetsAt: typeof window.resets_at === "string" ? window.resets_at : null
|
|
12559
|
+
});
|
|
12560
|
+
};
|
|
12561
|
+
addWindow("five_hour", usage.rate_limits?.five_hour);
|
|
12562
|
+
addWindow("seven_day", usage.rate_limits?.seven_day);
|
|
12563
|
+
this.subscriptionUsageWindows.clear();
|
|
12564
|
+
for (const [id, window] of nextWindows) {
|
|
12565
|
+
this.subscriptionUsageWindows.set(id, window);
|
|
12566
|
+
}
|
|
12567
|
+
this.subscriptionAuthKind = usage.subscription_type ? "subscription" : usage.rate_limits_available ? "subscription" : "apiKey";
|
|
12568
|
+
this.subscriptionUsageObservedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
12569
|
+
} catch {
|
|
12570
|
+
}
|
|
12571
|
+
}
|
|
12381
12572
|
updateToolboxItemsFromSystemInit(message) {
|
|
12382
12573
|
this.managementSchema.toolboxItems = buildClaudeToolboxItems(
|
|
12383
12574
|
normalizeClaudeSlashCommands(message.slash_commands)
|
|
@@ -12522,9 +12713,15 @@ var ClaudeRuntimeAdapter = class extends EventEmitter5 {
|
|
|
12522
12713
|
let providerSessionId = null;
|
|
12523
12714
|
let model = input.model;
|
|
12524
12715
|
const rawMessages = [];
|
|
12716
|
+
let capturedStructuredUsage = false;
|
|
12525
12717
|
try {
|
|
12526
12718
|
for await (const message of query) {
|
|
12527
12719
|
rawMessages.push(message);
|
|
12720
|
+
if (!capturedStructuredUsage) {
|
|
12721
|
+
capturedStructuredUsage = true;
|
|
12722
|
+
await this.captureStructuredSubscriptionUsage(query);
|
|
12723
|
+
}
|
|
12724
|
+
this.captureRateLimit(message);
|
|
12528
12725
|
if (message.type === "system" && message.subtype === "init") {
|
|
12529
12726
|
this.updateToolboxItemsFromSystemInit(message);
|
|
12530
12727
|
const sessionId = message.session_id;
|
|
@@ -12769,9 +12966,15 @@ var ClaudeRuntimeAdapter = class extends EventEmitter5 {
|
|
|
12769
12966
|
const rawMessages = [];
|
|
12770
12967
|
let terminalStatus = null;
|
|
12771
12968
|
let terminalError = null;
|
|
12969
|
+
let capturedStructuredUsage = false;
|
|
12772
12970
|
try {
|
|
12773
12971
|
for await (const message of state.query) {
|
|
12774
12972
|
rawMessages.push(message);
|
|
12973
|
+
if (!capturedStructuredUsage) {
|
|
12974
|
+
capturedStructuredUsage = true;
|
|
12975
|
+
await this.captureStructuredSubscriptionUsage(state.query);
|
|
12976
|
+
}
|
|
12977
|
+
this.captureRateLimit(message);
|
|
12775
12978
|
this.consumeMessage(state, message);
|
|
12776
12979
|
const status = queryResultStatus(message);
|
|
12777
12980
|
if (status) {
|
|
@@ -16074,6 +16277,7 @@ var ThreadAuxiliaryStateStore = class {
|
|
|
16074
16277
|
clientRequestId: record.clientRequestId ?? null,
|
|
16075
16278
|
turnId: record.turnId,
|
|
16076
16279
|
prompt: record.displayPrompt,
|
|
16280
|
+
delivery: record.delivery === "continuation" ? "continuation" : "steer",
|
|
16077
16281
|
createdAt: record.createdAt
|
|
16078
16282
|
}));
|
|
16079
16283
|
}
|
|
@@ -16090,6 +16294,11 @@ var ThreadAuxiliaryStateStore = class {
|
|
|
16090
16294
|
hasPendingSteersForTurn(localThreadId, turnId) {
|
|
16091
16295
|
return this.listPendingSteerRecordsForTurn(localThreadId, turnId).length > 0;
|
|
16092
16296
|
}
|
|
16297
|
+
hasQueuedContinuationsForTurn(localThreadId, turnId) {
|
|
16298
|
+
return this.listPendingSteerRecordsForTurn(localThreadId, turnId).some(
|
|
16299
|
+
(record) => record.delivery === "continuation"
|
|
16300
|
+
);
|
|
16301
|
+
}
|
|
16093
16302
|
deletePendingSteerRecord(localThreadId, id, turnId) {
|
|
16094
16303
|
deleteThreadPendingSteerRecordById(this.db, id);
|
|
16095
16304
|
this.callbacks.invalidateThreadDetailCache(localThreadId);
|
|
@@ -17889,6 +18098,7 @@ var ThreadRuntimeEventProjector = class {
|
|
|
17889
18098
|
const turnItems = event.turn.items;
|
|
17890
18099
|
updateThreadRecord(db, record.id, {
|
|
17891
18100
|
providerTurnId: null,
|
|
18101
|
+
activeTurnCollaborationMode: null,
|
|
17892
18102
|
status: event.turn.status === "failed" ? "failed" : event.turn.status === "interrupted" ? "interrupted" : "idle",
|
|
17893
18103
|
lastError: event.turn.error?.message ?? null,
|
|
17894
18104
|
lastTurnCompletedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -17910,7 +18120,9 @@ var ThreadRuntimeEventProjector = class {
|
|
|
17910
18120
|
}
|
|
17911
18121
|
}
|
|
17912
18122
|
callbacks.clearTerminalPendingRequests(record.id, true);
|
|
17913
|
-
if (event.turn.status === "completed" &&
|
|
18123
|
+
if (event.turn.status === "completed" && !preservePendingSteers && callbacks.normalizeCollaborationMode(
|
|
18124
|
+
record.activeTurnCollaborationMode ?? record.collaborationMode
|
|
18125
|
+
) === "plan" && turnItems.some((item) => item.kind === "plan") && !callbacks.hasPendingAskUserQuestion(record.id)) {
|
|
17914
18126
|
callbacks.createPendingPlanDecisionRequest(record.id, turnId, true);
|
|
17915
18127
|
} else {
|
|
17916
18128
|
callbacks.dismissPlanDecisionTurn(record.id);
|
|
@@ -17942,6 +18154,7 @@ var ThreadRuntimeEventProjector = class {
|
|
|
17942
18154
|
const turnId = liveState.displayTurnIdForRuntimeTurn(record.id, event.providerTurnId) ?? event.providerTurnId;
|
|
17943
18155
|
updateThreadRecord(db, record.id, {
|
|
17944
18156
|
providerTurnId: null,
|
|
18157
|
+
activeTurnCollaborationMode: null,
|
|
17945
18158
|
status: "failed",
|
|
17946
18159
|
lastError: event.error,
|
|
17947
18160
|
lastTurnCompletedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -19274,6 +19487,7 @@ var ThreadPromptTurnCoordinator = class {
|
|
|
19274
19487
|
model: input.effectiveModel,
|
|
19275
19488
|
reasoningEffort: input.normalizedReasoning,
|
|
19276
19489
|
collaborationMode: input.collaborationMode,
|
|
19490
|
+
activeTurnCollaborationMode: input.collaborationMode,
|
|
19277
19491
|
sandboxMode: input.sandboxMode
|
|
19278
19492
|
};
|
|
19279
19493
|
if (isAutoGeneratedTitle(record.title)) {
|
|
@@ -19396,7 +19610,8 @@ var ThreadPromptTurnCoordinator = class {
|
|
|
19396
19610
|
turnId: steerTurnId,
|
|
19397
19611
|
clientRequestId: input.clientRequestId,
|
|
19398
19612
|
displayPrompt: input.displayPrompt,
|
|
19399
|
-
submittedPrompt: input.prompt
|
|
19613
|
+
submittedPrompt: input.prompt,
|
|
19614
|
+
delivery: "steer"
|
|
19400
19615
|
});
|
|
19401
19616
|
this.callbacks.invalidateThreadDetailCache(localThreadId);
|
|
19402
19617
|
this.callbacks.emitThreadUpdated(localThreadId, {
|
|
@@ -19445,7 +19660,16 @@ var ThreadPromptTurnCoordinator = class {
|
|
|
19445
19660
|
turnId: displayTurnId,
|
|
19446
19661
|
clientRequestId: input.clientRequestId,
|
|
19447
19662
|
displayPrompt: input.displayPrompt,
|
|
19448
|
-
submittedPrompt: input.prompt
|
|
19663
|
+
submittedPrompt: input.prompt,
|
|
19664
|
+
delivery: "continuation",
|
|
19665
|
+
turnConfigJson: JSON.stringify({
|
|
19666
|
+
effectiveModel: input.effectiveModel,
|
|
19667
|
+
normalizedReasoning: input.normalizedReasoning,
|
|
19668
|
+
collaborationMode: input.collaborationMode,
|
|
19669
|
+
sandboxMode: input.sandboxMode,
|
|
19670
|
+
performanceMode: input.performanceMode,
|
|
19671
|
+
startNewTurn: input.collaborationMode !== (record.activeTurnCollaborationMode === "plan" ? "plan" : "default")
|
|
19672
|
+
})
|
|
19449
19673
|
});
|
|
19450
19674
|
this.callbacks.invalidateThreadDetailCache(localThreadId);
|
|
19451
19675
|
this.callbacks.emitThreadUpdated(localThreadId, {
|
|
@@ -19651,7 +19875,7 @@ var ThreadSessionCoordinator = class {
|
|
|
19651
19875
|
if (!isRemoteThreadBootstrapError(error)) {
|
|
19652
19876
|
throw error;
|
|
19653
19877
|
}
|
|
19654
|
-
return { status: "bootstrap_unavailable" };
|
|
19878
|
+
return { status: "bootstrap_unavailable", error };
|
|
19655
19879
|
}
|
|
19656
19880
|
const effectiveModel = input.resumeInput.model ?? input.currentModel ?? response.model ?? null;
|
|
19657
19881
|
const resumedReasoning = this.providerRuntime.normalizeReasoningForModel(
|
|
@@ -19827,6 +20051,37 @@ var ThreadSessionLifecycleCoordinator = class {
|
|
|
19827
20051
|
fastMode: record.fastMode
|
|
19828
20052
|
});
|
|
19829
20053
|
if (resumed.status === "bootstrap_unavailable") {
|
|
20054
|
+
if (!this.canRecreateUnmaterializedThread(record, resumed.error)) {
|
|
20055
|
+
return;
|
|
20056
|
+
}
|
|
20057
|
+
const workspace = getWorkspaceRecordById(this.db, record.workspaceId);
|
|
20058
|
+
const model = input.model ?? record.model;
|
|
20059
|
+
if (!workspace || !model) {
|
|
20060
|
+
return;
|
|
20061
|
+
}
|
|
20062
|
+
const recreated = await this.sessionCoordinator.startThreadSession({
|
|
20063
|
+
workspacePath: workspace.absPath,
|
|
20064
|
+
threadInput: {
|
|
20065
|
+
workspaceId: workspace.id,
|
|
20066
|
+
title: record.title,
|
|
20067
|
+
provider: record.provider,
|
|
20068
|
+
model,
|
|
20069
|
+
reasoningEffort: record.reasoningEffort,
|
|
20070
|
+
approvalMode: record.approvalMode ?? "yolo"
|
|
20071
|
+
},
|
|
20072
|
+
defaultTitle: record.title
|
|
20073
|
+
});
|
|
20074
|
+
updateThreadRecord(this.db, record.id, {
|
|
20075
|
+
...buildThreadPatch(
|
|
20076
|
+
recreated.response.session,
|
|
20077
|
+
model,
|
|
20078
|
+
recreated.response.reasoningEffort ?? recreated.reasoningEffort
|
|
20079
|
+
),
|
|
20080
|
+
providerSessionId: recreated.response.providerSessionId,
|
|
20081
|
+
sandboxMode: recreated.sandboxMode,
|
|
20082
|
+
isConnected: true
|
|
20083
|
+
});
|
|
20084
|
+
this.callbacks.invalidateThreadDetailCache(localThreadId);
|
|
19830
20085
|
return;
|
|
19831
20086
|
}
|
|
19832
20087
|
updateThreadRecord(
|
|
@@ -19850,6 +20105,9 @@ var ThreadSessionLifecycleCoordinator = class {
|
|
|
19850
20105
|
}
|
|
19851
20106
|
this.callbacks.invalidateThreadDetailCache(localThreadId);
|
|
19852
20107
|
}
|
|
20108
|
+
canRecreateUnmaterializedThread(record, error) {
|
|
20109
|
+
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);
|
|
20110
|
+
}
|
|
19853
20111
|
disconnectThread(localThreadId) {
|
|
19854
20112
|
const record = getThreadRecordById(this.db, localThreadId);
|
|
19855
20113
|
if (!record) {
|
|
@@ -20049,6 +20307,7 @@ var ThreadDeletionCoordinator = class {
|
|
|
20049
20307
|
deleteThreadGoalRecordsByThreadId(this.db, localThreadId);
|
|
20050
20308
|
deleteThreadHistoryItemRecordsByThreadId(this.db, localThreadId);
|
|
20051
20309
|
deleteThreadPendingSteerRecordsByThreadId(this.db, localThreadId);
|
|
20310
|
+
deleteThreadPromptRequestRecordsByThreadId(this.db, localThreadId);
|
|
20052
20311
|
deleteThreadTurnMetadataByThreadId(this.db, localThreadId);
|
|
20053
20312
|
deleteThreadRecord(this.db, localThreadId);
|
|
20054
20313
|
return { id: localThreadId };
|
|
@@ -21974,7 +22233,7 @@ var ThreadService = class {
|
|
|
21974
22233
|
normalizeCollaborationMode,
|
|
21975
22234
|
normalizeReasoningEffort: normalizeReasoningEffort2,
|
|
21976
22235
|
normalizeThreadGoalStatusForThread: (goal, record) => this.goalCoordinator.normalizeThreadGoalStatusForThread(goal, record),
|
|
21977
|
-
shouldPreservePendingSteersForCompletedTurn: (record, turnId) =>
|
|
22236
|
+
shouldPreservePendingSteersForCompletedTurn: (record, turnId) => this.shouldPreserveCompletedPendingSteer(record.id, turnId),
|
|
21978
22237
|
scheduleQueuedContinuationDrain: (localThreadId, turnId) => this.scheduleQueuedContinuationDrain(localThreadId, turnId),
|
|
21979
22238
|
persistLiveHistoryItem: (localThreadId, turnId, item) => this.historyPersistence.persistLiveHistoryItem(localThreadId, turnId, item),
|
|
21980
22239
|
persistFinalTurnOrderingHints: (localThreadId, turnId, items) => this.historyPersistence.persistFinalTurnOrderingHints(localThreadId, turnId, items),
|
|
@@ -22008,6 +22267,7 @@ var ThreadService = class {
|
|
|
22008
22267
|
config;
|
|
22009
22268
|
liveState = new ThreadLiveStateStore();
|
|
22010
22269
|
queuedContinuationDrains = /* @__PURE__ */ new Set();
|
|
22270
|
+
promptRequestsInFlight = /* @__PURE__ */ new Map();
|
|
22011
22271
|
detailAssembler;
|
|
22012
22272
|
usageAccounting;
|
|
22013
22273
|
requestCoordinator;
|
|
@@ -22326,6 +22586,57 @@ var ThreadService = class {
|
|
|
22326
22586
|
return this.getThreadDetail(localThreadId);
|
|
22327
22587
|
}
|
|
22328
22588
|
async sendPrompt(localThreadId, input, options = {}) {
|
|
22589
|
+
const clientRequestId = input.clientRequestId?.trim();
|
|
22590
|
+
if (!clientRequestId) {
|
|
22591
|
+
return this.sendPromptOnce(localThreadId, input, options);
|
|
22592
|
+
}
|
|
22593
|
+
const requestKey = `${localThreadId}:${clientRequestId}`;
|
|
22594
|
+
const activeRequest = this.promptRequestsInFlight.get(requestKey);
|
|
22595
|
+
if (activeRequest) {
|
|
22596
|
+
return activeRequest;
|
|
22597
|
+
}
|
|
22598
|
+
const request = this.sendPromptIdempotently(
|
|
22599
|
+
localThreadId,
|
|
22600
|
+
{ ...input, clientRequestId },
|
|
22601
|
+
options
|
|
22602
|
+
);
|
|
22603
|
+
this.promptRequestsInFlight.set(requestKey, request);
|
|
22604
|
+
try {
|
|
22605
|
+
return await request;
|
|
22606
|
+
} finally {
|
|
22607
|
+
if (this.promptRequestsInFlight.get(requestKey) === request) {
|
|
22608
|
+
this.promptRequestsInFlight.delete(requestKey);
|
|
22609
|
+
}
|
|
22610
|
+
}
|
|
22611
|
+
}
|
|
22612
|
+
async sendPromptIdempotently(localThreadId, input, options) {
|
|
22613
|
+
deleteExpiredThreadPromptRequestRecords(
|
|
22614
|
+
this.db,
|
|
22615
|
+
new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3).toISOString()
|
|
22616
|
+
);
|
|
22617
|
+
const existing = getThreadPromptRequestRecord(
|
|
22618
|
+
this.db,
|
|
22619
|
+
localThreadId,
|
|
22620
|
+
input.clientRequestId
|
|
22621
|
+
);
|
|
22622
|
+
if (existing) {
|
|
22623
|
+
const record = this.requireThreadRecord(localThreadId);
|
|
22624
|
+
return this.toThreadDto(
|
|
22625
|
+
record,
|
|
22626
|
+
await this.listLoadedProviderSessionIds(record.provider)
|
|
22627
|
+
);
|
|
22628
|
+
}
|
|
22629
|
+
createThreadPromptRequestRecord(this.db, localThreadId, input.clientRequestId);
|
|
22630
|
+
try {
|
|
22631
|
+
const result = await this.sendPromptOnce(localThreadId, input, options);
|
|
22632
|
+
markThreadPromptRequestAccepted(this.db, localThreadId, input.clientRequestId);
|
|
22633
|
+
return result;
|
|
22634
|
+
} catch (error) {
|
|
22635
|
+
deleteThreadPromptRequestRecord(this.db, localThreadId, input.clientRequestId);
|
|
22636
|
+
throw error;
|
|
22637
|
+
}
|
|
22638
|
+
}
|
|
22639
|
+
async sendPromptOnce(localThreadId, input, options = {}) {
|
|
22329
22640
|
let record = this.requireThreadRecord(localThreadId);
|
|
22330
22641
|
await this.importCoordinator.assertImportedThreadReadyForPrompt({
|
|
22331
22642
|
source: record.source,
|
|
@@ -22391,7 +22702,10 @@ var ThreadService = class {
|
|
|
22391
22702
|
};
|
|
22392
22703
|
const hasActiveProviderTurn = Boolean(record.providerTurnId) && (record.status === "running" || !turnConfig.supportsRunningTurnInput && !record.lastTurnCompletedAt) && record.status !== "failed" && record.status !== "interrupted";
|
|
22393
22704
|
if (hasActiveProviderTurn && record.providerTurnId) {
|
|
22394
|
-
|
|
22705
|
+
const activeTurnCollaborationMode = normalizeCollaborationMode(
|
|
22706
|
+
record.activeTurnCollaborationMode ?? record.collaborationMode
|
|
22707
|
+
);
|
|
22708
|
+
if (!turnConfig.supportsRunningTurnInput || activeTurnCollaborationMode !== turnConfig.collaborationMode) {
|
|
22395
22709
|
return this.promptTurnCoordinator.queueContinuationPromptTurn(localThreadId, {
|
|
22396
22710
|
...connectedRecord,
|
|
22397
22711
|
providerTurnId: record.providerTurnId
|
|
@@ -22735,7 +23049,7 @@ var ThreadService = class {
|
|
|
22735
23049
|
if (!record) {
|
|
22736
23050
|
return false;
|
|
22737
23051
|
}
|
|
22738
|
-
return
|
|
23052
|
+
return this.auxiliaryState.hasQueuedContinuationsForTurn(localThreadId, turnId);
|
|
22739
23053
|
}
|
|
22740
23054
|
shouldPreserveMissingPendingSteer(localThreadId, turnId) {
|
|
22741
23055
|
const record = getThreadRecordById(this.db, localThreadId);
|
|
@@ -22745,14 +23059,11 @@ var ThreadService = class {
|
|
|
22745
23059
|
if (record.status === "failed" || record.status === "interrupted") {
|
|
22746
23060
|
return false;
|
|
22747
23061
|
}
|
|
22748
|
-
if (this.runtimeSupportsLiveRunningTurnInput(record.provider)) {
|
|
22749
|
-
return false;
|
|
22750
|
-
}
|
|
22751
23062
|
const activeDisplayTurnId = this.liveState.displayTurnIdForRuntimeTurn(
|
|
22752
23063
|
localThreadId,
|
|
22753
23064
|
record.providerTurnId
|
|
22754
23065
|
);
|
|
22755
|
-
return record.providerTurnId === turnId || activeDisplayTurnId === turnId;
|
|
23066
|
+
return (record.providerTurnId === turnId || activeDisplayTurnId === turnId) && this.auxiliaryState.hasQueuedContinuationsForTurn(localThreadId, turnId);
|
|
22756
23067
|
}
|
|
22757
23068
|
scheduleQueuedContinuationDrain(localThreadId, turnId) {
|
|
22758
23069
|
const key = `${localThreadId}:${turnId}`;
|
|
@@ -22776,7 +23087,7 @@ var ThreadService = class {
|
|
|
22776
23087
|
const pending = this.auxiliaryState.listPendingSteerRecordsForTurn(
|
|
22777
23088
|
localThreadId,
|
|
22778
23089
|
turnId
|
|
22779
|
-
)
|
|
23090
|
+
).find((entry) => entry.delivery === "continuation");
|
|
22780
23091
|
if (!pending) {
|
|
22781
23092
|
return;
|
|
22782
23093
|
}
|
|
@@ -22795,7 +23106,8 @@ var ThreadService = class {
|
|
|
22795
23106
|
const developerInstructions = combineDeveloperInstructions([
|
|
22796
23107
|
pluginDeveloperInstructions(this.pluginService)
|
|
22797
23108
|
]);
|
|
22798
|
-
const
|
|
23109
|
+
const queuedConfig = parseQueuedTurnConfig(pending.turnConfigJson);
|
|
23110
|
+
const turnConfig = queuedConfig ?? await this.sessionCoordinator.resolvePromptTurnConfig({
|
|
22799
23111
|
provider: record.provider,
|
|
22800
23112
|
currentModel: record.model,
|
|
22801
23113
|
currentReasoningEffort: record.reasoningEffort,
|
|
@@ -22805,14 +23117,16 @@ var ThreadService = class {
|
|
|
22805
23117
|
approvalMode: record.approvalMode ?? "yolo",
|
|
22806
23118
|
promptInput: {}
|
|
22807
23119
|
});
|
|
22808
|
-
|
|
22809
|
-
|
|
22810
|
-
|
|
22811
|
-
|
|
22812
|
-
|
|
22813
|
-
|
|
22814
|
-
|
|
22815
|
-
|
|
23120
|
+
if (!queuedConfig?.startNewTurn) {
|
|
23121
|
+
const queuedUserItemId = `queued-continuation:${pending.id}:user`;
|
|
23122
|
+
this.historyPersistence.persistProjectedHistoryItem(localThreadId, turnId, {
|
|
23123
|
+
id: queuedUserItemId,
|
|
23124
|
+
kind: "userMessage",
|
|
23125
|
+
text: pending.displayPrompt,
|
|
23126
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
23127
|
+
sequence: this.liveState.recordTurnItemOrder(localThreadId, turnId, queuedUserItemId)
|
|
23128
|
+
});
|
|
23129
|
+
}
|
|
22816
23130
|
await this.promptTurnCoordinator.startPromptTurn(localThreadId, {
|
|
22817
23131
|
...record,
|
|
22818
23132
|
providerSessionId
|
|
@@ -22826,8 +23140,7 @@ var ThreadService = class {
|
|
|
22826
23140
|
sandboxMode: turnConfig.sandboxMode,
|
|
22827
23141
|
performanceMode: turnConfig.performanceMode,
|
|
22828
23142
|
workspacePath: workspace.absPath,
|
|
22829
|
-
hidden: true,
|
|
22830
|
-
displayTurnId: turnId
|
|
23143
|
+
...queuedConfig?.startNewTurn ? {} : { hidden: true, displayTurnId: turnId }
|
|
22831
23144
|
});
|
|
22832
23145
|
this.auxiliaryState.deletePendingSteerRecord(localThreadId, pending.id, turnId);
|
|
22833
23146
|
}
|
|
@@ -22915,6 +23228,27 @@ var ThreadService = class {
|
|
|
22915
23228
|
);
|
|
22916
23229
|
}
|
|
22917
23230
|
};
|
|
23231
|
+
function parseQueuedTurnConfig(value) {
|
|
23232
|
+
if (!value) {
|
|
23233
|
+
return null;
|
|
23234
|
+
}
|
|
23235
|
+
try {
|
|
23236
|
+
const parsed = JSON.parse(value);
|
|
23237
|
+
if (parsed.collaborationMode !== "default" && parsed.collaborationMode !== "plan" || parsed.performanceMode !== "fast" && parsed.performanceMode !== "standard" || typeof parsed.startNewTurn !== "boolean") {
|
|
23238
|
+
return null;
|
|
23239
|
+
}
|
|
23240
|
+
return {
|
|
23241
|
+
effectiveModel: typeof parsed.effectiveModel === "string" ? parsed.effectiveModel : null,
|
|
23242
|
+
normalizedReasoning: parsed.normalizedReasoning ?? null,
|
|
23243
|
+
collaborationMode: parsed.collaborationMode,
|
|
23244
|
+
sandboxMode: parsed.sandboxMode ?? "workspace-write",
|
|
23245
|
+
performanceMode: parsed.performanceMode,
|
|
23246
|
+
startNewTurn: parsed.startNewTurn
|
|
23247
|
+
};
|
|
23248
|
+
} catch {
|
|
23249
|
+
return null;
|
|
23250
|
+
}
|
|
23251
|
+
}
|
|
22918
23252
|
|
|
22919
23253
|
// src/routes/agent-runtimes.ts
|
|
22920
23254
|
import fs15 from "fs/promises";
|
|
@@ -22987,6 +23321,17 @@ async function registerAgentRuntimeRoutes(app2) {
|
|
|
22987
23321
|
const { provider: provider2 } = providerParamSchema.parse(request.params);
|
|
22988
23322
|
return runtimeDto(app2, provider2);
|
|
22989
23323
|
});
|
|
23324
|
+
app2.get("/api/agent-runtimes/:provider/subscription-usage", async (request) => {
|
|
23325
|
+
const { provider: provider2 } = providerParamSchema.parse(request.params);
|
|
23326
|
+
const runtime = app2.services.agentRuntimes.getOptional(provider2);
|
|
23327
|
+
if (!runtime) {
|
|
23328
|
+
throw providerNotConfigured(provider2);
|
|
23329
|
+
}
|
|
23330
|
+
if (!runtime.getSubscriptionUsage) {
|
|
23331
|
+
return { usage: null };
|
|
23332
|
+
}
|
|
23333
|
+
return { usage: await runtime.getSubscriptionUsage() };
|
|
23334
|
+
});
|
|
22990
23335
|
app2.post("/api/agent-runtimes/:provider/restart", async (request) => {
|
|
22991
23336
|
const { provider: provider2 } = providerParamSchema.parse(request.params);
|
|
22992
23337
|
const runtime = app2.services.agentRuntimes.getOptional(provider2);
|
|
@@ -23393,9 +23738,11 @@ function parseProviderHostFileParams(params) {
|
|
|
23393
23738
|
}
|
|
23394
23739
|
async function registerSystemRoutes(app2) {
|
|
23395
23740
|
app2.get("/healthz", async () => {
|
|
23741
|
+
const activeTurnCount = app2.services.database.sqlite.prepare("SELECT COUNT(*) AS count FROM threads WHERE status = 'running'").get();
|
|
23396
23742
|
return {
|
|
23397
23743
|
status: "ok",
|
|
23398
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
23744
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
23745
|
+
activeTurnCount: activeTurnCount.count
|
|
23399
23746
|
};
|
|
23400
23747
|
});
|
|
23401
23748
|
app2.get("/readyz", async () => {
|
|
@@ -25017,7 +25364,16 @@ async function registerWorkspaceRoutes(app2) {
|
|
|
25017
25364
|
await cloneRepository(body.gitUrl.trim(), targetPath);
|
|
25018
25365
|
validated = await validateWorkspacePath(app2.services.config.workspaceRoot, targetPath);
|
|
25019
25366
|
} else {
|
|
25020
|
-
|
|
25367
|
+
const requestedPath = body.absPath.trim();
|
|
25368
|
+
const isWorkspaceName = !path22.isAbsolute(requestedPath) && /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(requestedPath) && requestedPath !== "." && requestedPath !== "..";
|
|
25369
|
+
if (!path22.isAbsolute(requestedPath) && !isWorkspaceName) {
|
|
25370
|
+
throw new HttpError(400, {
|
|
25371
|
+
code: "bad_request",
|
|
25372
|
+
message: "Use a simple directory name, an absolute path, or a Git URL."
|
|
25373
|
+
});
|
|
25374
|
+
}
|
|
25375
|
+
const targetPath = isWorkspaceName ? path22.join(settings.devHome, requestedPath) : requestedPath;
|
|
25376
|
+
validated = await validateWorkspacePath(app2.services.config.workspaceRoot, targetPath, {
|
|
25021
25377
|
devHome: settings.devHome,
|
|
25022
25378
|
createMissingLeaf: true
|
|
25023
25379
|
});
|
|
@@ -27969,6 +28325,7 @@ var RelayTunnelClient = class {
|
|
|
27969
28325
|
reconnectDelayMs = RELAY_RECONNECT_INITIAL_DELAY_MS;
|
|
27970
28326
|
stopped = false;
|
|
27971
28327
|
relayClientCleanup = /* @__PURE__ */ new Map();
|
|
28328
|
+
pendingActivity = /* @__PURE__ */ new Map();
|
|
27972
28329
|
validateConfig() {
|
|
27973
28330
|
if (!this.config.serverUrl || !this.config.agentToken) {
|
|
27974
28331
|
throw new Error(
|
|
@@ -27983,7 +28340,10 @@ var RelayTunnelClient = class {
|
|
|
27983
28340
|
if (this.socket) {
|
|
27984
28341
|
return;
|
|
27985
28342
|
}
|
|
27986
|
-
const url = new URL(
|
|
28343
|
+
const url = new URL(
|
|
28344
|
+
"/supervisor/tunnel",
|
|
28345
|
+
this.config.serverUrl ?? void 0
|
|
28346
|
+
);
|
|
27987
28347
|
url.searchParams.set("token", this.config.agentToken ?? "");
|
|
27988
28348
|
url.searchParams.set("deviceToken", this.config.agentToken ?? "");
|
|
27989
28349
|
const socket = new WebSocket(url);
|
|
@@ -27997,6 +28357,7 @@ var RelayTunnelClient = class {
|
|
|
27997
28357
|
this.clearConnectTimeout();
|
|
27998
28358
|
this.reconnectDelayMs = RELAY_RECONNECT_INITIAL_DELAY_MS;
|
|
27999
28359
|
this.sendHeartbeat();
|
|
28360
|
+
this.flushPendingActivity();
|
|
28000
28361
|
this.clearHeartbeat();
|
|
28001
28362
|
this.heartbeatHandle = setInterval(() => {
|
|
28002
28363
|
this.sendHeartbeat();
|
|
@@ -28021,6 +28382,29 @@ var RelayTunnelClient = class {
|
|
|
28021
28382
|
this.socket?.close();
|
|
28022
28383
|
this.socket = null;
|
|
28023
28384
|
}
|
|
28385
|
+
sendActivity(payload) {
|
|
28386
|
+
const key = `${payload.threadId}\0${payload.turnId}`;
|
|
28387
|
+
const socket = this.socket;
|
|
28388
|
+
if (socket?.readyState !== WebSocket.OPEN) {
|
|
28389
|
+
this.pendingActivity.set(key, payload);
|
|
28390
|
+
return;
|
|
28391
|
+
}
|
|
28392
|
+
const sent = this.sendEnvelope(socket, {
|
|
28393
|
+
type: "relay.activity",
|
|
28394
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
28395
|
+
payload
|
|
28396
|
+
});
|
|
28397
|
+
if (sent) {
|
|
28398
|
+
this.pendingActivity.delete(key);
|
|
28399
|
+
} else {
|
|
28400
|
+
this.pendingActivity.set(key, payload);
|
|
28401
|
+
}
|
|
28402
|
+
}
|
|
28403
|
+
flushPendingActivity() {
|
|
28404
|
+
for (const payload of this.pendingActivity.values()) {
|
|
28405
|
+
this.sendActivity(payload);
|
|
28406
|
+
}
|
|
28407
|
+
}
|
|
28024
28408
|
sendHeartbeat() {
|
|
28025
28409
|
const socket = this.socket;
|
|
28026
28410
|
if (socket?.readyState !== WebSocket.OPEN) {
|
|
@@ -28040,9 +28424,12 @@ var RelayTunnelClient = class {
|
|
|
28040
28424
|
}
|
|
28041
28425
|
if (parsed.type !== "relay.request") {
|
|
28042
28426
|
if (parsed.type === "relay.client.connected") {
|
|
28043
|
-
const cleanup = this.handleClientConnected(
|
|
28044
|
-
|
|
28045
|
-
|
|
28427
|
+
const cleanup = this.handleClientConnected(
|
|
28428
|
+
parsed.clientId,
|
|
28429
|
+
(message) => {
|
|
28430
|
+
this.sendClientMessage(parsed.clientId, message);
|
|
28431
|
+
}
|
|
28432
|
+
);
|
|
28046
28433
|
this.relayClientCleanup.set(parsed.clientId, cleanup);
|
|
28047
28434
|
return;
|
|
28048
28435
|
}
|
|
@@ -28052,9 +28439,13 @@ var RelayTunnelClient = class {
|
|
|
28052
28439
|
return;
|
|
28053
28440
|
}
|
|
28054
28441
|
if (parsed.type === "relay.client.message") {
|
|
28055
|
-
await this.handleClientMessage(
|
|
28056
|
-
|
|
28057
|
-
|
|
28442
|
+
await this.handleClientMessage(
|
|
28443
|
+
parsed.clientId,
|
|
28444
|
+
parsed.payload,
|
|
28445
|
+
(message) => {
|
|
28446
|
+
this.sendClientMessage(parsed.clientId, message);
|
|
28447
|
+
}
|
|
28448
|
+
);
|
|
28058
28449
|
return;
|
|
28059
28450
|
}
|
|
28060
28451
|
return;
|
|
@@ -28064,15 +28455,12 @@ var RelayTunnelClient = class {
|
|
|
28064
28455
|
if (socket?.readyState !== WebSocket.OPEN) {
|
|
28065
28456
|
return;
|
|
28066
28457
|
}
|
|
28067
|
-
this.sendEnvelope(
|
|
28068
|
-
|
|
28069
|
-
|
|
28070
|
-
|
|
28071
|
-
|
|
28072
|
-
|
|
28073
|
-
payload: response
|
|
28074
|
-
}
|
|
28075
|
-
);
|
|
28458
|
+
this.sendEnvelope(socket, {
|
|
28459
|
+
type: "relay.response",
|
|
28460
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
28461
|
+
requestId: parsed.requestId,
|
|
28462
|
+
payload: response
|
|
28463
|
+
});
|
|
28076
28464
|
}
|
|
28077
28465
|
sendClientMessage(clientId, message) {
|
|
28078
28466
|
const socket = this.socket;
|
|
@@ -28089,8 +28477,10 @@ var RelayTunnelClient = class {
|
|
|
28089
28477
|
sendEnvelope(socket, message) {
|
|
28090
28478
|
try {
|
|
28091
28479
|
socket.send(JSON.stringify(message));
|
|
28480
|
+
return true;
|
|
28092
28481
|
} catch {
|
|
28093
28482
|
this.closeAndReconnect(socket);
|
|
28483
|
+
return false;
|
|
28094
28484
|
}
|
|
28095
28485
|
}
|
|
28096
28486
|
closeAndReconnect(socket) {
|
|
@@ -28158,10 +28548,7 @@ var DEFAULT_WEBVIEW_CORS_ORIGINS = /* @__PURE__ */ new Set([
|
|
|
28158
28548
|
"https://localhost",
|
|
28159
28549
|
"https://appassets.androidplatform.net"
|
|
28160
28550
|
]);
|
|
28161
|
-
var WEBVIEW_CORS_ALLOW_HEADERS = [
|
|
28162
|
-
"authorization",
|
|
28163
|
-
"content-type"
|
|
28164
|
-
].join(", ");
|
|
28551
|
+
var WEBVIEW_CORS_ALLOW_HEADERS = ["authorization", "content-type"].join(", ");
|
|
28165
28552
|
var WEBVIEW_CORS_ALLOW_METHODS = [
|
|
28166
28553
|
"GET",
|
|
28167
28554
|
"POST",
|
|
@@ -28175,7 +28562,9 @@ function webViewCorsOrigins(env) {
|
|
|
28175
28562
|
return null;
|
|
28176
28563
|
}
|
|
28177
28564
|
const configured = env.REMOTE_CODEX_WEBVIEW_CORS_ORIGINS?.split(",").map((origin) => origin.trim()).filter(Boolean);
|
|
28178
|
-
return new Set(
|
|
28565
|
+
return new Set(
|
|
28566
|
+
configured?.length ? configured : DEFAULT_WEBVIEW_CORS_ORIGINS
|
|
28567
|
+
);
|
|
28179
28568
|
}
|
|
28180
28569
|
function applyWebViewCorsHeaders(reply, origin) {
|
|
28181
28570
|
reply.header("access-control-allow-origin", origin);
|
|
@@ -28224,7 +28613,11 @@ function createServiceLifecycle() {
|
|
|
28224
28613
|
});
|
|
28225
28614
|
}
|
|
28226
28615
|
const repoRoot = findRepoRoot();
|
|
28227
|
-
const restartScript = path28.join(
|
|
28616
|
+
const restartScript = path28.join(
|
|
28617
|
+
repoRoot,
|
|
28618
|
+
"scripts",
|
|
28619
|
+
"service-restart.mjs"
|
|
28620
|
+
);
|
|
28228
28621
|
if (!fs26.existsSync(restartScript) || !fs26.existsSync(path28.join(repoRoot, "pnpm-workspace.yaml"))) {
|
|
28229
28622
|
throw new HttpError(503, {
|
|
28230
28623
|
code: "service_unavailable",
|
|
@@ -28284,7 +28677,9 @@ function buildApp(options = {}) {
|
|
|
28284
28677
|
},
|
|
28285
28678
|
disableRequestLogging: config.disableRequestLogging
|
|
28286
28679
|
});
|
|
28287
|
-
const allowedWebViewCorsOrigins = webViewCorsOrigins(
|
|
28680
|
+
const allowedWebViewCorsOrigins = webViewCorsOrigins(
|
|
28681
|
+
options.env ?? process.env
|
|
28682
|
+
);
|
|
28288
28683
|
app2.addHook("onRequest", async (request, reply) => {
|
|
28289
28684
|
if (!allowedWebViewCorsOrigins) {
|
|
28290
28685
|
return;
|
|
@@ -28298,15 +28693,22 @@ function buildApp(options = {}) {
|
|
|
28298
28693
|
return reply.code(204).send();
|
|
28299
28694
|
}
|
|
28300
28695
|
});
|
|
28301
|
-
app2.register(
|
|
28302
|
-
|
|
28303
|
-
|
|
28304
|
-
|
|
28696
|
+
app2.register(
|
|
28697
|
+
multipart,
|
|
28698
|
+
{
|
|
28699
|
+
limits: {
|
|
28700
|
+
files: MAX_PROMPT_ATTACHMENTS2,
|
|
28701
|
+
fileSize: MAX_PROMPT_ATTACHMENT_BYTES2
|
|
28702
|
+
}
|
|
28305
28703
|
}
|
|
28306
|
-
|
|
28704
|
+
);
|
|
28307
28705
|
const backendPluginHost = new BackendPluginHost(app2);
|
|
28308
28706
|
backendPluginHost.register(createTerminalPluginBackendContribution());
|
|
28309
|
-
const relaySocketBridge = createRelaySocketBridge(
|
|
28707
|
+
const relaySocketBridge = createRelaySocketBridge(
|
|
28708
|
+
app2,
|
|
28709
|
+
eventBus,
|
|
28710
|
+
backendPluginHost
|
|
28711
|
+
);
|
|
28310
28712
|
const relayTunnelClient = config.mode === "relay" ? options.relayTunnelClient ?? new RelayTunnelClient(
|
|
28311
28713
|
config.relay,
|
|
28312
28714
|
createRelayRequestHandler(app2),
|
|
@@ -28314,6 +28716,31 @@ function buildApp(options = {}) {
|
|
|
28314
28716
|
relaySocketBridge.handleMessage
|
|
28315
28717
|
) : null;
|
|
28316
28718
|
relayTunnelClient?.validateConfig();
|
|
28719
|
+
const cleanupRelayActivity = relayTunnelClient ? eventBus.onThreadEvent((event) => {
|
|
28720
|
+
if (event.type === "thread.turn.started") {
|
|
28721
|
+
relayTunnelClient.sendActivity({
|
|
28722
|
+
kind: "turn_started",
|
|
28723
|
+
threadId: event.threadId,
|
|
28724
|
+
turnId: event.payload.turnId
|
|
28725
|
+
});
|
|
28726
|
+
return;
|
|
28727
|
+
}
|
|
28728
|
+
if (event.type === "thread.turn.completed") {
|
|
28729
|
+
relayTunnelClient.sendActivity({
|
|
28730
|
+
kind: "turn_terminal",
|
|
28731
|
+
threadId: event.threadId,
|
|
28732
|
+
turnId: event.payload.turnId
|
|
28733
|
+
});
|
|
28734
|
+
return;
|
|
28735
|
+
}
|
|
28736
|
+
if (event.type === "thread.turn.failed" && event.payload.willRetry !== true) {
|
|
28737
|
+
relayTunnelClient.sendActivity({
|
|
28738
|
+
kind: "turn_terminal",
|
|
28739
|
+
threadId: event.threadId,
|
|
28740
|
+
turnId: event.payload.turnId
|
|
28741
|
+
});
|
|
28742
|
+
}
|
|
28743
|
+
}) : null;
|
|
28317
28744
|
app2.decorate("services", {
|
|
28318
28745
|
config,
|
|
28319
28746
|
database,
|
|
@@ -28494,6 +28921,7 @@ function buildApp(options = {}) {
|
|
|
28494
28921
|
});
|
|
28495
28922
|
});
|
|
28496
28923
|
app2.addHook("onClose", async () => {
|
|
28924
|
+
cleanupRelayActivity?.();
|
|
28497
28925
|
await shellService.stop();
|
|
28498
28926
|
relayTunnelClient?.stop();
|
|
28499
28927
|
await Promise.all(agentRuntimes.all().map((runtime) => runtime.stop()));
|
|
@@ -28666,6 +29094,21 @@ if (fs27.existsSync(".env")) {
|
|
|
28666
29094
|
}
|
|
28667
29095
|
var app = buildApp();
|
|
28668
29096
|
var { host, port } = app.services.config;
|
|
29097
|
+
var closing = false;
|
|
29098
|
+
async function shutdown(signal) {
|
|
29099
|
+
if (closing) return;
|
|
29100
|
+
closing = true;
|
|
29101
|
+
app.log.info(`Supervisor API received ${signal}; closing cleanly.`);
|
|
29102
|
+
try {
|
|
29103
|
+
await app.close();
|
|
29104
|
+
process.exit(0);
|
|
29105
|
+
} catch (error) {
|
|
29106
|
+
app.log.error(error);
|
|
29107
|
+
process.exit(1);
|
|
29108
|
+
}
|
|
29109
|
+
}
|
|
29110
|
+
process.once("SIGTERM", () => void shutdown("SIGTERM"));
|
|
29111
|
+
process.once("SIGINT", () => void shutdown("SIGINT"));
|
|
28669
29112
|
app.listen({ host, port }).then(() => {
|
|
28670
29113
|
app.log.info(`Supervisor API listening on http://${host}:${port}`);
|
|
28671
29114
|
}).catch((error) => {
|