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
|
@@ -10,16 +10,16 @@
|
|
|
10
10
|
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
|
11
11
|
<link rel="manifest" href="/site.webmanifest" />
|
|
12
12
|
<title>Remote Codex</title>
|
|
13
|
-
<script type="module" crossorigin src="/assets/index-
|
|
13
|
+
<script type="module" crossorigin src="/assets/index-CGHHTNkM.js"></script>
|
|
14
14
|
<link rel="modulepreload" crossorigin href="/assets/react-vendor-Dfg_6BLf.js">
|
|
15
15
|
<link rel="modulepreload" crossorigin href="/assets/ui-vendor-CuR8GHb0.js">
|
|
16
16
|
<link rel="modulepreload" crossorigin href="/assets/graph-vendor-DVQUpZ8C.js">
|
|
17
17
|
<link rel="modulepreload" crossorigin href="/assets/terminal-vendor-C5bTa-Ka.js">
|
|
18
18
|
<link rel="modulepreload" crossorigin href="/assets/markdown-vendor-RZk8L7-L.js">
|
|
19
|
-
<link rel="modulepreload" crossorigin href="/assets/thread-ui-
|
|
19
|
+
<link rel="modulepreload" crossorigin href="/assets/thread-ui-B9eC2H4u.js">
|
|
20
20
|
<link rel="stylesheet" crossorigin href="/assets/graph-vendor-C5ap-Sga.css">
|
|
21
21
|
<link rel="stylesheet" crossorigin href="/assets/terminal-vendor-Beg8tuEN.css">
|
|
22
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
22
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CdjTdnJt.css">
|
|
23
23
|
</head>
|
|
24
24
|
<body class="bg-stone-950">
|
|
25
25
|
<div id="root"></div>
|
package/bin/remote-codex.mjs
CHANGED
|
@@ -851,9 +851,9 @@ What it starts:
|
|
|
851
851
|
API http://SERVICE_API_HOST:SERVICE_API_PORT
|
|
852
852
|
|
|
853
853
|
Environment:
|
|
854
|
-
SERVICE_HOST Web listen host. Default
|
|
854
|
+
SERVICE_HOST Web listen host. Default 0.0.0.0.
|
|
855
855
|
SERVICE_PORT Web listen port. Default ${defaultServicePort}.
|
|
856
|
-
SERVICE_API_HOST API listen host. Default
|
|
856
|
+
SERVICE_API_HOST API listen host. Default 0.0.0.0.
|
|
857
857
|
SERVICE_API_PORT API listen port. Default ${defaultApiPort}.
|
|
858
858
|
REMOTE_CODEX_SERVICE_DIR Service state/log directory.
|
|
859
859
|
LOG_LEVEL API log level. Default warn for service mode.
|
|
@@ -871,7 +871,10 @@ Supervisor configuration forwarded to the API:
|
|
|
871
871
|
OPENCODE_COMMAND OpenCode executable. Default opencode.
|
|
872
872
|
|
|
873
873
|
Example:
|
|
874
|
-
|
|
874
|
+
remote-codex start
|
|
875
|
+
|
|
876
|
+
Restrict the service to this machine:
|
|
877
|
+
SERVICE_HOST=127.0.0.1 SERVICE_API_HOST=127.0.0.1 remote-codex start
|
|
875
878
|
|
|
876
879
|
Expose over Tailscale after start:
|
|
877
880
|
tailscale serve --bg http://127.0.0.1:${defaultServicePort}
|
package/package.json
CHANGED
|
@@ -582,4 +582,17 @@ export interface AgentRuntime extends EventEmitter {
|
|
|
582
582
|
getGoal?(providerSessionId: string): Promise<AgentGoal | null>;
|
|
583
583
|
setGoal?(input: SetAgentGoalInput): Promise<AgentGoal>;
|
|
584
584
|
clearGoal?(providerSessionId: string): Promise<boolean>;
|
|
585
|
+
getSubscriptionUsage?(): Promise<{
|
|
586
|
+
provider: 'codex' | 'claude';
|
|
587
|
+
authKind: 'subscription' | 'apiKey' | 'unknown';
|
|
588
|
+
observedAt: string;
|
|
589
|
+
stale: boolean;
|
|
590
|
+
windows: Array<{
|
|
591
|
+
id: string;
|
|
592
|
+
durationMinutes: number | null;
|
|
593
|
+
label: string;
|
|
594
|
+
usedPercent: number;
|
|
595
|
+
resetsAt: string | null;
|
|
596
|
+
}>;
|
|
597
|
+
} | null>;
|
|
585
598
|
}
|
|
@@ -208,6 +208,70 @@ function makeAdapter(
|
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
describe('ClaudeRuntimeAdapter', () => {
|
|
211
|
+
it('captures Claude subscription rate-limit windows from SDK events', async () => {
|
|
212
|
+
const adapter = makeAdapter(() => [
|
|
213
|
+
systemInit(),
|
|
214
|
+
{
|
|
215
|
+
type: 'rate_limit_event',
|
|
216
|
+
rate_limit_info: {
|
|
217
|
+
status: 'allowed',
|
|
218
|
+
rateLimitType: 'five_hour',
|
|
219
|
+
utilization: 0.23,
|
|
220
|
+
resetsAt: 1_800_000_000,
|
|
221
|
+
},
|
|
222
|
+
uuid: '00000000-0000-4000-8000-000000000003',
|
|
223
|
+
session_id: 'claude-session-1',
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
type: 'rate_limit_event',
|
|
227
|
+
rate_limit_info: {
|
|
228
|
+
status: 'allowed_warning',
|
|
229
|
+
rateLimitType: 'seven_day',
|
|
230
|
+
utilization: 0.84,
|
|
231
|
+
resetsAt: 1_800_086_400,
|
|
232
|
+
},
|
|
233
|
+
uuid: '00000000-0000-4000-8000-000000000004',
|
|
234
|
+
session_id: 'claude-session-1',
|
|
235
|
+
},
|
|
236
|
+
result(),
|
|
237
|
+
]);
|
|
238
|
+
|
|
239
|
+
await adapter.start();
|
|
240
|
+
await expect(adapter.getSubscriptionUsage()).resolves.toMatchObject({
|
|
241
|
+
provider: 'claude',
|
|
242
|
+
authKind: 'unknown',
|
|
243
|
+
windows: [],
|
|
244
|
+
});
|
|
245
|
+
await adapter.startSession({
|
|
246
|
+
cwd: '/tmp/workspace',
|
|
247
|
+
model: 'sonnet',
|
|
248
|
+
approvalMode: 'guarded',
|
|
249
|
+
sandboxMode: 'workspace-write',
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
await expect(adapter.getSubscriptionUsage()).resolves.toMatchObject({
|
|
253
|
+
provider: 'claude',
|
|
254
|
+
authKind: 'subscription',
|
|
255
|
+
stale: false,
|
|
256
|
+
windows: [
|
|
257
|
+
{
|
|
258
|
+
id: 'five_hour',
|
|
259
|
+
durationMinutes: 300,
|
|
260
|
+
label: '5h',
|
|
261
|
+
usedPercent: 23,
|
|
262
|
+
resetsAt: new Date(1_800_000_000 * 1000).toISOString(),
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
id: 'seven_day',
|
|
266
|
+
durationMinutes: 10_080,
|
|
267
|
+
label: '7d',
|
|
268
|
+
usedPercent: 84,
|
|
269
|
+
resetsAt: new Date(1_800_086_400 * 1000).toISOString(),
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
211
275
|
it('passes the configured Claude executable to the SDK', async () => {
|
|
212
276
|
const sdkOptions: Record<string, unknown>[] = [];
|
|
213
277
|
const adapter = new ClaudeRuntimeAdapter({
|
|
@@ -119,6 +119,19 @@ interface SDKMessage {
|
|
|
119
119
|
modelUsage?: unknown;
|
|
120
120
|
errors?: string[];
|
|
121
121
|
stop_reason?: string;
|
|
122
|
+
rate_limit_info?: SDKRateLimitInfo;
|
|
123
|
+
}
|
|
124
|
+
interface SDKRateLimitInfo {
|
|
125
|
+
status: 'allowed' | 'allowed_warning' | 'rejected';
|
|
126
|
+
resetsAt?: number;
|
|
127
|
+
rateLimitType?:
|
|
128
|
+
| 'five_hour'
|
|
129
|
+
| 'seven_day'
|
|
130
|
+
| 'seven_day_opus'
|
|
131
|
+
| 'seven_day_sonnet'
|
|
132
|
+
| 'seven_day_overage_included'
|
|
133
|
+
| 'overage';
|
|
134
|
+
utilization?: number;
|
|
122
135
|
}
|
|
123
136
|
interface SDKUserMessage {
|
|
124
137
|
type: 'user';
|
|
@@ -1222,6 +1235,11 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
|
|
|
1222
1235
|
private readonly sessionModels = new Map<string, string | null>();
|
|
1223
1236
|
private readonly sessionApprovalModes = new Map<string, StartAgentSessionInput['approvalMode']>();
|
|
1224
1237
|
private readonly liveUserPrompts = new Map<string, Map<string, string>>();
|
|
1238
|
+
private readonly subscriptionUsageWindows = new Map<
|
|
1239
|
+
'five_hour' | 'seven_day',
|
|
1240
|
+
{ usedPercent: number; resetsAt: string | null }
|
|
1241
|
+
>();
|
|
1242
|
+
private subscriptionUsageObservedAt: string | null = null;
|
|
1225
1243
|
private readonly clientApp: string;
|
|
1226
1244
|
private sdkLoadError: string | null = null;
|
|
1227
1245
|
|
|
@@ -1242,6 +1260,46 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
|
|
|
1242
1260
|
return { ...this.status };
|
|
1243
1261
|
}
|
|
1244
1262
|
|
|
1263
|
+
async getSubscriptionUsage() {
|
|
1264
|
+
const observedAt = this.subscriptionUsageObservedAt ?? new Date().toISOString();
|
|
1265
|
+
const windows = [...this.subscriptionUsageWindows.entries()].map(([id, window]) => ({
|
|
1266
|
+
id,
|
|
1267
|
+
durationMinutes: id === 'five_hour' ? 300 : 10_080,
|
|
1268
|
+
label: id === 'five_hour' ? '5h' : '7d',
|
|
1269
|
+
usedPercent: window.usedPercent,
|
|
1270
|
+
resetsAt: window.resetsAt,
|
|
1271
|
+
}));
|
|
1272
|
+
return {
|
|
1273
|
+
provider: 'claude' as const,
|
|
1274
|
+
authKind: windows.length > 0 ? 'subscription' as const : 'unknown' as const,
|
|
1275
|
+
observedAt,
|
|
1276
|
+
stale: false,
|
|
1277
|
+
windows,
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
private captureRateLimit(message: SDKMessage) {
|
|
1282
|
+
if (message.type !== 'rate_limit_event') {
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
const info = message.rate_limit_info;
|
|
1286
|
+
if (
|
|
1287
|
+
!info
|
|
1288
|
+
|| (info.rateLimitType !== 'five_hour' && info.rateLimitType !== 'seven_day')
|
|
1289
|
+
|| typeof info.utilization !== 'number'
|
|
1290
|
+
|| !Number.isFinite(info.utilization)
|
|
1291
|
+
) {
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
this.subscriptionUsageWindows.set(info.rateLimitType, {
|
|
1295
|
+
usedPercent: Math.max(0, Math.min(100, info.utilization * 100)),
|
|
1296
|
+
resetsAt: typeof info.resetsAt === 'number'
|
|
1297
|
+
? new Date(info.resetsAt * 1000).toISOString()
|
|
1298
|
+
: null,
|
|
1299
|
+
});
|
|
1300
|
+
this.subscriptionUsageObservedAt = new Date().toISOString();
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1245
1303
|
private updateToolboxItemsFromSystemInit(message: SDKMessage) {
|
|
1246
1304
|
this.managementSchema.toolboxItems = buildClaudeToolboxItems(
|
|
1247
1305
|
normalizeClaudeSlashCommands(message.slash_commands),
|
|
@@ -1408,6 +1466,7 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
|
|
|
1408
1466
|
try {
|
|
1409
1467
|
for await (const message of query) {
|
|
1410
1468
|
rawMessages.push(message);
|
|
1469
|
+
this.captureRateLimit(message);
|
|
1411
1470
|
if (message.type === 'system' && message.subtype === 'init') {
|
|
1412
1471
|
this.updateToolboxItemsFromSystemInit(message);
|
|
1413
1472
|
const sessionId = message.session_id;
|
|
@@ -1699,6 +1758,7 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
|
|
|
1699
1758
|
try {
|
|
1700
1759
|
for await (const message of state.query) {
|
|
1701
1760
|
rawMessages.push(message);
|
|
1761
|
+
this.captureRateLimit(message);
|
|
1702
1762
|
this.consumeMessage(state, message);
|
|
1703
1763
|
const status = queryResultStatus(message);
|
|
1704
1764
|
if (status) {
|
|
@@ -320,6 +320,22 @@ export class CodexAppServerManager extends EventEmitter {
|
|
|
320
320
|
return response.data.map(mapModel);
|
|
321
321
|
}
|
|
322
322
|
|
|
323
|
+
async readAccount() {
|
|
324
|
+
await this.ensureReady();
|
|
325
|
+
return this.client!.request<{
|
|
326
|
+
account: { type: string } | null;
|
|
327
|
+
requiresOpenaiAuth: boolean;
|
|
328
|
+
}>('account/read', { refreshToken: false });
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async readAccountRateLimits() {
|
|
332
|
+
await this.ensureReady();
|
|
333
|
+
return this.client!.request<{
|
|
334
|
+
rateLimits: unknown;
|
|
335
|
+
rateLimitsByLimitId?: Record<string, unknown> | null;
|
|
336
|
+
}>('account/rateLimits/read', null);
|
|
337
|
+
}
|
|
338
|
+
|
|
323
339
|
async listThreads(): Promise<CodexThreadRecord[]> {
|
|
324
340
|
await this.ensureReady();
|
|
325
341
|
const response = await this.client!.request<{ data: any[] }>('thread/list', {
|
|
@@ -27,6 +27,22 @@ class FakeCodexManager extends EventEmitter {
|
|
|
27
27
|
items: [],
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
|
+
|
|
31
|
+
async readAccount() {
|
|
32
|
+
return { account: { type: 'chatgpt' }, requiresOpenaiAuth: true };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async readAccountRateLimits() {
|
|
36
|
+
return {
|
|
37
|
+
rateLimits: {},
|
|
38
|
+
rateLimitsByLimitId: {
|
|
39
|
+
codex: {
|
|
40
|
+
primary: { usedPercent: 40, windowDurationMins: 300, resetsAt: 1_800_000_000 },
|
|
41
|
+
secondary: { usedPercent: 75, windowDurationMins: 10_080, resetsAt: 1_800_604_800 },
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
30
46
|
}
|
|
31
47
|
|
|
32
48
|
describe('CodexRuntimeAdapter', () => {
|
|
@@ -73,4 +89,32 @@ describe('CodexRuntimeAdapter', () => {
|
|
|
73
89
|
{ type: 'text', text: '.', text_elements: [] },
|
|
74
90
|
]);
|
|
75
91
|
});
|
|
92
|
+
|
|
93
|
+
it('maps ChatGPT rate-limit windows without assuming fixed durations', async () => {
|
|
94
|
+
const adapter = new CodexRuntimeAdapter(new FakeCodexManager() as never);
|
|
95
|
+
|
|
96
|
+
await expect(adapter.getSubscriptionUsage()).resolves.toMatchObject({
|
|
97
|
+
provider: 'codex',
|
|
98
|
+
authKind: 'subscription',
|
|
99
|
+
stale: false,
|
|
100
|
+
windows: [
|
|
101
|
+
{ id: 'primary', label: '5h', durationMinutes: 300, usedPercent: 40 },
|
|
102
|
+
{ id: 'secondary', label: '7d', durationMinutes: 10_080, usedPercent: 75 },
|
|
103
|
+
],
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('hides subscription windows for API-key authentication', async () => {
|
|
108
|
+
const manager = new FakeCodexManager();
|
|
109
|
+
manager.readAccount = async () => ({
|
|
110
|
+
account: { type: 'apiKey' },
|
|
111
|
+
requiresOpenaiAuth: true,
|
|
112
|
+
});
|
|
113
|
+
const adapter = new CodexRuntimeAdapter(manager as never);
|
|
114
|
+
|
|
115
|
+
await expect(adapter.getSubscriptionUsage()).resolves.toMatchObject({
|
|
116
|
+
authKind: 'apiKey',
|
|
117
|
+
windows: [],
|
|
118
|
+
});
|
|
119
|
+
});
|
|
76
120
|
});
|
|
@@ -484,6 +484,20 @@ function mapCodexNotification(event: CodexServerEvent): AgentRuntimeEvent | null
|
|
|
484
484
|
}
|
|
485
485
|
}
|
|
486
486
|
|
|
487
|
+
function formatRateLimitWindowLabel(
|
|
488
|
+
durationMinutes: number | null,
|
|
489
|
+
fallback: string,
|
|
490
|
+
) {
|
|
491
|
+
if (durationMinutes === null) return fallback;
|
|
492
|
+
if (durationMinutes % (60 * 24) === 0) {
|
|
493
|
+
return `${durationMinutes / (60 * 24)}d`;
|
|
494
|
+
}
|
|
495
|
+
if (durationMinutes % 60 === 0) {
|
|
496
|
+
return `${durationMinutes / 60}h`;
|
|
497
|
+
}
|
|
498
|
+
return `${durationMinutes}m`;
|
|
499
|
+
}
|
|
500
|
+
|
|
487
501
|
function mapCodexRuntimeError(error: unknown): never {
|
|
488
502
|
if (error instanceof AgentRuntimeError) {
|
|
489
503
|
throw error;
|
|
@@ -617,6 +631,58 @@ export class CodexRuntimeAdapter extends EventEmitter implements AgentRuntime {
|
|
|
617
631
|
return mapStatus(this.manager.getStatus());
|
|
618
632
|
}
|
|
619
633
|
|
|
634
|
+
async getSubscriptionUsage() {
|
|
635
|
+
const account = await codexRuntimeCall(() => this.manager.readAccount());
|
|
636
|
+
if (account.account?.type === 'apiKey') {
|
|
637
|
+
return {
|
|
638
|
+
provider: 'codex' as const,
|
|
639
|
+
authKind: 'apiKey' as const,
|
|
640
|
+
observedAt: new Date().toISOString(),
|
|
641
|
+
stale: false,
|
|
642
|
+
windows: [],
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
if (account.account?.type !== 'chatgpt') {
|
|
646
|
+
return null;
|
|
647
|
+
}
|
|
648
|
+
const response = await codexRuntimeCall(() =>
|
|
649
|
+
this.manager.readAccountRateLimits(),
|
|
650
|
+
);
|
|
651
|
+
const buckets = response.rateLimitsByLimitId;
|
|
652
|
+
const snapshot =
|
|
653
|
+
(buckets && (buckets.codex ?? Object.values(buckets)[0])) ??
|
|
654
|
+
response.rateLimits;
|
|
655
|
+
const record = snapshot && typeof snapshot === 'object'
|
|
656
|
+
? (snapshot as Record<string, unknown>)
|
|
657
|
+
: {};
|
|
658
|
+
const windows = ['primary', 'secondary'].flatMap((id) => {
|
|
659
|
+
const value = record[id];
|
|
660
|
+
if (!value || typeof value !== 'object') return [];
|
|
661
|
+
const window = value as Record<string, unknown>;
|
|
662
|
+
const usedPercent = Number(window.usedPercent);
|
|
663
|
+
if (!Number.isFinite(usedPercent)) return [];
|
|
664
|
+
const duration = Number(window.windowDurationMins);
|
|
665
|
+
const durationMinutes = Number.isFinite(duration) ? duration : null;
|
|
666
|
+
const resetsAt = Number(window.resetsAt);
|
|
667
|
+
return [{
|
|
668
|
+
id,
|
|
669
|
+
durationMinutes,
|
|
670
|
+
label: formatRateLimitWindowLabel(durationMinutes, id),
|
|
671
|
+
usedPercent: Math.max(0, Math.min(100, usedPercent)),
|
|
672
|
+
resetsAt: Number.isFinite(resetsAt)
|
|
673
|
+
? new Date(resetsAt * 1000).toISOString()
|
|
674
|
+
: null,
|
|
675
|
+
}];
|
|
676
|
+
});
|
|
677
|
+
return {
|
|
678
|
+
provider: 'codex' as const,
|
|
679
|
+
authKind: 'subscription' as const,
|
|
680
|
+
observedAt: new Date().toISOString(),
|
|
681
|
+
stale: false,
|
|
682
|
+
windows,
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
|
|
620
686
|
start() {
|
|
621
687
|
return codexRuntimeCall(() => this.manager.start());
|
|
622
688
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
ALTER TABLE threads ADD COLUMN active_turn_collaboration_mode TEXT;
|
|
2
|
+
|
|
3
|
+
ALTER TABLE thread_pending_steers
|
|
4
|
+
ADD COLUMN delivery TEXT NOT NULL DEFAULT 'steer';
|
|
5
|
+
|
|
6
|
+
ALTER TABLE thread_pending_steers
|
|
7
|
+
ADD COLUMN turn_config_json TEXT;
|
|
8
|
+
|
|
9
|
+
CREATE TABLE IF NOT EXISTS thread_prompt_requests (
|
|
10
|
+
id TEXT PRIMARY KEY,
|
|
11
|
+
thread_id TEXT NOT NULL,
|
|
12
|
+
client_request_id TEXT NOT NULL,
|
|
13
|
+
status TEXT NOT NULL,
|
|
14
|
+
created_at TEXT NOT NULL,
|
|
15
|
+
updated_at TEXT NOT NULL
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
CREATE UNIQUE INDEX IF NOT EXISTS thread_prompt_requests_thread_client_request_idx
|
|
19
|
+
ON thread_prompt_requests(thread_id, client_request_id);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
|
|
3
|
-
import { and, desc, eq, inArray } from 'drizzle-orm';
|
|
3
|
+
import { and, desc, eq, inArray, lt } from 'drizzle-orm';
|
|
4
4
|
|
|
5
5
|
import { DatabaseClient } from './client';
|
|
6
6
|
import { getDefaultHostRecord } from './client';
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
threadGoals,
|
|
13
13
|
threadHistoryItems,
|
|
14
14
|
threadPendingSteers,
|
|
15
|
+
threadPromptRequests,
|
|
15
16
|
threadTurnMetadata,
|
|
16
17
|
threads,
|
|
17
18
|
viewerSessions,
|
|
@@ -36,6 +37,7 @@ export interface CreateThreadRecordInput {
|
|
|
36
37
|
fastBaseModel?: string | null;
|
|
37
38
|
fastBaseReasoningEffort?: string | null;
|
|
38
39
|
collaborationMode?: string;
|
|
40
|
+
activeTurnCollaborationMode?: string | null;
|
|
39
41
|
approvalMode: string;
|
|
40
42
|
sandboxMode?: string | null;
|
|
41
43
|
summaryText?: string | null;
|
|
@@ -54,6 +56,7 @@ export interface UpdateThreadRecordInput {
|
|
|
54
56
|
fastBaseModel?: string | null;
|
|
55
57
|
fastBaseReasoningEffort?: string | null;
|
|
56
58
|
collaborationMode?: string;
|
|
59
|
+
activeTurnCollaborationMode?: string | null;
|
|
57
60
|
approvalMode?: string;
|
|
58
61
|
sandboxMode?: string | null;
|
|
59
62
|
status?: string;
|
|
@@ -83,6 +86,8 @@ export interface CreateThreadPendingSteerRecordInput {
|
|
|
83
86
|
clientRequestId?: string | null;
|
|
84
87
|
displayPrompt: string;
|
|
85
88
|
submittedPrompt: string;
|
|
89
|
+
delivery?: 'steer' | 'continuation';
|
|
90
|
+
turnConfigJson?: string | null;
|
|
86
91
|
}
|
|
87
92
|
|
|
88
93
|
export interface UpsertThreadHistoryItemRecordInput {
|
|
@@ -283,6 +288,7 @@ export function createThreadRecord(db: DatabaseClient, input: CreateThreadRecord
|
|
|
283
288
|
fastBaseModel: input.fastBaseModel ?? null,
|
|
284
289
|
fastBaseReasoningEffort: input.fastBaseReasoningEffort ?? null,
|
|
285
290
|
collaborationMode: input.collaborationMode ?? 'default',
|
|
291
|
+
activeTurnCollaborationMode: input.activeTurnCollaborationMode ?? null,
|
|
286
292
|
approvalMode: input.approvalMode,
|
|
287
293
|
sandboxMode: input.sandboxMode ?? null,
|
|
288
294
|
status: 'idle',
|
|
@@ -538,6 +544,8 @@ export function createThreadPendingSteerRecord(
|
|
|
538
544
|
clientRequestId: input.clientRequestId ?? null,
|
|
539
545
|
displayPrompt: input.displayPrompt,
|
|
540
546
|
submittedPrompt: input.submittedPrompt,
|
|
547
|
+
delivery: input.delivery ?? 'steer',
|
|
548
|
+
turnConfigJson: input.turnConfigJson ?? null,
|
|
541
549
|
createdAt: now,
|
|
542
550
|
updatedAt: now,
|
|
543
551
|
};
|
|
@@ -557,6 +565,93 @@ export function deleteThreadPendingSteerRecordsByThreadId(
|
|
|
557
565
|
db.delete(threadPendingSteers).where(eq(threadPendingSteers.threadId, threadId)).run();
|
|
558
566
|
}
|
|
559
567
|
|
|
568
|
+
export function getThreadPromptRequestRecord(
|
|
569
|
+
db: DatabaseClient,
|
|
570
|
+
threadId: string,
|
|
571
|
+
clientRequestId: string,
|
|
572
|
+
) {
|
|
573
|
+
return db
|
|
574
|
+
.select()
|
|
575
|
+
.from(threadPromptRequests)
|
|
576
|
+
.where(
|
|
577
|
+
and(
|
|
578
|
+
eq(threadPromptRequests.threadId, threadId),
|
|
579
|
+
eq(threadPromptRequests.clientRequestId, clientRequestId),
|
|
580
|
+
),
|
|
581
|
+
)
|
|
582
|
+
.get();
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
export function createThreadPromptRequestRecord(
|
|
586
|
+
db: DatabaseClient,
|
|
587
|
+
threadId: string,
|
|
588
|
+
clientRequestId: string,
|
|
589
|
+
) {
|
|
590
|
+
const now = new Date().toISOString();
|
|
591
|
+
db.insert(threadPromptRequests)
|
|
592
|
+
.values({
|
|
593
|
+
id: randomUUID(),
|
|
594
|
+
threadId,
|
|
595
|
+
clientRequestId,
|
|
596
|
+
status: 'processing',
|
|
597
|
+
createdAt: now,
|
|
598
|
+
updatedAt: now,
|
|
599
|
+
})
|
|
600
|
+
.onConflictDoNothing()
|
|
601
|
+
.run();
|
|
602
|
+
return getThreadPromptRequestRecord(db, threadId, clientRequestId)!;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
export function markThreadPromptRequestAccepted(
|
|
606
|
+
db: DatabaseClient,
|
|
607
|
+
threadId: string,
|
|
608
|
+
clientRequestId: string,
|
|
609
|
+
) {
|
|
610
|
+
db.update(threadPromptRequests)
|
|
611
|
+
.set({ status: 'accepted', updatedAt: new Date().toISOString() })
|
|
612
|
+
.where(
|
|
613
|
+
and(
|
|
614
|
+
eq(threadPromptRequests.threadId, threadId),
|
|
615
|
+
eq(threadPromptRequests.clientRequestId, clientRequestId),
|
|
616
|
+
),
|
|
617
|
+
)
|
|
618
|
+
.run();
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
export function deleteExpiredThreadPromptRequestRecords(
|
|
622
|
+
db: DatabaseClient,
|
|
623
|
+
cutoff: string,
|
|
624
|
+
) {
|
|
625
|
+
return db
|
|
626
|
+
.delete(threadPromptRequests)
|
|
627
|
+
.where(lt(threadPromptRequests.updatedAt, cutoff))
|
|
628
|
+
.run();
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
export function deleteThreadPromptRequestRecord(
|
|
632
|
+
db: DatabaseClient,
|
|
633
|
+
threadId: string,
|
|
634
|
+
clientRequestId: string,
|
|
635
|
+
) {
|
|
636
|
+
db.delete(threadPromptRequests)
|
|
637
|
+
.where(
|
|
638
|
+
and(
|
|
639
|
+
eq(threadPromptRequests.threadId, threadId),
|
|
640
|
+
eq(threadPromptRequests.clientRequestId, clientRequestId),
|
|
641
|
+
),
|
|
642
|
+
)
|
|
643
|
+
.run();
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
export function deleteThreadPromptRequestRecordsByThreadId(
|
|
647
|
+
db: DatabaseClient,
|
|
648
|
+
threadId: string,
|
|
649
|
+
) {
|
|
650
|
+
db.delete(threadPromptRequests)
|
|
651
|
+
.where(eq(threadPromptRequests.threadId, threadId))
|
|
652
|
+
.run();
|
|
653
|
+
}
|
|
654
|
+
|
|
560
655
|
export function listThreadActivityNotesByThreadId(
|
|
561
656
|
db: DatabaseClient,
|
|
562
657
|
threadId: string,
|
|
@@ -33,6 +33,7 @@ export const threads = sqliteTable('threads', {
|
|
|
33
33
|
fastBaseModel: text('fast_base_model'),
|
|
34
34
|
fastBaseReasoningEffort: text('fast_base_reasoning_effort'),
|
|
35
35
|
collaborationMode: text('collaboration_mode').notNull().default('default'),
|
|
36
|
+
activeTurnCollaborationMode: text('active_turn_collaboration_mode'),
|
|
36
37
|
approvalMode: text('approval_mode'),
|
|
37
38
|
sandboxMode: text('sandbox_mode'),
|
|
38
39
|
status: text('status'),
|
|
@@ -113,10 +114,29 @@ export const threadPendingSteers = sqliteTable('thread_pending_steers', {
|
|
|
113
114
|
clientRequestId: text('client_request_id'),
|
|
114
115
|
displayPrompt: text('display_prompt').notNull(),
|
|
115
116
|
submittedPrompt: text('submitted_prompt').notNull(),
|
|
117
|
+
delivery: text('delivery').notNull().default('steer'),
|
|
118
|
+
turnConfigJson: text('turn_config_json'),
|
|
116
119
|
createdAt: text('created_at').notNull(),
|
|
117
120
|
updatedAt: text('updated_at').notNull(),
|
|
118
121
|
});
|
|
119
122
|
|
|
123
|
+
export const threadPromptRequests = sqliteTable(
|
|
124
|
+
'thread_prompt_requests',
|
|
125
|
+
{
|
|
126
|
+
id: text('id').primaryKey(),
|
|
127
|
+
threadId: text('thread_id').notNull(),
|
|
128
|
+
clientRequestId: text('client_request_id').notNull(),
|
|
129
|
+
status: text('status').notNull(),
|
|
130
|
+
createdAt: text('created_at').notNull(),
|
|
131
|
+
updatedAt: text('updated_at').notNull(),
|
|
132
|
+
},
|
|
133
|
+
(table) => ({
|
|
134
|
+
threadClientRequestUnique: uniqueIndex(
|
|
135
|
+
'thread_prompt_requests_thread_client_request_idx',
|
|
136
|
+
).on(table.threadId, table.clientRequestId),
|
|
137
|
+
}),
|
|
138
|
+
);
|
|
139
|
+
|
|
120
140
|
export const threadHistoryItems = sqliteTable(
|
|
121
141
|
'thread_history_items',
|
|
122
142
|
{
|