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.
@@ -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-BkwJP0KB.js"></script>
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-BOqbjoiB.js">
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-CJFMmjP5.css">
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>
@@ -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 127.0.0.1.
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 127.0.0.1.
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
- SERVICE_HOST=127.0.0.1 SERVICE_PORT=4173 remote-codex start
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remote-codex",
3
- "version": "0.11.31",
3
+ "version": "0.11.32",
4
4
  "description": "Local web supervisor for Codex workspaces and threads.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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
  {